diff options
| author | skidau <skidau@gmail.com> | 2015-07-03 10:28:08 +1000 |
|---|---|---|
| committer | skidau <skidau@gmail.com> | 2015-07-03 10:28:08 +1000 |
| commit | 8a50dc857bbfc86c41706a6f48770a6f78285628 (patch) | |
| tree | 3c7ac1088404678a1608fa0ff3ddea8b79c5ef1f /Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp | |
| parent | 6a33f174de618dc698ceba02e792cc6b2dba9732 (diff) | |
| parent | 77183899e0526a343b8429cf04a1c07b31aafc34 (diff) | |
Merge pull request #2681 from phire/evdev-stable
Linux: Add an evdev based controller backend, to replace SDL.
Diffstat (limited to 'Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp')
| -rw-r--r-- | Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp new file mode 100644 index 0000000000..9b31da73c4 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp @@ -0,0 +1,270 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <libudev.h> +#include <unistd.h> + +#include "Common/Logging/Log.h" +#include "InputCommon/ControllerInterface/evdev/evdev.h" + + +namespace ciface +{ +namespace evdev +{ + +void Init(std::vector<Core::Device*> &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_min = libevdev_get_abs_minimum(m_dev, m_code); + m_range = libevdev_get_abs_maximum(m_dev, m_code) + abs(m_min); +} + +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); + + // Value from 0.0 to 1.0 + ControlState fvalue = double(value - m_min) / double(m_range); + + // Split into two axis, each covering half the range from 0.0 to 1.0 + if (m_upper) + return std::max(0.0, fvalue - 0.5) * 2.0; + else + return (0.5 - std::min(0.5, fvalue)) * 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); + } +} + +} +} |
