ButtonRebindsFilter: Support keyboard modifiers with mouse buttons
This is needed to better support emitting mouse button events when pressing tablet buttons. It's common in many art programs that an action is tied to a mouse button + a modifier, such as panning the canvas. So being able to send keyboard modifiers in tandem with mouse buttons is very useful when rebinding. Tests are added for this new feature. CCBUG: 469232
This commit is contained in:
parent
d01e20b6a9
commit
34c2a36000
3 changed files with 103 additions and 2 deletions
|
@ -37,6 +37,9 @@ private Q_SLOTS:
|
|||
void testMouse_data();
|
||||
void testMouse();
|
||||
|
||||
void testMouseKeyboardMod_data();
|
||||
void testMouseKeyboardMod();
|
||||
|
||||
void testDisabled();
|
||||
|
||||
// NOTE: Mouse buttons are not tested because those are used in the other tests
|
||||
|
@ -157,6 +160,67 @@ void TestButtonRebind::testMouse()
|
|||
Test::pointerButtonReleased(0x119, timestamp++);
|
||||
}
|
||||
|
||||
void TestButtonRebind::testMouseKeyboardMod_data()
|
||||
{
|
||||
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
|
||||
QTest::addColumn<QList<quint32>>("expectedKeys");
|
||||
|
||||
QTest::newRow("single ctrl") << Qt::KeyboardModifiers(Qt::ControlModifier) << QList<quint32>{KEY_LEFTCTRL};
|
||||
QTest::newRow("single alt") << Qt::KeyboardModifiers(Qt::AltModifier) << QList<quint32>{KEY_LEFTALT};
|
||||
QTest::newRow("single shift") << Qt::KeyboardModifiers(Qt::ShiftModifier) << QList<quint32>{KEY_LEFTSHIFT};
|
||||
|
||||
// We have to test Meta with another key, because it will most likely trigger KWin to do some window operation.
|
||||
QTest::newRow("meta + alt") << Qt::KeyboardModifiers(Qt::MetaModifier | Qt::AltModifier) << QList<quint32>{KEY_LEFTALT, KEY_LEFTMETA};
|
||||
|
||||
QTest::newRow("ctrl + alt + shift + meta") << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::AltModifier | Qt::ShiftModifier | Qt::MetaModifier) << QList<quint32>{KEY_LEFTSHIFT, KEY_LEFTCTRL, KEY_LEFTALT, KEY_LEFTMETA};
|
||||
}
|
||||
|
||||
void TestButtonRebind::testMouseKeyboardMod()
|
||||
{
|
||||
QFETCH(Qt::KeyboardModifiers, modifiers);
|
||||
|
||||
KConfigGroup buttonGroup = KSharedConfig::openConfig(QStringLiteral("kcminputrc"))->group(QStringLiteral("ButtonRebinds")).group(QStringLiteral("TabletTool")).group(QStringLiteral("Virtual Tablet Tool 1"));
|
||||
buttonGroup.writeEntry(QString::number(BTN_STYLUS), QStringList{"MouseButton", QString::number(BTN_LEFT), QString::number(modifiers.toInt())}, KConfig::Notify);
|
||||
buttonGroup.sync();
|
||||
|
||||
std::unique_ptr<KWayland::Client::Keyboard> keyboard(Test::waylandSeat()->createKeyboard());
|
||||
QSignalSpy keyboardEnteredSpy(keyboard.get(), &KWayland::Client::Keyboard::entered);
|
||||
QSignalSpy keyboardKeyChangedSpy(keyboard.get(), &KWayland::Client::Keyboard::keyChanged);
|
||||
|
||||
std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
|
||||
std::unique_ptr<Test::XdgToplevel> shellSurface = Test::createXdgToplevelSurface(surface.get());
|
||||
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
|
||||
QVERIFY(keyboardEnteredSpy.wait());
|
||||
|
||||
std::unique_ptr<KWayland::Client::Pointer> pointer(Test::waylandSeat()->createPointer());
|
||||
QSignalSpy pointerEnteredSpy(pointer.get(), &KWayland::Client::Pointer::entered);
|
||||
QSignalSpy pointerButtonChangedSpy(pointer.get(), &KWayland::Client::Pointer::buttonStateChanged);
|
||||
|
||||
const QRectF startGeometry = window->frameGeometry();
|
||||
|
||||
input()->pointer()->warp(startGeometry.center());
|
||||
QVERIFY(pointerEnteredSpy.wait());
|
||||
|
||||
// Send the tablet button event so it can be processed by the filter
|
||||
Test::tabletToolButtonPressed(BTN_STYLUS, timestamp++);
|
||||
|
||||
// The keyboard modifier is sent first
|
||||
QVERIFY(keyboardKeyChangedSpy.wait());
|
||||
|
||||
QFETCH(QList<quint32>, expectedKeys);
|
||||
QCOMPARE(keyboardKeyChangedSpy.count(), expectedKeys.count());
|
||||
for (int i = 0; i < keyboardKeyChangedSpy.count(); i++) {
|
||||
QCOMPARE(keyboardKeyChangedSpy.at(i).at(0).value<quint32>(), expectedKeys.at(i));
|
||||
QCOMPARE(keyboardKeyChangedSpy.at(i).at(1).value<KWayland::Client::Keyboard::KeyState>(), KWayland::Client::Keyboard::KeyState::Pressed);
|
||||
}
|
||||
|
||||
// Then the mouse button is
|
||||
QCOMPARE(pointerButtonChangedSpy.count(), 1);
|
||||
QCOMPARE(pointerButtonChangedSpy.at(0).at(2).value<qint32>(), BTN_LEFT);
|
||||
|
||||
Test::tabletToolButtonReleased(BTN_STYLUS, timestamp++);
|
||||
}
|
||||
|
||||
void TestButtonRebind::testDisabled()
|
||||
{
|
||||
KConfigGroup buttonGroup = KSharedConfig::openConfig(QStringLiteral("kcminputrc"))->group(QStringLiteral("ButtonRebinds")).group(QStringLiteral("Mouse"));
|
||||
|
|
|
@ -248,13 +248,20 @@ void ButtonRebindsFilter::insert(TriggerType type, const Trigger &trigger, const
|
|||
m_actions.at(type).insert(trigger, keys);
|
||||
}
|
||||
} else if (entry.first() == QLatin1String("MouseButton")) {
|
||||
if (entry.size() != 2) {
|
||||
if (entry.size() < 2) {
|
||||
qCWarning(KWIN_BUTTONREBINDS) << "Invalid mouse button" << entry;
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
const MouseButton mb{entry.last().toUInt(&ok)};
|
||||
MouseButton mb{entry[1].toUInt(&ok), {}};
|
||||
|
||||
// Last bit is the keyboard mods
|
||||
if (entry.size() == 3) {
|
||||
const auto keyboardModsRaw = entry.last().toInt(&ok);
|
||||
mb.modifiers = Qt::KeyboardModifiers{keyboardModsRaw};
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
m_actions.at(type).insert(trigger, mb);
|
||||
} else {
|
||||
|
@ -294,6 +301,7 @@ bool ButtonRebindsFilter::send(TriggerType type, const Trigger &trigger, bool pr
|
|||
if (pressed && type != Pointer) {
|
||||
sendMousePosition(m_tabletCursorPos, timestamp);
|
||||
}
|
||||
sendKeyModifiers(mb->modifiers, pressed, timestamp);
|
||||
return sendMouseButton(mb->button, pressed, timestamp);
|
||||
}
|
||||
if (const auto tb = std::get_if<TabletToolButton>(&action)) {
|
||||
|
@ -384,6 +392,33 @@ bool ButtonRebindsFilter::sendKeySequence(const QKeySequence &keys, bool pressed
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ButtonRebindsFilter::sendKeyModifiers(const Qt::KeyboardModifiers &modifiers, bool pressed, std::chrono::microseconds time)
|
||||
{
|
||||
if (modifiers == Qt::NoModifier) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sendKey = [this, pressed, time](xkb_keycode_t key) {
|
||||
auto state = pressed ? KWin::InputRedirection::KeyboardKeyPressed : KWin::InputRedirection::KeyboardKeyReleased;
|
||||
Q_EMIT m_inputDevice.keyChanged(key, state, time, &m_inputDevice);
|
||||
};
|
||||
|
||||
if (modifiers.testFlag(Qt::ShiftModifier)) {
|
||||
sendKey(KEY_LEFTSHIFT);
|
||||
}
|
||||
if (modifiers.testFlag(Qt::ControlModifier)) {
|
||||
sendKey(KEY_LEFTCTRL);
|
||||
}
|
||||
if (modifiers.testFlag(Qt::AltModifier)) {
|
||||
sendKey(KEY_LEFTALT);
|
||||
}
|
||||
if (modifiers.testFlag(Qt::MetaModifier)) {
|
||||
sendKey(KEY_LEFTMETA);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ButtonRebindsFilter::sendMouseButton(quint32 button, bool pressed, std::chrono::microseconds time)
|
||||
{
|
||||
RebindScope scope;
|
||||
|
|
|
@ -67,6 +67,7 @@ public:
|
|||
struct MouseButton
|
||||
{
|
||||
quint32 button;
|
||||
Qt::KeyboardModifiers modifiers;
|
||||
};
|
||||
struct DisabledButton
|
||||
{
|
||||
|
@ -83,6 +84,7 @@ private:
|
|||
void insert(TriggerType type, const Trigger &trigger, const QStringList &action);
|
||||
bool send(TriggerType type, const Trigger &trigger, bool pressed, std::chrono::microseconds timestamp);
|
||||
bool sendKeySequence(const QKeySequence &sequence, bool pressed, std::chrono::microseconds time);
|
||||
bool sendKeyModifiers(const Qt::KeyboardModifiers &modifiers, bool pressed, std::chrono::microseconds time);
|
||||
bool sendMouseButton(quint32 button, bool pressed, std::chrono::microseconds time);
|
||||
bool sendMousePosition(QPointF position, std::chrono::microseconds time);
|
||||
bool sendTabletToolButton(quint32 button, bool pressed, std::chrono::microseconds time);
|
||||
|
|
Loading…
Reference in a new issue