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.
This commit is contained in:
Nate Graham 2020-06-12 13:50:24 +00:00
parent e9c68f36bd
commit 87578bfc15
4 changed files with 60 additions and 24 deletions

View file

@ -709,21 +709,25 @@ void QuickTilingTest::testX11QuickTilingAfterVertMaximize()
void QuickTilingTest::testShortcut_data()
{
QTest::addColumn<QString>("shortcut");
QTest::addColumn<QStringList>("shortcutList");
QTest::addColumn<QuickTileMode>("expectedMode");
QTest::addColumn<QRect>("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<QVariant>{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<QVariant>{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
}

View file

@ -33,6 +33,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QRect>
#include <QTextStream>
#include <QTimer>
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);
}

View file

@ -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<void> 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();

View file

@ -250,6 +250,8 @@ public:
private:
Compositor *m_compositor;
QTimer *m_quickTileCombineTimer;
QuickTileMode m_lastTilingMode;
//-------------------------------------------------
// Unsorted