From 2721fdf8a94e2e49f907eda24a1406d2225acaf6 Mon Sep 17 00:00:00 2001 From: Scott Mansell Date: Mon, 29 Jun 2015 12:17:35 +1200 Subject: [PATCH] Linux: Add an evdev based controller backend, to replace SDL. --- CMakeLists.txt | 13 + CMakeTests/FindLibevdev.cmake | 33 +++ CMakeTests/FindLibudev.cmake | 28 ++ Source/Core/InputCommon/CMakeLists.txt | 5 + .../ControllerInterface.cpp | 6 + .../ControllerInterface/ControllerInterface.h | 3 + .../ControllerInterface/evdev/evdev.cpp | 264 ++++++++++++++++++ .../ControllerInterface/evdev/evdev.h | 86 ++++++ 8 files changed, 438 insertions(+) create mode 100644 CMakeTests/FindLibevdev.cmake create mode 100644 CMakeTests/FindLibudev.cmake create mode 100644 Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp create mode 100644 Source/Core/InputCommon/ControllerInterface/evdev/evdev.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 91071aefdb..e64061a6b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -513,6 +513,19 @@ if(USE_EGL) add_definitions(-DUSE_EGL=1) endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + include(FindLibudev OPTIONAL) + include(FindLibevdev OPTIONAL) + if(LIBUDEV_FOUND AND LIBEVDEV_FOUND) + message("libevdev/libudev found, enabling evdev controller backend") + add_definitions(-DHAVE_LIBUDEV=1) + add_definitions(-DHAVE_LIBEVDEV=1) + include_directories(${LIBUDEV_INCLUDE_DIR} ${LIBEVDEV_INCLUDE_DIR}) + else() + message("Could find libevdev/libudev, disabling evdev controller backend") + endif() +endif() + ######################################## # Setup include directories (and make sure they are preferred over the Externals) # diff --git a/CMakeTests/FindLibevdev.cmake b/CMakeTests/FindLibevdev.cmake new file mode 100644 index 0000000000..e89a5f229d --- /dev/null +++ b/CMakeTests/FindLibevdev.cmake @@ -0,0 +1,33 @@ +# - Try to find libevdev +# Once done this will define +# LIBEVDEV_FOUND - System has libevdev +# LIBEVDEV_INCLUDE_DIRS - The libevdev include directories +# LIBEVDEV_LIBRARIES - The libraries needed to use libevdev + +find_package(PkgConfig) +pkg_check_modules(PC_LIBEVDEV QUIET libevdev) + +FIND_PATH( + LIBEVDEV_INCLUDE_DIR libevdev/libevdev.h + HINTS ${PC_LIBEVDEV_INCLUDEDIR} ${PC_LIBEVDEV_INCLUDE_DIRS} + /usr/include + /usr/local/include + ${LIBEVDEV_PATH_INCLUDES} +) + +FIND_LIBRARY( + LIBEVDEV_LIBRARY + NAMES evdev libevdev + HINTS ${PC_LIBEVDEV_LIBDIR} ${PC_LIBEVDEV_LIBRARY_DIRS} + PATHS ${ADDITIONAL_LIBRARY_PATHS} + ${LIBEVDEV_PATH_LIB} +) + +set(LIBEVDEV_LIBRARIES ${LIBEVDEV_LIBRARY} ) +set(LIBEVDEV_INCLUDE_DIRS ${LIBEVDEV_INCLUDE_DIR} ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(libevdev DEFAULT_MSG + LIBEVDEV_LIBRARY LIBEVDEV_INCLUDE_DIR) + +mark_as_advanced(LIBEVDEV_INCLUDE_DIR LIBEVDEV_LIBRARY ) diff --git a/CMakeTests/FindLibudev.cmake b/CMakeTests/FindLibudev.cmake new file mode 100644 index 0000000000..2b71e4e605 --- /dev/null +++ b/CMakeTests/FindLibudev.cmake @@ -0,0 +1,28 @@ +# - Try to find LIBUDEV +# Once done this will define +# LIBUDEV_FOUND - System has LIBUDEV +# LIBUDEV_INCLUDE_DIRS - The LIBUDEV include directories +# LIBUDEV_LIBRARIES - The libraries needed to use LIBUDEV + +FIND_PATH( + LIBUDEV_INCLUDE_DIR libudev.h + /usr/include + /usr/local/include + ${LIBUDEV_PATH_INCLUDES} +) + +FIND_LIBRARY( + LIBUDEV_LIBRARY + NAMES udev libudev + PATHS ${ADDITIONAL_LIBRARY_PATHS} + ${LIBUDEV_PATH_LIB} +) + +set(LIBUDEV_LIBRARIES ${LIBUDEV_LIBRARY} ) +set(LIBUDEV_INCLUDE_DIRS ${LIBUDEV_INCLUDE_DIR} ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LIBUDEV DEFAULT_MSG + LIBUDEV_LIBRARY LIBUDEV_INCLUDE_DIR) + +mark_as_advanced(LIBUDEV_INCLUDE_DIR LIBUDEV_LIBRARY ) diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index 75679b6a9f..549865b8e9 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -37,6 +37,11 @@ elseif(ANDROID) ControllerInterface/Android/Android.cpp) endif() +if(LIBEVDEV_FOUND AND LIBUDEV_FOUND) + set(SRCS ${SRCS} ControllerInterface/evdev/evdev.cpp) + set(LIBS ${LIBS} ${LIBEVDEV_LIBRARY} ${LIBUDEV_LIBRARY}) +endif() + if(SDL_FOUND OR SDL2_FOUND) set(SRCS ${SRCS} ControllerInterface/SDL/SDL.cpp) if (SDL2_FOUND) diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 6b8b11fd3a..0631069e16 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -26,6 +26,9 @@ #ifdef CIFACE_USE_ANDROID #include "InputCommon/ControllerInterface/Android/Android.h" #endif +#ifdef CIFACE_USE_EVDEV + #include "InputCommon/ControllerInterface/evdev/evdev.h" +#endif using namespace ciface::ExpressionParser; @@ -69,6 +72,9 @@ void ControllerInterface::Initialize(void* const hwnd) #ifdef CIFACE_USE_ANDROID ciface::Android::Init(m_devices); #endif +#ifdef CIFACE_USE_EVDEV + ciface::evdev::Init(m_devices); +#endif m_is_init = true; } diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 408a36fefa..3aca18e95c 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -35,6 +35,9 @@ #if defined(HAVE_SDL) && HAVE_SDL #define CIFACE_USE_SDL #endif +#if defined(HAVE_LIBEVDEV) && defined(HAVE_LIBUDEV) + #define CIFACE_USE_EVDEV +#endif // // ControllerInterface diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp new file mode 100644 index 0000000000..2f4d53e234 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp @@ -0,0 +1,264 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include + +#include "Common/Logging/Log.h" +#include "InputCommon/ControllerInterface/evdev/evdev.h" + + +namespace ciface +{ +namespace evdev +{ + +void Init(std::vector &controllerDevices) +{ + int num_controllers = 0; + + // We use Udev to find any devices. In the future this will allow for hotplugging. + // But for now it is essentially iterating over /dev/input/event0 to event31. However if the + // naming scheme is ever updated in the future, this *should* be forwards compatable. + + struct udev* udev = udev_new(); + _assert_msg_(PAD, udev != 0, "Couldn't initilize libudev."); + + // List all input devices + udev_enumerate* enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(enumerate, "input"); + udev_enumerate_scan_devices(enumerate); + udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate); + + // Iterate over all input devices + udev_list_entry* dev_list_entry; + udev_list_entry_foreach(dev_list_entry, devices) + { + const char* path = udev_list_entry_get_name(dev_list_entry); + + udev_device* dev = udev_device_new_from_syspath(udev, path); + + const char* devnode = udev_device_get_devnode(dev); + // We only care about devices which we have read/write access to. + if (access(devnode, W_OK) == 0) + { + // Unfortunately udev gives us no way to filter out the non event device interfaces. + // So we open it and see if it works with evdev ioctls or not. + evdevDevice* input = new evdevDevice(devnode, num_controllers); + + if (input->IsInteresting()) + { + controllerDevices.push_back(input); + num_controllers++; + } + else + { + // Either it wasn't a evdev device, or it didn't have at least 8 buttons or two axis. + delete input; + } + } + udev_device_unref(dev); + } + udev_enumerate_unref(enumerate); + udev_unref(udev); +} + +evdevDevice::evdevDevice(const std::string &devnode, int id) : m_devfile(devnode), m_id(id) +{ + // The device file will be read on one of the main threads, so we open in non-blocking mode. + m_fd = open(devnode.c_str(), O_RDWR|O_NONBLOCK); + int ret = libevdev_new_from_fd(m_fd, &m_dev); + + if (ret != 0) + { + // This useally fails because the device node isn't an evdev device, such as /dev/input/js0 + m_initialized = false; + close(m_fd); + return; + } + + m_name = libevdev_get_name(m_dev); + + // Controller buttons (and keyboard keys) + int num_buttons = 0; + for (int key = 0; key < KEY_MAX; key++) + if (libevdev_has_event_code(m_dev, EV_KEY, key)) + AddInput(new Button(num_buttons++, key, m_dev)); + + // Absolute axis (thumbsticks) + int num_axis = 0; + for (int axis = 0; axis < 0x100; axis++) + if (libevdev_has_event_code(m_dev, EV_ABS, axis)) + { + AddAnalogInputs(new Axis(num_axis, axis, false, m_dev), + new Axis(num_axis, axis, true, m_dev)); + num_axis++; + } + + // Force feedback + if (libevdev_has_event_code(m_dev, EV_FF, FF_PERIODIC)) + { + for (auto type : {FF_SINE, FF_SQUARE, FF_TRIANGLE, FF_SAW_UP, FF_SAW_DOWN}) + if (libevdev_has_event_code(m_dev, EV_FF, type)) + AddOutput(new ForceFeedback(type, m_dev)); + } + if (libevdev_has_event_code(m_dev, EV_FF, FF_RUMBLE)) + { + AddOutput(new ForceFeedback(FF_RUMBLE, m_dev)); + } + + // TODO: Add leds as output devices + + m_initialized = true; + m_interesting = num_axis >= 2 || num_buttons >= 8; +} + +evdevDevice::~evdevDevice() +{ + if (m_initialized) + { + libevdev_free(m_dev); + close(m_fd); + } +} + +void evdevDevice::UpdateInput() +{ + // Run through all evdev events + // libevdev will keep track of the actual controller state internally which can be queried + // later with libevdev_fetch_event_value() + input_event ev; + int rc = LIBEVDEV_READ_STATUS_SUCCESS; + do + { + if (rc == LIBEVDEV_READ_STATUS_SYNC) + rc = libevdev_next_event(m_dev, LIBEVDEV_READ_FLAG_SYNC, &ev); + else + rc = libevdev_next_event(m_dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + } while (rc >= 0); +} + + +std::string evdevDevice::Button::GetName() const +{ + // Buttons below 0x100 are mostly keyboard keys, and the names make sense + if (m_code < 0x100) + { + const char* name = libevdev_event_code_get_name(EV_KEY, m_code); + if (name) + return std::string(name); + } + // But controllers use codes above 0x100, and the standard label often doesn't match. + // We are better off with Button 0 and so on. + return "Button " + std::to_string(m_index); +} + +ControlState evdevDevice::Button::GetState() const +{ + int value = 0; + libevdev_fetch_event_value(m_dev, EV_KEY, m_code, &value); + return value; +} + +evdevDevice::Axis::Axis(u8 index, u16 code, bool upper, libevdev* dev) : + m_code(code), m_index(index), m_upper(upper), m_dev(dev) +{ + m_range = libevdev_get_abs_maximum(m_dev, m_code); +} + +std::string evdevDevice::Axis::GetName() const +{ + return "Axis " + std::to_string(m_index) + (m_upper ? "+" : "-"); +} + +ControlState evdevDevice::Axis::GetState() const +{ + int value = 0; + libevdev_fetch_event_value(m_dev, EV_ABS, m_code, &value); + if (m_upper) + return std::max(0.0, double(value) / double(m_range) - 0.5) * 2.0; + else + return (0.5 - std::min(0.5, double(value) / double(m_range))) * 2.0; +} + +std::string evdevDevice::ForceFeedback::GetName() const +{ + // We have some default names. + switch (m_type) + { + case FF_SINE: + return "Sine"; + case FF_TRIANGLE: + return "Triangle"; + case FF_SQUARE: + return "Square"; + case FF_RUMBLE: + return "LeftRight"; + default: + { + const char* name = libevdev_event_code_get_name(EV_FF, m_type); + if (name) + return std::string(name); + return "Unknown"; + } + } +} + +void evdevDevice::ForceFeedback::SetState(ControlState state) +{ + // libevdev doesn't have nice helpers for forcefeedback + // we will use the file descriptors directly. + + if (state > 0) // Upload and start an effect. + { + ff_effect effect; + + effect.id = -1; + effect.direction = 0; // down + effect.replay.length = 500; // 500ms + effect.replay.delay = 0; + effect.trigger.button = 0; // don't trigger on button press + effect.trigger.interval = 0; + + // This is the the interface that XInput uses, with 2 motors of differing sizes/frequencies that + // are controlled seperatally + if (m_type == FF_RUMBLE) + { + effect.type = FF_RUMBLE; + // max ranges tuned to 'feel' similar in magnitude to triangle/sine on xbox360 controller + effect.u.rumble.strong_magnitude = u16(state * 0x4000); + effect.u.rumble.weak_magnitude = u16(state * 0xFFFF); + } + else // FF_PERIODIC, a more generic interface. + { + effect.type = FF_PERIODIC; + effect.u.periodic.waveform = m_type; + effect.u.periodic.phase = 0x7fff; // 180 degrees + effect.u.periodic.offset = 0; + effect.u.periodic.period = 10; + effect.u.periodic.magnitude = s16(state * 0x7FFF); + effect.u.periodic.envelope.attack_length = 0; // no attack + effect.u.periodic.envelope.attack_level = 0; + effect.u.periodic.envelope.fade_length = 0; + effect.u.periodic.envelope.fade_level = 0; + } + + ioctl(m_fd, EVIOCSFF, &effect); + m_id = effect.id; + + input_event play; + play.type = EV_FF; + play.code = m_id; + play.value = 1; + + write(m_fd, (const void*) &play, sizeof(play)); + } + else if (m_id != -1) // delete the effect (which also stops it) + { + ioctl(m_id, EVIOCRMFF, m_id); + } +} + +} +} diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h new file mode 100644 index 0000000000..7290fbc379 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h @@ -0,0 +1,86 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "InputCommon/ControllerInterface/Device.h" + +namespace ciface +{ +namespace evdev +{ + +void Init(std::vector& devices); + +class evdevDevice : public Core::Device +{ +private: + class Button : public Core::Device::Input + { + public: + std::string GetName() const override; + Button(u8 index, u16 code, libevdev* dev) : m_index(index), m_code(code), m_dev(dev) {} + ControlState GetState() const override; + private: + const u8 m_index; + const u16 m_code; + libevdev* m_dev; + }; + + class Axis : public Core::Device::Input + { + public: + std::string GetName() const override; + Axis(u8 index, u16 code, bool upper, libevdev* dev); + ControlState GetState() const override; + private: + const u16 m_code; + const u8 m_index; + const bool m_upper; + int m_range; + libevdev* m_dev; + }; + + class ForceFeedback : public Core::Device::Output + { + public: + std::string GetName() const override; + ForceFeedback(u16 type, libevdev* dev) : m_type(type), m_dev(dev), m_id(-1) { m_fd = libevdev_get_fd(dev); } + void SetState(ControlState state) override; + private: + const u16 m_type; + libevdev* m_dev; + int m_fd; + int m_id; + }; + +public: + void UpdateInput() override; + + evdevDevice(const std::string &devnode, int id); + ~evdevDevice(); + + std::string GetName() const override { return m_name; } + int GetId() const override { return m_id; } + std::string GetSource() const override { return "evdev"; } + + bool IsInteresting() const { return m_initialized && m_interesting; } + +private: + const std::string m_devfile; + int m_fd; + libevdev* m_dev; + std::string m_name; + const int m_id; + bool m_initialized; + bool m_interesting; +}; + +} + +}