mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-26 07:45:33 +01:00
6cc4591226
The values are expected to be in the 0.0-1.0 range (as indicated by the comment), and other parts of Dolphin also expect it to be in that range since the "full" axis has a -1.0 to 1.0 range. However, this is not always the case and fvalue can end up being outside of the range. This clamps fvalue to always be in the 0.0 and 1.0 range.
299 lines
8.3 KiB
C++
299 lines
8.3 KiB
C++
// Copyright 2015 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <fcntl.h>
|
|
#include <libudev.h>
|
|
#include <map>
|
|
#include <unistd.h>
|
|
|
|
#include "Common/Assert.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "Common/MathUtil.h"
|
|
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
|
#include "InputCommon/ControllerInterface/evdev/evdev.h"
|
|
|
|
namespace ciface
|
|
{
|
|
namespace evdev
|
|
{
|
|
static std::string GetName(const std::string& devnode)
|
|
{
|
|
int fd = open(devnode.c_str(), O_RDWR | O_NONBLOCK);
|
|
libevdev* dev = nullptr;
|
|
int ret = libevdev_new_from_fd(fd, &dev);
|
|
if (ret != 0)
|
|
{
|
|
close(fd);
|
|
return std::string();
|
|
}
|
|
std::string res = libevdev_get_name(dev);
|
|
libevdev_free(dev);
|
|
close(fd);
|
|
return res;
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
// this is used to number the joysticks
|
|
// multiple joysticks with the same name shall get unique ids starting at 0
|
|
std::map<std::string, int> name_counts;
|
|
|
|
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 (devnode && 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.
|
|
std::string name = GetName(devnode);
|
|
auto input = std::make_shared<evdevDevice>(devnode, name_counts[name]++);
|
|
|
|
if (input->IsInteresting())
|
|
{
|
|
g_controller_interface.AddDevice(std::move(input));
|
|
num_controllers++;
|
|
}
|
|
}
|
|
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 = MathUtil::Clamp(double(value - m_min) / double(m_range), 0.0, 1.0);
|
|
|
|
// 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 (m_id != -1) // delete the previous effect (which also stops it)
|
|
{
|
|
ioctl(m_fd, EVIOCRMFF, m_id);
|
|
m_id = -1;
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
evdevDevice::ForceFeedback::~ForceFeedback()
|
|
{
|
|
// delete the uploaded effect, so we don't leak it.
|
|
if (m_id != -1)
|
|
{
|
|
ioctl(m_fd, EVIOCRMFF, m_id);
|
|
}
|
|
}
|
|
}
|
|
}
|