Mouse: Add support to swap mouse primary button
Bug: 359349392
Bug: 352598211
Test: atest CursorInputMapperUnitTest
Flag: com.android.hardware.input.mouse_swap_primary_button
Change-Id: I37d70bc0ce37fc0832e6733a0a1c8936cab36ab1
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 69ae587..756a29b 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -96,7 +96,8 @@
// The key remapping has changed.
KEY_REMAPPING = 1u << 14,
- // The mouse settings changed, this includes mouse reverse vertical scrolling.
+ // The mouse settings changed, this includes mouse reverse vertical scrolling and swap
+ // primary button.
MOUSE_SETTINGS = 1u << 15,
// All devices must be reopened.
@@ -259,6 +260,11 @@
// wheel downwards scrolls the content upwards.
bool mouseReverseVerticalScrollingEnabled;
+ // True if the connected mouse should have its primary button (default: left click) swapped,
+ // so that the right click will be the primary action button and the left click will be the
+ // secondary action.
+ bool mouseSwapPrimaryButtonEnabled;
+
InputReaderConfiguration()
: virtualKeyQuietTime(0),
defaultPointerDisplayId(ui::LogicalDisplayId::DEFAULT),
@@ -290,7 +296,8 @@
touchpadRightClickZoneEnabled(false),
stylusButtonMotionEventsEnabled(true),
stylusPointerIconEnabled(false),
- mouseReverseVerticalScrollingEnabled(false) {}
+ mouseReverseVerticalScrollingEnabled(false),
+ mouseSwapPrimaryButtonEnabled(false) {}
std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const;
std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueDisplayId)
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 302caa7..630bd9b 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -548,6 +548,7 @@
void CursorInputMapper::configureOnChangeMouseSettings(const InputReaderConfiguration& config) {
mMouseReverseVerticalScrolling = config.mouseReverseVerticalScrollingEnabled;
+ mCursorButtonAccumulator.setSwapLeftRightButtons(config.mouseSwapPrimaryButtonEnabled);
}
} // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
index 9e722d4..456562c 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
@@ -47,6 +47,10 @@
mBtnTask = 0;
}
+void CursorButtonAccumulator::setSwapLeftRightButtons(bool shouldSwap) {
+ mSwapLeftRightButtons = shouldSwap;
+}
+
void CursorButtonAccumulator::process(const RawEvent& rawEvent) {
if (rawEvent.type == EV_KEY) {
switch (rawEvent.code) {
@@ -81,10 +85,12 @@
uint32_t CursorButtonAccumulator::getButtonState() const {
uint32_t result = 0;
if (mBtnLeft) {
- result |= AMOTION_EVENT_BUTTON_PRIMARY;
+ result |= mSwapLeftRightButtons ? AMOTION_EVENT_BUTTON_SECONDARY
+ : AMOTION_EVENT_BUTTON_PRIMARY;
}
if (mBtnRight) {
- result |= AMOTION_EVENT_BUTTON_SECONDARY;
+ result |= mSwapLeftRightButtons ? AMOTION_EVENT_BUTTON_PRIMARY
+ : AMOTION_EVENT_BUTTON_SECONDARY;
}
if (mBtnMiddle) {
result |= AMOTION_EVENT_BUTTON_TERTIARY;
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
index 256b2bb..6990030 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
@@ -41,6 +41,8 @@
inline bool isExtraPressed() const { return mBtnExtra; }
inline bool isTaskPressed() const { return mBtnTask; }
+ void setSwapLeftRightButtons(bool shouldSwap);
+
private:
bool mBtnLeft;
bool mBtnRight;
@@ -51,6 +53,8 @@
bool mBtnExtra;
bool mBtnTask;
+ bool mSwapLeftRightButtons = false;
+
void clearButtons();
};
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index a43b64c..1762a45 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -205,9 +205,14 @@
args.clear();
args += process(EV_KEY, BTN_LEFT, 1);
args += process(EV_SYN, SYN_REPORT, 0);
+
ASSERT_THAT(args,
ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
- VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS))));
+ VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY)))));
+ ASSERT_THAT(args,
+ Each(VariantWith<NotifyMotionArgs>(WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))));
// Move some more.
args.clear();
@@ -221,9 +226,76 @@
args += process(EV_KEY, BTN_LEFT, 0);
args += process(EV_SYN, SYN_REPORT, 0);
ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+}
+
+/**
+ * Test that enabling mouse swap primary button will have the left click result in a
+ * `SECONDARY_BUTTON` event and a right click will result in a `PRIMARY_BUTTON` event.
+ */
+TEST_F(CursorInputMapperUnitTest, SwappedPrimaryButtonPress) {
+ mReaderConfiguration.mouseSwapPrimaryButtonEnabled = true;
+ createMapper();
+ std::list<NotifyArgs> args;
+
+ // Now click the left mouse button , expect a `SECONDARY_BUTTON` button state.
+ args.clear();
+ args += process(EV_KEY, BTN_LEFT, 1);
+ args += process(EV_SYN, SYN_REPORT, 0);
+
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+ VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY)))));
+ ASSERT_THAT(args,
+ Each(VariantWith<NotifyMotionArgs>(
+ WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY))));
+
+ // Release the left button.
+ args.clear();
+ args += process(EV_KEY, BTN_LEFT, 0);
+ args += process(EV_SYN, SYN_REPORT, 0);
+
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY))),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+
+ // Now click the right mouse button , expect a `PRIMARY_BUTTON` button state.
+ args.clear();
+ args += process(EV_KEY, BTN_RIGHT, 1);
+ args += process(EV_SYN, SYN_REPORT, 0);
+
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+ VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY)))));
+ ASSERT_THAT(args,
+ Each(VariantWith<NotifyMotionArgs>(WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))));
+
+ // Release the right button.
+ args.clear();
+ args += process(EV_KEY, BTN_RIGHT, 0);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
}
/**