From 87578bfc15c281279eec8261c3c7969c2b2163b4 Mon Sep 17 00:00:00 2001 From: Nate Graham Date: Fri, 12 Jun 2020 13:50:24 +0000 Subject: [PATCH] Allow corner-tiling by quickly combining edge tiling shortcuts Currently the only way for a uuser to invoke corner-tiling is to manually set shortcuts for them. This patch adds another option: invoke the existing shortcuts for edge tiling in a combined manner in quick succession. For example, hitting Meta+Left and then Meta+Up within a one-second period will tile the active window into the top left corner. In practice you hold down the Meta key and then press Left then Up (or Up and then Left), and I think it feels very natural. Linux Mint's window manager has this feature and I always missed it when I left Mint for the KDE world. Autotests for existing tiling shortcuts are adjusted to not break, and additional tests for the new tiling options are added. --- autotests/integration/quick_tiling_test.cpp | 58 ++++++++++++--------- placement.cpp | 19 +++++++ workspace.cpp | 5 ++ workspace.h | 2 + 4 files changed, 60 insertions(+), 24 deletions(-) diff --git a/autotests/integration/quick_tiling_test.cpp b/autotests/integration/quick_tiling_test.cpp index 5e54977d4a..2cb1c74549 100644 --- a/autotests/integration/quick_tiling_test.cpp +++ b/autotests/integration/quick_tiling_test.cpp @@ -709,21 +709,25 @@ void QuickTilingTest::testX11QuickTilingAfterVertMaximize() void QuickTilingTest::testShortcut_data() { - QTest::addColumn("shortcut"); + QTest::addColumn("shortcutList"); QTest::addColumn("expectedMode"); QTest::addColumn("expectedGeometry"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) - QTest::newRow("top") << QStringLiteral("Window Quick Tile Top") << FLAG(Top) << QRect(0, 0, 1280, 512); - QTest::newRow("left") << QStringLiteral("Window Quick Tile Left") << FLAG(Left) << QRect(0, 0, 640, 1024); - QTest::newRow("bottom") << QStringLiteral("Window Quick Tile Bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); - QTest::newRow("right") << QStringLiteral("Window Quick Tile Right") << FLAG(Right) << QRect(640, 0, 640, 1024); - - QTest::newRow("top right") << QStringLiteral("Window Quick Tile Top Right") << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); - QTest::newRow("top left") << QStringLiteral("Window Quick Tile Top Left") << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); - QTest::newRow("bottom right") << QStringLiteral("Window Quick Tile Bottom Right") << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); - QTest::newRow("bottom left") << QStringLiteral("Window Quick Tile Bottom Left") << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); + QTest::newRow("top") << QStringList{QStringLiteral("Window Quick Tile Top")} << FLAG(Top) << QRect(0, 0, 1280, 512); + QTest::newRow("bottom") << QStringList{QStringLiteral("Window Quick Tile Bottom")} << FLAG(Bottom) << QRect(0, 512, 1280, 512); + QTest::newRow("top right") << QStringList{QStringLiteral("Window Quick Tile Top Right")} << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); + QTest::newRow("top left") << QStringList{QStringLiteral("Window Quick Tile Top Left")} << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); + QTest::newRow("bottom right") << QStringList{QStringLiteral("Window Quick Tile Bottom Right")} << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); + QTest::newRow("bottom left") << QStringList{QStringLiteral("Window Quick Tile Bottom Left")} << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); + QTest::newRow("left") << QStringList{QStringLiteral("Window Quick Tile Left")} << FLAG(Left) << QRect(0, 0, 640, 1024); + QTest::newRow("right") << QStringList{QStringLiteral("Window Quick Tile Right")} << FLAG(Right) << QRect(640, 0, 640, 1024); + // Test combined actions for corner tiling + QTest::newRow("top left combined") << QStringList{QStringLiteral("Window Quick Tile Left"), QStringLiteral("Window Quick Tile Top")} << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); + QTest::newRow("top right combined") << QStringList{QStringLiteral("Window Quick Tile Right"), QStringLiteral("Window Quick Tile Top")} << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); + QTest::newRow("bottom left combined") << QStringList{QStringLiteral("Window Quick Tile Left"), QStringLiteral("Window Quick Tile Bottom")} << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); + QTest::newRow("bottom right combined") << QStringList{QStringLiteral("Window Quick Tile Right"), QStringLiteral("Window Quick Tile Bottom")} << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); #undef FLAG } @@ -749,22 +753,30 @@ void QuickTilingTest::testShortcut() QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); - QFETCH(QString, shortcut); + QFETCH(QStringList, shortcutList); QFETCH(QRect, expectedGeometry); - // invoke global shortcut through dbus - auto msg = QDBusMessage::createMethodCall( - QStringLiteral("org.kde.kglobalaccel"), - QStringLiteral("/component/kwin"), - QStringLiteral("org.kde.kglobalaccel.Component"), - QStringLiteral("invokeShortcut")); - msg.setArguments(QList{shortcut}); - QDBusConnection::sessionBus().asyncCall(msg); + const int numberOfQuickTileActions = shortcutList.count(); + + if (numberOfQuickTileActions > 1) { + QTest::qWait(1001); + } + + for (QString shortcut : shortcutList) { + // invoke global shortcut through dbus + auto msg = QDBusMessage::createMethodCall( + QStringLiteral("org.kde.kglobalaccel"), + QStringLiteral("/component/kwin"), + QStringLiteral("org.kde.kglobalaccel.Component"), + QStringLiteral("invokeShortcut")); + msg.setArguments(QList{shortcut}); + QDBusConnection::sessionBus().asyncCall(msg); + } QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QVERIFY(quickTileChangedSpy.wait()); - QCOMPARE(quickTileChangedSpy.count(), 1); + QCOMPARE(quickTileChangedSpy.count(), numberOfQuickTileActions); // at this point the geometry did not yet change QCOMPARE(c->frameGeometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed @@ -795,15 +807,13 @@ void QuickTilingTest::testScript_data() #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("top") << QStringLiteral("Top") << FLAG(Top) << QRect(0, 0, 1280, 512); - QTest::newRow("left") << QStringLiteral("Left") << FLAG(Left) << QRect(0, 0, 640, 1024); QTest::newRow("bottom") << QStringLiteral("Bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); - QTest::newRow("right") << QStringLiteral("Right") << FLAG(Right) << QRect(640, 0, 640, 1024); - QTest::newRow("top right") << QStringLiteral("TopRight") << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); QTest::newRow("top left") << QStringLiteral("TopLeft") << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); QTest::newRow("bottom right") << QStringLiteral("BottomRight") << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); QTest::newRow("bottom left") << QStringLiteral("BottomLeft") << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); - + QTest::newRow("left") << QStringLiteral("Left") << FLAG(Left) << QRect(0, 0, 640, 1024); + QTest::newRow("right") << QStringLiteral("Right") << FLAG(Right) << QRect(640, 0, 640, 1024); #undef FLAG } diff --git a/placement.cpp b/placement.cpp index f15860f525..a0fe02ac83 100644 --- a/placement.cpp +++ b/placement.cpp @@ -33,6 +33,7 @@ along with this program. If not, see . #include #include +#include namespace KWin { @@ -837,6 +838,24 @@ void Workspace::quickTileWindow(QuickTileMode mode) return; } + // If the user invokes two of these commands in a one second period, try to + // combine them together to enable easy and intuitive corner tiling +#define FLAG(name) QuickTileMode(QuickTileFlag::name) + if (!m_quickTileCombineTimer->isActive()) { + m_quickTileCombineTimer->start(1000); + m_lastTilingMode = mode; + } else { + if ( + ( (m_lastTilingMode == FLAG(Left) || m_lastTilingMode == FLAG(Right)) && (mode == FLAG(Top) || mode == FLAG(Bottom)) ) + || + ( (m_lastTilingMode == FLAG(Top) || m_lastTilingMode == FLAG(Bottom)) && (mode == FLAG(Left) || mode == FLAG(Right)) ) +#undef FLAG + ) { + mode |= m_lastTilingMode; + } + m_quickTileCombineTimer->stop(); + } + active_client->setQuickTileMode(mode, true); } diff --git a/workspace.cpp b/workspace.cpp index 7b4c6a84d6..bf9b100beb 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -129,6 +129,8 @@ Workspace::Workspace() , set_active_client_recursion(0) , block_stacking_updates(0) , m_sessionManager(new SessionManager(this)) + , m_quickTileCombineTimer(nullptr) + , m_lastTilingMode(0) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); @@ -155,6 +157,9 @@ Workspace::Workspace() delayFocusTimer = nullptr; + m_quickTileCombineTimer = new QTimer(this); + m_quickTileCombineTimer->setSingleShot(true); + RuleBook::create(this)->load(); kwinApp()->createScreens(); diff --git a/workspace.h b/workspace.h index 831084e088..e6884d6b92 100644 --- a/workspace.h +++ b/workspace.h @@ -250,6 +250,8 @@ public: private: Compositor *m_compositor; + QTimer *m_quickTileCombineTimer; + QuickTileMode m_lastTilingMode; //------------------------------------------------- // Unsorted