* 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 <algorithm>
#include <iostream>
#include <string>
#include <sys/select.h>
#include <unistd.h>
#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<InputDevice>& devices)
{
if (running_) {
return false;
}
if (devices.empty()) {
PrintError("No devices to record from");
return false;
}
char resolvedPath[PATH_MAX] = {};
if (realpath(outputPath_.c_str(), resolvedPath) == nullptr) {
PrintError("Realpath failed. path:%{private}s", outputPath_.c_str());
return false;
}
outputFile_.open(resolvedPath, std::ios::binary | std::ios::trunc);
if (!outputFile_) {
PrintError("Failed to open output file: %s", resolvedPath);
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<<std::endl;
outputFile_ << "DEVICES: " << deviceEventBuffers_.size() << std::endl;
std::cout << "DEVICES: " << deviceEventBuffers_.size() << std::endl;
for (const auto& device : devices_) {
if (deviceEventBuffers_.find(device.GetId()) != deviceEventBuffers_.end()) {
std::string deviceName = device.GetName();
std::replace(deviceName.begin(), deviceName.end(), '|', '_');
outputFile_ << "DEVICE: " << device.GetId() << "|" << device.GetPath() << "|" << deviceName << "|";
outputFile_ << device.GetHash() << std::endl;
std::cout << "DEVICE: " << device.GetId() << "|" << device.GetPath() << "|" << deviceName << "|";
std::cout << device.GetHash() << std::endl;
}
}
outputFile_.close();
}
devices_.clear();
deviceEventBuffers_.clear();
PrintInfo("Recording stopped");
}
void EventRecorder::FlushDeviceEvents(const EventRecord& record)
{
if (record.event.type != EV_SYN || record.event.code != SYN_REPORT) {
return;
}
auto& currentDeviceBuffer = deviceEventBuffers_[record.deviceId];
for (const auto& record : currentDeviceBuffer) {
WriteEventText(record);
}
std::cout << std::endl;
currentDeviceBuffer.clear();
}
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;
}
std::string result = "CODE(";
result += std::to_string(code);
result += ")";
return result;
}
std::string EventRecorder::GetEventTypeString(uint16_t type)
{
auto it = EVENT_TYPE_MAP.find(type);
if (it != EVENT_TYPE_MAP.end()) {
return it->second;
}
std::string result = "UNKNOWN_TYPE(";
result += std::to_string(type);
result += ")";
return result;
}
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 "";
}
void EventRecorder::MainLoop()
{
fd_set readFds;
struct timeval timeout;
PrintDebug("Started event recording main loop");
PrintInfo("Recording started. Press Ctrl+C to stop.");
while (running_ && !g_shutdown.load()) {
FD_ZERO(&readFds);
int32_t maxFd = -1;
for (const auto& device : devices_) {
if (device.IsOpen()) {
int32_t fd = device.GetFd();
FD_SET(fd, &readFds);
maxFd = std::max(maxFd, fd);
}
}
if (maxFd < 0) {
PrintError("No valid devices to monitor");
break;
}
timeout.tv_sec = 0;
timeout.tv_usec = TIME_OUT;
int32_t result = select(maxFd + 1, &readFds, nullptr, nullptr, &timeout);
if (result < 0) {
if (errno == EINTR) {
continue;
}
PrintError("Select error: %d", errno);
break;
}
if (result > 0) {
ProcessDeviceEvents(readFds);
}
}
PrintDebug("Stopped event recording main loop");
}
void EventRecorder::ProcessDeviceEvents(fd_set& readFds)
{
for (InputDevice& device : devices_) {
if (!device.IsOpen()) {
continue;
}
int32_t fd = device.GetFd();
if (FD_ISSET(fd, &readFds)) {
input_event event;
if (device.ReadEvent(event)) {
EventRecord record;
record.deviceId = device.GetId();
record.event = event;
auto& currentDeviceBuffer = deviceEventBuffers_[record.deviceId];
currentDeviceBuffer.push_back(record);
FlushDeviceEvents(record);
}
}
}
}
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;
}
}
}