* Copyright (c) 2026 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 "keyboard_controller_impl.h"
#include <algorithm>
#include "define_multimodal.h"
#include "input_manager.h"
#include "input_manager_impl.h"
#include "mmi_log.h"
#include "util.h"
#undef MMI_LOG_TAG
#define MMI_LOG_TAG "KeyboardControllerImpl"
namespace OHOS {
namespace MMI {
namespace {
constexpr int32_t ERROR_CODE_KEY_STATE_ERROR = 4300001;
}
KeyboardControllerImpl::KeyboardControllerImpl()
{
MMI_HILOGD("KeyboardControllerImpl created");
}
KeyboardControllerImpl::~KeyboardControllerImpl()
{
MMI_HILOGD("KeyboardControllerImpl destroying, cleaning up state");
std::vector<int32_t> keysToRelease = pressedKeys_;
for (int32_t keyCode : keysToRelease) {
MMI_HILOGW("Auto-releasing key %{private}d in destructor", keyCode);
auto keyEvent = CreateKeyEvent(KeyEvent::KEY_ACTION_UP, keyCode);
if (keyEvent != nullptr) {
int32_t ret = InjectKeyEvent(keyEvent);
if (ret != RET_OK) {
MMI_HILOGE("Failed to auto-release key %{private}d, ret=%{public}d", keyCode, ret);
}
} else {
MMI_HILOGE("Failed to create key event for key %{private}d", keyCode);
}
auto it = std::find(pressedKeys_.begin(), pressedKeys_.end(), keyCode);
if (it != pressedKeys_.end()) {
pressedKeys_.erase(it);
}
keyDownTimes_.erase(keyCode);
}
pressedKeys_.clear();
keyDownTimes_.clear();
}
int32_t KeyboardControllerImpl::PressKey(int32_t keyCode)
{
MMI_HILOGD("PressKey: keyCode=%{private}d", keyCode);
bool isNewKey = false;
std::shared_ptr<KeyEvent> keyEvent;
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = std::find(pressedKeys_.begin(), pressedKeys_.end(), keyCode);
if (it != pressedKeys_.end()) {
if (pressedKeys_.back() != keyCode) {
MMI_HILOGE("key %{private}d already pressed but not last pressed key", keyCode);
return ERROR_CODE_KEY_STATE_ERROR;
}
MMI_HILOGD("Repeat press last key: %{private}d", keyCode);
} else {
if (pressedKeys_.size() >= MAX_PRESSED_KEYS) {
MMI_HILOGE("Maximum %{public}zu keys already pressed", MAX_PRESSED_KEYS);
return ERROR_CODE_KEY_STATE_ERROR;
}
pressedKeys_.push_back(keyCode);
keyDownTimes_[keyCode] = GetSysClockTime();
isNewKey = true;
}
keyEvent = CreateKeyEvent(KeyEvent::KEY_ACTION_DOWN, keyCode);
if (keyEvent == nullptr) {
MMI_HILOGE("Failed to create key event");
if (isNewKey) {
pressedKeys_.pop_back();
keyDownTimes_.erase(keyCode);
}
return RET_ERR;
}
}
int32_t ret = InjectKeyEvent(keyEvent);
if (ret != RET_OK) {
if (isNewKey) {
std::lock_guard<std::mutex> lock(mutex_);
pressedKeys_.pop_back();
keyDownTimes_.erase(keyCode);
}
}
return ret;
}
int32_t KeyboardControllerImpl::ReleaseKey(int32_t keyCode)
{
MMI_HILOGD("ReleaseKey: keyCode=%{private}d", keyCode);
std::shared_ptr<KeyEvent> keyEvent;
std::vector<int32_t>::iterator it;
{
std::lock_guard<std::mutex> lock(mutex_);
it = std::find(pressedKeys_.begin(), pressedKeys_.end(), keyCode);
if (it == pressedKeys_.end()) {
MMI_HILOGE("key %{private}d not pressed", keyCode);
return ERROR_CODE_KEY_STATE_ERROR;
}
keyEvent = CreateKeyEvent(KeyEvent::KEY_ACTION_UP, keyCode);
if (keyEvent == nullptr) {
MMI_HILOGE("Failed to create key event");
return RET_ERR;
}
}
int32_t ret = InjectKeyEvent(keyEvent);
if (ret == RET_OK) {
std::lock_guard<std::mutex> lock(mutex_);
it = std::find(pressedKeys_.begin(), pressedKeys_.end(), keyCode);
if (it != pressedKeys_.end()) {
pressedKeys_.erase(it);
}
keyDownTimes_.erase(keyCode);
}
return ret;
}
std::shared_ptr<KeyEvent> KeyboardControllerImpl::CreateKeyEvent(int32_t action, int32_t keyCode)
{
auto keyEvent = KeyEvent::Create();
if (keyEvent == nullptr) {
MMI_HILOGE("Failed to create KeyEvent");
return nullptr;
}
keyEvent->SetKeyCode(keyCode);
keyEvent->SetKeyAction(action);
int64_t time = GetSysClockTime();
keyEvent->SetActionTime(time);
keyEvent->SetDeviceId(-1);
keyEvent->AddFlag(InputEvent::EVENT_FLAG_SIMULATE);
KeyEvent::KeyItem item;
item.SetKeyCode(keyCode);
item.SetPressed(action == KeyEvent::KEY_ACTION_DOWN);
item.SetDownTime(action == KeyEvent::KEY_ACTION_DOWN ? time : keyDownTimes_[keyCode]);
item.SetDeviceId(-1);
keyEvent->AddKeyItem(item);
int32_t addedCount = 0;
for (int32_t pressedKey : pressedKeys_) {
if (pressedKey == keyCode) {
if (action == KeyEvent::KEY_ACTION_DOWN) {
MMI_HILOGD("Skip adding key %{private}d to pressedKeys (already in KeyItem for KEY_DOWN)", keyCode);
continue;
} else {
MMI_HILOGD("Skip adding key %{private}d to pressedKeys (being released in KEY_UP)", keyCode);
continue;
}
}
KeyEvent::KeyItem pressedItem;
pressedItem.SetKeyCode(pressedKey);
pressedItem.SetPressed(true);
pressedItem.SetDownTime(keyDownTimes_[pressedKey]);
pressedItem.SetDeviceId(-1);
keyEvent->AddPressedKeyItems(pressedItem);
addedCount++;
}
MMI_HILOGD("CreateKeyEvent: action=%{public}d, keyCode=%{private}d, added %{public}d other pressed keys",
action, keyCode, addedCount);
return keyEvent;
}
int32_t KeyboardControllerImpl::InjectKeyEvent(std::shared_ptr<KeyEvent> event)
{
if (event == nullptr) {
MMI_HILOGE("KeyEvent is nullptr");
return RET_ERR;
}
event->AddFlag(InputEvent::EVENT_FLAG_CONTROLLER);
int32_t ret = InputMgrImpl.SimulateInputEvent(event);
if (ret != RET_OK) {
MMI_HILOGE("SimulateInputEvent failed, ret=%{public}d", ret);
return ret;
}
return RET_OK;
}
}
}