From 3be4882642765cf59ff99d08d7dc6d4e1e0a4f56 Mon Sep 17 00:00:00 2001 From: Hosung Kim Date: Mon, 15 Apr 2024 15:03:58 +0900 Subject: [PATCH] feat(packages): add tvinputdevice in device-api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implement HandleRuntimeSyncMessage - read input device from /dev/input/eventX - handle key press and release event - emit key event to JavaScript - add test Signed-off-by: Hosung Kim hs852.kim@samsung.com --- include/lwnode/lwnode.h | 6 + modules/packages/device-api/CMakeLists.txt | 8 +- modules/packages/device-api/README.md | 7 + modules/packages/device-api/src/Extension.cpp | 5 + .../device-api/src/ExtensionManager.cpp | 43 ++- .../device-api/src/ExtensionManager.h | 10 +- .../src/TizenDeviceAPILoaderForEscargot.cpp | 38 +- .../src/TizenDeviceAPILoaderForEscargot.h | 3 +- .../device-api/src/TizenInputDeviceKeyMap.h | 73 ++++ .../src/TizenInputDeviceManager.cpp | 355 ++++++++++++++++++ .../device-api/src/TizenInputDeviceManager.h | 114 ++++++ .../packages/device-api/test/tvinputdevice.js | 93 +++++ .../packages/packaging/lwnode-modules.spec | 3 + src/api/isolate.cc | 4 + src/api/isolate.h | 6 + src/lwnode/lwnode.cc | 14 + 16 files changed, 771 insertions(+), 11 deletions(-) create mode 100644 modules/packages/device-api/src/TizenInputDeviceKeyMap.h create mode 100644 modules/packages/device-api/src/TizenInputDeviceManager.cpp create mode 100644 modules/packages/device-api/src/TizenInputDeviceManager.h create mode 100644 modules/packages/device-api/test/tvinputdevice.js diff --git a/include/lwnode/lwnode.h b/include/lwnode/lwnode.h index e7cb50bf09..8c66b1010a 100644 --- a/include/lwnode/lwnode.h +++ b/include/lwnode/lwnode.h @@ -72,6 +72,12 @@ class LWNODE_EXPORT Utils { static v8::Local NewLocal(v8::Isolate* isolate, Escargot::ValueRef* ptr); + static bool CompileRun(Escargot::ContextRef* context, + const char* source, + bool isModule = false); + + static bool IsRunningIsolate(Escargot::ContextRef* context); + static std::string trimLastNewLineIfNeeded(std::string&& str); }; diff --git a/modules/packages/device-api/CMakeLists.txt b/modules/packages/device-api/CMakeLists.txt index f4659b0868..3d46280060 100644 --- a/modules/packages/device-api/CMakeLists.txt +++ b/modules/packages/device-api/CMakeLists.txt @@ -1,9 +1,9 @@ cmake_minimum_required(VERSION 2.8) -set (CMAKE_CXX_STANDARD 11) +set (CMAKE_CXX_STANDARD 14) project (device-api) find_package (PkgConfig REQUIRED) -pkg_check_modules(PACKAGES REQUIRED dlog glib-2.0 pkgmgr-info tpk-manifest-handlers) +pkg_check_modules(PACKAGES REQUIRED dlog glib-2.0 pkgmgr-info tpk-manifest-handlers libevdev) set(CMAKE_C_FLAGS "-std=gnu++14 -fPIE -Wno-invalid-offsetof -Wno-error=format=") @@ -15,6 +15,8 @@ include_directories( ${PROJECT_ROOT_PATH}/deps/escargot/src/api ${PROJECT_ROOT_PATH}/deps/escargot/third_party/GCutil ${PROJECT_ROOT_PATH}/deps/escargot/third_party/GCutil/bdwgc/include + ${PROJECT_ROOT_PATH}/deps/node/deps/uv/include + ${PROJECT_ROOT_PATH}/src include ${PACKAGES_INCLUDE_DIRS} ) @@ -22,4 +24,4 @@ include_directories( add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") -target_link_libraries(${PROJECT_NAME} ${PACKAGES_LIBRARIES}) +target_link_libraries(${PROJECT_NAME} ${PACKAGES_LIBRARIES} udev) diff --git a/modules/packages/device-api/README.md b/modules/packages/device-api/README.md index a369000b76..5ea0127aac 100644 --- a/modules/packages/device-api/README.md +++ b/modules/packages/device-api/README.md @@ -15,3 +15,10 @@ Support tizen device api. let adapter = tizen.bluetooth.getDefaultAdapter(); ``` + +## Requirements + +### tvinputdevice + * When using tvinputdevice api in tpk package, you should add the following privilege to `tizen-manifest.xml`. + * `http://tizen.org/privilege/tv.inputdevice` + * `http://developer.samsung.com/privilege/inputdevice` diff --git a/modules/packages/device-api/src/Extension.cpp b/modules/packages/device-api/src/Extension.cpp index 644a534589..86bb06272e 100644 --- a/modules/packages/device-api/src/Extension.cpp +++ b/modules/packages/device-api/src/Extension.cpp @@ -7,6 +7,7 @@ #include #include "TizenDeviceAPIBase.h" +#include "TizenInputDeviceManager.h" #include "ExtensionAdapter.h" #include "EscargotPublic.h" #include "TizenDeviceAPILoaderForEscargot.h" @@ -93,6 +94,10 @@ bool Extension::Initialize() { return false; } + if (name_ == "tizen.tvinputdevice") { + DeviceAPI::TizenInputDeviceManager::getInstance()->start(); + } + initialized_ = true; DEVICEAPI_LOG_INFO("========== << Initialize >> END =========="); return true; diff --git a/modules/packages/device-api/src/ExtensionManager.cpp b/modules/packages/device-api/src/ExtensionManager.cpp index 3dc303b856..9f85191972 100644 --- a/modules/packages/device-api/src/ExtensionManager.cpp +++ b/modules/packages/device-api/src/ExtensionManager.cpp @@ -10,11 +10,14 @@ #include #include #include - +#include "lwnode/lwnode.h" +#include "TizenInputDeviceManager.h" // #include "runtime_variable_provider.h" // #include "picojson.h" #include "TizenDeviceAPILoaderForEscargot.h" +using namespace DeviceAPI; + namespace wrt { namespace xwalk { @@ -179,5 +182,43 @@ void ExtensionManager::RegisterExtensionsByMetadata( } } + std::string ExtensionManager::HandleRuntimeSyncMessage( + Escargot::ContextRef* contextRef, + const std::string& type, + const std::string& value) { + if (!LWNode::Utils::IsRunningIsolate(contextRef)) { + return "error"; + } + + if (type == "tizen://api/inputdevice/registerKey") { + if (!TizenInputDeviceManager::getInstance()->registerKey(contextRef, + value)) { + return "error"; + } + } else if (type == "tizen://api/inputdevice/unregisterKey") { + if (!TizenInputDeviceManager::getInstance()->unregisterKey(contextRef, + value)) { + return "error"; + } + } else if (type == "tizen://api/inputdevice/registerKeyBatch") { + if (!TizenInputDeviceManager::getInstance()->registerKeyBatch( + contextRef, value)) { + return "error"; + } + } else if (type == "tizen://api/inputdevice/unregisterKeyBatch") { + if (!TizenInputDeviceManager::getInstance()->unregisterKeyBatch( + contextRef, value)) { + return "error"; + } + + } else { + DEVICEAPI_LOG_ERROR("NOT IMPLEMENTED: HandleRuntimeSyncMessage(%s)", + type.c_str()); + return "error"; + } + + return "success"; + } + } // namespace xwalk } // namespace wrt diff --git a/modules/packages/device-api/src/ExtensionManager.h b/modules/packages/device-api/src/ExtensionManager.h index 86969d2308..33be4434ed 100644 --- a/modules/packages/device-api/src/ExtensionManager.h +++ b/modules/packages/device-api/src/ExtensionManager.h @@ -13,6 +13,10 @@ #include "XW_Extension_SyncMessage.h" #include "Extension.h" +namespace Escargot { + class ContextRef; +} + namespace wrt { namespace xwalk { @@ -39,7 +43,11 @@ namespace xwalk { void AddRuntimeVariable(const std::string& key, const std::string& value); void GetRuntimeVariable(const char* key, char* value, size_t value_len) override; - private: + std::string HandleRuntimeSyncMessage(Escargot::ContextRef* contextRef, + const std::string& type, + const std::string& value); + + private: ExtensionManager(); virtual ~ExtensionManager(); diff --git a/modules/packages/device-api/src/TizenDeviceAPILoaderForEscargot.cpp b/modules/packages/device-api/src/TizenDeviceAPILoaderForEscargot.cpp index 2700178ecb..84a9119ba8 100644 --- a/modules/packages/device-api/src/TizenDeviceAPILoaderForEscargot.cpp +++ b/modules/packages/device-api/src/TizenDeviceAPILoaderForEscargot.cpp @@ -7,9 +7,10 @@ #include #include "ExtensionAdapter.h" #include "ExtensionManager.h" - +#include "TizenInputDeviceManager.h" using namespace Escargot; +using namespace wrt::xwalk; namespace DeviceAPI { @@ -498,11 +499,38 @@ ObjectRef* ExtensionManagerInstance::createExtensionObject( m_strings->sendRuntimeSyncMessage, [](ExecutionStateRef* state, ValueRef* thisValue, size_t argc, ValueRef** argv, bool isNewExpression) -> ValueRef* { - DEVICEAPI_LOG_ERROR( - "extension.sendRuntimeSyncMessage UNIMPLEMENTED"); printArguments(state->context(), argc, argv); - DEVICEAPI_ASSERT_SHOULD_NOT_BE_HERE(); - return ValueRef::createUndefined(); + + ExtensionManagerInstance* extensionManagerInstance = + get(state->context()); + ExtensionInstance* extensionInstance = + extensionManagerInstance + ->getExtensionInstanceFromCallingContext( + state->context(), thisValue); + + if (!extensionInstance || argc < 1) { + return ValueRef::createUndefined(); + } + + ValueRef* typeValue = argv[0]; + if (!typeValue->isString()) { + return ValueRef::createUndefined(); + } + + std::string dataString; + if (argc > 1) { + dataString = argv[1]->isString() + ? argv[1]->asString()->toStdUTF8String() + : std::string(); + } + + std::string reply = + ExtensionManager::GetInstance()->HandleRuntimeSyncMessage( + state->context(), + typeValue->asString()->toStdUTF8String(), + dataString); + + return StringRef::createFromUTF8(reply.data(), reply.length()); }, 0, true, true)); diff --git a/modules/packages/device-api/src/TizenDeviceAPILoaderForEscargot.h b/modules/packages/device-api/src/TizenDeviceAPILoaderForEscargot.h index a837508f7a..217f56d2fc 100644 --- a/modules/packages/device-api/src/TizenDeviceAPILoaderForEscargot.h +++ b/modules/packages/device-api/src/TizenDeviceAPILoaderForEscargot.h @@ -60,7 +60,8 @@ class ESPostListener; F(tvaudiocontrol) \ F(preference) \ F(power) \ - F(time) + F(time) \ + F(tvinputdevice) #define SUPPORTED_TIZEN_ENTRYPOINTS(F) \ F(ApplicationControl) \ diff --git a/modules/packages/device-api/src/TizenInputDeviceKeyMap.h b/modules/packages/device-api/src/TizenInputDeviceKeyMap.h new file mode 100644 index 0000000000..474440db49 --- /dev/null +++ b/modules/packages/device-api/src/TizenInputDeviceKeyMap.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024-present Samsung Electronics 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 __TizenInputDeviceKeyMap__ +#define __TizenInputDeviceKeyMap__ + +namespace DeviceAPI { + +// tizen, kernal, webapi name, webapi code +#define FOR_EACH_TIZEN_KEY_MAP_TV(F) \ + F("0", 11, "0", 48) \ + F("1", 2, "1", 49) \ + F("2", 3, "2", 50) \ + F("3", 4, "3", 51) \ + F("4", 5, "4", 52) \ + F("5", 6, "5", 53) \ + F("6", 7, "6", 54) \ + F("7", 8, "7", 55) \ + F("8", 9, "8", 56) \ + F("9", 10, "9", 57) \ + F("Minus", 22, "minus", 189) \ + F("XF86AudioRaiseVolume", 68, "VolumeUp", 447) \ + F("XF86AudioLowerVolume", 67, "VolumeDown", 448) \ + F("XF86AudioMute", 66, "VolumeMute", 449) \ + F("XF86RaiseChannel", 88, "ChannelUp", 427) \ + F("XF86LowerChannel", 87, "ChannelDown", 428) \ + F("XF86Red", 59, "ColorF0Red", 403) \ + F("XF86Green", 60, "ColorF1Green", 404) \ + F("XF86Yellow", 61, "ColorF2Yellow", 405) \ + F("XF86Blue", 62, "ColorF3Blue", 406) \ + F("XF86SysMenu", 125, "Menu", 10133) \ + F("XF86SimpleMenu", 127, "Tools", 10135) \ + F("XF86Info", 188, "Info", 457) \ + F("XF86Exit", 174, "Exit", 10182) \ + F("XF86Search", 217, "Search", 10225) \ + F("XF86ChannelGuide", 130, "Guide", 458) \ + F("XF86AudioRewind", 168, "MediaRewind", 412) \ + F("XF86AudioPause", 201, "MediaPause", 19) \ + F("XF86AudioNext", 208, "MediaFastForward", 417) \ + F("XF86AudioRecord", 167, "MediaRecord", 416) \ + F("XF86AudioPlay", 200, "MediaPlay", 415) \ + F("XF86AudioStop", 166, "MediaStop", 413) \ + F("XF86PlayBack", 244, "MediaPlayPause", 10252) \ + F("XF86PreviousChapter", 224, "MediaTrackPrevious", 10232) \ + F("XF86NextChapter", 225, "MediaTrackNext", 10233) \ + F("XF86Display", 64, "Source", 10072) \ + F("XF86PictureSize", 132, "PictureSize", 10140) \ + F("XF86PreviousChannel", 182, "PreviousChannel", 10190) \ + F("XF86ChannelList", 65, "ChannelList", 10073) \ + F("XF86EManual", 138, "E-Manual", 10146) \ + F("XF86MTS", 187, "MTS", 10195) \ + F("XF863D", 191, "3D", 10199) \ + F("XF86SoccerMode", 220, "Soccer", 10228) \ + F("XF86Caption", 213, "Caption", 10221) \ + F("XF86TTXMIX", 192, "Teletext", 10200) \ + F("XF86ExtraApp", 245, "Extra", 10253) + +} // namespace DeviceAPI + +#endif diff --git a/modules/packages/device-api/src/TizenInputDeviceManager.cpp b/modules/packages/device-api/src/TizenInputDeviceManager.cpp new file mode 100644 index 0000000000..bf1206c6f3 --- /dev/null +++ b/modules/packages/device-api/src/TizenInputDeviceManager.cpp @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2024-present Samsung Electronics 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 +#include +#include "uv.h" +#include "TizenDeviceAPIBase.h" +#include "TizenInputDeviceKeyMap.h" +#include "lwnode/lwnode.h" +#include "TizenInputDeviceManager.h" + +namespace DeviceAPI { + +static const std::set remoteControlDevices = { + {"wt61p807 rc device"}, + {"SMART_VIEW"}, + {"Smart Control 2014"}, + {"Smart Control 2015"}, + {"Smart Control 2016"}, + {"Smart Control 2017"}, + {"wt61p807 panel key device"}}; + +DeviceReceiver::DeviceReceiver(const std::string& path, const std::string& name) + : state_(State::None), + path_(path), + name_(name), + fd_(-1), + evdev_(nullptr), + uvPollHandler_(nullptr) {} + +DeviceReceiver::~DeviceReceiver() { + // When device-api terminates, the DeviceReceiver automatically stops and + // releases resources. + stop(); + + if (uvPollHandler_) { + delete uvPollHandler_; + } + + if (evdev_) { + libevdev_free(evdev_); + } +} + +void DeviceReceiver::start() { + if (state_ != State::None) { + return; + } + + fd_ = open(path_.c_str(), O_RDONLY | O_NONBLOCK); + if (fd_ < 0) { + DEVICEAPI_LOG_ERROR( + "could not open %s: %s", path_.c_str(), strerror(errno)); + return; + } + + if (libevdev_new_from_fd(fd_, &evdev_) < 0) { + DEVICEAPI_LOG_ERROR("libevdev_new_from_fd failed"); + close(fd_); + return; + } + + uvPollHandler_ = new uv_poll_t; + int error = uv_poll_init(uv_default_loop(), uvPollHandler_, fd_); + if (error < 0) { + DEVICEAPI_LOG_ERROR( + "uv_poll_init failed(%s): %s", uv_err_name(error), uv_strerror(error)); + close(fd_); + return; + } + uvPollHandler_->data = this; + + // This is to prevent the problem that the main loop of the node is not shut + // down forever. When the device-api terminates, the handle are released. + uv_unref(reinterpret_cast(uvPollHandler_)); + + error = uv_poll_start( + uvPollHandler_, + UV_READABLE, + [](uv_poll_t* handle, int status, int events) { + DeviceReceiver* self = static_cast(handle->data); + input_event event; + if (libevdev_next_event(self->evdev_, + LIBEVDEV_READ_FLAG_NORMAL, + &event) != LIBEVDEV_READ_STATUS_SUCCESS) { + return; + } + + if (event.type != EV_KEY || event.value > 1) { + return; + } + + TizenInputDeviceManager::getInstance()->emitKeyEventToJavaScript( + event.value, event.code); + }); + + if (error < 0) { + DEVICEAPI_LOG_ERROR( + "uv_poll_start failed(%s): %s", uv_err_name(error), uv_strerror(error)); + close(fd_); + return; + } + + state_ = State::Start; +} + +void DeviceReceiver::stop() { + if (state_ != State::Start) { + return; + } + + DEVICEAPI_LOG_INFO("stop input device: %s", path_.c_str()); + + close(fd_); + uv_poll_stop(uvPollHandler_); + uv_close((uv_handle_t*)uvPollHandler_, nullptr); + + state_ = State::Stop; +} + +static std::string findNameAttr(struct udev_device* device) { + struct udev_device* deviceParent = device; + + while (deviceParent != nullptr) { + struct udev_list_entry* sysattr; + udev_list_entry_foreach(sysattr, + udev_device_get_sysattr_list_entry(deviceParent)) { + std::string name = udev_list_entry_get_name(sysattr); + if (name == "name") { + return udev_device_get_sysattr_value(deviceParent, name.c_str()); + } + } + + deviceParent = udev_device_get_parent(deviceParent); + } + + return std::string(); +} + +TizenInputDeviceManager* TizenInputDeviceManager::getInstance() { + static TizenInputDeviceManager s_instance; + return &s_instance; +} + +TizenInputDeviceManager::TizenInputDeviceManager() + : eventType_({"keyup", "keydown"}), isRunning_(false) { +#define ADD_TIZEN_KEY_TV(name, key, webapiName, webapiKey) \ + keyInfoMap_.push_back({name, key, webapiName, webapiKey}); + + FOR_EACH_TIZEN_KEY_MAP_TV(ADD_TIZEN_KEY_TV) +#undef ADD_TIZEN_KEY_TV +} + +void TizenInputDeviceManager::start() { + if (isRunning_) { + return; + } + + scanDeivceReceivers(); + startDeivceReceivers(); + + isRunning_ = true; +} + +void TizenInputDeviceManager::scanDeivceReceivers() { + udev* udevContext; + udev_enumerate* enumerate = udev_enumerate_new(udevContext); + if (enumerate == nullptr) { + DEVICEAPI_LOG_ERROR("udev_enumerate_new failed"); + return; + } + + // scan all input devices + udev_enumerate_add_match_subsystem(enumerate, "input"); + udev_enumerate_scan_devices(enumerate); + udev_list_entry* devs = udev_enumerate_get_list_entry(enumerate); + + for (udev_list_entry* item = devs; item; + item = udev_list_entry_get_next(item)) { + const char* path = udev_list_entry_get_name(item); + udev_device* device = udev_device_new_from_syspath(udevContext, path); + const char* devnode = udev_device_get_devnode(device); + if (device != nullptr && devnode != nullptr) { + std::string deviceName = findNameAttr(device); + if (!deviceName.empty()) { + if (remoteControlDevices.find(deviceName) != + remoteControlDevices.end()) { + auto receiver = std::make_unique(devnode, deviceName); + + addDeviceReceiver(std::move(receiver)); + } + } + + udev_device_unref(device); + } + } +} + +bool TizenInputDeviceManager::findTizenKey(const std::string& keyName, + KeyInfo& key) { + auto iter = std::find_if( + keyInfoMap_.begin(), keyInfoMap_.end(), [keyName](const KeyInfo& key) { + return key.name == keyName; + }); + + if (iter == keyInfoMap_.end()) { + DEVICEAPI_LOG_INFO("cannot find key information: %s", keyName.c_str()); + return false; + } + + key = *iter; + + return true; +} + +void TizenInputDeviceManager::addDeviceReceiver( + std::unique_ptr&& receiver) { + std::string path = receiver->path(); + auto iter = + std::find_if(deviceReceivers_.begin(), + deviceReceivers_.end(), + [path](const std::unique_ptr& receiver_) { + return receiver_->path() == path; + }); + + if (iter == deviceReceivers_.end()) { + DEVICEAPI_LOG_INFO("add input device: %s", path.c_str()); + deviceReceivers_.emplace_back(std::move(receiver)); + } +} + +void TizenInputDeviceManager::startDeivceReceivers() { + for (const auto& receiver : deviceReceivers_) { + receiver->start(); + } +} + +bool TizenInputDeviceManager::registerKey(Escargot::ContextRef* contextRef, + const std::string& keyName) { + if (isEmptyDeviceReceiver()) { + return false; + } + + KeyInfo key; + if (!findTizenKey(keyName, key)) { + return false; + } + + if (registeredKeyInfos_.find(contextRef) == registeredKeyInfos_.end()) { + registeredKeyInfos_[contextRef] = std::set(); + } + + DEVICEAPI_LOG_INFO("register key: %s", keyName.c_str()); + registeredKeyInfos_[contextRef].insert(std::move(key)); + + return true; +} + +bool TizenInputDeviceManager::unregisterKey(Escargot::ContextRef* contextRef, + const std::string& keyName) { + if (isEmptyDeviceReceiver()) { + return false; + } + + KeyInfo key; + if (!findTizenKey(keyName, key)) { + return false; + } + + if (registeredKeyInfos_.find(contextRef) == registeredKeyInfos_.end()) { + return true; + } + + DEVICEAPI_LOG_INFO("unregister key: %s", keyName.c_str()); + registeredKeyInfos_[contextRef].erase(std::move(key)); + + return true; +} + +bool TizenInputDeviceManager::registerKeyBatch(Escargot::ContextRef* contextRef, + const std::string& keyName) { + std::stringstream stream(keyName); + std::string key; + + while (std::getline(stream, key, ',')) { + if (!registerKey(contextRef, key)) { + return false; + } + } + + return true; +} + +bool TizenInputDeviceManager::unregisterKeyBatch( + Escargot::ContextRef* contextRef, const std::string& keyName) { + std::stringstream stream(keyName); + std::string key; + + while (std::getline(stream, key, ',')) { + if (!unregisterKey(contextRef, key)) { + return false; + } + } + + return true; +} + +void TizenInputDeviceManager::emitKeyEventToJavaScript(const int value, + const short code) { + for (const auto& info : registeredKeyInfos_) { + auto iter = std::find_if( + info.second.begin(), info.second.end(), [code](const KeyInfo& data) { + return data.code == code; + }); + + if (iter == info.second.end()) { + continue; + } + + if (!LWNode::Utils::IsRunningIsolate(info.first)) { + return; + } + + std::ostringstream oss; + oss << "process.nextTick(() => { "; + oss << "process.emit('" << eventType_[value] + << "', {keyCode: " << iter->webapiCode << "}); "; + oss << "});"; + + LWNode::Utils::CompileRun(info.first, oss.str().c_str()); + } +} + +bool TizenInputDeviceManager::isEmptyDeviceReceiver() { + return deviceReceivers_.empty(); +} + +} // namespace DeviceAPI diff --git a/modules/packages/device-api/src/TizenInputDeviceManager.h b/modules/packages/device-api/src/TizenInputDeviceManager.h new file mode 100644 index 0000000000..75c89bc1d5 --- /dev/null +++ b/modules/packages/device-api/src/TizenInputDeviceManager.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2024-present Samsung Electronics 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 __TizenInputDeviceManager__ +#define __TizenInputDeviceManager__ + +#include +#include +#include +#include +#include + +struct libevdev; +struct uv_poll_s; +typedef struct uv_poll_s uv_poll_t; + +namespace Escargot { +class ContextRef; +} + +namespace DeviceAPI { + +struct KeyInfo { + std::string name; + uint16_t code; + std::string webapiName; + uint16_t webapiCode; + + bool operator<(const KeyInfo& key) const { return code < key.code; } +}; + +struct RegisteredKeyData { + RegisteredKeyData(Escargot::ContextRef* context_, KeyInfo key_) + : context(context_), key(key_) {} + Escargot::ContextRef* context; + KeyInfo key; +}; + +class DeviceReceiver { + public: + enum class State { None, Start, Stop }; + + DeviceReceiver(const std::string& path, const std::string& name); + ~DeviceReceiver(); + + void start(); + + const std::string& path() { return path_; } + + private: + State state_; + const std::string path_; + const std::string name_; + int fd_; + libevdev* evdev_; + uv_poll_t* uvPollHandler_; + + void stop(); +}; + +class TizenInputDeviceManager { + public: + static TizenInputDeviceManager* getInstance(); + + void start(); + + bool registerKey(Escargot::ContextRef* contextRef, + const std::string& keyName); + + bool unregisterKey(Escargot::ContextRef* contextRef, + const std::string& keyName); + + bool registerKeyBatch(Escargot::ContextRef* contextRef, + const std::string& keyName); + + bool unregisterKeyBatch(Escargot::ContextRef* contextRef, + const std::string& keyName); + + void emitKeyEventToJavaScript(const int value, const short code); + + private: + TizenInputDeviceManager(); + + void addDeviceReceiver(std::unique_ptr&& receiver); + void scanDeivceReceivers(); + void startDeivceReceivers(); + + bool findTizenKey(const std::string& keyName, KeyInfo& key); + + bool isEmptyDeviceReceiver(); + + std::vector> deviceReceivers_; + std::vector keyInfoMap_; + std::map> registeredKeyInfos_; + const std::string eventType_[2]; + bool isRunning_; +}; + +} // namespace DeviceAPI + +#endif diff --git a/modules/packages/device-api/test/tvinputdevice.js b/modules/packages/device-api/test/tvinputdevice.js new file mode 100644 index 0000000000..bfd4ffdfec --- /dev/null +++ b/modules/packages/device-api/test/tvinputdevice.js @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024-present Samsung Electronics 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. + */ + +// https://developer.samsung.com/smarttv/develop/api-references/tizen-web-device-api-references/tvinputdevice-api.html + +require('./device-api').init(); + +let i, keyCode = {}; +let supportedKeys = tizen.tvinputdevice.getSupportedKeys(); + +for (i = 0; i < supportedKeys.length; i++) { + keyCode[supportedKeys[i].name] = supportedKeys[i].code; +} + +if (keyCode.hasOwnProperty("ChannelUp")) { + console.log("register ChannelUp"); + tizen.tvinputdevice.registerKey("ChannelUp"); +} + +function errorCB(err) { + console.log('The following error occurred: ' + err.name); +} + +function successCB() { + console.log('Registered successfully'); +} + +console.log("register VolumeUp, VolumeDown"); +const keys = ["VolumeUp", "VolumeDown"]; +tizen.tvinputdevice.registerKeyBatch(keys, successCB, errorCB); + +process.on("keydown", function (keyEvent) { + // identify the key by the numeric code from the keyEvent + if (keyEvent.keyCode === keyCode.ChannelUp) { + console.log("+ The CHANNEL UP was pressed"); + } else if (keyEvent.keyCode === keyCode.VolumeUp) { + console.log("+ The VOLUME UP was pressed"); + } else if (keyEvent.keyCode === keyCode.VolumeDown) { + console.log("+ The VOLUME DOWN was pressed"); + } else { + console.log("fail test"); + } +}); + +process.on("keyup", function (keyEvent) { + if (keyEvent.keyCode === keyCode.ChannelUp) { + console.log("- The CHANNEL UP was released"); + } else if (keyEvent.keyCode === keyCode.VolumeUp) { + console.log("- The VOLUME UP was released"); + } else if (keyEvent.keyCode === keyCode.VolumeDown) { + console.log("- The VOLUME DOWN was released"); + } else { + console.log("fail test"); + } +}); + +setTimeout(() => { + console.log("unregister ChannelUp"); + + tizen.tvinputdevice.unregisterKey("ChannelUp"); +}, 10000); + + +setTimeout(() => { + console.log("unregister VolumeUp, VolumeDown"); + + function errorCB(err) { + console.log('The following error occurred: ' + err.name); + } + + function successCB() { + console.log('Unregistered successfully'); + } + tizen.tvinputdevice.unregisterKeyBatch(keys, successCB, errorCB); +}, 10000); + + +setTimeout(() => { + console.log("test done!"); +}, 50000); diff --git a/modules/packages/packaging/lwnode-modules.spec b/modules/packages/packaging/lwnode-modules.spec index 89db260fcd..33f1fbf2dd 100644 --- a/modules/packages/packaging/lwnode-modules.spec +++ b/modules/packages/packaging/lwnode-modules.spec @@ -24,11 +24,14 @@ BuildRequires: cmake BuildRequires: make BuildRequires: ninja BuildRequires: boost-devel +BuildRequires: curl BuildRequires: pkgconfig(dlog) BuildRequires: pkgconfig(glib-2.0) BuildRequires: pkgconfig(pkgmgr-info) BuildRequires: pkgconfig(manifest-parser) BuildRequires: pkgconfig(tpk-manifest-handlers) +BuildRequires: pkgconfig(libevdev) +BuildRequires: pkgconfig(systemd) ############################################## # Packages for profiles diff --git a/src/api/isolate.cc b/src/api/isolate.cc index 81d820890f..65f1b5b6e8 100755 --- a/src/api/isolate.cc +++ b/src/api/isolate.cc @@ -301,6 +301,8 @@ IsolateWrap::IsolateWrap() { RegisterExtension(std::make_unique()); RegisterExtension(std::make_unique()); + + state_ = State::Active; } IsolateWrap::~IsolateWrap() { @@ -321,6 +323,8 @@ void IsolateWrap::Dispose() { global_handles()->dispose(); RegisteredExtension::unregisterAll(); + state_ = State::Disposed; + LWNODE_CALL_TRACE_GC_END(); } diff --git a/src/api/isolate.h b/src/api/isolate.h index 81f787c3ea..71d10647ff 100755 --- a/src/api/isolate.h +++ b/src/api/isolate.h @@ -157,6 +157,8 @@ struct BackingStoreComparator { class IsolateWrap final : public v8::internal::Isolate { public: + enum class State { None, Active, Disposed }; + ~IsolateWrap(); const std::string PRIVATE_VALUES = "__private_values__"; @@ -275,6 +277,8 @@ class IsolateWrap final : public v8::internal::Isolate { v8::MicrotasksScope::PerformCheckpoint(toV8(this)); } + State getState() { return state_; } + private: IsolateWrap(); @@ -306,6 +310,8 @@ class IsolateWrap final : public v8::internal::Isolate { ThreadManager* threadManager_ = nullptr; v8::PromiseRejectCallback promise_reject_callback_{nullptr}; + + State state_ = State::None; }; } // namespace EscargotShim diff --git a/src/lwnode/lwnode.cc b/src/lwnode/lwnode.cc index a9e41c3b93..3b437319ca 100644 --- a/src/lwnode/lwnode.cc +++ b/src/lwnode/lwnode.cc @@ -302,6 +302,20 @@ std::string Utils::trimLastNewLineIfNeeded(std::string&& str) { return std::move(str); } +bool Utils::CompileRun(Escargot::ContextRef* context, + const char* source, + bool isModule) { + Evaluator::EvaluatorResult result = + EvalResultHelper::compileRun(context, source, isModule); + + return result.isSuccessful(); +} + +bool Utils::IsRunningIsolate(Escargot::ContextRef* context) { + ContextWrap* contextWrap = ContextWrap::fromEscargot(context); + return contextWrap->GetIsolate()->getState() == IsolateWrap::State::Active; +} + SystemInfo::SystemInfo() { #ifdef HOST_TIZEN add("tizen");