1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "input_replay_command.h"
17
18 #include <atomic>
19 #include <charconv>
20 #include <cstring>
21 #include <csignal>
22 #include <getopt.h>
23 #include <iostream>
24 #include <thread>
25 #include <unistd.h>
26
27 #include "common.h"
28 #include "device_manager.h"
29 #include "event_recorder.h"
30 #include "event_replayer.h"
31
32 namespace OHOS {
33 namespace MMI {
34 namespace {
35 constexpr int32_t MIN_ARGC = 2;
36 }
37
38 std::atomic<bool> g_shutdown { false };
39
InputReplayCommand(int32_t argc,char ** argv)40 InputReplayCommand::InputReplayCommand(int32_t argc, char** argv)
41 : argc_(argc), argv_(argv)
42 {
43 programName_ = (argc > 0) ? argv[0] : "uinput";
44 }
45
ParseOptions()46 bool InputReplayCommand::ParseOptions()
47 {
48 static struct option longOptions[] = {
49 {"help", no_argument, 0, 'h'},
50 {"list", no_argument, 0, 'l'},
51 {"all", no_argument, 0, 'a'},
52 {"map", required_argument, 0, 'm'},
53 {0, 0, 0, 0}
54 };
55 int32_t opt;
56 int32_t optionIndex = 0;
57 optind = 1;
58 while ((opt = getopt_long(argc_, argv_, "hlam:", longOptions, &optionIndex)) != -1) {
59 switch (opt) {
60 case 'h':
61 PrintUsage();
62 exit(0);
63 case 'l':
64 DeviceManager().PrintDeviceList();
65 exit(0);
66 case 'a':
67 useAllDevices_ = true;
68 break;
69 case 'm':
70 if (!ParseDeviceMapping(optarg)) {
71 return false;
72 }
73 break;
74 default:
75 return false;
76 }
77 }
78 return true;
79 }
80
Parse()81 bool InputReplayCommand::Parse()
82 {
83 if (argc_ < MIN_ARGC) {
84 PrintUsage();
85 return false;
86 }
87 if (!ParseOptions()) {
88 return false;
89 }
90 if (optind >= argc_) {
91 PrintError("Missing command (record/replay)");
92 return false;
93 }
94 command_ = argv_[optind++];
95 if (optind >= argc_) {
96 PrintError("Missing file path");
97 return false;
98 }
99 filePath_ = argv_[optind++];
100 if (command_ == "record") {
101 return ParseRecordCommand();
102 } else if (command_ == "replay") {
103 return ParseReplayCommand();
104 } else {
105 PrintError("Invalid command, supported: record/replay");
106 return false;
107 }
108 }
109
SignalHandler(int32_t sig)110 inline void SignalHandler(int32_t sig)
111 {
112 if (sig == SIGINT || sig == SIGTERM) {
113 g_shutdown.store(true);
114 std::cout << "\nShutdown signal received, cleaning up..." << std::endl;
115 }
116 }
117
HandleRecordReplayCommand(int32_t argc,char ** argv)118 int32_t InputReplayCommand::HandleRecordReplayCommand(int32_t argc, char** argv)
119 {
120 OHOS::MMI::InputReplayCommand parser(argc, argv);
121 if (geteuid() != 0) {
122 std::cerr << "Error: This program must be run as root" << std::endl;
123 return RET_ERR;
124 }
125 if (!parser.Parse()) {
126 std::cerr << "Failed to parse record/replay command" << std::endl;
127 return RET_ERR;
128 }
129
130 if (!parser.Execute()) {
131 return RET_ERR;
132 }
133 return RET_OK;
134 }
135
Execute()136 bool InputReplayCommand::Execute()
137 {
138 if (command_ == "record") {
139 return ExecuteRecordCommand();
140 } else if (command_ == "replay") {
141 return ExecuteReplayCommand();
142 }
143 return false;
144 }
145
ParseDeviceMapping(const std::string & mappingStr)146 bool InputReplayCommand::ParseDeviceMapping(const std::string& mappingStr)
147 {
148 deviceMapping_.clear();
149 const char* ptr = mappingStr.c_str();
150 const char* endPtr = ptr + mappingStr.length();
151 while (ptr < endPtr) {
152 uint16_t key;
153 auto keyResult = std::from_chars(ptr, endPtr, key);
154 if (keyResult.ec != std::errc() || keyResult.ptr >= endPtr || *keyResult.ptr != ':') {
155 return false;
156 }
157 ptr = keyResult.ptr + 1;
158 uint16_t value;
159 auto valueResult = std::from_chars(ptr, endPtr, value);
160 if (valueResult.ec != std::errc()) {
161 return false;
162 }
163 deviceMapping_[key] = value;
164 ptr = valueResult.ptr;
165 if (ptr < endPtr) {
166 if (*ptr != ',') {
167 return false;
168 }
169 ptr++;
170 }
171 }
172 return !deviceMapping_.empty();
173 }
174
SetupSignalHandlers()175 void InputReplayCommand::SetupSignalHandlers()
176 {
177 struct sigaction sa;
178 sa.sa_handler = SignalHandler;
179 sigemptyset(&sa.sa_mask);
180 sa.sa_flags = 0;
181 sigaction(SIGINT, &sa, nullptr);
182 sigaction(SIGTERM, &sa, nullptr);
183 }
184
ParseRecordCommand()185 bool InputReplayCommand::ParseRecordCommand()
186 {
187 if (!useAllDevices_) {
188 while (optind < argc_) {
189 devicePaths_.push_back(argv_[optind++]);
190 }
191 if (devicePaths_.empty()) {
192 PrintError("No devices specified for recording");
193 PrintError("Use --all to record from all devices or specify device paths");
194 return false;
195 }
196 }
197 if (optind < argc_) {
198 PrintError("Unexpected arguments for record command");
199 return false;
200 }
201 return true;
202 }
203
ParseReplayCommand()204 bool InputReplayCommand::ParseReplayCommand()
205 {
206 if (useAllDevices_) {
207 PrintError("Not use -a option for replay command!");
208 return false;
209 }
210 if (optind < argc_) {
211 PrintError("Unexpected arguments for replay command");
212 return false;
213 }
214 return true;
215 }
216
ExecuteRecordCommand()217 bool InputReplayCommand::ExecuteRecordCommand()
218 {
219 std::vector<InputDevice> devices;
220 if (useAllDevices_) {
221 DeviceManager deviceManager;
222 devices = deviceManager.DiscoverDevices();
223 } else {
224 const std::string PREFIX = "/dev/input/event";
225 for (size_t i = 0; i < devicePaths_.size(); i++) {
226 const std::string& path = devicePaths_[i];
227 if (path.substr(0, PREFIX.length()) != PREFIX) {
228 PrintError("Invalid input device path format: %s", path.c_str());
229 return false;
230 }
231 uint16_t deviceId = 0;
232 const char* start = path.c_str() + PREFIX.length();
233 const char* end = path.c_str() + path.length();
234 std::from_chars_result result = std::from_chars(start, end, deviceId);
235 if (result.ec != std::errc() || result.ptr != end) {
236 PrintError("Invalid device number in path: %s", path.c_str());
237 return false;
238 }
239 InputDevice device(path, deviceId);
240 if (device.IsOpen()) {
241 devices.push_back(std::move(device));
242 } else {
243 PrintWarning("Failed to open device: %s", path.c_str());
244 }
245 }
246 }
247 if (devices.empty()) {
248 PrintError("No valid input devices specified");
249 return false;
250 }
251 SetupSignalHandlers();
252 EventRecorder recorder(filePath_);
253 if (!recorder.Start(devices)) {
254 return false;
255 }
256 recorder.Stop();
257 return true;
258 }
259
ExecuteReplayCommand()260 bool InputReplayCommand::ExecuteReplayCommand()
261 {
262 SetupSignalHandlers();
263 PrintInfo("Press Enter to start replay...");
264 std::cin.get();
265 EventReplayer replayer(filePath_, deviceMapping_);
266 return replayer.Replay();
267 }
268
PrintUsage() const269 void InputReplayCommand::PrintUsage() const
270 {
271 std::cout << "Usage:" << std::endl
272 << " " << programName_ << " [options] record <output-file> [device paths...]" << std::endl
273 << " " << programName_ << " [options] replay <input-file>" << std::endl
274 << std::endl
275 << "Options:" << std::endl
276 << " -h, --help Show this help message" << std::endl
277 << " -l, --list List available input devices" << std::endl
278 << " -a, --all Record from all available input devices" << std::endl
279 << " -m, --map Specify device mapping for replay (e.g., \"0:0,1:2,4:2\")" << std::endl
280 << " Format: sourceDeviceId:targetDeviceId,..." << std::endl
281 << std::endl
282 << "Examples:" << std::endl
283 << " " << programName_ << " record -a events.bin # Record from all devices" << std::endl
284 << " " << programName_ << " record events.bin /dev/input/event0 /dev/input/event1 "
285 "# Record from specific devices" << std::endl
286 << " " << programName_ << " replay events.bin # Replay to original devices" << std::endl
287 << " " << programName_
288 << " replay -m \"0:1,1:0\" events.bin # Replay with custom device mapping" << std::endl;
289 }
290 } // namespace MMI
291 } // namespace OHOS