diff --git a/multimodalinput_mini.gni b/multimodalinput_mini.gni index 1ceb68d3f1026d36e52dc4e2324f1035f718e2ab..779be30f1e5b5813d0a914bd05860a57a5cab3ec 100644 --- a/multimodalinput_mini.gni +++ b/multimodalinput_mini.gni @@ -58,6 +58,7 @@ declare_args() { input_feature_one_hand_mode_enable = false input_feature_touch_drawing = true input_feature_key_pressed_handler = true + input_feature_event_recorder = true if (defined(global_parts_info) && defined(global_parts_info.resourceschedule_resource_schedule_service)) { @@ -345,6 +346,7 @@ if (input_feature_fingerprint) { if (input_feature_crown) { input_default_defines += [ "OHOS_BUILD_ENABLE_CROWN" ] + input_feature_event_recorder = false } if (input_feature_one_hand_mode_enable) { @@ -477,3 +479,7 @@ if (!input_feature_keyboard) { if (input_feature_key_pressed_handler) { input_default_defines += [ "OHOS_BUILD_ENABLE_KEY_PRESSED_HANDLER" ] } + +if (input_feature_event_recorder) { + input_default_defines += [ "OHOS_BUILD_ENABLE_EVENT_RECORDER" ] +} diff --git a/tools/inject_event/BUILD.gn b/tools/inject_event/BUILD.gn index 22fdcb4bb005929e7561e4958da1aa67063f1f27..11f53a80f8b3805c0cd5ecfb14685c6ca417cfd8 100644 --- a/tools/inject_event/BUILD.gn +++ b/tools/inject_event/BUILD.gn @@ -17,6 +17,15 @@ import("../../multimodalinput_mini.gni") ohos_source_set("input-manager") { sources = [ "src/input_manager_command.cpp" ] + if (input_feature_event_recorder) { + sources += [ + "src/device_manager.cpp", + "src/event_recorder.cpp", + "src/event_replayer.cpp", + "src/input_device.cpp", + "src/input_replay_command.cpp", + ] + } branch_protector_ret = "pac_ret" sanitize = { cfi = true @@ -50,6 +59,7 @@ ohos_source_set("input-manager") { } ohos_executable("uinput") { + defines = input_default_defines sources = [ "src/main.cpp" ] configs = [ "${mmi_path}:coverage_flags" ] include_dirs = [ @@ -78,6 +88,15 @@ ohos_unittest("InjectEventTest") { include_dirs = [ "include" ] sources = [ "test/inject_event_test.cpp" ] + if (input_feature_event_recorder) { + sources += [ + "test/device_manager_test.cpp", + "test/event_recorder_test.cpp", + "test/event_replayer_test.cpp", + "test/input_device_test.cpp", + "test/input_replay_command_test.cpp", + ] + } deps = [ "${mmi_path}/frameworks/proxy:libmmi-client", @@ -85,6 +104,10 @@ ohos_unittest("InjectEventTest") { "${mmi_path}/util:libmmi-util", ] + if (input_feature_event_recorder) { + defines = [ "private = public" ] + } + external_deps = [ "c_utils:utils", "eventhandler:libeventhandler", diff --git a/tools/inject_event/include/common.h b/tools/inject_event/include/common.h new file mode 100644 index 0000000000000000000000000000000000000000..4a3916ffa55c5f706d6588268053f9a158ca4bb6 --- /dev/null +++ b/tools/inject_event/include/common.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include + +#include + +#include "mmi_log.h" + +namespace OHOS { +namespace MMI { + +struct EventRecord { + uint32_t deviceId; + input_event event; +}; + +// Global shutdown flag for event recorder tool use +extern std::atomic g_shutdown; + +// Print functions in place of Logger +inline void PrintWithPrefix(const char* prefix, const char* format, va_list args) +{ + if (prefix && *prefix) { + printf("%s", prefix); // Only print prefix if it's not empty + } + vprintf(format, args); + printf("\n"); +} + +inline void PrintDebug(const char* format, ...) +{ + va_list args; + va_start(args, format); + PrintWithPrefix("", format, args); + va_end(args); +} + +inline void PrintInfo(const char* format, ...) +{ + va_list args; + va_start(args, format); + PrintWithPrefix("", format, args); + va_end(args); +} + +inline void PrintWarning(const char* format, ...) +{ + va_list args; + va_start(args, format); + PrintWithPrefix("[WARNING] ", format, args); + va_end(args); +} + +inline void PrintError(const char* format, ...) +{ + va_list args; + va_start(args, format); + PrintWithPrefix("[ERROR] ", format, args); + va_end(args); +} + +inline void TrimString(std::string& str) +{ + size_t start = str.find_first_not_of(" \t"); + if (start == std::string::npos) { + str.clear(); + return; + } + size_t end = str.find_last_not_of(" \t"); + str = str.substr(start, end - start + 1); +} + +inline bool RemovePrefix(std::string& str, const std::string& prefix) +{ + if (str.find(prefix) == 0) { + str.erase(0, prefix.length()); + TrimString(str); + return true; + } + return false; +} +} // namespace MMI +} // namespace OHOS +#endif // COMMON_H \ No newline at end of file diff --git a/tools/inject_event/include/device_manager.h b/tools/inject_event/include/device_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..9975bb4bdd9468e42934ef6c674d17b7c31065ec --- /dev/null +++ b/tools/inject_event/include/device_manager.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DEVICE_MANAGER_H +#define DEVICE_MANAGER_H + +#include + +#include "input_device.h" + +namespace OHOS { +namespace MMI { +class DeviceManager { +public: + DeviceManager() = default; + std::vector DiscoverDevices(); + void PrintDeviceList(); + + static int32_t ExtractEventNumber(const std::string& fileName); +private: + std::string BuildDevicePath(const std::string& fileName) const; +}; +} // namespace MMI +} // namespace OHOS +#endif // DEVICE_MANAGER_H \ No newline at end of file diff --git a/tools/inject_event/include/event_recorder.h b/tools/inject_event/include/event_recorder.h new file mode 100644 index 0000000000000000000000000000000000000000..1025c628dee3811ae2a80a2a5dd01480559433c1 --- /dev/null +++ b/tools/inject_event/include/event_recorder.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EVENT_RECORDER_H +#define EVENT_RECORDER_H + +#include +#include +#include +#include + +#include "input_device.h" + +namespace OHOS { +namespace MMI { +class EventRecorder { +public: + EventRecorder(const std::string& outputPath); + ~EventRecorder(); + + bool Start(std::vector& devices); + void Stop(); + +private: + void ProcessDeviceEvents(fd_set& readFds); + void MainLoop(); + void FlushDeviceEvents(const EventRecord& record); + static std::string GetEventTypeString(uint16_t type); + static std::string GetSecondaryEventCodeString(uint16_t type, uint16_t code); + static std::string GetEventCodeString(uint16_t type, uint16_t code); + void WriteEventText(const EventRecord& record); + + std::string outputPath_; + std::ofstream outputFile_; + std::vector devices_; + std::unordered_map> deviceEventBuffers_; + bool running_; +}; +} // namespace MMI +} // namespace OHOS +#endif // EVENT_RECORDER_H \ No newline at end of file diff --git a/tools/inject_event/include/event_replayer.h b/tools/inject_event/include/event_replayer.h new file mode 100644 index 0000000000000000000000000000000000000000..2f9bba79c1fb5c5c5718aea9179a13009a86b2a7 --- /dev/null +++ b/tools/inject_event/include/event_replayer.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EVENT_REPLAYER_H +#define EVENT_REPLAYER_H + +#include +#include +#include +#include + +#include "input_device.h" + +namespace OHOS { +namespace MMI { +class EventReplayer { +public: + EventReplayer(const std::string& inputPath, const std::map& deviceMapping = {}); + bool Replay(); + + static bool ParseInputLine(const std::string& line, uint32_t& deviceId, struct input_event& evt); + +private: + bool SeekToDevicesSection(std::ifstream& inputFile); + bool SeekToEventsSection(std::ifstream& inputFile); + bool ProcessDeviceLines(std::ifstream& inputFile, + std::map>& outputDevices, uint32_t deviceCount); + bool InitializeOutputDevices(std::ifstream& inputFile, + std::map>& outputDevices); + bool ReplayEvents(std::ifstream& inputFile, + const std::map>& outputDevices); + void ApplyEventDelay(const struct input_event& currentEvent); + + std::string inputPath_; + std::unordered_map> deviceEventBuffers_; + std::map deviceMapping_; + unsigned long lastSec_{0}; + unsigned long lastUsec_{0}; + bool firstEvent_{true}; +}; +} // namespace MMI +} // namespace OHOS +#endif // EVENT_REPLAYER_H \ No newline at end of file diff --git a/tools/inject_event/include/event_utils.h b/tools/inject_event/include/event_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..53c10750bcfb27736450b0d0cd6adddf96251b15 --- /dev/null +++ b/tools/inject_event/include/event_utils.h @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EVENT_UTILS_H +#define EVENT_UTILS_H + +#include +#include + +#include + +#include "common.h" + +namespace OHOS { +namespace MMI { + +// Event type string mapping +static const std::unordered_map EVENT_TYPE_MAP = { + {EV_SYN, "EV_SYN"}, + {EV_KEY, "EV_KEY"}, + {EV_REL, "EV_REL"}, + {EV_ABS, "EV_ABS"}, + {EV_MSC, "EV_MSC"}, + {EV_SW, "EV_SW"}, + {EV_LED, "EV_LED"}, + {EV_SND, "EV_SND"}, + {EV_REP, "EV_REP"}, + {EV_FF, "EV_FF"}, + {EV_PWR, "EV_PWR"}, + {EV_FF_STATUS, "EV_FF_STATUS"} +}; + +// SYN event code mapping +static const std::unordered_map SYN_CODE_MAP = { + {SYN_REPORT, "SYN_REPORT"}, + {SYN_CONFIG, "SYN_CONFIG"}, + {SYN_MT_REPORT, "SYN_MT_REPORT"}, + {SYN_DROPPED, "SYN_DROPPED"} +}; + +// Keyboard and button event code mapping +static const std::unordered_map KEY_CODE_MAP = { + // Regular keyboard keys + {KEY_ESC, "KEY_ESC"}, + {KEY_1, "KEY_1"}, + {KEY_2, "KEY_2"}, + {KEY_3, "KEY_3"}, + {KEY_4, "KEY_4"}, + {KEY_5, "KEY_5"}, + {KEY_6, "KEY_6"}, + {KEY_7, "KEY_7"}, + {KEY_8, "KEY_8"}, + {KEY_9, "KEY_9"}, + {KEY_0, "KEY_0"}, + {KEY_MINUS, "KEY_MINUS"}, + {KEY_EQUAL, "KEY_EQUAL"}, + {KEY_BACKSPACE, "KEY_BACKSPACE"}, + {KEY_TAB, "KEY_TAB"}, + {KEY_Q, "KEY_Q"}, + {KEY_W, "KEY_W"}, + {KEY_E, "KEY_E"}, + {KEY_R, "KEY_R"}, + {KEY_T, "KEY_T"}, + {KEY_Y, "KEY_Y"}, + {KEY_U, "KEY_U"}, + {KEY_I, "KEY_I"}, + {KEY_O, "KEY_O"}, + {KEY_P, "KEY_P"}, + {KEY_LEFTBRACE, "KEY_LEFTBRACE"}, + {KEY_RIGHTBRACE, "KEY_RIGHTBRACE"}, + {KEY_ENTER, "KEY_ENTER"}, + {KEY_LEFTCTRL, "KEY_LEFTCTRL"}, + {KEY_A, "KEY_A"}, + {KEY_S, "KEY_S"}, + {KEY_D, "KEY_D"}, + {KEY_F, "KEY_F"}, + {KEY_G, "KEY_G"}, + {KEY_H, "KEY_H"}, + {KEY_J, "KEY_J"}, + {KEY_K, "KEY_K"}, + {KEY_L, "KEY_L"}, + {KEY_SEMICOLON, "KEY_SEMICOLON"}, + {KEY_APOSTROPHE, "KEY_APOSTROPHE"}, + {KEY_GRAVE, "KEY_GRAVE"}, + {KEY_LEFTSHIFT, "KEY_LEFTSHIFT"}, + {KEY_BACKSLASH, "KEY_BACKSLASH"}, + {KEY_Z, "KEY_Z"}, + {KEY_X, "KEY_X"}, + {KEY_C, "KEY_C"}, + {KEY_V, "KEY_V"}, + {KEY_B, "KEY_B"}, + {KEY_N, "KEY_N"}, + {KEY_M, "KEY_M"}, + {KEY_COMMA, "KEY_COMMA"}, + {KEY_DOT, "KEY_DOT"}, + {KEY_SLASH, "KEY_SLASH"}, + {KEY_RIGHTSHIFT, "KEY_RIGHTSHIFT"}, + {KEY_KPASTERISK, "KEY_KPASTERISK"}, + {KEY_LEFTALT, "KEY_LEFTALT"}, + {KEY_SPACE, "KEY_SPACE"}, + {KEY_CAPSLOCK, "KEY_CAPSLOCK"}, + + // Function keys + {KEY_F1, "KEY_F1"}, + {KEY_F2, "KEY_F2"}, + {KEY_F3, "KEY_F3"}, + {KEY_F4, "KEY_F4"}, + {KEY_F5, "KEY_F5"}, + {KEY_F6, "KEY_F6"}, + {KEY_F7, "KEY_F7"}, + {KEY_F8, "KEY_F8"}, + {KEY_F9, "KEY_F9"}, + {KEY_F10, "KEY_F10"}, + {KEY_F11, "KEY_F11"}, + {KEY_F12, "KEY_F12"}, + + // Navigation keys + {KEY_HOME, "KEY_HOME"}, + {KEY_UP, "KEY_UP"}, + {KEY_PAGEUP, "KEY_PAGEUP"}, + {KEY_LEFT, "KEY_LEFT"}, + {KEY_RIGHT, "KEY_RIGHT"}, + {KEY_END, "KEY_END"}, + {KEY_DOWN, "KEY_DOWN"}, + {KEY_PAGEDOWN, "KEY_PAGEDOWN"}, + {KEY_INSERT, "KEY_INSERT"}, + {KEY_DELETE, "KEY_DELETE"}, + + // Multimedia control keys + {KEY_MUTE, "KEY_MUTE"}, + {KEY_VOLUMEDOWN, "KEY_VOLUMEDOWN"}, + {KEY_VOLUMEUP, "KEY_VOLUMEUP"}, + {KEY_POWER, "KEY_POWER"}, + {KEY_PAUSE, "KEY_PAUSE"}, + {KEY_PLAYPAUSE, "KEY_PLAYPAUSE"}, + {KEY_NEXTSONG, "KEY_NEXTSONG"}, + {KEY_PREVIOUSSONG, "KEY_PREVIOUSSONG"}, + + // Mouse buttons + {BTN_LEFT, "BTN_LEFT"}, + {BTN_RIGHT, "BTN_RIGHT"}, + {BTN_MIDDLE, "BTN_MIDDLE"}, + {BTN_SIDE, "BTN_SIDE"}, + {BTN_EXTRA, "BTN_EXTRA"}, + {BTN_FORWARD, "BTN_FORWARD"}, + {BTN_BACK, "BTN_BACK"}, + + // Gamepad buttons + {BTN_SOUTH, "BTN_SOUTH"}, + {BTN_EAST, "BTN_EAST"}, + {BTN_NORTH, "BTN_NORTH"}, + {BTN_WEST, "BTN_WEST"}, + {BTN_TL, "BTN_TL"}, + {BTN_TR, "BTN_TR"}, + {BTN_TL2, "BTN_TL2"}, + {BTN_TR2, "BTN_TR2"}, + {BTN_SELECT, "BTN_SELECT"}, + {BTN_START, "BTN_START"}, + {BTN_MODE, "BTN_MODE"}, + {BTN_THUMBL, "BTN_THUMBL"}, + {BTN_THUMBR, "BTN_THUMBR"}, + + // D-Pad buttons + {BTN_DPAD_UP, "BTN_DPAD_UP"}, + {BTN_DPAD_DOWN, "BTN_DPAD_DOWN"}, + {BTN_DPAD_LEFT, "BTN_DPAD_LEFT"}, + {BTN_DPAD_RIGHT, "BTN_DPAD_RIGHT"}, + + // Pen/digitizer + {BTN_TOOL_PEN, "BTN_TOOL_PEN"}, + {BTN_TOOL_RUBBER, "BTN_TOOL_RUBBER"}, + {BTN_TOOL_BRUSH, "BTN_TOOL_BRUSH"}, + {BTN_TOOL_PENCIL, "BTN_TOOL_PENCIL"}, + {BTN_TOOL_FINGER, "BTN_TOOL_FINGER"}, + {BTN_TOUCH, "BTN_TOUCH"}, + {BTN_STYLUS, "BTN_STYLUS"}, + {BTN_STYLUS2, "BTN_STYLUS2"} +}; + +// Relative coordinate event code mapping +static const std::unordered_map REL_CODE_MAP = { + {REL_X, "REL_X"}, + {REL_Y, "REL_Y"}, + {REL_Z, "REL_Z"}, + {REL_RX, "REL_RX"}, + {REL_RY, "REL_RY"}, + {REL_RZ, "REL_RZ"}, + {REL_HWHEEL, "REL_HWHEEL"}, + {REL_DIAL, "REL_DIAL"}, + {REL_WHEEL, "REL_WHEEL"}, + {REL_MISC, "REL_MISC"}, + {REL_WHEEL_HI_RES, "REL_WHEEL_HI_RES"}, + {REL_HWHEEL_HI_RES, "REL_HWHEEL_HI_RES"} +}; + +// Absolute coordinate event code mapping +static const std::unordered_map ABS_CODE_MAP = { + {ABS_X, "ABS_X"}, + {ABS_Y, "ABS_Y"}, + {ABS_Z, "ABS_Z"}, + {ABS_RX, "ABS_RX"}, + {ABS_RY, "ABS_RY"}, + {ABS_RZ, "ABS_RZ"}, + {ABS_THROTTLE, "ABS_THROTTLE"}, + {ABS_RUDDER, "ABS_RUDDER"}, + {ABS_WHEEL, "ABS_WHEEL"}, + {ABS_GAS, "ABS_GAS"}, + {ABS_BRAKE, "ABS_BRAKE"}, + {ABS_HAT0X, "ABS_HAT0X"}, + {ABS_HAT0Y, "ABS_HAT0Y"}, + {ABS_HAT1X, "ABS_HAT1X"}, + {ABS_HAT1Y, "ABS_HAT1Y"}, + {ABS_HAT2X, "ABS_HAT2X"}, + {ABS_HAT2Y, "ABS_HAT2Y"}, + {ABS_HAT3X, "ABS_HAT3X"}, + {ABS_HAT3Y, "ABS_HAT3Y"}, + {ABS_PRESSURE, "ABS_PRESSURE"}, + {ABS_DISTANCE, "ABS_DISTANCE"}, + {ABS_TILT_X, "ABS_TILT_X"}, + {ABS_TILT_Y, "ABS_TILT_Y"}, + {ABS_TOOL_WIDTH, "ABS_TOOL_WIDTH"}, + {ABS_VOLUME, "ABS_VOLUME"}, + {ABS_MISC, "ABS_MISC"}, + + // Multi-touch + {ABS_MT_SLOT, "ABS_MT_SLOT"}, + {ABS_MT_TOUCH_MAJOR, "ABS_MT_TOUCH_MAJOR"}, + {ABS_MT_TOUCH_MINOR, "ABS_MT_TOUCH_MINOR"}, + {ABS_MT_WIDTH_MAJOR, "ABS_MT_WIDTH_MAJOR"}, + {ABS_MT_WIDTH_MINOR, "ABS_MT_WIDTH_MINOR"}, + {ABS_MT_ORIENTATION, "ABS_MT_ORIENTATION"}, + {ABS_MT_POSITION_X, "ABS_MT_POSITION_X"}, + {ABS_MT_POSITION_Y, "ABS_MT_POSITION_Y"}, + {ABS_MT_TOOL_TYPE, "ABS_MT_TOOL_TYPE"}, + {ABS_MT_BLOB_ID, "ABS_MT_BLOB_ID"}, + {ABS_MT_TRACKING_ID, "ABS_MT_TRACKING_ID"}, + {ABS_MT_PRESSURE, "ABS_MT_PRESSURE"}, + {ABS_MT_DISTANCE, "ABS_MT_DISTANCE"}, + {ABS_MT_TOOL_X, "ABS_MT_TOOL_X"}, + {ABS_MT_TOOL_Y, "ABS_MT_TOOL_Y"} +}; + +// Switch event code mapping +static const std::unordered_map SW_CODE_MAP = { + {SW_LID, "SW_LID"}, + {SW_TABLET_MODE, "SW_TABLET_MODE"}, + {SW_HEADPHONE_INSERT, "SW_HEADPHONE_INSERT"}, + {SW_RFKILL_ALL, "SW_RFKILL_ALL"}, + {SW_MICROPHONE_INSERT, "SW_MICROPHONE_INSERT"}, + {SW_DOCK, "SW_DOCK"}, + {SW_LINEOUT_INSERT, "SW_LINEOUT_INSERT"}, + {SW_JACK_PHYSICAL_INSERT, "SW_JACK_PHYSICAL_INSERT"}, + {SW_VIDEOOUT_INSERT, "SW_VIDEOOUT_INSERT"}, + {SW_CAMERA_LENS_COVER, "SW_CAMERA_LENS_COVER"}, + {SW_KEYPAD_SLIDE, "SW_KEYPAD_SLIDE"}, + {SW_FRONT_PROXIMITY, "SW_FRONT_PROXIMITY"}, + {SW_ROTATE_LOCK, "SW_ROTATE_LOCK"}, + {SW_LINEIN_INSERT, "SW_LINEIN_INSERT"}, + {SW_MUTE_DEVICE, "SW_MUTE_DEVICE"}, + {SW_PEN_INSERTED, "SW_PEN_INSERTED"}, + {SW_MACHINE_COVER, "SW_MACHINE_COVER"} +}; + +// Miscellaneous event code mapping +static const std::unordered_map MSC_CODE_MAP = { + {MSC_SERIAL, "MSC_SERIAL"}, + {MSC_PULSELED, "MSC_PULSELED"}, + {MSC_GESTURE, "MSC_GESTURE"}, + {MSC_RAW, "MSC_RAW"}, + {MSC_SCAN, "MSC_SCAN"}, + {MSC_TIMESTAMP, "MSC_TIMESTAMP"} +}; + +// LED event code mapping +static const std::unordered_map LED_CODE_MAP = { + {LED_NUML, "LED_NUML"}, + {LED_CAPSL, "LED_CAPSL"}, + {LED_SCROLLL, "LED_SCROLLL"}, + {LED_COMPOSE, "LED_COMPOSE"}, + {LED_KANA, "LED_KANA"}, + {LED_SLEEP, "LED_SLEEP"}, + {LED_SUSPEND, "LED_SUSPEND"}, + {LED_MUTE, "LED_MUTE"}, + {LED_MISC, "LED_MISC"}, + {LED_MAIL, "LED_MAIL"}, + {LED_CHARGING, "LED_CHARGING"} +}; + +// Auto-repeat event code mapping +static const std::unordered_map REP_CODE_MAP = { + {REP_DELAY, "REP_DELAY"}, + {REP_PERIOD, "REP_PERIOD"} +}; + +// Sound event code mapping +static const std::unordered_map SND_CODE_MAP = { + {SND_CLICK, "SND_CLICK"}, + {SND_BELL, "SND_BELL"}, + {SND_TONE, "SND_TONE"} +}; +} // namespace MMI +} // namespace OHOS +#endif // EVENT_UTILS_H \ No newline at end of file diff --git a/tools/inject_event/include/input_device.h b/tools/inject_event/include/input_device.h new file mode 100644 index 0000000000000000000000000000000000000000..07b559c76bd72133c0afa3deba2c23f70cb687cb --- /dev/null +++ b/tools/inject_event/include/input_device.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INPUT_DEVICE_H +#define INPUT_DEVICE_H + +#include +#include +#include + +#include "common.h" + +namespace OHOS { +namespace MMI { +class InputDevice { +public: + InputDevice(const InputDevice&) = delete; + InputDevice& operator=(const InputDevice&) = delete; + InputDevice(); + InputDevice(const std::string& path, uint32_t id); + InputDevice(InputDevice&& other) noexcept; + ~InputDevice(); + + InputDevice& operator=(InputDevice&& other) noexcept; + + bool IsOpen() const; + void Close(); + bool OpenForReading(); + bool OpenForWriting(); + + int32_t GetFd() const; + const std::string& GetPath() const; + const std::string& GetName() const; + uint32_t GetId() const; + + void SetId(uint32_t id); + void SetPath(const std::string& path); + void SetName(const std::string& name); + + bool ReadEvent(input_event& event); + bool WriteEvents(const std::vector& events); + bool InitFromTextLine(const std::string& line); + +private: + bool VerifyDeviceMatch() const; + bool OpenDevice(int32_t flags); + void QueryDeviceInfo(); + + std::string path_; + std::string name_; + uint32_t id_; + int32_t fd_; + std::map deviceMapping_; +}; +} // namespace MMI +} // namespace OHOS +#endif // INPUT_DEVICE_H \ No newline at end of file diff --git a/tools/inject_event/include/input_replay_command.h b/tools/inject_event/include/input_replay_command.h new file mode 100644 index 0000000000000000000000000000000000000000..029e970bdda24176dc16b51d72ce9526425eeee5 --- /dev/null +++ b/tools/inject_event/include/input_replay_command.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CMD_PARSER_H +#define CMD_PARSER_H + +#include +#include +#include + +namespace OHOS { +namespace MMI { +class InputReplayCommand { +public: + InputReplayCommand(int32_t argc, char** argv); + bool Parse(); + bool Execute(); + + static int32_t HandleRecordReplayCommand(int32_t argc, char** argv); +private: + bool ParseOptions(bool& useAllDevices); + bool ParseDeviceMapping(const std::string& mappingStr); + void SetupSignalHandlers(); + bool ParseRecordCommand(bool useAllDevices); + bool ParseReplayCommand(); + bool ExecuteRecordCommand(); + bool ExecuteReplayCommand(); + void PrintUsage() const; + + int32_t argc_; + char *const *argv_; + std::string programName_; + std::string command_; + std::string filePath_; + std::vector devicePaths_; + bool useAllDevices_ { false }; + std::map deviceMapping_; +}; +} // namespace MMI +} // namespace OHOS +#endif // CMD_PARSER_H \ No newline at end of file diff --git a/tools/inject_event/src/device_manager.cpp b/tools/inject_event/src/device_manager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..92ccc9a8078b5cc9645b7383bd64145a86f48cf4 --- /dev/null +++ b/tools/inject_event/src/device_manager.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "device_manager.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace OHOS { +namespace MMI { +namespace { +constexpr size_t EVENT_PREFIX_LENGTH = 5; +constexpr const char* EVENT_PREFIX = "event"; +constexpr int32_t ID_WIDTH = 5; +constexpr int32_t PATH_WIDTH = 20; +constexpr int32_t TOTAL_WIDTH = 80; +constexpr int32_t INVALID_DEVICE_ID = -1; +constexpr const char* INPUT_DEVICE_DIR = "/dev/input"; +} + +int32_t DeviceManager::ExtractEventNumber(const std::string& fileName) +{ + if (fileName.length() < EVENT_PREFIX_LENGTH || fileName.substr(0, EVENT_PREFIX_LENGTH) != EVENT_PREFIX) { + return INVALID_DEVICE_ID; + } + std::string numberPart = fileName.substr(EVENT_PREFIX_LENGTH); + if (!std::all_of(numberPart.begin(), numberPart.end(), ::isdigit)) { + return INVALID_DEVICE_ID; + } + int32_t eventNum; + auto [ptr, ec] = std::from_chars(numberPart.data(), numberPart.data() + numberPart.size(), eventNum); + if (ec == std::errc() && + ptr == numberPart.data() + numberPart.size() && + eventNum >= 0) { + return eventNum; + } + return INVALID_DEVICE_ID; +} + +std::string DeviceManager::BuildDevicePath(const std::string& fileName) const +{ + return std::string(INPUT_DEVICE_DIR) + "/" + fileName; +} + +std::vector DeviceManager::DiscoverDevices() +{ + std::vector devices; + DIR* dir = opendir(INPUT_DEVICE_DIR); + if (!dir) { + PrintError("Cannot open /dev/input directory: %s", strerror(errno)); + return devices; + } + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + std::string fileName(entry->d_name); + std::string path = BuildDevicePath(entry->d_name); + int32_t eventNum = ExtractEventNumber(fileName); + if (eventNum >= 0) { + InputDevice device(path, static_cast(eventNum)); + if (device.IsOpen()) { + devices.push_back(std::move(device)); + } + } + } + closedir(dir); + std::sort(devices.begin(), devices.end(), + [](const InputDevice& a, const InputDevice& b) { + return a.GetId() < b.GetId(); + }); + return devices; +} + +void DeviceManager::PrintDeviceList() +{ + auto devices = DiscoverDevices(); + if (devices.empty()) { + std::cout << "No input devices found." << std::endl; + return; + } + std::cout << "Available input devices:" << std::endl; + std::cout << std::setw(ID_WIDTH) << "ID" << " | " + << std::setw(PATH_WIDTH) << "Path" << " | " + << "Name" << std::endl; + std::cout << std::string(TOTAL_WIDTH, '-') << std::endl; + for (const auto& device : devices) { + std::cout << std::setw(ID_WIDTH) << device.GetId() << " | " + << std::setw(PATH_WIDTH) << device.GetPath() << " | " + << device.GetName() << std::endl; + } +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/tools/inject_event/src/event_recorder.cpp b/tools/inject_event/src/event_recorder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b8392cae787d0549939ca22aa89cd5c3dc8f9e0c --- /dev/null +++ b/tools/inject_event/src/event_recorder.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "event_recorder.h" + +#include +#include +#include +#include + +#include + +#include "event_utils.h" + +namespace OHOS { +namespace MMI { +namespace { +constexpr suseconds_t TIME_OUT = 100000; +} +EventRecorder::EventRecorder(const std::string& outputPath) + : outputPath_(outputPath), running_(false) +{ +} + +EventRecorder::~EventRecorder() +{ + Stop(); +} + +bool EventRecorder::Start(std::vector& devices) +{ + if (running_) { + return false; + } + if (devices.empty()) { + PrintError("No devices to record from"); + return false; + } + outputFile_.open(outputPath_, std::ios::binary | std::ios::trunc); + if (!outputFile_) { + PrintError("Failed to open output file: %s", outputPath_.c_str()); + return false; + } + devices_.clear(); + deviceEventBuffers_.clear(); + for (InputDevice& device : devices) { + PrintInfo("Recording from device %u: %s (%s)", + device.GetId(), device.GetPath().c_str(), device.GetName().c_str()); + if (!device.IsOpen() && !device.OpenForReading()) { + PrintError("Failed to open device for reading: %s", device.GetPath().c_str()); + continue; + } + devices_.push_back(std::move(device)); + } + bool hasValidDevices = false; + for (const auto& device : devices_) { + if (device.IsOpen()) { + hasValidDevices = true; + break; + } + } + if (!hasValidDevices) { + PrintError("No devices could be opened for recording"); + outputFile_.close(); + return false; + } + running_ = true; + outputFile_ << "EVENTS_BEGIN" << std::endl; + MainLoop(); + return true; +} + +void EventRecorder::Stop() +{ + if (!running_) { + return; + } + running_ = false; + if (outputFile_.is_open()) { + outputFile_ << "EVENTS_END" << std::endl< 0) { + ProcessDeviceEvents(readFds); + } + } + PrintDebug("Stopped event recording main loop"); +} + +void EventRecorder::FlushDeviceEvents(const EventRecord& record) +{ + if (record.event.type != EV_SYN) { + return; + } + auto& currentDeviceBuffer = deviceEventBuffers_[record.deviceId]; + for (const auto& record : currentDeviceBuffer) { + WriteEventText(record); + } + currentDeviceBuffer.clear(); +} + +void EventRecorder::WriteEventText(const EventRecord& record) +{ + struct input_event event = record.event; + std::string typeStr = GetEventTypeString(event.type); + std::string codeStr = GetEventCodeString(event.type, event.code); + outputFile_ << "[" + << record.deviceId << ", " + << event.type << ", " + << event.code << ", " + << event.value<<", " + << event.input_event_sec << ", " + << event.input_event_usec + << "] # " << typeStr << " / " << codeStr << " " << event.value + << std::endl; + std::cout << "[" + << record.deviceId << ", " + << event.type << ", " + << event.code << ", " + << event.value << ", " + << event.input_event_sec << ", " + << event.input_event_usec + << "] # " << typeStr << " / " << codeStr << " " << event.value + << std::endl; +} + +std::string EventRecorder::GetEventTypeString(uint16_t type) +{ + auto it = EVENT_TYPE_MAP.find(type); + if (it != EVENT_TYPE_MAP.end()) { + return it->second; + } + return "UNKNOWN_TYPE(" + std::to_string(type) + ")"; +} + +std::string EventRecorder::GetSecondaryEventCodeString(uint16_t type, uint16_t code) +{ + switch (type) { + case EV_LED: { + auto it = LED_CODE_MAP.find(code); + if (it != LED_CODE_MAP.end()) { + return it->second; + } + break; + } + case EV_REP: { + auto it = REP_CODE_MAP.find(code); + if (it != REP_CODE_MAP.end()) { + return it->second; + } + break; + } + case EV_SND: { + auto it = SND_CODE_MAP.find(code); + if (it != SND_CODE_MAP.end()) { + return it->second; + } + break; + } + case EV_MSC: { + auto it = MSC_CODE_MAP.find(code); + if (it != MSC_CODE_MAP.end()) { + return it->second; + } + break; + } + case EV_SW: { + auto it = SW_CODE_MAP.find(code); + if (it != SW_CODE_MAP.end()) { + return it->second; + } + break; + } + } + return ""; +} + +std::string EventRecorder::GetEventCodeString(uint16_t type, uint16_t code) +{ + switch (type) { + case EV_SYN: { + auto it = SYN_CODE_MAP.find(code); + if (it != SYN_CODE_MAP.end()) { + return it->second; + } + break; + } + case EV_KEY: { + auto it = KEY_CODE_MAP.find(code); + if (it != KEY_CODE_MAP.end()) { + return it->second; + } + if (code >= KEY_A && code <= KEY_Z) { + return "KEY_" + std::string(1, 'A' + (code - KEY_A)); + } + break; + } + case EV_REL: { + auto it = REL_CODE_MAP.find(code); + if (it != REL_CODE_MAP.end()) { + return it->second; + } + break; + } + case EV_ABS: { + auto it = ABS_CODE_MAP.find(code); + if (it != ABS_CODE_MAP.end()) { + return it->second; + } + break; + } + } + std::string secondaryResult = GetSecondaryEventCodeString(type, code); + if (!secondaryResult.empty()) { + return secondaryResult; + } + return "CODE(" + std::to_string(code) + ")"; +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/tools/inject_event/src/event_replayer.cpp b/tools/inject_event/src/event_replayer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..64558c8fb0bf993ef284f2b4826dcde7f6785708 --- /dev/null +++ b/tools/inject_event/src/event_replayer.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "event_replayer.h" + +#include +#include +#include +#include +#include + +#include "common.h" + +namespace OHOS { +namespace MMI { +namespace { +constexpr int32_t MICROSECONDS_PER_SECOND = 1000000; +const char* DEVICES_PREFIX = "DEVICES:"; +const char* EVENTS_BEGIN = "EVENTS_BEGIN"; +const char* EVENTS_END = "EVENTS_END"; +constexpr char FIELD_COMMA = ','; +constexpr char COMMENT_CHAR = '#'; +constexpr char BRACKET_START = '['; +constexpr char BRACKET_END = ']'; +} + +EventReplayer::EventReplayer(const std::string& inputPath, const std::map& deviceMapping) + : inputPath_(inputPath), lastSec_(0), lastUsec_(0), firstEvent_(true) +{ + for (const auto &[sourceId, targetId] : deviceMapping) { + deviceMapping_[sourceId] = "/dev/input/event" + std::to_string(targetId); + } +} + +bool EventReplayer::SeekToDevicesSection(std::ifstream& inputFile) +{ + if (!inputFile.good()) { + PrintError("File stream is not in good state"); + return false; + } + inputFile.seekg(0, std::ios::end); + std::streampos fileSize = inputFile.tellg(); + constexpr std::streamoff SEARCH_OFFSET = 2048; + if (fileSize <= SEARCH_OFFSET) { + inputFile.seekg(0, std::ios::beg); + } else { + inputFile.seekg(-SEARCH_OFFSET, std::ios::end); + std::string discardLine; + std::getline(inputFile, discardLine); + if (inputFile.eof()) { + inputFile.clear(); + inputFile.seekg(0, std::ios::beg); + } + } + std::string line; + while (std::getline(inputFile, line)) { + if (line.find(DEVICES_PREFIX) == 0) { + inputFile.seekg(-static_cast(line.length() + 1), std::ios::cur); + if (inputFile.peek() == '\r') { + inputFile.seekg(-1, std::ios::cur); + } + return true; + } + } + PrintWarning("DEVICES section not found in file"); + return false; +} + +bool EventReplayer::SeekToEventsSection(std::ifstream& inputFile) +{ + if (!inputFile.good()) { + PrintError("File stream is not in good state"); + return false; + } + inputFile.seekg(0, std::ios::beg); + std::string line; + while (std::getline(inputFile, line)) { + if (line == EVENTS_BEGIN) { + return true; + } + } + return false; +} + +bool EventReplayer::Replay() +{ + std::ifstream inputFile(inputPath_); + if (!SeekToDevicesSection(inputFile)) { + PrintError("seek to DEVICES_PREFIX tag error"); + return false; + } + std::map> outputDevices; + if (!InitializeOutputDevices(inputFile, outputDevices)) { + return false; + } + if (outputDevices.empty()) { + PrintError("No output devices available for replay"); + return false; + } + PrintInfo("Starting replay..."); + firstEvent_ = true; + bool result = ReplayEvents(inputFile, outputDevices); + PrintInfo(result ? "Replay completed" : "Replay interrupted"); + return true; +} + +bool EventReplayer::ProcessDeviceLines(std::ifstream& inputFile, + std::map>& outputDevices, uint32_t deviceCount) +{ + std::string line; + for (uint32_t i = 0; i < deviceCount; i++) { + line.clear(); + if (!std::getline(inputFile, line)) { + PrintWarning("Reached end of file after reading %u of %u devices", i, deviceCount); + break; + } + if (line.empty() || line[0] == COMMENT_CHAR) { + --i; + continue; + } + auto device = std::make_unique(); + if (!device) { + PrintError("Failed to allocate device object"); + return false; + } + if (!device->InitFromTextLine(line)) { + PrintWarning("Failed to parse device line: %s", line.c_str()); + continue; + } + uint16_t deviceId = static_cast(device->GetId()); + auto mappingIt = deviceMapping_.find(deviceId); + if (mappingIt != deviceMapping_.end()) { + PrintInfo("Mapping device %u to %s", deviceId, mappingIt->second.c_str()); + device->SetPath(mappingIt->second); + } + if (device->OpenForWriting()) { + PrintInfo("Using device %u: %s", device->GetId(), device->GetName().c_str()); + outputDevices[device->GetId()] = std::move(device); + } else { + PrintWarning("Failed to open device for replay: %s", device->GetPath().c_str()); + } + } + return true; +} + +bool EventReplayer::InitializeOutputDevices(std::ifstream& inputFile, + std::map>& outputDevices) +{ + outputDevices.clear(); + std::string line; + if (!std::getline(inputFile, line)) { + PrintError("Failed to read device count line"); + return false; + } + std::string countStr = line; + if (!RemovePrefix(countStr, DEVICES_PREFIX)) { + PrintError("Invalid device count line format"); + return false; + } + TrimString(countStr); + uint32_t deviceCount = 0; + auto result = std::from_chars(countStr.data(), countStr.data() + countStr.size(), deviceCount); + if (result.ec != std::errc()) { + PrintError("Failed to parse device count: %s", countStr.c_str()); + return false; + } + PrintInfo("Found %u devices", deviceCount); + return ProcessDeviceLines(inputFile, outputDevices, deviceCount); +} + +void EventReplayer::ApplyEventDelay(const struct input_event& currentEvent) +{ + if (!firstEvent_) { + unsigned long nowSec = currentEvent.input_event_sec; + unsigned long nowUsec = currentEvent.input_event_usec; + long diffSec = nowSec - lastSec_; + long diffUsec = nowUsec - lastUsec_; + if (diffUsec < 0) { + diffSec--; + diffUsec += MICROSECONDS_PER_SECOND; + } + if (diffSec > 0 || diffUsec > 0) { + std::this_thread::sleep_for( + std::chrono::seconds(diffSec) + std::chrono::microseconds(diffUsec)); + } + } + firstEvent_ = false; + lastSec_ = currentEvent.input_event_sec; + lastUsec_ = currentEvent.input_event_usec; +} + +bool EventReplayer::ReplayEvents(std::ifstream& inputFile, + const std::map>& outputDevices) +{ + if (!SeekToEventsSection(inputFile)) { + PrintError("Failed to locate events section"); + return false; + } + deviceEventBuffers_.clear(); + std::string line; + while (std::getline(inputFile, line)) { + if (!inputFile || inputFile.eof()) { + PrintWarning("Error reading event record"); + return false; + } + if (line.empty()) { + continue; + } + if (line == EVENTS_END) { + PrintDebug("Reached end of events section"); + break; + } + uint32_t deviceId; + input_event event; + if (!ParseInputLine(line, deviceId, event)) { + PrintError("Failed to parse event line: %s", line.c_str()); + return false; + } + auto deviceIt = outputDevices.find(deviceId); + if (deviceIt == outputDevices.end()) { + continue; + } + ApplyEventDelay(event); + auto& currentDeviceBuffer = deviceEventBuffers_[deviceId]; + currentDeviceBuffer.push_back(event); + if (event.type == EV_SYN) { + if (!deviceIt->second->WriteEvents(currentDeviceBuffer)) { + PrintError("Failed to write events for device %u", deviceId); + return false; + } + currentDeviceBuffer.clear(); + } + if (g_shutdown.load()) { + return false; + } + line.clear(); + } + return true; +} + +template +static bool ParseField(const char*& ptr, const char* endPtr, T& value) +{ + while (ptr < endPtr && (*ptr == ' ' || *ptr == '\t')) { + ptr++; + } + auto result = std::from_chars(ptr, endPtr, value); + if (result.ec != std::errc()) { + return false; + } + ptr = result.ptr; + if (ptr >= endPtr) { + return false; + } + if (*ptr++ != FIELD_COMMA) { + return false; + } + return true; +} + +bool EventReplayer::ParseInputLine(const std::string& line, uint32_t& deviceId, struct input_event& evt) +{ + size_t commentPos = line.find(COMMENT_CHAR); + std::string content = (commentPos != std::string::npos) ? line.substr(0, commentPos) : line; + size_t startPos = content.find(BRACKET_START); + size_t endPos = content.find(BRACKET_END); + if (startPos == std::string::npos || endPos == std::string::npos || startPos >= endPos) { + return false; + } + std::string data = content.substr(startPos + 1, endPos - startPos - 1); + const char* ptr = data.c_str(); + const char* endPtr = ptr + data.length(); + if (!ParseField(ptr, endPtr, deviceId)) { + return false; + } + uint16_t type; + if (!ParseField(ptr, endPtr, type)) { + return false; + } + evt.type = type; + uint16_t code; + if (!ParseField(ptr, endPtr, code)) { + return false; + } + evt.code = code; + int32_t value; + if (!ParseField(ptr, endPtr, value)) { + return false; + } + evt.value = value; + long s; + if (!ParseField(ptr, endPtr, s)) { + return false; + } + evt.input_event_sec = s; + long us; + while (ptr < endPtr && (*ptr == ' ' || *ptr == '\t')) { + ptr++; + } + auto result = std::from_chars(ptr, endPtr, us); + if (result.ec != std::errc()) { + return false; + } + evt.input_event_usec = us; + return true; +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/tools/inject_event/src/input_device.cpp b/tools/inject_event/src/input_device.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e15fa5a21c44efb5f26ae0d6592fef51c0304aba --- /dev/null +++ b/tools/inject_event/src/input_device.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "input_device.h" + +#include +#include +#include + +#include +#include + +namespace OHOS { +namespace MMI { +namespace { +constexpr char DEVICE_PREFIX[] = "DEVICE:"; +constexpr char FIELD_SEPARATOR = '|'; +constexpr int32_t MAX_DEVICE_NAME = 128; +} + +InputDevice::InputDevice() : id_(0), fd_(-1) +{ +} + +InputDevice::InputDevice(const std::string& path, uint32_t id) + : path_(path), id_(id), fd_(-1) +{ + OpenDevice(O_RDONLY | O_NONBLOCK); + if (fd_ >= 0) { + QueryDeviceInfo(); + } +} + +InputDevice::InputDevice(InputDevice&& other) noexcept + : path_(std::move(other.path_)), + name_(std::move(other.name_)), + id_(other.id_), + fd_(other.fd_) +{ + other.fd_ = -1; +} + +InputDevice::~InputDevice() +{ + Close(); +} + +InputDevice& InputDevice::operator=(InputDevice&& other) noexcept +{ + if (this != &other) { + Close(); + path_ = std::move(other.path_); + name_ = std::move(other.name_); + id_ = other.id_; + fd_ = other.fd_; + other.fd_ = -1; + } + return *this; +} + +bool InputDevice::IsOpen() const +{ + return fd_ >= 0; +} + +void InputDevice::Close() +{ + if (fd_ >= 0) { + ::close(fd_); + fd_ = -1; + } +} + +bool InputDevice::OpenForReading() +{ + return OpenDevice(O_RDONLY | O_NONBLOCK); +} + +bool InputDevice::OpenForWriting() +{ + if (!VerifyDeviceMatch()) { + return false; + } + return OpenDevice(O_WRONLY); +} + +int32_t InputDevice::GetFd() const +{ + return fd_; +} + +const std::string& InputDevice::GetPath() const +{ + return path_; +} + +const std::string& InputDevice::GetName() const +{ + return name_; +} + +uint32_t InputDevice::GetId() const +{ + return id_; +} + +void InputDevice::SetId(uint32_t id) +{ + id_ = id; +} + +void InputDevice::SetPath(const std::string& path) +{ + path_ = path; +} + +void InputDevice::SetName(const std::string& name) +{ + name_ = name; +} + +bool InputDevice::ReadEvent(input_event& event) +{ + if (fd_ < 0) { + return false; + } + ssize_t bytesRead = read(fd_, &event, sizeof(event)); + return bytesRead == sizeof(event); +} + +bool InputDevice::WriteEvents(const std::vector& events) +{ + if (fd_ < 0 || events.empty()) { + return false; + } + ssize_t eventBytes = sizeof(input_event) * events.size(); + ssize_t bytesWritten = write(fd_, &events[0], eventBytes); + return bytesWritten == eventBytes; +} + +bool InputDevice::VerifyDeviceMatch() const +{ + if (name_.empty()) { + return true; + } + int32_t tempFd = ::open(path_.c_str(), O_RDONLY | O_NONBLOCK); + if (tempFd < 0) { + PrintWarning("Cannot verify device %s: %s", path_.c_str(), strerror(errno)); + return true; + } + bool matches = false; + char currentDeviceName[MAX_DEVICE_NAME] = "Unknown"; + if (ioctl(tempFd, EVIOCGNAME(sizeof(currentDeviceName)), currentDeviceName) >= 0) { + if (name_ == currentDeviceName) { + matches = true; + } else { + PrintError("Device name mismatch for %s: expected '%s' but got '%s'", + path_.c_str(), name_.c_str(), currentDeviceName); + } + } else { + PrintWarning("Could not get device name for verification: %s", strerror(errno)); + matches = true; + } + ::close(tempFd); + return matches; +} + +bool InputDevice::OpenDevice(int32_t flags) +{ + Close(); // Close if already open + fd_ = ::open(path_.c_str(), flags); + if (fd_ < 0) { + PrintError("Failed to open device %s: %s", path_.c_str(), strerror(errno)); + return false; + } + return true; +} + +void InputDevice::QueryDeviceInfo() +{ + char name[MAX_DEVICE_NAME] = "Unknown"; + if (ioctl(fd_, EVIOCGNAME(sizeof(name)), name) >= 0) { + name_ = name; + } +} + +bool InputDevice::InitFromTextLine(const std::string& line) +{ + std::string workLine = line; + if (!RemovePrefix(workLine, DEVICE_PREFIX)) { + PrintError("Invalid device line format: %s", line.c_str()); + return false; + } + size_t firstSep = workLine.find(FIELD_SEPARATOR); + if (firstSep == std::string::npos) { + PrintError("Missing first separator in device line: %s", line.c_str()); + return false; + } + std::string idStr = workLine.substr(0, firstSep); + TrimString(idStr); + uint32_t deviceId = 0; + auto result = std::from_chars(idStr.data(), idStr.data() + idStr.size(), deviceId); + if (result.ec != std::errc()) { + PrintError("Invalid device ID: %s", idStr.c_str()); + return false; + } + size_t secondSep = workLine.find(FIELD_SEPARATOR, firstSep + 1); + if (secondSep == std::string::npos) { + PrintError("Missing second separator in device line: %s", line.c_str()); + return false; + } + std::string path = workLine.substr(firstSep + 1, secondSep - firstSep - 1); + std::string name = workLine.substr(secondSep + 1); + TrimString(path); + TrimString(name); + id_ = deviceId; + path_ = path; + name_ = name; + return true; +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/tools/inject_event/src/input_replay_command.cpp b/tools/inject_event/src/input_replay_command.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75f7cb79dd674cef41ebe735875a71a9cc53e02d --- /dev/null +++ b/tools/inject_event/src/input_replay_command.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "input_replay_command.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "device_manager.h" +#include "event_recorder.h" +#include "event_replayer.h" + +namespace OHOS { +namespace MMI { +namespace { +constexpr int32_t MIN_ARGC = 2; +} + +std::atomic g_shutdown { false }; + +InputReplayCommand::InputReplayCommand(int32_t argc, char** argv) + : argc_(argc), argv_(argv) +{ + programName_ = (argc > 0) ? argv[0] : "uinput"; +} + +bool InputReplayCommand::ParseOptions(bool& useAllDevices) +{ + static struct option longOptions[] = { + {"help", no_argument, 0, 'h'}, + {"list", no_argument, 0, 'l'}, + {"all", no_argument, 0, 'a'}, + {"map", required_argument, 0, 'm'}, + {0, 0, 0, 0} + }; + int32_t opt; + int32_t optionIndex = 0; + optind = 1; + while ((opt = getopt_long(argc_, argv_, "hlam:", longOptions, &optionIndex)) != -1) { + switch (opt) { + case 'h': + PrintUsage(); + exit(0); + case 'l': + DeviceManager().PrintDeviceList(); + exit(0); + case 'a': + useAllDevices = true; + break; + case 'm': + if (!ParseDeviceMapping(optarg)) { + return false; + } + break; + default: + return false; + } + } + return true; +} + +bool InputReplayCommand::Parse() +{ + if (argc_ < MIN_ARGC) { + PrintUsage(); + return false; + } + bool useAllDevices = false; + if (!ParseOptions(useAllDevices)) { + return false; + } + if (optind >= argc_) { + PrintError("Missing command (record/replay)"); + return false; + } + command_ = argv_[optind++]; + if (optind >= argc_) { + PrintError("Missing file path"); + return false; + } + filePath_ = argv_[optind++]; + if (command_ == "record") { + return ParseRecordCommand(useAllDevices); + } else if (command_ == "replay") { + return ParseReplayCommand(); + } else { + PrintError("Invalid command, supported: record/replay"); + return false; + } +} + +inline void SignalHandler(int32_t sig) +{ + if (sig == SIGINT || sig == SIGTERM) { + g_shutdown.store(true); + std::cout << "\nShutdown signal received, cleaning up..." << std::endl; + } +} + +int32_t InputReplayCommand::HandleRecordReplayCommand(int32_t argc, char** argv) +{ + OHOS::MMI::InputReplayCommand parser(argc, argv); + if (geteuid() != 0) { + std::cerr << "Error: This program must be run as root" << std::endl; + return RET_ERR; + } + if (!parser.Parse()) { + std::cerr << "Failed to parse record/replay command" << std::endl; + return RET_ERR; + } + + if (!parser.Execute()) { + return RET_ERR; + } + return RET_OK; +} + +bool InputReplayCommand::Execute() +{ + if (command_ == "record") { + return ExecuteRecordCommand(); + } else if (command_ == "replay") { + return ExecuteReplayCommand(); + } + return false; +} + +bool InputReplayCommand::ParseDeviceMapping(const std::string& mappingStr) +{ + deviceMapping_.clear(); + const char* ptr = mappingStr.c_str(); + const char* endPtr = ptr + mappingStr.length(); + while (ptr < endPtr) { + uint16_t key; + auto keyResult = std::from_chars(ptr, endPtr, key); + if (keyResult.ec != std::errc() || keyResult.ptr >= endPtr || *keyResult.ptr != ':') { + return false; + } + ptr = keyResult.ptr + 1; + uint16_t value; + auto valueResult = std::from_chars(ptr, endPtr, value); + if (valueResult.ec != std::errc()) { + return false; + } + deviceMapping_[key] = value; + ptr = valueResult.ptr; + if (ptr < endPtr) { + if (*ptr != ',') { + return false; + } + ptr++; + } + } + return !deviceMapping_.empty(); +} + +void InputReplayCommand::SetupSignalHandlers() +{ + struct sigaction sa; + sa.sa_handler = SignalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGINT, &sa, nullptr); + sigaction(SIGTERM, &sa, nullptr); +} + +bool InputReplayCommand::ParseRecordCommand(bool useAllDevices) +{ + useAllDevices_ = useAllDevices; + if (!useAllDevices_) { + for (int32_t i = optind; i < argc_; i++) { + devicePaths_.push_back(argv_[i]); + } + if (devicePaths_.empty()) { + PrintError("No devices specified for recording"); + PrintError("Use --all to record from all devices or specify device paths"); + return false; + } + } + return true; +} + +bool InputReplayCommand::ParseReplayCommand() +{ + if (optind < argc_) { + PrintError("Unexpected arguments for replay command"); + return false; + } + return true; +} + +bool InputReplayCommand::ExecuteRecordCommand() +{ + std::vector devices; + if (useAllDevices_) { + DeviceManager deviceManager; + devices = deviceManager.DiscoverDevices(); + } else { + const std::string PREFIX = "/dev/input/event"; + for (size_t i = 0; i < devicePaths_.size(); i++) { + const std::string& path = devicePaths_[i]; + if (path.substr(0, PREFIX.length()) != PREFIX) { + PrintError("Invalid input device path format: %s", path.c_str()); + return false; + } + uint16_t deviceId = 0; + const char* start = path.c_str() + PREFIX.length(); + const char* end = path.c_str() + path.length(); + std::from_chars_result result = std::from_chars(start, end, deviceId); + if (result.ec != std::errc() || result.ptr != end) { + PrintError("Invalid device number in path: %s", path.c_str()); + return false; + } + InputDevice device(path, deviceId); + if (device.IsOpen()) { + devices.push_back(std::move(device)); + } else { + PrintWarning("Failed to open device: %s", path.c_str()); + } + } + } + if (devices.empty()) { + PrintError("No valid input devices specified"); + return false; + } + SetupSignalHandlers(); + EventRecorder recorder(filePath_); + if (!recorder.Start(devices)) { + return false; + } + recorder.Stop(); + return true; +} + +bool InputReplayCommand::ExecuteReplayCommand() +{ + SetupSignalHandlers(); + PrintInfo("Press Enter to start replay..."); + std::cin.get(); + EventReplayer replayer(filePath_, deviceMapping_); + return replayer.Replay(); +} + +void InputReplayCommand::PrintUsage() const +{ + std::cout << "Usage:" << std::endl + << " " << programName_ << " [options] record [device paths...]" << std::endl + << " " << programName_ << " [options] replay " << std::endl + << std::endl + << "Options:" << std::endl + << " -h, --help Show this help message" << std::endl + << " -l, --list List available input devices" << std::endl + << " -a, --all Record from all available input devices" << std::endl + << " -m, --map Specify device mapping for replay (e.g., \"0:0,1:2,4:2\")" << std::endl + << " Format: sourceDeviceId:targetDeviceId,..." << std::endl + << std::endl + << "Examples:" << std::endl + << " " << programName_ << " record -a events.bin # Record from all devices" << std::endl + << " " << programName_ << " record events.bin /dev/input/event0 /dev/input/event1" << std::endl + << " " << programName_ << " replay events.bin # Replay to original devices" << std::endl + << " " << programName_ + << " replay -m \"0:1,1:0\" events.bin # Replay with custom device mapping" << std::endl; +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/tools/inject_event/src/main.cpp b/tools/inject_event/src/main.cpp index 5332c835cac147052399ab2ef787f7d24242ab75..b687bb19500ac5b0d86cd70577a6fcd963d0a110 100644 --- a/tools/inject_event/src/main.cpp +++ b/tools/inject_event/src/main.cpp @@ -14,9 +14,17 @@ */ #include "input_manager_command.h" +#ifdef OHOS_BUILD_ENABLE_EVENT_RECORDER +#include "input_replay_command.h" +#endif // OHOS_BUILD_ENABLE_EVENT_RECORDER int32_t main(int32_t argc, char** argv) { +#ifdef OHOS_BUILD_ENABLE_EVENT_RECORDER + if (argc > 1 && (std::string(argv[1]) == "replay" || std::string(argv[1]) == "record")) { + return OHOS::MMI::InputReplayCommand::HandleRecordReplayCommand(argc, argv); + } +#endif // OHOS_BUILD_ENABLE_EVENT_RECORDER OHOS::MMI::InputManagerCommand command; return command.ParseCommand(argc, argv); } \ No newline at end of file diff --git a/tools/inject_event/test/device_manager_test.cpp b/tools/inject_event/test/device_manager_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d4d45e0d793711a1c091bce528cd66553e7dba8d --- /dev/null +++ b/tools/inject_event/test/device_manager_test.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "device_manager.h" + +namespace OHOS { +namespace MMI { +namespace { +using namespace testing::ext; +} // namespace + +class DeviceManagerTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} + void SetUp() {} + void TearDown() {} +}; + +/** + * @tc.name: DeviceManagerTest_ExtractEventNumber_Valid + * @tc.desc: Test extracting event number from valid file names + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(DeviceManagerTest, DeviceManagerTest_ExtractEventNumber_Valid, TestSize.Level1) +{ + DeviceManager deviceManager; + EXPECT_EQ(deviceManager.ExtractEventNumber("event0"), 0); + EXPECT_EQ(deviceManager.ExtractEventNumber("event1"), 1); + EXPECT_EQ(deviceManager.ExtractEventNumber("event10"), 10); + EXPECT_EQ(deviceManager.ExtractEventNumber("event999"), 999); +} + +/** + * @tc.name: DeviceManagerTest_ExtractEventNumber_Invalid + * @tc.desc: Test extracting event number from invalid file names + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(DeviceManagerTest, DeviceManagerTest_ExtractEventNumber_Invalid, TestSize.Level1) +{ + DeviceManager deviceManager; + EXPECT_EQ(deviceManager.ExtractEventNumber(""), -1); + EXPECT_EQ(deviceManager.ExtractEventNumber("event"), -1); + EXPECT_EQ(deviceManager.ExtractEventNumber("eventX"), -1); + EXPECT_EQ(deviceManager.ExtractEventNumber("event-1"), -1); + EXPECT_EQ(deviceManager.ExtractEventNumber("EVENT0"), -1); + EXPECT_EQ(deviceManager.ExtractEventNumber("myevent0"), -1); + EXPECT_EQ(deviceManager.ExtractEventNumber("event0suffix"), -1); +} + +/** + * @tc.name: DeviceManagerTest_BuildDevicePath + * @tc.desc: Test building device path from file name + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(DeviceManagerTest, DeviceManagerTest_BuildDevicePath, TestSize.Level1) +{ + DeviceManager deviceManager; + EXPECT_EQ(deviceManager.BuildDevicePath("event0"), "/dev/input/event0"); + EXPECT_EQ(deviceManager.BuildDevicePath("test"), "/dev/input/test"); + EXPECT_EQ(deviceManager.BuildDevicePath(""), "/dev/input/"); +} + +/** + * @tc.name: DeviceManagerTest_DiscoverDevices_NoException + * @tc.desc: Test that DiscoverDevices doesn't throw exceptions + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(DeviceManagerTest, DeviceManagerTest_DiscoverDevices_NoException, TestSize.Level1) +{ + DeviceManager deviceManager; + ASSERT_NO_FATAL_FAILURE({ + auto devices = deviceManager.DiscoverDevices(); + }); +} + +/** + * @tc.name: DeviceManagerTest_PrintDeviceList_NoException + * @tc.desc: Test that PrintDeviceList doesn't throw exceptions + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(DeviceManagerTest, DeviceManagerTest_PrintDeviceList_NoException, TestSize.Level1) +{ + DeviceManager deviceManager; + ASSERT_NO_FATAL_FAILURE({ + deviceManager.PrintDeviceList(); + }); +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/tools/inject_event/test/event_recorder_test.cpp b/tools/inject_event/test/event_recorder_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ded0b66ef4bcd498783b1283d102dc0f520868c0 --- /dev/null +++ b/tools/inject_event/test/event_recorder_test.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "event_recorder.h" + +namespace OHOS { +namespace MMI { +namespace { +using namespace testing::ext; + +const std::string SANDBOX_PATH = "/data/service/el1/public/multimodalinput/"; +const std::string TEST_FILE_PATH = SANDBOX_PATH + "test_events.bin"; + +void CleanupTestFiles() +{ + std::remove(TEST_FILE_PATH.c_str()); +} +} // namespace + +class EventRecorderTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) + { + CleanupTestFiles(); + } + void SetUp() {} + void TearDown() + { + CleanupTestFiles(); + } +}; + +/** + * @tc.name: EventRecorderTest_Constructor + * @tc.desc: Test constructor of EventRecorder + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_Constructor, TestSize.Level1) +{ + EventRecorder recorder(TEST_FILE_PATH); + SUCCEED(); +} + +/** + * @tc.name: EventRecorderTest_GetEventTypeString + * @tc.desc: Test getting event type string representation + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_GetEventTypeString, TestSize.Level1) +{ + EXPECT_EQ(EventRecorder::GetEventTypeString(EV_SYN), "EV_SYN"); + EXPECT_EQ(EventRecorder::GetEventTypeString(EV_KEY), "EV_KEY"); + EXPECT_EQ(EventRecorder::GetEventTypeString(EV_REL), "EV_REL"); + EXPECT_EQ(EventRecorder::GetEventTypeString(EV_ABS), "EV_ABS"); + EXPECT_EQ(EventRecorder::GetEventTypeString(0xFF), "UNKNOWN_TYPE(255)"); +} + +/** + * @tc.name: EventRecorderTest_GetEventCodeString_EVKey + * @tc.desc: Test getting event code string representation for EV_KEY + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_GetEventCodeString_EVKey, TestSize.Level1) +{ + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_KEY, KEY_ENTER), "KEY_ENTER"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_KEY, KEY_ESC), "KEY_ESC"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_KEY, KEY_SPACE), "KEY_SPACE"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_KEY, KEY_A), "KEY_A"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_KEY, KEY_Z), "KEY_Z"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_KEY, 0xFFFF), "CODE(65535)"); +} + +/** + * @tc.name: EventRecorderTest_GetEventCodeString_EVSyn + * @tc.desc: Test getting event code string representation for EV_SYN + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_GetEventCodeString_EVSyn, TestSize.Level1) +{ + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_SYN, SYN_REPORT), "SYN_REPORT"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_SYN, SYN_CONFIG), "SYN_CONFIG"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_SYN, 0xFF), "CODE(255)"); +} + +/** + * @tc.name: EventRecorderTest_GetEventCodeString_EVRel + * @tc.desc: Test getting event code string representation for EV_REL + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_GetEventCodeString_EVRel, TestSize.Level1) +{ + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_REL, REL_X), "REL_X"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_REL, REL_Y), "REL_Y"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_REL, REL_WHEEL), "REL_WHEEL"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_REL, 0xFF), "CODE(255)"); +} + +/** + * @tc.name: EventRecorderTest_GetEventCodeString_EVAbs + * @tc.desc: Test getting event code string representation for EV_ABS + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_GetEventCodeString_EVAbs, TestSize.Level1) +{ + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_ABS, ABS_X), "ABS_X"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_ABS, ABS_Y), "ABS_Y"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_ABS, ABS_MT_POSITION_X), "ABS_MT_POSITION_X"); + EXPECT_EQ(EventRecorder::GetEventCodeString(EV_ABS, 0xFF), "CODE(255)"); +} + +/** + * @tc.name: EventRecorderTest_GetSecondaryEventCodeString + * @tc.desc: Test getting secondary event code string representation + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_GetSecondaryEventCodeString, TestSize.Level1) +{ + EXPECT_EQ(EventRecorder::GetSecondaryEventCodeString(EV_LED, LED_NUML), "LED_NUML"); + EXPECT_EQ(EventRecorder::GetSecondaryEventCodeString(EV_LED, LED_CAPSL), "LED_CAPSL"); + EXPECT_EQ(EventRecorder::GetSecondaryEventCodeString(EV_REP, REP_DELAY), "REP_DELAY"); + EXPECT_EQ(EventRecorder::GetSecondaryEventCodeString(EV_REP, REP_PERIOD), "REP_PERIOD"); + EXPECT_EQ(EventRecorder::GetSecondaryEventCodeString(EV_MSC, MSC_SERIAL), "MSC_SERIAL"); + EXPECT_EQ(EventRecorder::GetSecondaryEventCodeString(EV_LED, 0xFF), ""); + EXPECT_EQ(EventRecorder::GetSecondaryEventCodeString(0xFF, 0), ""); +} + +/** + * @tc.name: EventRecorderTest_Start_EmptyDevices + * @tc.desc: Test start with empty device list + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_Start_EmptyDevices, TestSize.Level1) +{ + EventRecorder recorder(TEST_FILE_PATH); + std::vector devices; + EXPECT_FALSE(recorder.Start(devices)); +} + +/** + * @tc.name: EventRecorderTest_Stop_NotRunning + * @tc.desc: Test stop when not running + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_Stop_NotRunning, TestSize.Level1) +{ + EventRecorder recorder(TEST_FILE_PATH); + ASSERT_NO_FATAL_FAILURE({ + recorder.Stop(); + }); +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/tools/inject_event/test/event_replayer_test.cpp b/tools/inject_event/test/event_replayer_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a9b95830eab03fa45c0890c1e2584014417489e6 --- /dev/null +++ b/tools/inject_event/test/event_replayer_test.cpp @@ -0,0 +1,459 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "event_replayer.h" + +namespace OHOS { +namespace MMI { +namespace { +using namespace testing::ext; + +const std::string SANDBOX_PATH = "/data/service/el1/public/multimodalinput/"; +const std::string TEST_FILE_PATH = SANDBOX_PATH + "mmi_test_events.rec"; +const std::string INVALID_FILE_PATH = SANDBOX_PATH + "mmi_invalid_events.rec"; +const std::string EMPTY_FILE_PATH = SANDBOX_PATH + "mmi_empty_events.rec"; +const std::string PARTIAL_FILE_PATH = SANDBOX_PATH + "mmi_partial_events.rec"; + +bool CreateTestEventFile(const std::string& path) +{ + std::ofstream file(path); + if (!file.is_open()) { + return false; + } + file << "EVENTS_BEGIN" << std::endl; + file << "[1, 1, 30, 1, 1682345678, 123456] # EV_KEY / KEY_A 1" << std::endl; + file << "[1, 0, 0, 0, 1682345678, 123456] # EV_SYN / SYN_REPORT 0" << std::endl; + file << "[1, 1, 30, 0, 1682345678, 223456] # EV_KEY / KEY_A 0" << std::endl; + file << "[1, 0, 0, 0, 1682345678, 223456] # EV_SYN / SYN_REPORT 0" << std::endl; + file << "EVENTS_END" << std::endl; + file << std::endl; + file << "DEVICES: 1" << std::endl; + file << "DEVICE: 1|/dev/input/event2|Test Keyboard" << std::endl; + file.close(); + return true; +} + +bool CreateInvalidEventFile(const std::string& path) +{ + std::ofstream file(path); + if (!file.is_open()) { + return false; + } + file << "INVALID CONTENT" << std::endl; + file.close(); + return true; +} + +bool CreateEmptyEventFile(const std::string& path) +{ + std::ofstream file(path); + if (!file.is_open()) { + return false; + } + file.close(); + return true; +} + +bool CreatePartialEventFile(const std::string& path, bool includeDevices, bool includeEvents) +{ + std::ofstream file(path); + if (!file.is_open()) { + return false; + } + file << "HEADER INFORMATION" << std::endl; + if (includeEvents) { + file << "EVENTS_BEGIN" << std::endl; + file << "[4, 2, 0, -2, 1501837700, 841124] # EV_REL / REL_X -2" << std::endl; + file << "[4, 0, 0, 0, 1501837700, 841124] # EV_SYN / SYN_REPORT 0" << std::endl; + file << "EVENTS_END" << std::endl; + } + if (includeDevices) { + file << "DEVICES: 2" << std::endl; + file << "DEVICE: 4|/dev/input/event4|Compx 2.4G Receiver Mouse" << std::endl; + file << "DEVICE: 10|/dev/input/event10|VSoC touchscreen" << std::endl; + } + file.close(); + return true; +} + +void CleanupTestFiles() +{ + std::remove(TEST_FILE_PATH.c_str()); + std::remove(INVALID_FILE_PATH.c_str()); + std::remove(EMPTY_FILE_PATH.c_str()); + std::remove(PARTIAL_FILE_PATH.c_str()); +} +} // namespace + +class EventReplayerTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) + { + CleanupTestFiles(); + } + void SetUp() {} + void TearDown() + { + CleanupTestFiles(); + } +}; + +/** + * @tc.name: EventReplayerTest_Constructor + * @tc.desc: Test constructor of EventReplayer + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_Constructor, TestSize.Level1) +{ + EventReplayer replayer1(TEST_FILE_PATH); + SUCCEED(); + + std::map deviceMapping = {{1, 3}, {2, 4}}; + EventReplayer replayer2(TEST_FILE_PATH, deviceMapping); + SUCCEED(); +} + +/** + * @tc.name: EventReplayerTest_ParseInputLine_Valid + * @tc.desc: Test parsing valid input lines + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_ParseInputLine_Valid, TestSize.Level1) +{ + uint32_t deviceId; + input_event event; + + EXPECT_TRUE(EventReplayer::ParseInputLine("[1, 1, 30, 1, 1682345678, 123456] # EV_KEY / KEY_A 1", + deviceId, event)); + EXPECT_EQ(deviceId, 1); + EXPECT_EQ(event.type, 1); + EXPECT_EQ(event.code, 30); + EXPECT_EQ(event.value, 1); + EXPECT_EQ(event.input_event_sec, 1682345678); + EXPECT_EQ(event.input_event_usec, 123456); + + EXPECT_TRUE(EventReplayer::ParseInputLine("[ 2, 3, 40, 0, 1682345679, 234567 ] # Comment", + deviceId, event)); + EXPECT_EQ(deviceId, 2); + EXPECT_EQ(event.type, 3); + EXPECT_EQ(event.code, 40); + EXPECT_EQ(event.value, 0); + EXPECT_EQ(event.input_event_sec, 1682345679); + EXPECT_EQ(event.input_event_usec, 234567); + + EXPECT_TRUE(EventReplayer::ParseInputLine("[3, 0, 0, 0, 1682345680, 345678]", + deviceId, event)); + EXPECT_EQ(deviceId, 3); + EXPECT_EQ(event.type, 0); + EXPECT_EQ(event.code, 0); + EXPECT_EQ(event.value, 0); + EXPECT_EQ(event.input_event_sec, 1682345680); + EXPECT_EQ(event.input_event_usec, 345678); +} + +/** + * @tc.name: EventReplayerTest_ParseInputLine_Invalid + * @tc.desc: Test parsing invalid input lines + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_ParseInputLine_Invalid, TestSize.Level1) +{ + uint32_t deviceId; + input_event event; + + EXPECT_FALSE(EventReplayer::ParseInputLine("", deviceId, event)); + EXPECT_FALSE(EventReplayer::ParseInputLine("1, 1, 30, 1, 1682345678, 123456", deviceId, event)); + EXPECT_FALSE(EventReplayer::ParseInputLine("[1, 1, 30]", deviceId, event)); + EXPECT_FALSE(EventReplayer::ParseInputLine("[a, 1, 30, 1, 1682345678, 123456]", deviceId, event)); + EXPECT_FALSE(EventReplayer::ParseInputLine("[ 1, 1, 30, 1, 1682345678, 123456", deviceId, event)); + EXPECT_FALSE(EventReplayer::ParseInputLine("1, 1, 30, 1, 1682345678, 123456 ]", deviceId, event)); + EXPECT_FALSE(EventReplayer::ParseInputLine("[1 1, 30, 1, 1682345678, 123456]", deviceId, event)); +} + +/** + * @tc.name: EventReplayerTest_FileOpenError + * @tc.desc: Test file opening error cases + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_FileOpenError, TestSize.Level1) +{ + std::string nonExistentPath = SANDBOX_PATH + "non_existent_file.rec"; + EventReplayer replayer(nonExistentPath); + EXPECT_FALSE(replayer.Replay()); +} + +/** + * @tc.name: EventReplayerTest_EmptyFileError + * @tc.desc: Test with empty file error cases + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_EmptyFileError, TestSize.Level1) +{ + if (!CreateEmptyEventFile(EMPTY_FILE_PATH)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + EventReplayer replayer(EMPTY_FILE_PATH); + EXPECT_FALSE(replayer.Replay()); +} + +/** + * @tc.name: EventReplayerTest_InvalidFileFormat + * @tc.desc: Test with invalid file format + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_InvalidFileFormat, TestSize.Level1) +{ + if (!CreateInvalidEventFile(INVALID_FILE_PATH)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + EventReplayer replayer(INVALID_FILE_PATH); + EXPECT_FALSE(replayer.Replay()); +} + +/** + * @tc.name: EventReplayerTest_DeviceMapping + * @tc.desc: Test device mapping functionality + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_DeviceMapping, TestSize.Level1) +{ + std::map deviceMapping = {{1, 3}, {2, 4}}; + EventReplayer replayer(TEST_FILE_PATH, deviceMapping); + SUCCEED(); +} + +/** + * @tc.name: EventReplayerTest_InvalidDeviceError + * @tc.desc: Test error handling when device is invalid + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_InvalidDeviceError, TestSize.Level1) +{ + if (!CreateTestEventFile(TEST_FILE_PATH)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + std::map deviceMapping = {{2, 3}, {3, 4}}; + EventReplayer replayer(TEST_FILE_PATH, deviceMapping); + ASSERT_NO_FATAL_FAILURE({ + replayer.Replay(); + }); +} + +/** + * @tc.name: EventReplayerTest_SeekToDevicesSection_Found + * @tc.desc: Test seeking to DEVICES section when it exists + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_SeekToDevicesSection_Found, TestSize.Level1) +{ + if (!CreatePartialEventFile(PARTIAL_FILE_PATH, true, false)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + EventReplayer replayer(PARTIAL_FILE_PATH); + std::ifstream inputFile(PARTIAL_FILE_PATH); + EXPECT_TRUE(inputFile.is_open()) << "Failed to open test file"; + EXPECT_TRUE(replayer.SeekToDevicesSection(inputFile)); + // Verify we're at the right position + std::string line; + std::getline(inputFile, line); + EXPECT_EQ(line, "DEVICES: 2") << "File position should be at DEVICES line"; + + inputFile.close(); +} + +/** + * @tc.name: EventReplayerTest_SeekToDevicesSection_NotFound + * @tc.desc: Test seeking to DEVICES section when it doesn't exist + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_SeekToDevicesSection_NotFound, TestSize.Level1) +{ + if (!CreatePartialEventFile(PARTIAL_FILE_PATH, false, true)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + EventReplayer replayer(PARTIAL_FILE_PATH); + std::ifstream inputFile(PARTIAL_FILE_PATH); + EXPECT_TRUE(inputFile.is_open()) << "Failed to open test file"; + EXPECT_FALSE(replayer.SeekToDevicesSection(inputFile)); + inputFile.close(); +} + +/** + * @tc.name: EventReplayerTest_SeekToEventsSection_Found + * @tc.desc: Test seeking to EVENTS_BEGIN section when it exists + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_SeekToEventsSection_Found, TestSize.Level1) +{ + if (!CreatePartialEventFile(PARTIAL_FILE_PATH, false, true)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + EventReplayer replayer(PARTIAL_FILE_PATH); + std::ifstream inputFile(PARTIAL_FILE_PATH); + EXPECT_TRUE(inputFile.is_open()) << "Failed to open test file"; + EXPECT_TRUE(replayer.SeekToEventsSection(inputFile)); + // Verify we're at the right position (after EVENTS_BEGIN) + std::string line; + std::getline(inputFile, line); + EXPECT_EQ(line, "[4, 2, 0, -2, 1501837700, 841124] # EV_REL / REL_X -2") << + "File position should be after EVENTS_BEGIN line"; + inputFile.close(); +} + +/** + * @tc.name: EventReplayerTest_SeekToEventsSection_NotFound + * @tc.desc: Test seeking to EVENTS_BEGIN section when it doesn't exist + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_SeekToEventsSection_NotFound, TestSize.Level1) +{ + if (!CreatePartialEventFile(PARTIAL_FILE_PATH, true, false)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + EventReplayer replayer(PARTIAL_FILE_PATH); + std::ifstream inputFile(PARTIAL_FILE_PATH); + EXPECT_TRUE(inputFile.is_open()) << "Failed to open test file"; + EXPECT_FALSE(replayer.SeekToEventsSection(inputFile)); + inputFile.close(); +} + +/** + * @tc.name: EventReplayerTest_SeekToDevicesSection_BadStream + * @tc.desc: Test seeking to DEVICES section with a bad file stream + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_SeekToDevicesSection_BadStream, TestSize.Level1) +{ + EventReplayer replayer(INVALID_FILE_PATH); + std::ifstream inputFile(INVALID_FILE_PATH); + inputFile.close(); // Deliberately close to make it bad + EXPECT_FALSE(replayer.SeekToDevicesSection(inputFile)); +} + +/** + * @tc.name: EventReplayerTest_SeekToEventsSection_BadStream + * @tc.desc: Test seeking to EVENTS_BEGIN section with a bad file stream + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_SeekToEventsSection_BadStream, TestSize.Level1) +{ + EventReplayer replayer(INVALID_FILE_PATH); + std::ifstream inputFile(INVALID_FILE_PATH); + inputFile.close(); // Deliberately close to make it bad + EXPECT_FALSE(replayer.SeekToEventsSection(inputFile)); +} + +/** + * @tc.name: EventReplayerTest_SeekToDevicesSection_CompleteFile + * @tc.desc: Test seeking to DEVICES section in a complete file with both sections + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_SeekToDevicesSection_CompleteFile, TestSize.Level1) +{ + if (!CreateTestEventFile(TEST_FILE_PATH)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + EventReplayer replayer(TEST_FILE_PATH); + std::ifstream inputFile(TEST_FILE_PATH); + EXPECT_TRUE(inputFile.is_open()) << "Failed to open test file"; + EXPECT_TRUE(replayer.SeekToDevicesSection(inputFile)); + // Verify we're at the right position + std::string line; + std::getline(inputFile, line); + EXPECT_EQ(line, "DEVICES: 1") << "File position should be at DEVICES line"; + inputFile.close(); +} + +/** + * @tc.name: EventReplayerTest_SeekToEventsSection_CompleteFile + * @tc.desc: Test seeking to EVENTS_BEGIN section in a complete file with both sections + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_SeekToEventsSection_CompleteFile, TestSize.Level1) +{ + if (!CreateTestEventFile(TEST_FILE_PATH)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + EventReplayer replayer(TEST_FILE_PATH); + std::ifstream inputFile(TEST_FILE_PATH); + EXPECT_TRUE(inputFile.is_open()) << "Failed to open test file"; + EXPECT_TRUE(replayer.SeekToEventsSection(inputFile)); + // Verify we're at the right position (after EVENTS_BEGIN) + std::string line; + std::getline(inputFile, line); + EXPECT_EQ(line, "[1, 1, 30, 1, 1682345678, 123456] # EV_KEY / KEY_A 1") << + "File position should be after EVENTS_BEGIN line"; + inputFile.close(); +} + +/** + * @tc.name: EventReplayerTest_SeekToDevicesSection_EmptyFile + * @tc.desc: Test seeking to DEVICES section in an empty file + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_SeekToDevicesSection_EmptyFile, TestSize.Level1) +{ + if (!CreateEmptyEventFile(EMPTY_FILE_PATH)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + EventReplayer replayer(EMPTY_FILE_PATH); + std::ifstream inputFile(EMPTY_FILE_PATH); + EXPECT_TRUE(inputFile.is_open()) << "Failed to open test file"; + EXPECT_FALSE(replayer.SeekToDevicesSection(inputFile)); + inputFile.close(); +} + +/** + * @tc.name: EventReplayerTest_SeekToEventsSection_EmptyFile + * @tc.desc: Test seeking to EVENTS_BEGIN section in an empty file + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventReplayerTest, EventReplayerTest_SeekToEventsSection_EmptyFile, TestSize.Level1) +{ + if (!CreateEmptyEventFile(EMPTY_FILE_PATH)) { + GTEST_SKIP() << "Failed to create test file, skipping test"; + } + EventReplayer replayer(EMPTY_FILE_PATH); + std::ifstream inputFile(EMPTY_FILE_PATH); + EXPECT_TRUE(inputFile.is_open()) << "Failed to open test file"; + EXPECT_FALSE(replayer.SeekToEventsSection(inputFile)); + inputFile.close(); +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/tools/inject_event/test/input_device_test.cpp b/tools/inject_event/test/input_device_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d363576e92bb2e5f74d8f60005cc7c0279987440 --- /dev/null +++ b/tools/inject_event/test/input_device_test.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "input_device.h" + +namespace OHOS { +namespace MMI { +namespace { +using namespace testing::ext; +} // namespace + +class InputDeviceTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} + void SetUp() {} + void TearDown() {} +}; + +/** + * @tc.name: InputDeviceTest_DefaultConstructor + * @tc.desc: Test default constructor of InputDevice + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputDeviceTest, InputDeviceTest_DefaultConstructor, TestSize.Level1) +{ + InputDevice device; + EXPECT_EQ(device.GetId(), 0); + EXPECT_EQ(device.GetFd(), -1); + EXPECT_TRUE(device.GetPath().empty()); + EXPECT_TRUE(device.GetName().empty()); + EXPECT_FALSE(device.IsOpen()); +} + +/** + * @tc.name: InputDeviceTest_ParamConstructor + * @tc.desc: Test parameterized constructor of InputDevice + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputDeviceTest, InputDeviceTest_ParamConstructor, TestSize.Level1) +{ + InputDevice device("/non/existent/path", 123); + EXPECT_EQ(device.GetId(), 123); + EXPECT_EQ(device.GetPath(), "/non/existent/path"); + EXPECT_FALSE(device.IsOpen()); +} + +/** + * @tc.name: InputDeviceTest_MoveConstructor + * @tc.desc: Test move constructor of InputDevice + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputDeviceTest, InputDeviceTest_MoveConstructor, TestSize.Level1) +{ + InputDevice device1; + device1.SetId(42); + device1.SetPath("/test/path"); + device1.SetName("TestDevice"); + + InputDevice device2(std::move(device1)); + EXPECT_EQ(device2.GetId(), 42); + EXPECT_EQ(device2.GetPath(), "/test/path"); + EXPECT_EQ(device2.GetName(), "TestDevice"); + + EXPECT_TRUE(device1.GetPath().empty()); + EXPECT_TRUE(device1.GetName().empty()); +} + +/** + * @tc.name: InputDeviceTest_MoveAssignment + * @tc.desc: Test move assignment operator of InputDevice + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputDeviceTest, InputDeviceTest_MoveAssignment, TestSize.Level1) +{ + InputDevice device1; + device1.SetId(42); + device1.SetPath("/test/path"); + device1.SetName("TestDevice"); + + InputDevice device2; + device2 = std::move(device1); + EXPECT_EQ(device2.GetId(), 42); + EXPECT_EQ(device2.GetPath(), "/test/path"); + EXPECT_EQ(device2.GetName(), "TestDevice"); + + EXPECT_TRUE(device1.GetPath().empty()); + EXPECT_TRUE(device1.GetName().empty()); +} + +/** + * @tc.name: InputDeviceTest_SettersGetters + * @tc.desc: Test setters and getters of InputDevice + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputDeviceTest, InputDeviceTest_SettersGetters, TestSize.Level1) +{ + InputDevice device; + + device.SetId(123); + EXPECT_EQ(device.GetId(), 123); + + device.SetPath("/some/path"); + EXPECT_EQ(device.GetPath(), "/some/path"); + + device.SetName("Device Name"); + EXPECT_EQ(device.GetName(), "Device Name"); +} + +/** + * @tc.name: InputDeviceTest_TrimString + * @tc.desc: Test TrimString method + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputDeviceTest, InputDeviceTest_TrimString, TestSize.Level1) +{ + std::string str1 = " test string "; + TrimString(str1); + EXPECT_EQ(str1, "test string"); + + std::string str2 = "test"; + TrimString(str2); + EXPECT_EQ(str2, "test"); + + std::string str3 = " "; + TrimString(str3); + EXPECT_TRUE(str3.empty()); + + std::string str4 = ""; + TrimString(str4); + EXPECT_TRUE(str4.empty()); +} + +/** + * @tc.name: InputDeviceTest_RemovePrefix + * @tc.desc: Test RemovePrefix method + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputDeviceTest, InputDeviceTest_RemovePrefix, TestSize.Level1) +{ + std::string str1 = "PREFIX:value"; + EXPECT_TRUE(RemovePrefix(str1, "PREFIX:")); + EXPECT_EQ(str1, "value"); + + std::string str2 = "NON_PREFIX:value"; + EXPECT_FALSE(RemovePrefix(str2, "PREFIX:")); + EXPECT_EQ(str2, "NON_PREFIX:value"); + + std::string str3 = "PREFIX: value "; + EXPECT_TRUE(RemovePrefix(str3, "PREFIX:")); + EXPECT_EQ(str3, "value"); +} + +/** + * @tc.name: InputDeviceTest_InitFromTextLine_Valid + * @tc.desc: Test initializing device from valid text line + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputDeviceTest, InputDeviceTest_InitFromTextLine_Valid, TestSize.Level1) +{ + InputDevice device; + EXPECT_TRUE(device.InitFromTextLine("DEVICE: 42 | /dev/input/event0 | Keyboard")); + EXPECT_EQ(device.GetId(), 42); + EXPECT_EQ(device.GetPath(), "/dev/input/event0"); + EXPECT_EQ(device.GetName(), "Keyboard"); +} + +/** + * @tc.name: InputDeviceTest_InitFromTextLine_Invalid + * @tc.desc: Test initializing device from invalid text lines + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputDeviceTest, InputDeviceTest_InitFromTextLine_Invalid, TestSize.Level1) +{ + InputDevice device; + EXPECT_FALSE(device.InitFromTextLine("42 | /dev/input/event0 | Keyboard")); + EXPECT_FALSE(device.InitFromTextLine("DEVICE: 42 /dev/input/event0 | Keyboard")); + EXPECT_FALSE(device.InitFromTextLine("DEVICE: 42 | /dev/input/event0 Keyboard")); + EXPECT_FALSE(device.InitFromTextLine("DEVICE: abc | /dev/input/event0 | Keyboard")); +} + +/** + * @tc.name: InputDeviceTest_WriteEvents_EmptyVector + * @tc.desc: Test writing empty vector of events + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputDeviceTest, InputDeviceTest_WriteEvents_EmptyVector, TestSize.Level1) +{ + InputDevice device; + std::vector events; + EXPECT_FALSE(device.WriteEvents(events)); +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/tools/inject_event/test/input_replay_command_test.cpp b/tools/inject_event/test/input_replay_command_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b26a1b778e61bede9aa38392a56be76cd3f11ae3 --- /dev/null +++ b/tools/inject_event/test/input_replay_command_test.cpp @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "mmi_log.h" +#include "input_replay_command.h" + +namespace OHOS { +namespace MMI { +namespace { +using namespace testing::ext; +} // namespace + +class InputReplayCommandTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} + void SetUp() {} + void TearDown() {} +}; + +/** + * @tc.name: InputReplayCommandTest_Constructor + * @tc.desc: Test constructor of InputReplayCommand + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_Constructor, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char* argv[] = {programName, nullptr}; + InputReplayCommand command(1, argv); + SUCCEED(); +} + +/** + * @tc.name: InputReplayCommandTest_ParseDeviceMapping_Valid + * @tc.desc: Test parsing valid device mapping + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_ParseDeviceMapping_Valid, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char mapArg[] = {"--map"}; + char mapValue[] = {"0:1,2:3,4:5"}; + char recordArg[] = {"record"}; + char outputPath[] = {"output.bin"}; + char devicePath[] = {"/dev/input/event0"}; + + char* mappingArgv[] = { + programName, + mapArg, mapValue, + recordArg, outputPath, + devicePath + }; + InputReplayCommand mappingCommand(6, mappingArgv); + EXPECT_TRUE(mappingCommand.Parse()); +} + +/** + * @tc.name: InputReplayCommandTest_ParseDeviceMapping_Invalid + * @tc.desc: Test parsing invalid device mapping + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_ParseDeviceMapping_Invalid, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char mapArg[] = {"--map"}; + char invalidMapValue[] = {"0:1,invalid,4:5"}; + char invalidFormatValue[] = {"0:1:2"}; + char emptyMapValue[] = {""}; + char recordArg[] = {"record"}; + char outputPath[] = {"output.bin"}; + char devicePath[] = {"/dev/input/event0"}; + + // Invalid mapping value + char* invalidMappingArgv[] = { + programName, + mapArg, invalidMapValue, + recordArg, outputPath, + devicePath + }; + InputReplayCommand invalidCommand(6, invalidMappingArgv); + EXPECT_FALSE(invalidCommand.Parse()); + + // Invalid format + char* invalidFormatArgv[] = { + programName, + mapArg, invalidFormatValue, + recordArg, outputPath, + devicePath + }; + InputReplayCommand invalidFormatCommand(6, invalidFormatArgv); + EXPECT_FALSE(invalidFormatCommand.Parse()); + + // Empty mapping + char* emptyMappingArgv[] = { + programName, + mapArg, emptyMapValue, + recordArg, outputPath, + devicePath + }; + InputReplayCommand emptyMappingCommand(6, emptyMappingArgv); + EXPECT_FALSE(emptyMappingCommand.Parse()); +} + +/** + * @tc.name: InputReplayCommandTest_ParseRecord_WithDevices + * @tc.desc: Test parsing record command with specific devices + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_ParseRecord_WithDevices, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char recordArg[] = {"record"}; + char outputPath[] = {"output.bin"}; + char device1[] = {"/dev/input/event0"}; + char device2[] = {"/dev/input/event1"}; + + char* argv[] = { + programName, + recordArg, outputPath, + device1, device2 + }; + InputReplayCommand command(5, argv); + EXPECT_TRUE(command.Parse()); +} + +/** + * @tc.name: InputReplayCommandTest_ParseRecord_AllDevices + * @tc.desc: Test parsing record command with all devices flag + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_ParseRecord_AllDevices, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char allArg[] = {"--all"}; + char recordArg[] = {"record"}; + char outputPath[] = {"output.bin"}; + + char* argv[] = { + programName, + allArg, + recordArg, outputPath + }; + InputReplayCommand command(4, argv); + EXPECT_TRUE(command.Parse()); +} + +/** + * @tc.name: InputReplayCommandTest_ParseRecord_NoDevices + * @tc.desc: Test parsing record command with no devices specified + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_ParseRecord_NoDevices, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char recordArg[] = {"record"}; + char outputPath[] = {"output.bin"}; + + char* argv[] = { + programName, + recordArg, outputPath + }; + InputReplayCommand command(3, argv); + EXPECT_FALSE(command.Parse()); +} + +/** + * @tc.name: InputReplayCommandTest_ParseReplay_Valid + * @tc.desc: Test parsing replay command with valid arguments + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_ParseReplay_Valid, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char replayArg[] = {"replay"}; + char inputPath[] = {"input.bin"}; + + char* argv[] = { + programName, + replayArg, inputPath + }; + InputReplayCommand command(3, argv); + EXPECT_TRUE(command.Parse()); +} + +/** + * @tc.name: InputReplayCommandTest_ParseReplay_Invalid + * @tc.desc: Test parsing replay command with extra arguments + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_ParseReplay_Invalid, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char replayArg[] = {"replay"}; + char inputPath[] = {"input.bin"}; + char extraArg[] = {"extra"}; + + char* argv[] = { + programName, + replayArg, inputPath, extraArg + }; + InputReplayCommand command(4, argv); + EXPECT_FALSE(command.Parse()); +} + +/** + * @tc.name: InputReplayCommandTest_Parse_MissingCommand + * @tc.desc: Test parsing with missing command + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_Parse_MissingCommand, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char* argv[] = {programName, nullptr}; + InputReplayCommand command(1, argv); + EXPECT_FALSE(command.Parse()); +} + +/** + * @tc.name: InputReplayCommandTest_Parse_MissingFilePath + * @tc.desc: Test parsing with missing file path + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_Parse_MissingFilePath, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char recordArg[] = {"record"}; + char* argv[] = {programName, recordArg, nullptr}; + InputReplayCommand command(2, argv); + EXPECT_FALSE(command.Parse()); +} + +/** + * @tc.name: InputReplayCommandTest_Parse_InvalidCommand + * @tc.desc: Test parsing with invalid command + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_Parse_InvalidCommand, TestSize.Level1) +{ + char programName[] = {"program_name"}; + char invalidArg[] = {"invalid"}; + char filePath[] = {"file.bin"}; + + char* argv[] = { + programName, + invalidArg, filePath + }; + InputReplayCommand command(3, argv); + EXPECT_FALSE(command.Parse()); +} + +/** + * @tc.name: InputReplayCommandTest_HandleCommand + * @tc.desc: Test the static HandleRecordReplayCommand function with error cases + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputReplayCommandTest, InputReplayCommandTest_HandleCommand, TestSize.Level1) +{ + // Invalid command + char programName1[] = {"program_name"}; + char invalidArg[] = {"invalid"}; + char* invalidArgv[] = { + programName1, + invalidArg + }; + EXPECT_EQ(RET_ERR, InputReplayCommand::HandleRecordReplayCommand(2, invalidArgv)); + + // Incomplete arguments + char programName2[] = {"program_name"}; + char* incompleteArgv[] = { + programName2 + }; + EXPECT_EQ(RET_ERR, InputReplayCommand::HandleRecordReplayCommand(1, incompleteArgv)); +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file