decoration_input_test.cpp 34.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.

Copyright (C) 2016 Martin Gräßlin <mgraesslin@kde.org>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "kwin_wayland_test.h"
#include "abstract_client.h"
#include "cursor.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
23 24
#include "internal_client.h"
#include "platform.h"
25
#include "pointer_input.h"
26
#include "touch_input.h"
27 28 29 30 31
#include "screenedge.h"
#include "screens.h"
#include "wayland_server.h"
#include "workspace.h"
#include <kwineffects.h>
32

33
#include "decorations/decoratedclient.h"
34 35
#include "decorations/decorationbridge.h"
#include "decorations/settings.h"
36 37 38

#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/compositor.h>
39
#include <KWayland/Client/keyboard.h>
40 41 42 43 44 45
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/server_decoration.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/shm_pool.h>
#include <KWayland/Client/surface.h>

46
#include <KDecoration2/Decoration>
47
#include <KDecoration2/DecorationSettings>
48

49 50
#include <linux/input.h>

51 52
Q_DECLARE_METATYPE(Qt::WindowFrameSection)

53 54 55 56 57 58 59 60 61 62 63 64
namespace KWin
{

static const QString s_socketName = QStringLiteral("wayland_test_kwin_decoration_input-0");

class DecorationInputTest : public QObject
{
    Q_OBJECT
private Q_SLOTS:
    void initTestCase();
    void init();
    void cleanup();
65
    void testAxis_data();
66
    void testAxis();
67
    void testDoubleClick_data();
68
    void testDoubleClick();
69 70
    void testDoubleTap_data();
    void testDoubleTap();
71
    void testHover_data();
72
    void testHover();
73
    void testPressToMove_data();
74
    void testPressToMove();
75 76
    void testTapToMove_data();
    void testTapToMove();
77 78
    void testResizeOutsideWindow_data();
    void testResizeOutsideWindow();
79 80 81 82
    void testModifierClickUnrestrictedMove_data();
    void testModifierClickUnrestrictedMove();
    void testModifierScrollOpacity_data();
    void testModifierScrollOpacity();
83 84
    void testTouchEvents_data();
    void testTouchEvents();
85 86
    void testTooltipDoesntEatKeyEvents_data();
    void testTooltipDoesntEatKeyEvents();
87 88

private:
89
    AbstractClient *showWindow(Test::XdgShellSurfaceType type);
90 91 92
};

#define MOTION(target) \
93
    kwinApp()->platform()->pointerMotion(target, timestamp++)
94 95

#define PRESS \
96
    kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++)
97 98

#define RELEASE \
99
    kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++)
100

101
AbstractClient *DecorationInputTest::showWindow(Test::XdgShellSurfaceType type)
102 103 104 105 106 107 108 109 110
{
    using namespace KWayland::Client;
#define VERIFY(statement) \
    if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\
        return nullptr;
#define COMPARE(actual, expected) \
    if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\
        return nullptr;

111
    Surface *surface = Test::createSurface(Test::waylandCompositor());
112
    VERIFY(surface);
113
    XdgShellSurface *shellSurface = Test::createXdgShellSurface(type, surface, surface);
114
    VERIFY(shellSurface);
115
    auto deco = Test::waylandServerSideDecoration()->create(surface, surface);
116 117 118 119 120 121 122
    QSignalSpy decoSpy(deco, &ServerSideDecoration::modeChanged);
    VERIFY(decoSpy.isValid());
    VERIFY(decoSpy.wait());
    deco->requestMode(ServerSideDecoration::Mode::Server);
    VERIFY(decoSpy.wait());
    COMPARE(deco->mode(), ServerSideDecoration::Mode::Server);
    // let's render
123
    auto c = Test::renderAndWaitForShown(surface, QSize(500, 50), Qt::blue);
124
    VERIFY(c);
125
    COMPARE(workspace()->activeClient(), c);
126 127 128 129 130 131 132 133 134

#undef VERIFY
#undef COMPARE

    return c;
}

void DecorationInputTest::initTestCase()
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
135 136
    qRegisterMetaType<KWin::AbstractClient *>();
    qRegisterMetaType<KWin::InternalClient *>();
137 138
    QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated);
    QVERIFY(workspaceCreatedSpy.isValid());
139
    kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
140
    QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit()));
141
    QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2));
142 143 144 145

    // change some options
    KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
    config->group(QStringLiteral("MouseBindings")).writeEntry("CommandTitlebarWheel", QStringLiteral("above/below"));
146 147
    config->group(QStringLiteral("Windows")).writeEntry("TitlebarDoubleClickCommand", QStringLiteral("OnAllDesktops"));
    config->group(QStringLiteral("Desktops")).writeEntry("Number", 2);
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
    config->sync();

    kwinApp()->setConfig(config);

    kwinApp()->start();
    QVERIFY(workspaceCreatedSpy.wait());
    QCOMPARE(screens()->count(), 2);
    QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024));
    QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024));
    setenv("QT_QPA_PLATFORM", "wayland", true);
    waylandServer()->initWorkspace();
}

void DecorationInputTest::init()
{
    using namespace KWayland::Client;
164
    QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration));
165
    QVERIFY(Test::waitForWaylandPointer());
166 167

    screens()->setCurrent(0);
168
    Cursors::self()->mouse()->setPos(QPoint(640, 512));
169 170 171 172
}

void DecorationInputTest::cleanup()
{
173
    Test::destroyWaylandConnection();
174 175
}

176 177 178 179
void DecorationInputTest::testAxis_data()
{
    QTest::addColumn<QPoint>("decoPoint");
    QTest::addColumn<Qt::WindowFrameSection>("expectedSection");
180 181 182 183 184
    QTest::addColumn<Test::XdgShellSurfaceType>("type");

    QTest::newRow("topLeft|xdgWmBase") << QPoint(0, 0) << Qt::TopLeftSection << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("top|xdgWmBase") << QPoint(250, 0) << Qt::TopSection << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("topRight|xdgWmBase") << QPoint(499, 0) << Qt::TopRightSection << Test::XdgShellSurfaceType::XdgShellStable;
185 186
}

187 188
void DecorationInputTest::testAxis()
{
189
    QFETCH(Test::XdgShellSurfaceType, type);
190
    AbstractClient *c = showWindow(type);
191 192 193 194 195 196 197 198
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());
    QCOMPARE(c->titlebarPosition(), AbstractClient::PositionTop);
    QVERIFY(!c->keepAbove());
    QVERIFY(!c->keepBelow());

    quint32 timestamp = 1;
199
    MOTION(QPoint(c->frameGeometry().center().x(), c->clientPos().y() / 2));
200 201
    QVERIFY(!input()->pointer()->decoration().isNull());
    QCOMPARE(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), Qt::TitleBarArea);
202 203 204

    // TODO: mouse wheel direction looks wrong to me
    // simulate wheel
205
    kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++);
206 207
    QVERIFY(c->keepBelow());
    QVERIFY(!c->keepAbove());
208
    kwinApp()->platform()->pointerAxisVertical(-5.0, timestamp++);
209 210
    QVERIFY(!c->keepBelow());
    QVERIFY(!c->keepAbove());
211
    kwinApp()->platform()->pointerAxisVertical(-5.0, timestamp++);
212 213
    QVERIFY(!c->keepBelow());
    QVERIFY(c->keepAbove());
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230

    // test top most deco pixel, BUG: 362860
    c->move(0, 0);
    QFETCH(QPoint, decoPoint);
    MOTION(decoPoint);
    QVERIFY(!input()->pointer()->decoration().isNull());
    QCOMPARE(input()->pointer()->decoration()->client(), c);
    QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection");
    kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++);
    QVERIFY(!c->keepBelow());
    QVERIFY(!c->keepAbove());
}

void DecorationInputTest::testDoubleClick_data()
{
    QTest::addColumn<QPoint>("decoPoint");
    QTest::addColumn<Qt::WindowFrameSection>("expectedSection");
231 232 233 234 235
    QTest::addColumn<Test::XdgShellSurfaceType>("type");

    QTest::newRow("topLeft|xdgWmBase") << QPoint(0, 0) << Qt::TopLeftSection << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("top|xdgWmBase") << QPoint(250, 0) << Qt::TopSection << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("topRight|xdgWmBase") << QPoint(499, 0) << Qt::TopRightSection << Test::XdgShellSurfaceType::XdgShellStable;
236 237
}

238 239
void KWin::DecorationInputTest::testDoubleClick()
{
240
    QFETCH(Test::XdgShellSurfaceType, type);
241
    AbstractClient *c = showWindow(type);
242 243 244 245 246
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());
    QVERIFY(!c->isOnAllDesktops());
    quint32 timestamp = 1;
247
    MOTION(QPoint(c->frameGeometry().center().x(), c->clientPos().y() / 2));
248 249 250 251 252 253 254 255 256 257 258 259 260 261

    // double click
    PRESS;
    RELEASE;
    PRESS;
    RELEASE;
    QVERIFY(c->isOnAllDesktops());
    // double click again
    PRESS;
    RELEASE;
    QVERIFY(c->isOnAllDesktops());
    PRESS;
    RELEASE;
    QVERIFY(!c->isOnAllDesktops());
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276

    // test top most deco pixel, BUG: 362860
    c->move(0, 0);
    QFETCH(QPoint, decoPoint);
    MOTION(decoPoint);
    QVERIFY(!input()->pointer()->decoration().isNull());
    QCOMPARE(input()->pointer()->decoration()->client(), c);
    QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection");
    // double click
    PRESS;
    RELEASE;
    QVERIFY(!c->isOnAllDesktops());
    PRESS;
    RELEASE;
    QVERIFY(c->isOnAllDesktops());
277 278
}

279 280 281 282
void DecorationInputTest::testDoubleTap_data()
{
    QTest::addColumn<QPoint>("decoPoint");
    QTest::addColumn<Qt::WindowFrameSection>("expectedSection");
283 284 285 286 287
    QTest::addColumn<Test::XdgShellSurfaceType>("type");

    QTest::newRow("topLeft|xdgWmBase") << QPoint(10, 10) << Qt::TopLeftSection << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("top|xdgWmBase") << QPoint(260, 10) << Qt::TopSection << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("topRight|xdgWmBase") << QPoint(509, 10) << Qt::TopRightSection << Test::XdgShellSurfaceType::XdgShellStable;
288 289 290 291
}

void KWin::DecorationInputTest::testDoubleTap()
{
292
    QFETCH(Test::XdgShellSurfaceType, type);
293
    AbstractClient *c = showWindow(type);
294 295 296 297 298
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());
    QVERIFY(!c->isOnAllDesktops());
    quint32 timestamp = 1;
299
    const QPoint tapPoint(c->frameGeometry().center().x(), c->clientPos().y() / 2);
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315

    // double tap
    kwinApp()->platform()->touchDown(0, tapPoint, timestamp++);
    kwinApp()->platform()->touchUp(0, timestamp++);
    kwinApp()->platform()->touchDown(0, tapPoint, timestamp++);
    kwinApp()->platform()->touchUp(0, timestamp++);
    QVERIFY(c->isOnAllDesktops());
    // double tap again
    kwinApp()->platform()->touchDown(0, tapPoint, timestamp++);
    kwinApp()->platform()->touchUp(0, timestamp++);
    QVERIFY(c->isOnAllDesktops());
    kwinApp()->platform()->touchDown(0, tapPoint, timestamp++);
    kwinApp()->platform()->touchUp(0, timestamp++);
    QVERIFY(!c->isOnAllDesktops());

    // test top most deco pixel, BUG: 362860
316 317 318 319
    //
    // Not directly at (0, 0), otherwise ScreenEdgeInputFilter catches
    // event before DecorationEventFilter.
    c->move(10, 10);
320 321 322 323 324 325 326 327 328 329 330 331 332
    QFETCH(QPoint, decoPoint);
    // double click
    kwinApp()->platform()->touchDown(0, decoPoint, timestamp++);
    QVERIFY(!input()->touch()->decoration().isNull());
    QCOMPARE(input()->touch()->decoration()->client(), c);
    QTEST(input()->touch()->decoration()->decoration()->sectionUnderMouse(), "expectedSection");
    kwinApp()->platform()->touchUp(0, timestamp++);
    QVERIFY(!c->isOnAllDesktops());
    kwinApp()->platform()->touchDown(0, decoPoint, timestamp++);
    kwinApp()->platform()->touchUp(0, timestamp++);
    QVERIFY(c->isOnAllDesktops());
}

333 334
void DecorationInputTest::testHover_data()
{
335
    QTest::addColumn<Test::XdgShellSurfaceType>("type");
336

337
    QTest::newRow("xdgWmBase") << Test::XdgShellSurfaceType::XdgShellStable;
338 339
}

340 341
void DecorationInputTest::testHover()
{
342
    QFETCH(Test::XdgShellSurfaceType, type);
343
    AbstractClient *c = showWindow(type);
344 345 346 347 348 349 350 351
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());

    // our left border is moved out of the visible area, so move the window to a better place
    c->move(QPoint(20, 0));

    quint32 timestamp = 1;
352
    MOTION(QPoint(c->frameGeometry().center().x(), c->clientPos().y() / 2));
353
    QCOMPARE(c->cursor(), CursorShape(Qt::ArrowCursor));
354

355 356 357 358 359 360 361 362 363 364 365
    // There is a mismatch of the cursor key positions between windows
    // with and without borders (with borders one can move inside a bit and still
    // be on an edge, without not). We should make this consistent in KWin's core.
    //
    // TODO: Test input position with different border sizes.
    // TODO: We should test with the fake decoration to have a fixed test environment.
    const bool hasBorders = Decoration::DecorationBridge::self()->settings()->borderSize() != KDecoration2::BorderSize::None;
    auto deviation = [hasBorders] {
        return hasBorders ? -1 : 0;
    };

366
    MOTION(QPoint(c->frameGeometry().x(), 0));
367
    QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthWest));
368
    MOTION(QPoint(c->frameGeometry().x() + c->frameGeometry().width() / 2, 0));
369
    QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorth));
370
    MOTION(QPoint(c->frameGeometry().x() + c->frameGeometry().width() - 1, 0));
371
    QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthEast));
372
    MOTION(QPoint(c->frameGeometry().x() + c->frameGeometry().width() + deviation(), c->height() / 2));
373
    QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeEast));
374
    MOTION(QPoint(c->frameGeometry().x() + c->frameGeometry().width() + deviation(), c->height() - 1));
375
    QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthEast));
376
    MOTION(QPoint(c->frameGeometry().x() + c->frameGeometry().width() / 2, c->height() + deviation()));
377
    QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouth));
378
    MOTION(QPoint(c->frameGeometry().x(), c->height() + deviation()));
379
    QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthWest));
380
    MOTION(QPoint(c->frameGeometry().x() - 1, c->height() / 2));
381
    QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeWest));
382

383
    MOTION(c->frameGeometry().center());
384
    QEXPECT_FAIL("", "Cursor not set back on leave", Continue);
385
    QCOMPARE(c->cursor(), CursorShape(Qt::ArrowCursor));
386 387
}

388 389 390 391 392
void DecorationInputTest::testPressToMove_data()
{
    QTest::addColumn<QPoint>("offset");
    QTest::addColumn<QPoint>("offset2");
    QTest::addColumn<QPoint>("offset3");
393 394 395 396 397 398
    QTest::addColumn<Test::XdgShellSurfaceType>("type");

    QTest::newRow("To right|xdgWmBase")  << QPoint(10, 0)  << QPoint(20, 0)  << QPoint(30, 0) << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("To left|xdgWmBase")   << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("To bottom|xdgWmBase") << QPoint(0, 10)  << QPoint(0, 20)  << QPoint(0, 30) << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("To top|xdgWmBase")    << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::XdgShellSurfaceType::XdgShellStable;
399 400
}

401 402
void DecorationInputTest::testPressToMove()
{
403
    QFETCH(Test::XdgShellSurfaceType, type);
404
    AbstractClient *c = showWindow(type);
405 406 407
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());
408
    c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2));
409 410 411 412 413 414
    QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized);
    QVERIFY(startMoveResizedSpy.isValid());
    QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized);
    QVERIFY(clientFinishUserMovedResizedSpy.isValid());

    quint32 timestamp = 1;
415
    MOTION(QPoint(c->frameGeometry().center().x(), c->y() + c->clientPos().y() / 2));
416
    QCOMPARE(c->cursor(), CursorShape(Qt::ArrowCursor));
417 418 419

    PRESS;
    QVERIFY(!c->isMove());
420
    QFETCH(QPoint, offset);
421
    MOTION(QPoint(c->frameGeometry().center().x(), c->y() + c->clientPos().y() / 2) + offset);
422 423
    const QPoint oldPos = c->pos();
    QVERIFY(c->isMove());
424 425 426 427 428 429
    QCOMPARE(startMoveResizedSpy.count(), 1);

    RELEASE;
    QTRY_VERIFY(!c->isMove());
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
    QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue);
430
    QCOMPARE(c->pos(), oldPos + offset);
431 432 433 434

    // again
    PRESS;
    QVERIFY(!c->isMove());
435
    QFETCH(QPoint, offset2);
436
    MOTION(QPoint(c->frameGeometry().center().x(), c->y() + c->clientPos().y() / 2) + offset2);
437
    QVERIFY(c->isMove());
438
    QCOMPARE(startMoveResizedSpy.count(), 2);
439
    QFETCH(QPoint, offset3);
440
    MOTION(QPoint(c->frameGeometry().center().x(), c->y() + c->clientPos().y() / 2) + offset3);
441 442 443 444

    RELEASE;
    QTRY_VERIFY(!c->isMove());
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2);
445 446
    // TODO: the offset should also be included
    QCOMPARE(c->pos(), oldPos + offset2 + offset3);
447 448
}

449 450 451 452 453
void DecorationInputTest::testTapToMove_data()
{
    QTest::addColumn<QPoint>("offset");
    QTest::addColumn<QPoint>("offset2");
    QTest::addColumn<QPoint>("offset3");
454 455 456 457 458 459
    QTest::addColumn<Test::XdgShellSurfaceType>("type");

    QTest::newRow("To right|xdgWmBase")  << QPoint(10, 0)  << QPoint(20, 0)  << QPoint(30, 0) << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("To left|xdgWmBase")   << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("To bottom|xdgWmBase") << QPoint(0, 10)  << QPoint(0, 20)  << QPoint(0, 30) << Test::XdgShellSurfaceType::XdgShellStable;
    QTest::newRow("To top|xdgWmBase")    << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::XdgShellSurfaceType::XdgShellStable;
460 461 462 463
}

void DecorationInputTest::testTapToMove()
{
464
    QFETCH(Test::XdgShellSurfaceType, type);
465
    AbstractClient *c = showWindow(type);
466 467 468 469 470 471 472 473 474 475
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());
    c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2));
    QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized);
    QVERIFY(startMoveResizedSpy.isValid());
    QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized);
    QVERIFY(clientFinishUserMovedResizedSpy.isValid());

    quint32 timestamp = 1;
476
    QPoint p = QPoint(c->frameGeometry().center().x(), c->y() + c->clientPos().y() / 2);
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497

    kwinApp()->platform()->touchDown(0, p, timestamp++);
    QVERIFY(!c->isMove());
    QFETCH(QPoint, offset);
    QCOMPARE(input()->touch()->decorationPressId(), 0);
    kwinApp()->platform()->touchMotion(0, p + offset, timestamp++);
    const QPoint oldPos = c->pos();
    QVERIFY(c->isMove());
    QCOMPARE(startMoveResizedSpy.count(), 1);

    kwinApp()->platform()->touchUp(0, timestamp++);
    QTRY_VERIFY(!c->isMove());
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
    QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue);
    QCOMPARE(c->pos(), oldPos + offset);

    // again
    kwinApp()->platform()->touchDown(1, p + offset, timestamp++);
    QCOMPARE(input()->touch()->decorationPressId(), 1);
    QVERIFY(!c->isMove());
    QFETCH(QPoint, offset2);
498
    kwinApp()->platform()->touchMotion(1, QPoint(c->frameGeometry().center().x(), c->y() + c->clientPos().y() / 2) + offset2, timestamp++);
499 500 501
    QVERIFY(c->isMove());
    QCOMPARE(startMoveResizedSpy.count(), 2);
    QFETCH(QPoint, offset3);
502
    kwinApp()->platform()->touchMotion(1, QPoint(c->frameGeometry().center().x(), c->y() + c->clientPos().y() / 2) + offset3, timestamp++);
503 504 505 506 507 508 509 510

    kwinApp()->platform()->touchUp(1, timestamp++);
    QTRY_VERIFY(!c->isMove());
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2);
    // TODO: the offset should also be included
    QCOMPARE(c->pos(), oldPos + offset2 + offset3);
}

511 512
void DecorationInputTest::testResizeOutsideWindow_data()
{
513
    QTest::addColumn<Test::XdgShellSurfaceType>("type");
514 515 516
    QTest::addColumn<Qt::Edge>("edge");
    QTest::addColumn<Qt::CursorShape>("expectedCursor");

517 518 519
    QTest::newRow("xdgWmBase - left") << Test::XdgShellSurfaceType::XdgShellStable << Qt::LeftEdge << Qt::SizeHorCursor;
    QTest::newRow("xdgWmBase - right") << Test::XdgShellSurfaceType::XdgShellStable << Qt::RightEdge << Qt::SizeHorCursor;
    QTest::newRow("xdgWmBase - bottom") << Test::XdgShellSurfaceType::XdgShellStable << Qt::BottomEdge << Qt::SizeVerCursor;
520 521 522 523 524 525 526 527 528 529 530 531
}

void DecorationInputTest::testResizeOutsideWindow()
{
    // this test verifies that one can resize the window outside the decoration with NoSideBorder

    // first adjust config
    kwinApp()->config()->group("org.kde.kdecoration2").writeEntry("BorderSize", QStringLiteral("None"));
    kwinApp()->config()->sync();
    workspace()->slotReconfigure();

    // now create window
532
    QFETCH(Test::XdgShellSurfaceType, type);
533 534 535 536 537
    AbstractClient *c = showWindow(type);
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());
    c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2));
538 539
    QVERIFY(c->frameGeometry() != c->inputGeometry());
    QVERIFY(c->inputGeometry().contains(c->frameGeometry()));
540 541 542 543 544 545 546 547
    QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized);
    QVERIFY(startMoveResizedSpy.isValid());

    // go to border
    quint32 timestamp = 1;
    QFETCH(Qt::Edge, edge);
    switch (edge) {
    case Qt::LeftEdge:
548
        MOTION(QPoint(c->frameGeometry().x() -1, c->frameGeometry().center().y()));
549 550
        break;
    case Qt::RightEdge:
551
        MOTION(QPoint(c->frameGeometry().x() + c->frameGeometry().width() +1, c->frameGeometry().center().y()));
552 553
        break;
    case Qt::BottomEdge:
554
        MOTION(QPoint(c->frameGeometry().center().x(), c->frameGeometry().y() + c->frameGeometry().height() + 1));
555 556 557 558
        break;
    default:
        break;
    }
559
    QVERIFY(!c->frameGeometry().contains(KWin::Cursors::self()->mouse()->pos()));
560 561 562 563 564 565 566 567 568 569 570

    // pressing should trigger resize
    PRESS;
    QVERIFY(!c->isResize());
    QVERIFY(startMoveResizedSpy.wait());
    QVERIFY(c->isResize());

    RELEASE;
    QVERIFY(!c->isResize());
}

571 572 573 574 575 576
void DecorationInputTest::testModifierClickUnrestrictedMove_data()
{
    QTest::addColumn<int>("modifierKey");
    QTest::addColumn<int>("mouseButton");
    QTest::addColumn<QString>("modKey");
    QTest::addColumn<bool>("capsLock");
577
    QTest::addColumn<Test::XdgShellSurfaceType>("surfaceType");
578 579 580 581

    const QString alt = QStringLiteral("Alt");
    const QString meta = QStringLiteral("Meta");

582 583
    const QVector<std::pair<Test::XdgShellSurfaceType, QByteArray>> surfaceTypes{
        { Test::XdgShellSurfaceType::XdgShellStable, QByteArrayLiteral("XdgWmBase") },
584 585
    };

586
    for (const auto &type : surfaceTypes) {
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
        QTest::newRow("Left Alt + Left Click" + type.second)    << KEY_LEFTALT  << BTN_LEFT   << alt << false << type.first;
        QTest::newRow("Left Alt + Right Click" + type.second)   << KEY_LEFTALT  << BTN_RIGHT  << alt << false << type.first;
        QTest::newRow("Left Alt + Middle Click" + type.second)  << KEY_LEFTALT  << BTN_MIDDLE << alt << false << type.first;
        QTest::newRow("Right Alt + Left Click" + type.second)   << KEY_RIGHTALT << BTN_LEFT   << alt << false << type.first;
        QTest::newRow("Right Alt + Right Click" + type.second)  << KEY_RIGHTALT << BTN_RIGHT  << alt << false << type.first;
        QTest::newRow("Right Alt + Middle Click" + type.second) << KEY_RIGHTALT << BTN_MIDDLE << alt << false << type.first;
        // now everything with meta
        QTest::newRow("Left Meta + Left Click" + type.second)    << KEY_LEFTMETA  << BTN_LEFT   << meta << false << type.first;
        QTest::newRow("Left Meta + Right Click" + type.second)   << KEY_LEFTMETA  << BTN_RIGHT  << meta << false << type.first;
        QTest::newRow("Left Meta + Middle Click" + type.second)  << KEY_LEFTMETA  << BTN_MIDDLE << meta << false << type.first;
        QTest::newRow("Right Meta + Left Click" + type.second)   << KEY_RIGHTMETA << BTN_LEFT   << meta << false << type.first;
        QTest::newRow("Right Meta + Right Click" + type.second)  << KEY_RIGHTMETA << BTN_RIGHT  << meta << false << type.first;
        QTest::newRow("Right Meta + Middle Click" + type.second) << KEY_RIGHTMETA << BTN_MIDDLE << meta << false << type.first;

        // and with capslock
        QTest::newRow("Left Alt + Left Click/CapsLock" + type.second)    << KEY_LEFTALT  << BTN_LEFT   << alt << true << type.first;
        QTest::newRow("Left Alt + Right Click/CapsLock" + type.second)   << KEY_LEFTALT  << BTN_RIGHT  << alt << true << type.first;
        QTest::newRow("Left Alt + Middle Click/CapsLock" + type.second)  << KEY_LEFTALT  << BTN_MIDDLE << alt << true << type.first;
        QTest::newRow("Right Alt + Left Click/CapsLock" + type.second)   << KEY_RIGHTALT << BTN_LEFT   << alt << true << type.first;
        QTest::newRow("Right Alt + Right Click/CapsLock" + type.second)  << KEY_RIGHTALT << BTN_RIGHT  << alt << true << type.first;
        QTest::newRow("Right Alt + Middle Click/CapsLock" + type.second) << KEY_RIGHTALT << BTN_MIDDLE << alt << true << type.first;
        // now everything with meta
        QTest::newRow("Left Meta + Left Click/CapsLock" + type.second)    << KEY_LEFTMETA  << BTN_LEFT   << meta << true << type.first;
        QTest::newRow("Left Meta + Right Click/CapsLock" + type.second)   << KEY_LEFTMETA  << BTN_RIGHT  << meta << true << type.first;
        QTest::newRow("Left Meta + Middle Click/CapsLock" + type.second)  << KEY_LEFTMETA  << BTN_MIDDLE << meta << true << type.first;
        QTest::newRow("Right Meta + Left Click/CapsLock" + type.second)   << KEY_RIGHTMETA << BTN_LEFT   << meta << true << type.first;
        QTest::newRow("Right Meta + Right Click/CapsLock" + type.second)  << KEY_RIGHTMETA << BTN_RIGHT  << meta << true << type.first;
        QTest::newRow("Right Meta + Middle Click/CapsLock" + type.second) << KEY_RIGHTMETA << BTN_MIDDLE << meta << true << type.first;
    }
}

void DecorationInputTest::testModifierClickUnrestrictedMove()
{
    // this test ensures that Alt+mouse button press triggers unrestricted move

    // first modify the config for this run
    QFETCH(QString, modKey);
    KConfigGroup group = kwinApp()->config()->group("MouseBindings");
    group.writeEntry("CommandAllKey", modKey);
    group.writeEntry("CommandAll1", "Move");
    group.writeEntry("CommandAll2", "Move");
    group.writeEntry("CommandAll3", "Move");
    group.sync();
    workspace()->slotReconfigure();
    QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier);
    QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove);
    QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove);
    QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove);

    // create a window
637
    QFETCH(Test::XdgShellSurfaceType, surfaceType);
638 639 640 641 642 643
    AbstractClient *c = showWindow(surfaceType);
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());
    c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2));
    // move cursor on window
644
    Cursors::self()->mouse()->setPos(QPoint(c->frameGeometry().center().x(), c->y() + c->clientPos().y() / 2));
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673

    // simulate modifier+click
    quint32 timestamp = 1;
    QFETCH(bool, capsLock);
    if (capsLock) {
        kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
    }
    QFETCH(int, modifierKey);
    QFETCH(int, mouseButton);
    kwinApp()->platform()->keyboardKeyPressed(modifierKey, timestamp++);
    QVERIFY(!c->isMove());
    kwinApp()->platform()->pointerButtonPressed(mouseButton, timestamp++);
    QVERIFY(c->isMove());
    // release modifier should not change it
    kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++);
    QVERIFY(c->isMove());
    // but releasing the key should end move/resize
    kwinApp()->platform()->pointerButtonReleased(mouseButton, timestamp++);
    QVERIFY(!c->isMove());
    if (capsLock) {
        kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
    }
}

void DecorationInputTest::testModifierScrollOpacity_data()
{
    QTest::addColumn<int>("modifierKey");
    QTest::addColumn<QString>("modKey");
    QTest::addColumn<bool>("capsLock");
674
    QTest::addColumn<Test::XdgShellSurfaceType>("surfaceType");
675 676 677 678

    const QString alt = QStringLiteral("Alt");
    const QString meta = QStringLiteral("Meta");

679 680
    const QVector<std::pair<Test::XdgShellSurfaceType, QByteArray>> surfaceTypes{
        { Test::XdgShellSurfaceType::XdgShellStable, QByteArrayLiteral("XdgWmBase") },
681 682
    };

683
    for (const auto &type : surfaceTypes) {
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
        QTest::newRow("Left Alt" + type.second)   << KEY_LEFTALT  << alt << false << type.first;
        QTest::newRow("Right Alt" + type.second)  << KEY_RIGHTALT << alt << false << type.first;
        QTest::newRow("Left Meta" + type.second)  << KEY_LEFTMETA  << meta << false << type.first;
        QTest::newRow("Right Meta" + type.second) << KEY_RIGHTMETA << meta << false << type.first;
        QTest::newRow("Left Alt/CapsLock" + type.second)   << KEY_LEFTALT  << alt << true << type.first;
        QTest::newRow("Right Alt/CapsLock" + type.second)  << KEY_RIGHTALT << alt << true << type.first;
        QTest::newRow("Left Meta/CapsLock" + type.second)  << KEY_LEFTMETA  << meta << true << type.first;
        QTest::newRow("Right Meta/CapsLock" + type.second) << KEY_RIGHTMETA << meta << true << type.first;
    }
}

void DecorationInputTest::testModifierScrollOpacity()
{
    // this test verifies that mod+wheel performs a window operation

    // first modify the config for this run
    QFETCH(QString, modKey);
    KConfigGroup group = kwinApp()->config()->group("MouseBindings");
    group.writeEntry("CommandAllKey", modKey);
    group.writeEntry("CommandAllWheel", "change opacity");
    group.sync();
    workspace()->slotReconfigure();

707
    QFETCH(Test::XdgShellSurfaceType, surfaceType);
708 709 710 711 712 713
    AbstractClient *c = showWindow(surfaceType);
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());
    c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2));
    // move cursor on window
714
    Cursors::self()->mouse()->setPos(QPoint(c->frameGeometry().center().x(), c->y() + c->clientPos().y() / 2));
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
    // set the opacity to 0.5
    c->setOpacity(0.5);
    QCOMPARE(c->opacity(), 0.5);

    // simulate modifier+wheel
    quint32 timestamp = 1;
    QFETCH(bool, capsLock);
    if (capsLock) {
        kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
    }
    QFETCH(int, modifierKey);
    kwinApp()->platform()->keyboardKeyPressed(modifierKey, timestamp++);
    kwinApp()->platform()->pointerAxisVertical(-5, timestamp++);
    QCOMPARE(c->opacity(), 0.6);
    kwinApp()->platform()->pointerAxisVertical(5, timestamp++);
    QCOMPARE(c->opacity(), 0.5);
    kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++);
    if (capsLock) {
        kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
    }
}

737 738
void DecorationInputTest::testTouchEvents_data()
{
739
    QTest::addColumn<Test::XdgShellSurfaceType>("type");
740

741
    QTest::newRow("xdgWmBase") << Test::XdgShellSurfaceType::XdgShellStable;
742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
}

class EventHelper : public QObject
{
    Q_OBJECT
public:
    EventHelper() : QObject() {}
    ~EventHelper() override = default;

    bool eventFilter(QObject *watched, QEvent *event) override
    {
        Q_UNUSED(watched)
        if (event->type() == QEvent::HoverMove) {
            emit hoverMove();
        } else if (event->type() == QEvent::HoverLeave) {
            emit hoverLeave();
        }
        return false;
    }

Q_SIGNALS:
    void hoverMove();
    void hoverLeave();
};

void DecorationInputTest::testTouchEvents()
{
    // this test verifies that the decoration gets a hover leave event on touch release
    // see BUG 386231
771
    QFETCH(Test::XdgShellSurfaceType, type);
772 773 774 775 776 777 778 779 780 781 782 783 784
    AbstractClient *c = showWindow(type);
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());

    EventHelper helper;
    c->decoration()->installEventFilter(&helper);
    QSignalSpy hoverMoveSpy(&helper, &EventHelper::hoverMove);
    QVERIFY(hoverMoveSpy.isValid());
    QSignalSpy hoverLeaveSpy(&helper, &EventHelper::hoverLeave);
    QVERIFY(hoverLeaveSpy.isValid());

    quint32 timestamp = 1;
785
    const QPoint tapPoint(c->frameGeometry().center().x(), c->clientPos().y() / 2);
786 787 788 789 790 791 792 793 794 795 796 797 798 799

    QVERIFY(!input()->touch()->decoration());
    kwinApp()->platform()->touchDown(0, tapPoint, timestamp++);
    QVERIFY(input()->touch()->decoration());
    QCOMPARE(input()->touch()->decoration()->decoration(), c->decoration());
    QCOMPARE(hoverMoveSpy.count(), 1);
    QCOMPARE(hoverLeaveSpy.count(), 0);
    kwinApp()->platform()->touchUp(0, timestamp++);
    QCOMPARE(hoverMoveSpy.count(), 1);
    QCOMPARE(hoverLeaveSpy.count(), 1);

    QCOMPARE(c->isMove(), false);

    // let's check that a hover motion is sent if the pointer is on deco, when touch release
800
    Cursors::self()->mouse()->setPos(tapPoint);
801 802 803 804 805
    QCOMPARE(hoverMoveSpy.count(), 2);
    kwinApp()->platform()->touchDown(0, tapPoint, timestamp++);
    QCOMPARE(hoverMoveSpy.count(), 3);
    QCOMPARE(hoverLeaveSpy.count(), 1);
    kwinApp()->platform()->touchUp(0, timestamp++);
806 807
    QCOMPARE(hoverMoveSpy.count(), 3);
    QCOMPARE(hoverLeaveSpy.count(), 2);
808 809
}

810 811
void DecorationInputTest::testTooltipDoesntEatKeyEvents_data()
{
812
    QTest::addColumn<Test::XdgShellSurfaceType>("type");
813

814
    QTest::newRow("xdgWmBase") << Test::XdgShellSurfaceType::XdgShellStable;
815 816 817 818 819 820 821 822 823 824 825 826 827
}

void DecorationInputTest::testTooltipDoesntEatKeyEvents()
{
    // this test verifies that a tooltip on the decoration does not steal key events
    // BUG: 393253

    // first create a keyboard
    auto keyboard = Test::waylandSeat()->createKeyboard(Test::waylandSeat());
    QVERIFY(keyboard);
    QSignalSpy enteredSpy(keyboard, &KWayland::Client::Keyboard::entered);
    QVERIFY(enteredSpy.isValid());

828
    QFETCH(Test::XdgShellSurfaceType, type);
829 830 831 832 833 834 835 836 837
    AbstractClient *c = showWindow(type);
    QVERIFY(c);
    QVERIFY(c->isDecorated());
    QVERIFY(!c->noBorder());
    QTRY_COMPARE(enteredSpy.count(), 1);

    QSignalSpy keyEvent(keyboard, &KWayland::Client::Keyboard::keyChanged);
    QVERIFY(keyEvent.isValid());

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
838
    QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded);
839 840 841 842
    QVERIFY(clientAddedSpy.isValid());
    c->decoratedClient()->requestShowToolTip(QStringLiteral("test"));
    // now we should get an internal window
    QVERIFY(clientAddedSpy.wait());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
843
    InternalClient *internal = clientAddedSpy.first().first().value<InternalClient *>();
844 845 846 847 848 849 850 851 852 853 854 855 856 857
    QVERIFY(internal->isInternal());
    QVERIFY(internal->internalWindow()->flags().testFlag(Qt::ToolTip));

    // now send a key
    quint32 timestamp = 0;
    kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++);
    QVERIFY(keyEvent.wait());
    kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++);
    QVERIFY(keyEvent.wait());

    c->decoratedClient()->requestHideToolTip();
    Test::waitForWindowDestroyed(internal);
}

858 859
}

Martin Flöser's avatar
Martin Flöser committed
860
WAYLANDTEST_MAIN(KWin::DecorationInputTest)
861
#include "decoration_input_test.moc"