1/*
2 * Copyright (c) 2021-2024 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 "libinput_adapter.h"
17
18#include <cinttypes>
19#include <climits>
20#include <regex>
21
22#include <dirent.h>
23#include <fcntl.h>
24#include <sys/epoll.h>
25#include <unistd.h>
26
27#include "define_multimodal.h"
28#include "i_input_windows_manager.h"
29#include "param_wrapper.h"
30#include "util.h"
31
32#undef MMI_LOG_DOMAIN
33#define MMI_LOG_DOMAIN MMI_LOG_SERVER
34#undef MMI_LOG_TAG
35#define MMI_LOG_TAG "LibinputAdapter"
36
37namespace OHOS {
38namespace MMI {
39namespace {
40constexpr int32_t WAIT_TIME_FOR_INPUT { 10 };
41constexpr int32_t MAX_RETRY_COUNT { 5 };
42constexpr int32_t MIN_RIGHT_BTN_AREA_PERCENT { 0 };
43constexpr int32_t MAX_RIGHT_BTN_AREA_PERCENT { 100 };
44constexpr int32_t INVALID_RIGHT_BTN_AREA { -1 };
45
46void HiLogFunc(struct libinput* input, libinput_log_priority priority, const char* fmt, va_list args)
47{
48    CHKPV(input);
49    CHKPV(fmt);
50    char buffer[256] = {};
51    if (vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, fmt, args) == -1) {
52        MMI_HILOGE("Call vsnprintf_s failed");
53        va_end(args);
54        return;
55    }
56    if (strstr(buffer, "LOG_LEVEL_I") != NULL) {
57        MMI_HILOGI("PrintLog_Info:%{public}s", buffer);
58    } else if (strstr(buffer, "LOG_LEVEL_D") != NULL) {
59        MMI_HILOGD("PrintLog_Info:%{public}s", buffer);
60    } else if (strstr(buffer, "LOG_LEVEL_E") != NULL) {
61        MMI_HILOGE("PrintLog_Info:%{public}s", buffer);
62    } else {
63        MMI_HILOGD("PrintLog_Info:%{public}s", buffer);
64    }
65    va_end(args);
66}
67} // namespace
68
69int32_t LibinputAdapter::DeviceLedUpdate(struct libinput_device *device, int32_t funcKey, bool enable)
70{
71    CHKPR(device, RET_ERR);
72    return libinput_set_led_state(device, funcKey, enable);
73}
74
75void LibinputAdapter::InitRightButtonAreaConfig()
76{
77    CHKPV(input_);
78
79    int32_t height_percent = OHOS::system::GetIntParameter("const.multimodalinput.rightclick_y_percentage",
80                                                           INVALID_RIGHT_BTN_AREA);
81    if ((height_percent <= MIN_RIGHT_BTN_AREA_PERCENT) || (height_percent > MAX_RIGHT_BTN_AREA_PERCENT)) {
82        MMI_HILOGE("Right button area height percent param is invalid");
83        return;
84    }
85
86    int32_t width_percent = OHOS::system::GetIntParameter("const.multimodalinput.rightclick_x_percentage",
87                                                          INVALID_RIGHT_BTN_AREA);
88    if ((width_percent <= MIN_RIGHT_BTN_AREA_PERCENT) || (width_percent > MAX_RIGHT_BTN_AREA_PERCENT)) {
89        MMI_HILOGE("Right button area width percent param is invalid");
90        return;
91    }
92
93    auto status = libinput_config_rightbutton_area(input_, height_percent, width_percent);
94    if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) {
95        MMI_HILOGE("Config the touchpad right button area failed");
96    }
97}
98
99constexpr static libinput_interface LIBINPUT_INTERFACE = {
100    .open_restricted = [](const char *path, int32_t flags, void *user_data)->int32_t {
101        if (path == nullptr) {
102            MMI_HILOGWK("Input device path is nullptr");
103            return RET_ERR;
104        }
105        char realPath[PATH_MAX] = {};
106        if (realpath(path, realPath) == nullptr) {
107            std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_FOR_INPUT));
108            MMI_HILOGWK("The error path is %{public}s", path);
109            return RET_ERR;
110        }
111        int32_t fd = 0;
112        for (int32_t i = 0; i < MAX_RETRY_COUNT; i++) {
113            fd = open(realPath, flags);
114            if (fd >= 0) {
115                break;
116            }
117            std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_FOR_INPUT));
118        }
119        int32_t errNo = errno;
120        std::regex re("(\\d+)");
121        std::string str_path(path);
122        std::smatch match;
123        int32_t id;
124        if (std::regex_search(str_path, match, re)) {
125            id = std::stoi(match[0]);
126        } else {
127            id = -1;
128        }
129        MMI_HILOGWK("Libinput .open_restricted id:%{public}d, fd:%{public}d, errno:%{public}d",
130            id, fd, errNo);
131        return fd < 0 ? RET_ERR : fd;
132    },
133    .close_restricted = [](int32_t fd, void *user_data)
134    {
135        if (fd < 0) {
136            return;
137        }
138        MMI_HILOGI("Libinput .close_restricted fd:%{public}d", fd);
139        close(fd);
140    },
141};
142
143bool LibinputAdapter::Init(FunInputEvent funInputEvent)
144{
145    CALL_DEBUG_ENTER;
146    CHKPF(funInputEvent);
147    funInputEvent_ = funInputEvent;
148    input_ = libinput_path_create_context(&LIBINPUT_INTERFACE, nullptr);
149    CHKPF(input_);
150    libinput_log_set_handler(input_, &HiLogFunc);
151    fd_ = libinput_get_fd(input_);
152    if (fd_ < 0) {
153        libinput_unref(input_);
154        fd_ = -1;
155        MMI_HILOGE("The fd_ is less than 0");
156        return false;
157    }
158    InitRightButtonAreaConfig();
159    return hotplugDetector_.Init([this](std::string path) { OnDeviceAdded(std::move(path)); },
160        [this](std::string path) { OnDeviceRemoved(std::move(path)); });
161}
162
163void LibinputAdapter::EventDispatch(int32_t fd)
164{
165    CALL_DEBUG_ENTER;
166    if (fd == fd_) {
167        MMI_HILOGD("Start to libinput_dispatch");
168        if (libinput_dispatch(input_) != 0) {
169            MMI_HILOGE("Failed to dispatch libinput");
170            return;
171        }
172        OnEventHandler();
173        MMI_HILOGD("End to OnEventHandler");
174    } else if (fd == hotplugDetector_.GetFd()) {
175        hotplugDetector_.OnEvent();
176    } else {
177        MMI_HILOGE("EventDispatch() called with unknown fd:%{public}d", fd);
178    }
179}
180
181void LibinputAdapter::Stop()
182{
183    CALL_DEBUG_ENTER;
184    hotplugDetector_.Stop();
185    if (fd_ >= 0) {
186        close(fd_);
187        fd_ = -1;
188    }
189    if (input_ != nullptr) {
190        libinput_unref(input_);
191        input_ = nullptr;
192    }
193}
194
195void LibinputAdapter::ProcessPendingEvents()
196{
197    OnEventHandler();
198}
199
200void LibinputAdapter::OnEventHandler()
201{
202    CALL_DEBUG_ENTER;
203    CHKPV(funInputEvent_);
204    libinput_event *event = nullptr;
205    int64_t frameTime = GetSysClockTime();
206    while ((event = libinput_get_event(input_))) {
207        funInputEvent_(event, frameTime);
208        libinput_event_destroy(event);
209    }
210    if (event == nullptr) {
211        funInputEvent_(nullptr, 0);
212    }
213}
214
215void LibinputAdapter::ReloadDevice()
216{
217    CALL_DEBUG_ENTER;
218    CHKPV(input_);
219    libinput_suspend(input_);
220    libinput_resume(input_);
221}
222
223void LibinputAdapter::OnDeviceAdded(std::string path)
224{
225    std::regex re("(\\d+)");
226    std::string str_path(path);
227    std::smatch match;
228    int32_t id;
229    if (std::regex_search(str_path, match, re)) {
230        id = std::stoi(match[0]);
231    } else {
232        id = -1;
233    }
234    MMI_HILOGI("OnDeviceAdded id:%{public}d", id);
235    auto pos = devices_.find(path);
236    if (pos != devices_.end()) {
237        MMI_HILOGD("Path is found");
238        return;
239    }
240    libinput_device* device = libinput_path_add_device(input_, path.c_str());
241    if (device != nullptr) {
242        devices_[std::move(path)] = libinput_device_ref(device);
243        // Libinput doesn't signal device adding event in path mode. Process manually.
244        OnEventHandler();
245    }
246}
247
248void LibinputAdapter::OnDeviceRemoved(std::string path)
249{
250    std::regex re("(\\d+)");
251    std::string str_path(path);
252    std::smatch match;
253    int32_t id;
254    if (std::regex_search(str_path, match, re)) {
255        id = std::stoi(match[0]);
256    } else {
257        id = -1;
258    }
259    MMI_HILOGI("OnDeviceRemoved id:%{public}d", id);
260    auto pos = devices_.find(path);
261    if (pos != devices_.end()) {
262        libinput_path_remove_device(pos->second);
263        libinput_device_unref(pos->second);
264        devices_.erase(pos);
265        // Libinput doesn't signal device removing event in path mode. Process manually.
266        OnEventHandler();
267    }
268}
269} // namespace MMI
270} // namespace OHOS
271