From dd438e2a4b541fbe88b85346b450a1106b799109 Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Tue, 13 Feb 2024 11:47:54 +0100 Subject: Virtual rotary encoder native support - detect rotary encoder input devices if they have a single scroll axis and nothing else - make the rotary mapper display-aware as the virtual rotary is constrained to a single virtual display, just like the other virtual input devices Fix: 320328752 Test: atest inputflinger_tests Test: see CTS in topic Flag: android.companion.virtualdevice.flags.virtual_rotary Change-Id: I5581013d06708cbcc2c3ac8a622cd259aea8a9b4 --- libs/input/VirtualInputDevice.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'libs/input/VirtualInputDevice.cpp') diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp index eea06f1720..b73ee65504 100644 --- a/libs/input/VirtualInputDevice.cpp +++ b/libs/input/VirtualInputDevice.cpp @@ -509,4 +509,15 @@ bool VirtualStylus::handleStylusUp(uint16_t tool, std::chrono::nanoseconds event return true; } +// --- VirtualRotaryEncoder --- +VirtualRotaryEncoder::VirtualRotaryEncoder(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +VirtualRotaryEncoder::~VirtualRotaryEncoder() {} + +bool VirtualRotaryEncoder::writeScrollEvent(float scrollAmount, + std::chrono::nanoseconds eventTime) { + return writeInputEvent(EV_REL, REL_WHEEL, static_cast(scrollAmount), eventTime) && + writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); +} + } // namespace android -- cgit v1.2.3-59-g8ed1b From 8ff5e5ecde93d80549208f25563f351da90a8dfc Mon Sep 17 00:00:00 2001 From: Biswarup Pal Date: Sat, 15 Jun 2024 12:58:20 +0000 Subject: Support high-resolution scroll VirtualMouse currently supports -1f to 1f float scroll values, but it worked for integer values only as input framework supported only REL_HWHEEL and REL_WHEEL events. With the introduction of high-res scroll event support (REL_HWHEEL_HI_RES and REL_WHEEL_HI_RES), granular mouse scrolling can be done, and VirtualMouse scroll API would work for all float values. Flag: android.companion.virtualdevice.flags.high_resolution_scroll Test: atest VirtualMouseTest Bug: 335160780 Change-Id: I7b13ac1722b6fd31736fe1c0117d4de6e838261a --- include/input/Input.h | 7 ++ include/input/VirtualInputDevice.h | 2 + libs/input/Android.bp | 1 + libs/input/VirtualInputDevice.cpp | 48 ++++++- services/inputflinger/reader/Android.bp | 3 +- .../inputflinger/reader/mapper/TouchInputMapper.h | 4 +- .../mapper/accumulator/CursorScrollAccumulator.cpp | 37 +++++- .../mapper/accumulator/CursorScrollAccumulator.h | 11 +- services/inputflinger/tests/Android.bp | 1 + .../inputflinger/tests/CursorInputMapper_test.cpp | 139 +++++++++++++++++++++ .../tests/RotaryEncoderInputMapper_test.cpp | 4 + services/inputflinger/tests/TestEventMatchers.h | 9 ++ 12 files changed, 251 insertions(+), 15 deletions(-) (limited to 'libs/input/VirtualInputDevice.cpp') diff --git a/include/input/Input.h b/include/input/Input.h index 456977bb34..17672d11f7 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -195,6 +195,13 @@ static constexpr size_t MAX_POINTERS = 16; */ #define MAX_POINTER_ID 31 +/* + * Number of high resolution mouse scroll units for one detent (mouse wheel click), as defined in + * evdev. This is relevant when an input device is emitting REL_WHEEL_HI_RES or REL_HWHEEL_HI_RES + * events. + */ +constexpr int32_t kEvdevMouseHighResScrollUnitsPerDetent = 120; + /* * Declare a concrete type for the NDK's input event forward declaration. */ diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h index 9bbaa0c5ec..9fbae73167 100644 --- a/include/input/VirtualInputDevice.h +++ b/include/input/VirtualInputDevice.h @@ -77,6 +77,8 @@ public: private: static const std::map BUTTON_CODE_MAPPING; + int32_t mAccumulatedHighResScrollX; + int32_t mAccumulatedHighResScrollY; }; class VirtualTouchscreen : public VirtualInputDevice { diff --git a/libs/input/Android.bp b/libs/input/Android.bp index c2a7ebb614..45ebc66ddc 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -258,6 +258,7 @@ cc_library { ], shared_libs: [ + "android.companion.virtualdevice.flags-aconfig-cc-host", "libbase", "libbinder", "libbinder_ndk", diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp index b73ee65504..2e3e1a09b8 100644 --- a/libs/input/VirtualInputDevice.cpp +++ b/libs/input/VirtualInputDevice.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,8 @@ static bool isDebug() { namespace android { +namespace vd_flags = android::companion::virtualdevice::flags; + VirtualInputDevice::VirtualInputDevice(unique_fd fd) : mFd(std::move(fd)) {} VirtualInputDevice::~VirtualInputDevice() { @@ -253,7 +256,10 @@ const std::map VirtualMouse::BUTTON_CODE_MAPPING = { // clang-format on }; -VirtualMouse::VirtualMouse(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} +VirtualMouse::VirtualMouse(unique_fd fd) + : VirtualInputDevice(std::move(fd)), + mAccumulatedHighResScrollX(0), + mAccumulatedHighResScrollY(0) {} VirtualMouse::~VirtualMouse() {} @@ -272,9 +278,43 @@ bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY, bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement, std::chrono::nanoseconds eventTime) { - return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement, eventTime) && - writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement, eventTime) && - writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); + if (!vd_flags::high_resolution_scroll()) { + return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement, eventTime) && + writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement, eventTime) && + writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); + } + + const int32_t highResScrollX = xAxisMovement * kEvdevMouseHighResScrollUnitsPerDetent; + const int32_t highResScrollY = yAxisMovement * kEvdevMouseHighResScrollUnitsPerDetent; + bool highResScrollResult = + writeInputEvent(EV_REL, REL_HWHEEL_HI_RES, highResScrollX, eventTime) && + writeInputEvent(EV_REL, REL_WHEEL_HI_RES, highResScrollY, eventTime); + if (!highResScrollResult) { + return false; + } + + // According to evdev spec, a high-resolution mouse needs to emit REL_WHEEL / REL_HWHEEL events + // in addition to high-res scroll events. Regular scroll events can approximate high-res scroll + // events, so we send a regular scroll event when the accumulated scroll motion reaches a detent + // (single mouse wheel click). + mAccumulatedHighResScrollX += highResScrollX; + mAccumulatedHighResScrollY += highResScrollY; + const int32_t scrollX = mAccumulatedHighResScrollX / kEvdevMouseHighResScrollUnitsPerDetent; + const int32_t scrollY = mAccumulatedHighResScrollY / kEvdevMouseHighResScrollUnitsPerDetent; + if (scrollX != 0) { + if (!writeInputEvent(EV_REL, REL_HWHEEL, scrollX, eventTime)) { + return false; + } + mAccumulatedHighResScrollX %= kEvdevMouseHighResScrollUnitsPerDetent; + } + if (scrollY != 0) { + if (!writeInputEvent(EV_REL, REL_WHEEL, scrollY, eventTime)) { + return false; + } + mAccumulatedHighResScrollY %= kEvdevMouseHighResScrollUnitsPerDetent; + } + + return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); } // --- VirtualTouchscreen --- diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index b2f15b400d..a052a4eb83 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -78,7 +78,6 @@ cc_defaults { name: "libinputreader_defaults", srcs: [":libinputreader_sources"], shared_libs: [ - "android.companion.virtualdevice.flags-aconfig-cc-host", "libbase", "libcap", "libcrypto", @@ -116,6 +115,7 @@ cc_library_static { "libinputreader_defaults", ], shared_libs: [ + "android.companion.virtualdevice.flags-aconfig-cc-host", "libinputflinger_base", ], export_header_lib_headers: [ @@ -141,6 +141,7 @@ cc_library_shared { shared_libs: [ // This should consist only of dependencies from inputflinger. Other dependencies should be // in cc_defaults so that they are included in the tests. + "android.companion.virtualdevice.flags-aconfig-cc-host", "libinputflinger_base", "libjsoncpp", ], diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index beab6e7179..87b72afe7c 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -339,8 +339,8 @@ protected: int32_t buttonState{}; // Scroll state. - int32_t rawVScroll{}; - int32_t rawHScroll{}; + float rawVScroll{}; + float rawHScroll{}; inline void clear() { *this = RawState(); } }; diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp index f85cab205b..06315e2259 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp @@ -16,18 +16,29 @@ #include "CursorScrollAccumulator.h" +#include #include "EventHub.h" #include "InputDevice.h" namespace android { -CursorScrollAccumulator::CursorScrollAccumulator() : mHaveRelWheel(false), mHaveRelHWheel(false) { +namespace vd_flags = android::companion::virtualdevice::flags; + +CursorScrollAccumulator::CursorScrollAccumulator() + : mHaveRelWheel(false), + mHaveRelHWheel(false), + mHaveRelWheelHighRes(false), + mHaveRelHWheelHighRes(false) { clearRelativeAxes(); } void CursorScrollAccumulator::configure(InputDeviceContext& deviceContext) { mHaveRelWheel = deviceContext.hasRelativeAxis(REL_WHEEL); mHaveRelHWheel = deviceContext.hasRelativeAxis(REL_HWHEEL); + if (vd_flags::high_resolution_scroll()) { + mHaveRelWheelHighRes = deviceContext.hasRelativeAxis(REL_WHEEL_HI_RES); + mHaveRelHWheelHighRes = deviceContext.hasRelativeAxis(REL_HWHEEL_HI_RES); + } } void CursorScrollAccumulator::reset(InputDeviceContext& deviceContext) { @@ -42,11 +53,31 @@ void CursorScrollAccumulator::clearRelativeAxes() { void CursorScrollAccumulator::process(const RawEvent& rawEvent) { if (rawEvent.type == EV_REL) { switch (rawEvent.code) { + case REL_WHEEL_HI_RES: + if (mHaveRelWheelHighRes) { + mRelWheel = rawEvent.value / + static_cast(kEvdevMouseHighResScrollUnitsPerDetent); + } + break; + case REL_HWHEEL_HI_RES: + if (mHaveRelHWheelHighRes) { + mRelHWheel = rawEvent.value / + static_cast(kEvdevMouseHighResScrollUnitsPerDetent); + } + break; case REL_WHEEL: - mRelWheel = rawEvent.value; + // We should ignore regular scroll events, if we have already have high-res scroll + // enabled. + if (!mHaveRelWheelHighRes) { + mRelWheel = rawEvent.value; + } break; case REL_HWHEEL: - mRelHWheel = rawEvent.value; + // We should ignore regular scroll events, if we have already have high-res scroll + // enabled. + if (!mHaveRelHWheelHighRes) { + mRelHWheel = rawEvent.value; + } break; } } diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h index e563620252..6990d208f9 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h @@ -24,7 +24,6 @@ class InputDeviceContext; struct RawEvent; /* Keeps track of cursor scrolling motions. */ - class CursorScrollAccumulator { public: CursorScrollAccumulator(); @@ -39,17 +38,19 @@ public: inline int32_t getRelativeX() const { return mRelX; } inline int32_t getRelativeY() const { return mRelY; } - inline int32_t getRelativeVWheel() const { return mRelWheel; } - inline int32_t getRelativeHWheel() const { return mRelHWheel; } + inline float getRelativeVWheel() const { return mRelWheel; } + inline float getRelativeHWheel() const { return mRelHWheel; } private: bool mHaveRelWheel; bool mHaveRelHWheel; + bool mHaveRelWheelHighRes; + bool mHaveRelHWheelHighRes; int32_t mRelX; int32_t mRelY; - int32_t mRelWheel; - int32_t mRelHWheel; + float mRelWheel; + float mRelHWheel; void clearRelativeAxes(); }; diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 189f117897..65e04291a1 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -109,6 +109,7 @@ cc_test { }, }, static_libs: [ + "android.companion.virtualdevice.flags-aconfig-cc-test", "libflagtest", "libgmock", ], diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp index 83074ff899..727237f287 100644 --- a/services/inputflinger/tests/CursorInputMapper_test.cpp +++ b/services/inputflinger/tests/CursorInputMapper_test.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -127,6 +128,7 @@ private: } // namespace namespace input_flags = com::android::input::flags; +namespace vd_flags = android::companion::virtualdevice::flags; /** * Unit tests for CursorInputMapper. @@ -151,6 +153,10 @@ protected: .WillRepeatedly(Return(false)); EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL)) .WillRepeatedly(Return(false)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES)) + .WillRepeatedly(Return(false)); mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0)); @@ -194,6 +200,7 @@ class CursorInputMapperUnitTest : public CursorInputMapperUnitTestBase { protected: void SetUp() override { input_flags::enable_new_mouse_pointer_ballistics(false); + vd_flags::high_resolution_scroll(false); CursorInputMapperUnitTestBase::SetUp(); } }; @@ -840,6 +847,72 @@ TEST_F(CursorInputMapperUnitTest, ProcessWhenModeIsPointerShouldKeepZeroCoords) WithOrientation(0.0f), WithDistance(0.0f))))); } +TEST_F(CursorInputMapperUnitTest, ProcessRegularScroll) { + createMapper(); + + std::list args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1); + args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))), + VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), + WithScroll(1.0f, 1.0f))))); +} + +TEST_F(CursorInputMapperUnitTest, ProcessHighResScroll) { + vd_flags::high_resolution_scroll(true); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + createMapper(); + + std::list args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))), + VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), + WithScroll(0.5f, 0.5f))))); +} + +TEST_F(CursorInputMapperUnitTest, HighResScrollIgnoresRegularScroll) { + vd_flags::high_resolution_scroll(true); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + createMapper(); + + std::list args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1); + args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))), + VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), + WithScroll(0.5f, 0.5f))))); +} + /** * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any * pointer acceleration or speed processing should not be applied. @@ -1030,6 +1103,72 @@ TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationOnDispla WithRelativeMotion(10, 20))))); } +TEST_F(CursorInputMapperUnitTestWithNewBallistics, ProcessRegularScroll) { + createMapper(); + + std::list args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1); + args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))), + VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), + WithScroll(1.0f, 1.0f))))); +} + +TEST_F(CursorInputMapperUnitTestWithNewBallistics, ProcessHighResScroll) { + vd_flags::high_resolution_scroll(true); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + createMapper(); + + std::list args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))), + VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), + WithScroll(0.5f, 0.5f))))); +} + +TEST_F(CursorInputMapperUnitTestWithNewBallistics, HighResScrollIgnoresRegularScroll) { + vd_flags::high_resolution_scroll(true); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + createMapper(); + + std::list args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1); + args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))), + VariantWith( + AllOf(WithSource(AINPUT_SOURCE_MOUSE), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), + WithScroll(0.5f, 0.5f))))); +} + namespace { // Minimum timestamp separation between subsequent input events from a Bluetooth device. diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp index 94cfc3274b..2b8071bd88 100644 --- a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp +++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp @@ -122,6 +122,10 @@ protected: .WillRepeatedly(Return(true)); EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL)) .WillRepeatedly(Return(false)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES)) + .WillRepeatedly(Return(false)); } }; diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h index 65fb9c6659..f643fb11e5 100644 --- a/services/inputflinger/tests/TestEventMatchers.h +++ b/services/inputflinger/tests/TestEventMatchers.h @@ -697,6 +697,15 @@ MATCHER_P(WithDistance, distance, "MotionEvent with specified distance") { return argDistance == distance; } +MATCHER_P2(WithScroll, scrollX, scrollY, "InputEvent with specified scroll values") { + const auto argScrollX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_HSCROLL); + const auto argScrollY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_VSCROLL); + *result_listener << "expected scroll values " << scrollX << " scroll x " << scrollY + << " scroll y, but got " << argScrollX << " scroll x " << argScrollY + << " scroll y"; + return argScrollX == scrollX && argScrollY == scrollY; +} + MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") { const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR); const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR); -- cgit v1.2.3-59-g8ed1b From ba27d1d9484c25a9ab58d13dcc59d35ad73fc7d3 Mon Sep 17 00:00:00 2001 From: Biswarup Pal Date: Tue, 9 Jul 2024 19:57:33 +0000 Subject: Native support for rotary encoder high-res scroll Test: atest RotaryEncoderInputMapperTest Test: atest VirtualRotaryEncoderTest Flag: android.companion.virtualdevice.flags.high_resolution_scroll Bug: 320328752 Change-Id: Iac9092597010582bd3f55e51ee63e9eb9c8d9433 --- include/input/Input.h | 4 +- include/input/VirtualInputDevice.h | 3 ++ libs/input/VirtualInputDevice.cpp | 50 ++++++++++++++++----- .../reader/mapper/RotaryEncoderInputMapper.cpp | 14 +++--- .../mapper/accumulator/CursorScrollAccumulator.cpp | 8 ++-- .../mapper/accumulator/CursorScrollAccumulator.h | 6 --- .../tests/RotaryEncoderInputMapper_test.cpp | 52 ++++++++++++++++++++++ services/inputflinger/tests/TestEventMatchers.h | 6 +++ 8 files changed, 115 insertions(+), 28 deletions(-) (limited to 'libs/input/VirtualInputDevice.cpp') diff --git a/include/input/Input.h b/include/input/Input.h index 17672d11f7..77d7448db3 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -196,11 +196,11 @@ static constexpr size_t MAX_POINTERS = 16; #define MAX_POINTER_ID 31 /* - * Number of high resolution mouse scroll units for one detent (mouse wheel click), as defined in + * Number of high resolution scroll units for one detent (scroll wheel click), as defined in * evdev. This is relevant when an input device is emitting REL_WHEEL_HI_RES or REL_HWHEEL_HI_RES * events. */ -constexpr int32_t kEvdevMouseHighResScrollUnitsPerDetent = 120; +constexpr int32_t kEvdevHighResScrollUnitsPerDetent = 120; /* * Declare a concrete type for the NDK's input event forward declaration. diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h index 9fbae73167..dabe45ce21 100644 --- a/include/input/VirtualInputDevice.h +++ b/include/input/VirtualInputDevice.h @@ -129,6 +129,9 @@ public: VirtualRotaryEncoder(android::base::unique_fd fd); virtual ~VirtualRotaryEncoder() override; bool writeScrollEvent(float scrollAmount, std::chrono::nanoseconds eventTime); + +private: + int32_t mAccumulatedHighResScrollAmount; }; } // namespace android diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp index 2e3e1a09b8..0579967698 100644 --- a/libs/input/VirtualInputDevice.cpp +++ b/libs/input/VirtualInputDevice.cpp @@ -279,13 +279,17 @@ bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY, bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement, std::chrono::nanoseconds eventTime) { if (!vd_flags::high_resolution_scroll()) { - return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement, eventTime) && - writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement, eventTime) && + return writeInputEvent(EV_REL, REL_HWHEEL, static_cast(xAxisMovement), + eventTime) && + writeInputEvent(EV_REL, REL_WHEEL, static_cast(yAxisMovement), + eventTime) && writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); } - const int32_t highResScrollX = xAxisMovement * kEvdevMouseHighResScrollUnitsPerDetent; - const int32_t highResScrollY = yAxisMovement * kEvdevMouseHighResScrollUnitsPerDetent; + const auto highResScrollX = + static_cast(xAxisMovement * kEvdevHighResScrollUnitsPerDetent); + const auto highResScrollY = + static_cast(yAxisMovement * kEvdevHighResScrollUnitsPerDetent); bool highResScrollResult = writeInputEvent(EV_REL, REL_HWHEEL_HI_RES, highResScrollX, eventTime) && writeInputEvent(EV_REL, REL_WHEEL_HI_RES, highResScrollY, eventTime); @@ -299,19 +303,19 @@ bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement, // (single mouse wheel click). mAccumulatedHighResScrollX += highResScrollX; mAccumulatedHighResScrollY += highResScrollY; - const int32_t scrollX = mAccumulatedHighResScrollX / kEvdevMouseHighResScrollUnitsPerDetent; - const int32_t scrollY = mAccumulatedHighResScrollY / kEvdevMouseHighResScrollUnitsPerDetent; + const int32_t scrollX = mAccumulatedHighResScrollX / kEvdevHighResScrollUnitsPerDetent; + const int32_t scrollY = mAccumulatedHighResScrollY / kEvdevHighResScrollUnitsPerDetent; if (scrollX != 0) { if (!writeInputEvent(EV_REL, REL_HWHEEL, scrollX, eventTime)) { return false; } - mAccumulatedHighResScrollX %= kEvdevMouseHighResScrollUnitsPerDetent; + mAccumulatedHighResScrollX %= kEvdevHighResScrollUnitsPerDetent; } if (scrollY != 0) { if (!writeInputEvent(EV_REL, REL_WHEEL, scrollY, eventTime)) { return false; } - mAccumulatedHighResScrollY %= kEvdevMouseHighResScrollUnitsPerDetent; + mAccumulatedHighResScrollY %= kEvdevHighResScrollUnitsPerDetent; } return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); @@ -550,14 +554,38 @@ bool VirtualStylus::handleStylusUp(uint16_t tool, std::chrono::nanoseconds event } // --- VirtualRotaryEncoder --- -VirtualRotaryEncoder::VirtualRotaryEncoder(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} +VirtualRotaryEncoder::VirtualRotaryEncoder(unique_fd fd) + : VirtualInputDevice(std::move(fd)), mAccumulatedHighResScrollAmount(0) {} VirtualRotaryEncoder::~VirtualRotaryEncoder() {} bool VirtualRotaryEncoder::writeScrollEvent(float scrollAmount, std::chrono::nanoseconds eventTime) { - return writeInputEvent(EV_REL, REL_WHEEL, static_cast(scrollAmount), eventTime) && - writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); + if (!vd_flags::high_resolution_scroll()) { + return writeInputEvent(EV_REL, REL_WHEEL, static_cast(scrollAmount), eventTime) && + writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); + } + + const auto highResScrollAmount = + static_cast(scrollAmount * kEvdevHighResScrollUnitsPerDetent); + if (!writeInputEvent(EV_REL, REL_WHEEL_HI_RES, highResScrollAmount, eventTime)) { + return false; + } + + // According to evdev spec, a high-resolution scroll device needs to emit REL_WHEEL / REL_HWHEEL + // events in addition to high-res scroll events. Regular scroll events can approximate high-res + // scroll events, so we send a regular scroll event when the accumulated scroll motion reaches a + // detent (single wheel click). + mAccumulatedHighResScrollAmount += highResScrollAmount; + const int32_t scroll = mAccumulatedHighResScrollAmount / kEvdevHighResScrollUnitsPerDetent; + if (scroll != 0) { + if (!writeInputEvent(EV_REL, REL_WHEEL, scroll, eventTime)) { + return false; + } + mAccumulatedHighResScrollAmount %= kEvdevHighResScrollUnitsPerDetent; + } + + return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); } } // namespace android diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 20fd3598f7..b72cc6e060 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -27,11 +27,14 @@ namespace android { +constexpr float kDefaultScaleFactor = 1.0f; + RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig) - : InputMapper(deviceContext, readerConfig), mOrientation(ui::ROTATION_0) { - mSource = AINPUT_SOURCE_ROTARY_ENCODER; -} + : InputMapper(deviceContext, readerConfig), + mSource(AINPUT_SOURCE_ROTARY_ENCODER), + mScalingFactor(kDefaultScaleFactor), + mOrientation(ui::ROTATION_0) {} RotaryEncoderInputMapper::~RotaryEncoderInputMapper() {} @@ -51,9 +54,10 @@ void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo& info) { std::optional scalingFactor = config.getFloat("device.scalingFactor"); if (!scalingFactor.has_value()) { ALOGW("Rotary Encoder device configuration file didn't specify scaling factor," - "default to 1.0!\n"); + "default to %f!\n", + kDefaultScaleFactor); } - mScalingFactor = scalingFactor.value_or(1.0f); + mScalingFactor = scalingFactor.value_or(kDefaultScaleFactor); info.addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, res.value_or(0.0f) * mScalingFactor); } diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp index 06315e2259..537344048b 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp @@ -55,14 +55,14 @@ void CursorScrollAccumulator::process(const RawEvent& rawEvent) { switch (rawEvent.code) { case REL_WHEEL_HI_RES: if (mHaveRelWheelHighRes) { - mRelWheel = rawEvent.value / - static_cast(kEvdevMouseHighResScrollUnitsPerDetent); + mRelWheel = + rawEvent.value / static_cast(kEvdevHighResScrollUnitsPerDetent); } break; case REL_HWHEEL_HI_RES: if (mHaveRelHWheelHighRes) { - mRelHWheel = rawEvent.value / - static_cast(kEvdevMouseHighResScrollUnitsPerDetent); + mRelHWheel = + rawEvent.value / static_cast(kEvdevHighResScrollUnitsPerDetent); } break; case REL_WHEEL: diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h index 6990d208f9..d3373ccd33 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h @@ -16,8 +16,6 @@ #pragma once -#include - namespace android { class InputDeviceContext; @@ -36,8 +34,6 @@ public: inline bool haveRelativeVWheel() const { return mHaveRelWheel; } inline bool haveRelativeHWheel() const { return mHaveRelHWheel; } - inline int32_t getRelativeX() const { return mRelX; } - inline int32_t getRelativeY() const { return mRelY; } inline float getRelativeVWheel() const { return mRelWheel; } inline float getRelativeHWheel() const { return mRelHWheel; } @@ -47,8 +43,6 @@ private: bool mHaveRelWheelHighRes; bool mHaveRelHWheelHighRes; - int32_t mRelX; - int32_t mRelY; float mRelWheel; float mRelHWheel; diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp index 2b8071bd88..366b3dcdd0 100644 --- a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp +++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -109,6 +110,8 @@ private: } // namespace +namespace vd_flags = android::companion::virtualdevice::flags; + /** * Unit tests for RotaryEncoderInputMapper. */ @@ -170,4 +173,53 @@ TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdNoAssociatedViewport) { WithDisplayId(ui::LogicalDisplayId::INVALID))))); } +TEST_F(RotaryEncoderInputMapperTest, ProcessRegularScroll) { + createDevice(); + mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); + + std::list args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith( + AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(1.0f))))); +} + +TEST_F(RotaryEncoderInputMapperTest, ProcessHighResScroll) { + vd_flags::high_resolution_scroll(true); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + createDevice(); + mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); + + std::list args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith( + AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f))))); +} + +TEST_F(RotaryEncoderInputMapperTest, HighResScrollIgnoresRegularScroll) { + vd_flags::high_resolution_scroll(true); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + createDevice(); + mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); + + std::list args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith( + AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f))))); +} + } // namespace android \ No newline at end of file diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h index f643fb11e5..f8e3c22ae9 100644 --- a/services/inputflinger/tests/TestEventMatchers.h +++ b/services/inputflinger/tests/TestEventMatchers.h @@ -697,6 +697,12 @@ MATCHER_P(WithDistance, distance, "MotionEvent with specified distance") { return argDistance == distance; } +MATCHER_P(WithScroll, scroll, "InputEvent with specified scroll value") { + const auto argScroll = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SCROLL); + *result_listener << "expected scroll value " << scroll << ", but got " << argScroll; + return argScroll == scroll; +} + MATCHER_P2(WithScroll, scrollX, scrollY, "InputEvent with specified scroll values") { const auto argScrollX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_HSCROLL); const auto argScrollY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_VSCROLL); -- cgit v1.2.3-59-g8ed1b From 8508b669382f9ae9a16a7809db0c53d08b0a38f7 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 8 Aug 2024 17:04:37 -0700 Subject: Move virtual device infra to libinput These pieces should be part of libinput, because they are needed to create uinput devices efficiently. Bug: 335496563 Test: build only Flag: EXEMPT refactor Change-Id: I506a592b83f549e6db9a03bc9bb86c047f31f872 --- include/input/VirtualInputDevice.h | 16 +++ libs/input/VirtualInputDevice.cpp | 242 ++++++++++++++++++++++++++++++++++++- 2 files changed, 254 insertions(+), 4 deletions(-) (limited to 'libs/input/VirtualInputDevice.cpp') diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h index dabe45ce21..b6c630529c 100644 --- a/include/input/VirtualInputDevice.h +++ b/include/input/VirtualInputDevice.h @@ -17,14 +17,30 @@ #pragma once #include +#include +#include namespace android { +enum class DeviceType { + KEYBOARD, + MOUSE, + TOUCHSCREEN, + DPAD, + STYLUS, + ROTARY_ENCODER, +}; + +android::base::unique_fd openUinput(const char* readableName, int32_t vendorId, int32_t productId, + const char* phys, DeviceType deviceType, int32_t screenHeight, + int32_t screenWidth); + enum class UinputAction { RELEASE = 0, PRESS = 1, MOVE = 2, CANCEL = 3, + ftl_last = CANCEL, }; class VirtualInputDevice { diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp index 0579967698..51edbf1533 100644 --- a/libs/input/VirtualInputDevice.cpp +++ b/libs/input/VirtualInputDevice.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "VirtualInputDevice" +#include #include #include #include @@ -23,26 +24,259 @@ #include #include #include -#include -#include -#include #include using android::base::unique_fd; +namespace { + /** * Log debug messages about native virtual input devices. * Enable this via "adb shell setprop log.tag.VirtualInputDevice DEBUG" */ -static bool isDebug() { +bool isDebug() { return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO); } +unique_fd invalidFd() { + return unique_fd(-1); +} + +} // namespace + namespace android { namespace vd_flags = android::companion::virtualdevice::flags; +/** Creates a new uinput device and assigns a file descriptor. */ +unique_fd openUinput(const char* readableName, int32_t vendorId, int32_t productId, + const char* phys, DeviceType deviceType, int32_t screenHeight, + int32_t screenWidth) { + unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK))); + if (fd < 0) { + ALOGE("Error creating uinput device: %s", strerror(errno)); + return invalidFd(); + } + + ioctl(fd, UI_SET_PHYS, phys); + + ioctl(fd, UI_SET_EVBIT, EV_KEY); + ioctl(fd, UI_SET_EVBIT, EV_SYN); + switch (deviceType) { + case DeviceType::DPAD: + for (const auto& [_, keyCode] : VirtualDpad::DPAD_KEY_CODE_MAPPING) { + ioctl(fd, UI_SET_KEYBIT, keyCode); + } + break; + case DeviceType::KEYBOARD: + for (const auto& [_, keyCode] : VirtualKeyboard::KEY_CODE_MAPPING) { + ioctl(fd, UI_SET_KEYBIT, keyCode); + } + break; + case DeviceType::MOUSE: + ioctl(fd, UI_SET_EVBIT, EV_REL); + ioctl(fd, UI_SET_KEYBIT, BTN_LEFT); + ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT); + ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE); + ioctl(fd, UI_SET_KEYBIT, BTN_BACK); + ioctl(fd, UI_SET_KEYBIT, BTN_FORWARD); + ioctl(fd, UI_SET_RELBIT, REL_X); + ioctl(fd, UI_SET_RELBIT, REL_Y); + ioctl(fd, UI_SET_RELBIT, REL_WHEEL); + ioctl(fd, UI_SET_RELBIT, REL_HWHEEL); + if (vd_flags::high_resolution_scroll()) { + ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES); + ioctl(fd, UI_SET_RELBIT, REL_HWHEEL_HI_RES); + } + break; + case DeviceType::TOUCHSCREEN: + ioctl(fd, UI_SET_EVBIT, EV_ABS); + ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR); + ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE); + ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); + break; + case DeviceType::STYLUS: + ioctl(fd, UI_SET_EVBIT, EV_ABS); + ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); + ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS); + ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS2); + ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN); + ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_RUBBER); + ioctl(fd, UI_SET_ABSBIT, ABS_X); + ioctl(fd, UI_SET_ABSBIT, ABS_Y); + ioctl(fd, UI_SET_ABSBIT, ABS_TILT_X); + ioctl(fd, UI_SET_ABSBIT, ABS_TILT_Y); + ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE); + ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); + break; + case DeviceType::ROTARY_ENCODER: + ioctl(fd, UI_SET_EVBIT, EV_REL); + ioctl(fd, UI_SET_RELBIT, REL_WHEEL); + if (vd_flags::high_resolution_scroll()) { + ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES); + } + break; + default: + ALOGE("Invalid input device type %d", static_cast(deviceType)); + return invalidFd(); + } + + int version; + if (ioctl(fd, UI_GET_VERSION, &version) == 0 && version >= 5) { + uinput_setup setup; + memset(&setup, 0, sizeof(setup)); + std::strncpy(setup.name, readableName, UINPUT_MAX_NAME_SIZE); + setup.id.version = 1; + setup.id.bustype = BUS_VIRTUAL; + setup.id.vendor = vendorId; + setup.id.product = productId; + if (deviceType == DeviceType::TOUCHSCREEN) { + uinput_abs_setup xAbsSetup; + xAbsSetup.code = ABS_MT_POSITION_X; + xAbsSetup.absinfo.maximum = screenWidth - 1; + xAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput x axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup yAbsSetup; + yAbsSetup.code = ABS_MT_POSITION_Y; + yAbsSetup.absinfo.maximum = screenHeight - 1; + yAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput y axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup majorAbsSetup; + majorAbsSetup.code = ABS_MT_TOUCH_MAJOR; + majorAbsSetup.absinfo.maximum = screenWidth - 1; + majorAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &majorAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput major axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup pressureAbsSetup; + pressureAbsSetup.code = ABS_MT_PRESSURE; + pressureAbsSetup.absinfo.maximum = 255; + pressureAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup slotAbsSetup; + slotAbsSetup.code = ABS_MT_SLOT; + slotAbsSetup.absinfo.maximum = MAX_POINTERS - 1; + slotAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &slotAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput slots: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup trackingIdAbsSetup; + trackingIdAbsSetup.code = ABS_MT_TRACKING_ID; + trackingIdAbsSetup.absinfo.maximum = MAX_POINTERS - 1; + trackingIdAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &trackingIdAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput tracking ids: %s", strerror(errno)); + return invalidFd(); + } + } else if (deviceType == DeviceType::STYLUS) { + uinput_abs_setup xAbsSetup; + xAbsSetup.code = ABS_X; + xAbsSetup.absinfo.maximum = screenWidth - 1; + xAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) { + ALOGE("Error creating stylus uinput x axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup yAbsSetup; + yAbsSetup.code = ABS_Y; + yAbsSetup.absinfo.maximum = screenHeight - 1; + yAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) { + ALOGE("Error creating stylus uinput y axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup tiltXAbsSetup; + tiltXAbsSetup.code = ABS_TILT_X; + tiltXAbsSetup.absinfo.maximum = 90; + tiltXAbsSetup.absinfo.minimum = -90; + if (ioctl(fd, UI_ABS_SETUP, &tiltXAbsSetup) != 0) { + ALOGE("Error creating stylus uinput tilt x axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup tiltYAbsSetup; + tiltYAbsSetup.code = ABS_TILT_Y; + tiltYAbsSetup.absinfo.maximum = 90; + tiltYAbsSetup.absinfo.minimum = -90; + if (ioctl(fd, UI_ABS_SETUP, &tiltYAbsSetup) != 0) { + ALOGE("Error creating stylus uinput tilt y axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup pressureAbsSetup; + pressureAbsSetup.code = ABS_PRESSURE; + pressureAbsSetup.absinfo.maximum = 255; + pressureAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno)); + return invalidFd(); + } + } + if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) { + ALOGE("Error creating uinput device: %s", strerror(errno)); + return invalidFd(); + } + } else { + // UI_DEV_SETUP was not introduced until version 5. Try setting up manually. + ALOGI("Falling back to version %d manual setup", version); + uinput_user_dev fallback; + memset(&fallback, 0, sizeof(fallback)); + std::strncpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE); + fallback.id.version = 1; + fallback.id.bustype = BUS_VIRTUAL; + fallback.id.vendor = vendorId; + fallback.id.product = productId; + if (deviceType == DeviceType::TOUCHSCREEN) { + fallback.absmin[ABS_MT_POSITION_X] = 0; + fallback.absmax[ABS_MT_POSITION_X] = screenWidth - 1; + fallback.absmin[ABS_MT_POSITION_Y] = 0; + fallback.absmax[ABS_MT_POSITION_Y] = screenHeight - 1; + fallback.absmin[ABS_MT_TOUCH_MAJOR] = 0; + fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1; + fallback.absmin[ABS_MT_PRESSURE] = 0; + fallback.absmax[ABS_MT_PRESSURE] = 255; + } else if (deviceType == DeviceType::STYLUS) { + fallback.absmin[ABS_X] = 0; + fallback.absmax[ABS_X] = screenWidth - 1; + fallback.absmin[ABS_Y] = 0; + fallback.absmax[ABS_Y] = screenHeight - 1; + fallback.absmin[ABS_TILT_X] = -90; + fallback.absmax[ABS_TILT_X] = 90; + fallback.absmin[ABS_TILT_Y] = -90; + fallback.absmax[ABS_TILT_Y] = 90; + fallback.absmin[ABS_PRESSURE] = 0; + fallback.absmax[ABS_PRESSURE] = 255; + } + if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) { + ALOGE("Error creating uinput device: %s", strerror(errno)); + return invalidFd(); + } + } + + if (ioctl(fd, UI_DEV_CREATE) != 0) { + ALOGE("Error creating uinput device: %s", strerror(errno)); + return invalidFd(); + } + + return fd; +} + VirtualInputDevice::VirtualInputDevice(unique_fd fd) : mFd(std::move(fd)) {} VirtualInputDevice::~VirtualInputDevice() { -- cgit v1.2.3-59-g8ed1b