diff --git a/CMakeLists.txt b/CMakeLists.txt index ab44908..a3b6cd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ option(USE_BACKEND_WESTEROS_MESA "Whether to enable support for the gbm based of option(USE_INPUT_LIBINPUT "Whether to enable support for the libinput input backend" ON) option(USE_INPUT_UDEV "Whether to enable support for the libinput input udev lib" ON) option(USE_INPUT_WAYLAND "Whether to enable support for the wayland input backend" OFF) +option(USE_GENERIC_GAMEPAD "Use generic gamepad implementation" OFF) find_package(WPE REQUIRED) find_package(EGL REQUIRED) @@ -98,6 +99,11 @@ elseif (USE_VIRTUAL_KEYBOARD) ) endif() +if (USE_GENERIC_GAMEPAD) + include(src/gamepad/CMakeLists.txt) + include(src/gamepad_test/CMakeLists.txt) +endif() + if (USE_BACKEND_BCM_NEXUS) include(src/bcm-nexus/CMakeLists.txt) endif () diff --git a/src/gamepad/CMakeLists.txt b/src/gamepad/CMakeLists.txt new file mode 100644 index 0000000..f6627a6 --- /dev/null +++ b/src/gamepad/CMakeLists.txt @@ -0,0 +1,5 @@ +add_definitions(-DENABLE_GAMEPAD=1) + +list(APPEND WPE_PLATFORM_SOURCES + src/gamepad/linux_gamepad.cpp +) diff --git a/src/gamepad/interfaces.h b/src/gamepad/interfaces.h new file mode 100644 index 0000000..f495548 --- /dev/null +++ b/src/gamepad/interfaces.h @@ -0,0 +1,36 @@ +/* + * If not stated otherwise in this file or this component's Licenses.txt file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * 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 linux_gamepad_interfaces_h +#define linux_gamepad_interfaces_h + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct wpe_gamepad_provider_interface gamepad_provider_interface; +extern struct wpe_gamepad_interface gamepad_interface; + +#ifdef __cplusplus +} +#endif + +#endif /* linux_gamepad_interfaces_h */ diff --git a/src/gamepad/linux_gamepad.cpp b/src/gamepad/linux_gamepad.cpp new file mode 100644 index 0000000..0f0c1e6 --- /dev/null +++ b/src/gamepad/linux_gamepad.cpp @@ -0,0 +1,774 @@ +/* + * If not stated otherwise in this file or this component's Licenses.txt file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define BITS_PER_LONG (sizeof(unsigned long) * CHAR_BIT) +#define NBITS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG) + +#define INVALID_SOURCE_ID (-1U) + +static bool getEvDevBits(int fd, unsigned int type, void* buf, unsigned int size) +{ + if (ioctl(fd, EVIOCGBIT(type, size), buf) < 0) + return false; + return true; +} + +static bool testBitIsSet(const unsigned long* data, int bit) +{ + return data[bit / BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG)); +} + +static bool hasGamepadButtons(const unsigned long* ev_bits, const unsigned long* key_bits) +{ + if (!testBitIsSet(ev_bits, EV_KEY)) + return false; + for (int key = BTN_GAMEPAD; key <= BTN_THUMBR; ++key) { + if (testBitIsSet(key_bits, key)) { + return true; + } + } + return false; +} + +static int32_t mapButtonId(int32_t code) +{ + // buttons[0] Bottom button in right cluster + // buttons[1] Right button in right cluster + // buttons[2] Left button in right cluster + // buttons[3] Top button in right cluster + // buttons[4] Top left front button + // buttons[5] Top right front button + // buttons[6] Bottom left front button (trigger) + // buttons[7] Bottom right front button (trigger) + // buttons[8] Left button in center cluster + // buttons[9] Right button in center cluster + // buttons[10] Left stick pressed button + // buttons[11] Right stick pressed button + // buttons[12] Top button in left cluster + // buttons[13] Bottom button in left cluster + // buttons[14] Right button in left cluster + // buttons[15] Left button in left cluster + + struct MapEntry { + const int32_t code; + const int32_t id; + }; + + static const MapEntry map[] = { + { BTN_A, 0 }, + { BTN_B, 1 }, + { BTN_X, 2 }, + { BTN_Y, 3 }, + { BTN_TL, 4 }, + { BTN_TR, 5 }, + { BTN_TL2, 6 }, + { BTN_TR2, 7 }, + { BTN_SELECT, 8 }, + { BTN_START, 9 }, + { BTN_THUMBL, 10 }, // stick pressed button + { BTN_THUMBR, 11 }, // stick pressed button + { ABS_HAT0Y, 12 }, + { ABS_HAT0X, 14 }, + + // TODO: Add configurable re-mapping for any controller + // + // Append: XBOX Wireless Controller + // + { ABS_BRAKE, 6 }, // trigger + { ABS_GAS, 7 }, // trigger + { KEY_BACK, 8 } // Left center "menu/start" button + + }; + + static const size_t mapSize = sizeof(map)/sizeof(map[0]); + + for (int i = 0; i < mapSize; ++i) + { + if (code == map[i].code) + return map[i].id; + } + return -1; +} + +static int mapAxisId(int32_t code) +{ + // axes[0] Horizontal axis for left stick (negative left/positive right) + // axes[1] Vertical axis for left stick (negative up/positive down) + // axes[2] Horizontal axis for right stick (negative left/positive right) + // axes[3] Vertical axis for right stick (negative up/positive down) + struct MapEntry { + const int32_t code; + const int32_t id; + }; + + static const MapEntry map[] = { + { ABS_X, 0 }, + { ABS_Y, 1 }, + { ABS_Z, 2 }, + { ABS_RZ, 3 }, + }; + + static const size_t mapSize = sizeof(map)/sizeof(map[0]); + + for (int i = 0; i < mapSize; ++i) + if (code == map[i].code) + return map[i].id; + + return -1; +} + +static double clamp(double d, double min, double max) +{ + const double t = d < min ? min : d; + return t > max ? max : t; +} + +static double mapAxisValue(int32_t val, int32_t min, int32_t max) +{ + const double kMappedMax = 1.0; + const double kMappedMin = -1.0; + + double scale = (kMappedMax - kMappedMin) / (max - min); + double offset = (max + min) / (kMappedMax - kMappedMin) * scale * kMappedMin; + double mappedVal = val * scale + offset; + mappedVal = clamp(mappedVal, kMappedMin, kMappedMax); + + if (mappedVal < 0.009 && mappedVal > -0.009) + mappedVal = 0.0; + + return mappedVal; +} + +struct GamepadProxy; + +struct GamepadProvider +{ + struct GamepadInfo + { + int32_t gamepad_id; + int fd; + std::string name; + std::string path; + guint source_id; + std::map axisInfo; + std::vector buttonValues; + std::vector axisValues; + }; + + struct wpe_gamepad_provider* provider { nullptr }; + + std::map gamepadProxies; + std::map gamepadsInfo; + guint discoverySourceId { INVALID_SOURCE_ID }; + bool running { false }; + + GamepadProvider(struct wpe_gamepad_provider* provider) + : provider(provider) + { } + + ~GamepadProvider(); + + void addGamepadProxy(uint32_t id, struct GamepadProxy* gamepad) + { + gamepadProxies[id] = gamepad; + } + + void removeGamepadProxy(uint32_t id, struct GamepadProxy*) + { + gamepadProxies.erase(id); + } + + const char* getDeviceName(uint32_t gamepad_id) + { + auto it = gamepadsInfo.find(gamepad_id); + if (it == gamepadsInfo.end()) + return nullptr; + return it->second.name.c_str(); + } + + gboolean discoverGamePads() + { + if (!running) { + discoverySourceId = INVALID_SOURCE_ID; + return G_SOURCE_REMOVE; + } + + if (gamepadsInfo.empty()) { + GDir* dir; + GPatternSpec* pspec; + const char* basePath = "/dev/input/"; + + dir = g_dir_open(basePath, 0, nullptr); + if (dir) { + pspec = g_pattern_spec_new("event*"); + while (const char* name = g_dir_read_name(dir)) { + if (!g_pattern_match_string(pspec, name)) + continue; + + gchar* devPath = g_build_filename(basePath, name, nullptr); + tryOpenGamePad(devPath); + g_free(devPath); + + if (!gamepadsInfo.empty()) + break; + } + g_pattern_spec_free(pspec); + g_dir_close(dir); + } + } + return G_SOURCE_CONTINUE; + } + + void startDiscovery() + { + if (discoverySourceId != INVALID_SOURCE_ID) + return; + discoverySourceId = g_timeout_add_seconds( + 1, + [](gpointer data) { + GamepadProvider* impl = static_cast(data); + return impl->discoverGamePads(); + }, + this); + discoverGamePads(); + } + + void stopDiscovery() + { + if (discoverySourceId != INVALID_SOURCE_ID) { + g_source_remove(discoverySourceId); + discoverySourceId = INVALID_SOURCE_ID; + } + } + + void start() + { + if (running) + return; + running = true; + startDiscovery(); + } + + void stop() + { + if (!running) + return; + running = false; + stopDiscovery(); + while(!gamepadsInfo.empty()) + closeGamepad(gamepadsInfo.begin()->first); + } + + bool tryOpenGamePad(const char* path) + { + using ValueType = decltype(gamepadsInfo)::value_type; + bool alreadyOpen = std::any_of( + gamepadsInfo.begin(), gamepadsInfo.end(), + [path](const ValueType& kv) { + return kv.second.path == path; + }); + if (alreadyOpen) + return false; + + int fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (fd < 0) + return false; + + unsigned long ev_bits[NBITS( EV_CNT)] = {0}; + unsigned long key_bits[NBITS(KEY_CNT)] = {0}; + unsigned long abs_bits[NBITS(ABS_CNT)] = {0}; + + if (!getEvDevBits(fd, 0, ev_bits, sizeof(ev_bits))) { + close(fd); + return false; + } + if (!getEvDevBits(fd, EV_KEY, key_bits, sizeof(key_bits))) { + close(fd); + return false; + } + if (!getEvDevBits(fd, EV_ABS, abs_bits, sizeof(abs_bits))) { + close(fd); + return false; + } + if (!hasGamepadButtons(ev_bits, key_bits)) { + close(fd); + return false; + } + + std::map axisInfo; + for (int32_t code = ABS_X; code < ABS_CNT; ++code) { + if (!testBitIsSet(abs_bits, code)) + continue; + + struct input_absinfo absinfo; + if (ioctl(fd, EVIOCGABS(code), &absinfo) < 0) + continue; + + axisInfo[code] = absinfo; + } + + char name[256] = {'\0'}; + if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) + strncpy(name, "Unknown", sizeof(name)); + + // todo: figureout a better way to identify gamepad + int32_t gamepad_id = std::hash{}(path) ^ std::hash{}(name); + + // oopsie + if (gamepadsInfo.find(gamepad_id) != gamepadsInfo.end()) { + close(fd); + g_warning("Gamepad ID already taken, ignoring '%s' \n", path); + return false; + } + + auto &info = gamepadsInfo[gamepad_id]; + info = GamepadInfo { gamepad_id, fd, name, path, INVALID_SOURCE_ID, axisInfo, {}, {} }; + + // fixme: figureout the actual mapping from the device + info.buttonValues.resize(16, 0.0); + info.axisValues.resize(4, 0.0); + + using SourceData = std::pair; + SourceData *sourcedata = new SourceData(this, gamepad_id); + + info.source_id = g_unix_fd_add_full( + G_PRIORITY_HIGH, + fd, + static_cast(G_IO_IN | G_IO_ERR | G_IO_HUP), + [](gint fd, GIOCondition condition, gpointer data) -> gboolean + { + SourceData *sourcedata = static_cast(data); + GamepadProvider& self = *(sourcedata->first); + + int32_t gamepad_id = sourcedata->second; + if (condition & (G_IO_HUP | G_IO_ERR)) { + self.closeGamepad(gamepad_id); + return G_SOURCE_REMOVE; + } + self.readAndDispatchEvents(fd, gamepad_id); + return G_SOURCE_CONTINUE; + }, + sourcedata, + [](gpointer data) + { + SourceData *sourcedata = static_cast(data); + delete sourcedata; + }); + + wpe_gamepad_provider_dispatch_gamepad_connected(provider, gamepad_id); + return true; + } + + void closeGamepad(uint32_t gamepad_id) + { + auto it = gamepadsInfo.find(gamepad_id); + if (it == gamepadsInfo.end()) + return; + + const auto& info = it->second; + if (info.fd > 0) + close(info.fd); + g_source_remove(info.source_id); + gamepadsInfo.erase(it); + + wpe_gamepad_provider_dispatch_gamepad_disconnected(provider, gamepad_id); + } + + void readAndDispatchEvents(int fd, uint32_t gamepad_id) + { + std::vector events; + bool keepReading = true; + while(keepReading) + { + input_event input; + ssize_t read_size = read(fd, &input, sizeof(input)); + if (read_size != sizeof(input)) { + if (errno == EINTR) + continue; + if (errno != EWOULDBLOCK) + perror("Gamepad read failed"); + break; + } + + switch(input.type) { + case EV_SYN: + { + if (input.code == SYN_REPORT) + keepReading = false; + break; + } + case EV_KEY: + case EV_ABS: + { + events.push_back(input); + break; + } + default: + break; + } + } + + auto it = gamepadsInfo.find(gamepad_id); + if (it != gamepadsInfo.end()) { + auto &info = it->second; + updateGamepad(info, std::move(events)); + } + } + + uint32_t getGamepadButtonCount(uint32_t gamepad_id) { + auto it = gamepadsInfo.find(gamepad_id); + if (it != gamepadsInfo.end()) + return it->second.buttonValues.size(); + return 0; + } + + uint32_t copyGamepadButtonValues(uint32_t gamepad_id, double *array, uint32_t length) { + auto it = gamepadsInfo.find(gamepad_id); + if (it != gamepadsInfo.end()) { + auto &info = it->second; + uint32_t buttonCount = info.buttonValues.size(); + uint32_t min = std::min(buttonCount, length); + memcpy(array, info.buttonValues.data(), min * sizeof(double)); + return min; + } + return 0; + } + + uint32_t getGamepadAxisCount(uint32_t gamepad_id) { + auto it = gamepadsInfo.find(gamepad_id); + if (it != gamepadsInfo.end()) + return it->second.axisValues.size(); + return 0; + } + + uint32_t copyGamepadAxisValues(uint32_t gamepad_id, double *array, uint32_t length) { + auto it = gamepadsInfo.find(gamepad_id); + if (it != gamepadsInfo.end()) { + auto &info = it->second; + uint32_t axisCount = info.axisValues.size(); + uint32_t min = std::min(axisCount, length); + memcpy(array, info.axisValues.data(), min * sizeof(double)); + return min; + } + return 0; + } + + void updateGamepad(GamepadInfo& info, std::vector events); +}; + +struct GamepadProxy +{ + struct GamepadProvider* provider; + struct wpe_gamepad* gamepad; + uint32_t gamepad_id; + + GamepadProxy(GamepadProvider* provider, struct wpe_gamepad* gamepad, uint32_t id) + : provider(provider) + , gamepad(gamepad) + , gamepad_id(id) + { + provider->addGamepadProxy(gamepad_id, this); + } + + ~GamepadProxy() + { + if (provider) + provider->removeGamepadProxy(gamepad_id, this); + } + + void cleanup() + { + provider = nullptr; + gamepad = nullptr; + gamepad_id = 0; + } + + const char* get_device_name() { + const char* result = nullptr; + if (provider) + result = provider->getDeviceName(gamepad_id); + return result ? result: "Unknown"; + } + + uint32_t get_button_count() { + if (provider) + return provider->getGamepadButtonCount(gamepad_id); + return 0; + } + + uint32_t copy_button_values(double *array, uint32_t length) { + if (provider) + return provider->copyGamepadButtonValues(gamepad_id, array, length); + return 0; + } + + uint32_t get_axis_count() { + if (provider) + return provider->getGamepadAxisCount(gamepad_id); + return 0; + } + + uint32_t copy_axis_values(double *array, uint32_t length) { + if (provider) + return provider->copyGamepadAxisValues(gamepad_id, array, length); + return 0; + } + + void dispatchButtonValuesChanged() + { + if (gamepad) + wpe_gamepad_dispatch_button_values_changed(gamepad); + } + + void dispatchAxisValuesChanged() + { + if (gamepad) + wpe_gamepad_dispatch_axis_values_changed(gamepad); + } +}; + +GamepadProvider::~GamepadProvider() +{ + stop(); + for(const auto &kv : gamepadProxies) + kv.second->cleanup(); +} + +void GamepadProvider::updateGamepad(GamepadInfo& info, std::vector events) +{ + bool buttonValuesChanged = false; + bool axisValuesChanged = false; + const int32_t gamepad_id = info.gamepad_id; + + const auto updateButtonValue = [&info, &buttonValuesChanged] (int32_t code, int32_t value) + { + const int32_t button_id = mapButtonId(code); + + if (button_id < 0 || button_id >= info.buttonValues.size()) { + g_warning("unmapped button code: %d 0x%04X\n", code, code); + return; + } + info.buttonValues[button_id] = value == 0 ? 0.0 : 1.0; + buttonValuesChanged = true; + }; + + const auto updateDpadButtonValue = [&info, &buttonValuesChanged] (int32_t code, int32_t value) + { + // todo: figure out a better to handle dpad + if (code != ABS_HAT0X && code != ABS_HAT0Y) { + g_warning("not supported code: %d 0x%04X\n", code, code); + return; + } + const int32_t idx_base = mapButtonId(code); + if ((idx_base < 0) || (idx_base + 1 >= info.buttonValues.size())) { + g_warning("unmapped button code: %d 0x%04X\n", code, code); + return; + } + info.buttonValues[idx_base] = 0.0; + info.buttonValues[idx_base + 1] = 0.0; + if (value != 0) + info.buttonValues[(value < 0 ? idx_base : idx_base + 1)] = 1.0; + buttonValuesChanged = true; + }; + + const auto updateTriggerValue = [&info, &buttonValuesChanged] (int32_t code, int32_t value) + { + const int32_t idx_base = mapButtonId(code); + if ((idx_base < 0) || (idx_base + 1 >= info.buttonValues.size())) { + g_warning("unmapped trigger code: %d 0x%04X\n", code, code); + return; + } + + auto info_iter = info.axisInfo.find(code); + if (info_iter == info.axisInfo.end()) { + g_warning("no trigger info: %d \n", code); + return; + } + + const input_absinfo& axisInfo = info_iter->second; + double mapped = mapAxisValue(value, axisInfo.minimum, axisInfo.maximum); + + // Need to Normalize values in [ -1 <-> 1 ] to [ 0 <-> 1 ] + // + // First [ -1 <-> 1 ] / 2 = [ -0.5 <-> 0.5 ] + // Then [ -0.5 <-> 0.5] + 0.5 = [ 0.0 <-> 1.0 ] + // + info.buttonValues[idx_base] = (mapped/2) + 0.5; + buttonValuesChanged = true; + }; + + const auto updateAxisValue = [&info, &axisValuesChanged] (int32_t code, int32_t value) + { + auto info_iter = info.axisInfo.find(code); + if (info_iter == info.axisInfo.end()) { + g_warning("no axis info: %d \n", code); + return; + } + const int32_t axis_id = mapAxisId(code); + if (axis_id < 0 || axis_id >= info.axisValues.size()) { + g_warning("unmapped axis code: %d \n", code); + return; + } + + const input_absinfo& axisInfo = info_iter->second; + double mapped = mapAxisValue(value, axisInfo.minimum, axisInfo.maximum); + + info.axisValues[axis_id] = mapped; + axisValuesChanged = true; + }; + + for (const auto& event : events) + { + switch(event.type) + { + case EV_KEY: + { + updateButtonValue(event.code, event.value); + break; + } + case EV_ABS: + { + if (event.code == ABS_GAS || event.code == ABS_BRAKE) // L/R triggers + updateTriggerValue(event.code, event.value); + else + if (event.code == ABS_HAT0X || event.code == ABS_HAT0Y) + updateDpadButtonValue(event.code, event.value); + else + updateAxisValue(event.code, event.value); + break; + } + default: + break; + } + } + + if (buttonValuesChanged) { + auto iter = gamepadProxies.find(gamepad_id); + if (iter != gamepadProxies.end()) { + auto *proxy = iter->second; + proxy->dispatchButtonValuesChanged(); + } + } + + if (axisValuesChanged) { + auto iter = gamepadProxies.find(gamepad_id); + if (iter != gamepadProxies.end()) { + auto *proxy = iter->second; + proxy->dispatchAxisValuesChanged(); + } + } +} + + +struct wpe_gamepad_provider_interface gamepad_provider_interface = { + // create + [](struct wpe_gamepad_provider* provider) -> void* { + GamepadProvider* impl = new GamepadProvider(provider); + return impl; + }, + // destroy + [](void *data) { + GamepadProvider* impl = (struct GamepadProvider*)data; + delete impl; + }, + // start + [](void *data) { + GamepadProvider* impl = (struct GamepadProvider*)data; + impl->start(); + }, + // stop + [](void *data) { + GamepadProvider* impl = (struct GamepadProvider*)data; + impl->stop(); + }, + // get_view_for_gamepad_input + [](void*, void*) -> struct wpe_view_backend* { + return nullptr; + } +}; + +struct wpe_gamepad_interface gamepad_interface = { + // create + [](void* data, struct wpe_gamepad* gamepad, uint32_t id) -> void* { + GamepadProvider* provider = (GamepadProvider*) data; + GamepadProxy* proxy = new GamepadProxy(provider, gamepad, id); + return proxy; + }, + // destroy + [](void* data) { + GamepadProxy* proxy = (GamepadProxy*) data; + delete proxy; + }, + // get_id + [](void* data) -> uint32_t { + GamepadProxy* proxy = (GamepadProxy*) data; + return proxy->gamepad_id; + }, + // get_device_name + [](void *data) -> const char* { + GamepadProxy* proxy = (GamepadProxy*) data; + return proxy->get_device_name(); + }, + // get_button_count + [](void* data) -> uint32_t { + GamepadProxy* proxy = (GamepadProxy*) data; + return proxy->get_button_count(); + }, + // copy_button_values + [] (void* data, double* array, uint32_t length) -> uint32_t { + GamepadProxy* proxy = (GamepadProxy*) data; + return proxy->copy_button_values(array, length); + }, + // get_axis_count + [](void* data) -> uint32_t { + GamepadProxy* proxy = (GamepadProxy*) data; + return proxy->get_axis_count(); + }, + // copy_axis_values + [] (void* data, double* array, uint32_t length) -> uint32_t { + GamepadProxy* proxy = (GamepadProxy*) data; + return proxy->copy_axis_values(array, length); + } +}; diff --git a/src/gamepad_test/CMakeLists.txt b/src/gamepad_test/CMakeLists.txt new file mode 100644 index 0000000..1ef9457 --- /dev/null +++ b/src/gamepad_test/CMakeLists.txt @@ -0,0 +1,13 @@ +add_executable(gamepad_test src/gamepad_test/gamepad_test.cpp) + +target_include_directories(gamepad_test PRIVATE + ${GLIB_INCLUDE_DIRS} + ${WPE_INCLUDE_DIRS}) + +target_link_libraries(gamepad_test + ${GLIB_GIO_LIBRARIES} + ${GLIB_GOBJECT_LIBRARIES} + ${GLIB_LIBRARIES} + ${WPE_LIBRARIES}) + +install(TARGETS gamepad_test DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") diff --git a/src/gamepad_test/gamepad_test.cpp b/src/gamepad_test/gamepad_test.cpp new file mode 100644 index 0000000..e797f58 --- /dev/null +++ b/src/gamepad_test/gamepad_test.cpp @@ -0,0 +1,120 @@ +/* + * If not stated otherwise in this file or this component's Licenses.txt file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * 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 + +static GMainLoop *loop; +static struct wpe_gamepad_provider* provider; +static std::vector gamepads; + +static struct wpe_gamepad_client s_gamepadClient = +{ + // button_values_changed + [](struct wpe_gamepad* gamepad, void *) { + uint32_t button_count = wpe_gamepad_get_button_count(gamepad); + std::vector button_values; + button_values.resize(button_count, 0.0); + wpe_gamepad_copy_button_values(gamepad, button_values.data(), button_values.size()); + g_print("button_values_changed:\n"); + for (const auto v : button_values) { + g_print(" %.3f", v); + } + g_print("\n"); + }, + // axis_values_changed + [] (struct wpe_gamepad* gamepad, void *) { + uint32_t axis_count = wpe_gamepad_get_axis_count(gamepad); + std::vector axis_values; + axis_values.resize(axis_count, 0.0); + wpe_gamepad_copy_axis_values(gamepad, axis_values.data(), axis_values.size()); + g_print("axis_values_changed:\n"); + for (const auto v : axis_values) { + g_print(" %.3f", v); + } + g_print("\n"); + } +}; + +static struct wpe_gamepad_provider_client s_providerClient = +{ + // gamepad_connected + [](void*, uint32_t gamepad_id) { + g_print("connected %u \n", gamepad_id); + + struct wpe_gamepad* gamepad = wpe_gamepad_create(provider, gamepad_id); + wpe_gamepad_set_client(gamepad, &s_gamepadClient, nullptr); + gamepads.push_back(gamepad); + + const char* name = wpe_gamepad_get_device_name(gamepad); + uint32_t button_count = wpe_gamepad_get_button_count(gamepad); + uint32_t axis_count = wpe_gamepad_get_axis_count(gamepad); + g_print("gamepad name = %s, button count = %u, axis count = %u \n", name, button_count, axis_count); + }, + // gamepad_disconnected + [](void*, uint32_t gamepad_id) { + g_print("disconnected %u \n", gamepad_id); + + auto iter = std::find_if( + gamepads.begin(), gamepads.end(), + [gamepad_id] (struct wpe_gamepad* gamepad) { + return gamepad_id == wpe_gamepad_get_id(gamepad); + }); + + if (iter != gamepads.end()) { + struct wpe_gamepad* gamepad = *iter; + gamepads.erase(iter); + wpe_gamepad_destroy(gamepad); + } + } +}; + +static gboolean terminate_signal(gpointer) +{ + g_print("\nGot terminate signal\n"); + g_main_loop_quit(loop); + return G_SOURCE_REMOVE; +} + +int main(int argc, char *argv[]) +{ + loop = g_main_loop_new(nullptr, FALSE); + + g_unix_signal_add(SIGINT, terminate_signal, nullptr); + g_unix_signal_add(SIGTERM, terminate_signal, nullptr); + + provider = wpe_gamepad_provider_create(); + assert(provider != nullptr); + + wpe_gamepad_provider_set_client(provider, &s_providerClient, nullptr); + wpe_gamepad_provider_start(provider); + + g_main_loop_run(loop); + + wpe_gamepad_provider_stop(provider); + wpe_gamepad_provider_destroy(provider); + + g_main_loop_unref(loop); + + return 0; +} diff --git a/src/loader-impl.cpp b/src/loader-impl.cpp index 5c38a2b..0fee7f5 100644 --- a/src/loader-impl.cpp +++ b/src/loader-impl.cpp @@ -62,6 +62,10 @@ #include "wpeframework/interfaces.h" #endif +#ifdef ENABLE_GAMEPAD +#include "gamepad/interfaces.h" +#endif + extern "C" { struct wpe_renderer_host_interface noop_renderer_host_interface = { @@ -184,6 +188,13 @@ struct wpe_loader_interface _wpe_loader_interface = { return &wpeframework_view_backend_interface; #endif +#ifdef ENABLE_GAMEPAD + if (!std::strcmp(object_name, "_wpe_gamepad_provider_interface")) + return &gamepad_provider_interface; + if (!std::strcmp(object_name, "_wpe_gamepad_interface")) + return &gamepad_interface; +#endif + return nullptr; }, };