inputmethod: Ensure InputPanelV1Window is always within the screen
Currently when input panel is using overlay mode and the cursor rectangle is below or above the screen area, the input panel may be placed off the screen. The change ensure it is always placed within the screen area using similar math like xdg_popup's slide_y constrain.
This commit is contained in:
parent
4f512c6571
commit
1fd5a6555e
4 changed files with 121 additions and 6 deletions
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "core/output.h"
|
||||
#include "inputmethod.h"
|
||||
#include "inputpanelv1window.h"
|
||||
#include "keyboard_input.h"
|
||||
#include "pointer_input.h"
|
||||
#include "qwayland-input-method-unstable-v1.h"
|
||||
|
@ -63,6 +64,8 @@ private Q_SLOTS:
|
|||
void testDisableShowInputPanel();
|
||||
void testModifierForwarding();
|
||||
void testFakeEventFallback();
|
||||
void testOverlayPositioning_data();
|
||||
void testOverlayPositioning();
|
||||
|
||||
private:
|
||||
void touchNow()
|
||||
|
@ -552,6 +555,10 @@ void InputMethodTest::testV3Styling()
|
|||
// Merged range should be [1,6).
|
||||
QCOMPARE(textInputPreeditSpy.last().at(1), 1);
|
||||
QCOMPARE(textInputPreeditSpy.last().at(2), 6);
|
||||
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowClosed(window));
|
||||
QVERIFY(!kwinApp()->inputMethod()->isActive());
|
||||
}
|
||||
|
||||
void InputMethodTest::testDisableShowInputPanel()
|
||||
|
@ -584,6 +591,9 @@ void InputMethodTest::testDisableShowInputPanel()
|
|||
textInputV2->showInputPanel();
|
||||
QVERIFY(requestShowInputPanelSpy.count() || requestShowInputPanelSpy.wait());
|
||||
QVERIFY(!kwinApp()->inputMethod()->isActive());
|
||||
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowClosed(window));
|
||||
}
|
||||
|
||||
void InputMethodTest::testModifierForwarding()
|
||||
|
@ -655,6 +665,10 @@ void InputMethodTest::testModifierForwarding()
|
|||
QVERIFY(modifierSpy.count() == 3 || modifierSpy.wait());
|
||||
disconnect(keyChangedConnection);
|
||||
disconnect(modifiersChangedConnection);
|
||||
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowClosed(window));
|
||||
QVERIFY(!kwinApp()->inputMethod()->isActive());
|
||||
}
|
||||
|
||||
void InputMethodTest::testFakeEventFallback()
|
||||
|
@ -726,6 +740,71 @@ void InputMethodTest::testFakeEventFallback()
|
|||
|
||||
compare(keySpy.at(0), KEY_ENTER, KWayland::Client::Keyboard::KeyState::Pressed);
|
||||
compare(keySpy.at(1), KEY_ENTER, KWayland::Client::Keyboard::KeyState::Released);
|
||||
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowClosed(window));
|
||||
kwinApp()->inputMethod()->setActive(false);
|
||||
QVERIFY(!kwinApp()->inputMethod()->isActive());
|
||||
}
|
||||
|
||||
void InputMethodTest::testOverlayPositioning_data()
|
||||
{
|
||||
QTest::addColumn<QRect>("cursorRectangle");
|
||||
QTest::addColumn<QRect>("result");
|
||||
|
||||
QTest::newRow("regular") << QRect(10, 20, 30, 40) << QRect(60, 160, 200, 50);
|
||||
QTest::newRow("offscreen-left") << QRect(-200, 40, 30, 40) << QRect(0, 180, 200, 50);
|
||||
QTest::newRow("offscreen-right") << QRect(1200, 40, 30, 40) << QRect(1080, 180, 200, 50);
|
||||
QTest::newRow("offscreen-top") << QRect(1200, -400, 30, 40) << QRect(1080, 0, 200, 50);
|
||||
// Check it is flipped near the bottom of screen (anchor point 844 + 100 + 40 = 1024 - 40)
|
||||
QTest::newRow("offscreen-bottom-flip") << QRect(1200, 844, 30, 40) << QRect(1080, 894, 200, 50);
|
||||
// Top is (screen height 1024 - window height 50) = 984
|
||||
QTest::newRow("offscreen-bottom-slide") << QRect(1200, 1200, 30, 40) << QRect(1080, 974, 200, 50);
|
||||
}
|
||||
|
||||
void InputMethodTest::testOverlayPositioning()
|
||||
{
|
||||
QFETCH(QRect, cursorRectangle);
|
||||
QFETCH(QRect, result);
|
||||
Test::inputMethod()->setMode(Test::MockInputMethod::Mode::Overlay);
|
||||
QVERIFY(!kwinApp()->inputMethod()->isActive());
|
||||
|
||||
touchNow();
|
||||
QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
|
||||
QSignalSpy windowRemovedSpy(workspace(), &Workspace::windowRemoved);
|
||||
|
||||
QSignalSpy activateSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged);
|
||||
std::unique_ptr<KWayland::Client::TextInput> textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
|
||||
|
||||
// Create an xdg_toplevel surface and wait for the compositor to catch up.
|
||||
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
||||
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
|
||||
// Make the window smaller than the screen and move it.
|
||||
Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1080, 824), Qt::red);
|
||||
window->move(QPointF(50, 100));
|
||||
waylandServer()->seat()->setFocusedTextInputSurface(window->surface());
|
||||
|
||||
textInput->setCursorRectangle(cursorRectangle);
|
||||
textInput->enable(surface.get());
|
||||
// Overlay is shown upon activate
|
||||
QVERIFY(windowAddedSpy.wait());
|
||||
|
||||
QCOMPARE(workspace()->activeWindow(), window);
|
||||
|
||||
QCOMPARE(windowAddedSpy.count(), 2);
|
||||
QVERIFY(activateSpy.count() || activateSpy.wait());
|
||||
QVERIFY(kwinApp()->inputMethod()->isActive());
|
||||
|
||||
auto keyboardWindow = kwinApp()->inputMethod()->panel();
|
||||
QVERIFY(keyboardWindow);
|
||||
// Check the overlay window is placed with cursor rectangle + window position.
|
||||
QCOMPARE(keyboardWindow->frameGeometry(), result);
|
||||
|
||||
// Destroy the test window.
|
||||
shellSurface.reset();
|
||||
QVERIFY(Test::waitForWindowClosed(window));
|
||||
|
||||
Test::inputMethod()->setMode(Test::MockInputMethod::Mode::TopLevel);
|
||||
}
|
||||
|
||||
WAYLANDTEST_MAIN(InputMethodTest)
|
||||
|
|
|
@ -446,6 +446,11 @@ class MockInputMethod : public QObject, QtWayland::zwp_input_method_v1
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Mode {
|
||||
TopLevel,
|
||||
Overlay,
|
||||
};
|
||||
|
||||
MockInputMethod(struct wl_registry *registry, int id, int version);
|
||||
~MockInputMethod();
|
||||
|
||||
|
@ -458,6 +463,8 @@ public:
|
|||
return m_context;
|
||||
}
|
||||
|
||||
void setMode(Mode mode);
|
||||
|
||||
Q_SIGNALS:
|
||||
void activate();
|
||||
|
||||
|
@ -469,6 +476,7 @@ private:
|
|||
std::unique_ptr<KWayland::Client::Surface> m_inputSurface;
|
||||
QtWayland::zwp_input_panel_surface_v1 *m_inputMethodSurface = nullptr;
|
||||
struct ::zwp_input_method_context_v1 *m_context = nullptr;
|
||||
Mode m_mode = Mode::TopLevel;
|
||||
};
|
||||
|
||||
class FractionalScaleManagerV1 : public QObject, public QtWayland::wp_fractional_scale_manager_v1
|
||||
|
@ -683,7 +691,8 @@ enum class CreationSetup {
|
|||
};
|
||||
|
||||
QtWayland::zwp_input_panel_surface_v1 *createInputPanelSurfaceV1(KWayland::Client::Surface *surface,
|
||||
KWayland::Client::Output *output);
|
||||
KWayland::Client::Output *output,
|
||||
MockInputMethod::Mode mode);
|
||||
|
||||
FractionalScaleV1 *createFractionalScaleV1(KWayland::Client::Surface *surface);
|
||||
|
||||
|
|
|
@ -326,14 +326,27 @@ void MockInputMethod::zwp_input_method_v1_activate(struct ::zwp_input_method_con
|
|||
{
|
||||
if (!m_inputSurface) {
|
||||
m_inputSurface = Test::createSurface();
|
||||
m_inputMethodSurface = Test::createInputPanelSurfaceV1(m_inputSurface.get(), s_waylandConnection.outputs.first());
|
||||
m_inputMethodSurface = Test::createInputPanelSurfaceV1(m_inputSurface.get(), s_waylandConnection.outputs.first(), m_mode);
|
||||
}
|
||||
m_context = context;
|
||||
|
||||
switch (m_mode) {
|
||||
case Mode::TopLevel:
|
||||
Test::render(m_inputSurface.get(), QSize(1280, 400), Qt::blue);
|
||||
break;
|
||||
case Mode::Overlay:
|
||||
Test::render(m_inputSurface.get(), QSize(200, 50), Qt::blue);
|
||||
break;
|
||||
}
|
||||
|
||||
Q_EMIT activate();
|
||||
}
|
||||
|
||||
void MockInputMethod::setMode(MockInputMethod::Mode mode)
|
||||
{
|
||||
m_mode = mode;
|
||||
}
|
||||
|
||||
void MockInputMethod::zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context)
|
||||
{
|
||||
QCOMPARE(context, m_context);
|
||||
|
@ -924,7 +937,7 @@ LayerSurfaceV1 *createLayerSurfaceV1(KWayland::Client::Surface *surface, const Q
|
|||
return shellSurface;
|
||||
}
|
||||
|
||||
QtWayland::zwp_input_panel_surface_v1 *createInputPanelSurfaceV1(KWayland::Client::Surface *surface, KWayland::Client::Output *output)
|
||||
QtWayland::zwp_input_panel_surface_v1 *createInputPanelSurfaceV1(KWayland::Client::Surface *surface, KWayland::Client::Output *output, MockInputMethod::Mode mode)
|
||||
{
|
||||
if (!s_waylandConnection.inputPanelV1) {
|
||||
qWarning() << "Unable to create the input panel surface. The interface input_panel global is not bound";
|
||||
|
@ -937,7 +950,14 @@ QtWayland::zwp_input_panel_surface_v1 *createInputPanelSurfaceV1(KWayland::Clien
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case MockInputMethod::Mode::TopLevel:
|
||||
s->set_toplevel(output->output(), QtWayland::zwp_input_panel_surface_v1::position_center_bottom);
|
||||
break;
|
||||
case MockInputMethod::Mode::Overlay:
|
||||
s->set_overlay_panel();
|
||||
break;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
|
|
@ -140,10 +140,17 @@ void InputPanelV1Window::reposition()
|
|||
QRectF(popupOffset(cursorRectangle, Qt::TopEdge | Qt::LeftEdge, Qt::RightEdge | Qt::TopEdge, m_windowGeometry.size()), m_windowGeometry.size());
|
||||
|
||||
// if it still doesn't fit we should continue with the unflipped version
|
||||
if (flippedPopupRect.top() >= screen.top() || flippedPopupRect.bottom() <= screen.bottom()) {
|
||||
if (flippedPopupRect.top() >= screen.top() && flippedPopupRect.bottom() <= screen.bottom()) {
|
||||
popupRect.moveTop(flippedPopupRect.top());
|
||||
}
|
||||
}
|
||||
if (popupRect.top() < screen.top()) {
|
||||
popupRect.moveTop(screen.top());
|
||||
}
|
||||
if (popupRect.bottom() > screen.bottom()) {
|
||||
popupRect.moveBottom(screen.bottom());
|
||||
}
|
||||
|
||||
moveResize(popupRect);
|
||||
}
|
||||
} break;
|
||||
|
|
Loading…
Reference in a new issue