File: evdev.cpp

package info (click to toggle)
dolphin-emu 5.0%2Bdfsg-5
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 29,052 kB
  • sloc: cpp: 213,146; java: 6,252; asm: 2,277; xml: 1,998; ansic: 1,514; python: 462; sh: 279; pascal: 247; makefile: 124; perl: 97
file content (305 lines) | stat: -rw-r--r-- 8,185 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
// 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 "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(std::vector<Core::Device*> &controllerDevices)
{
	// 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);
			evdevDevice* input = new evdevDevice(devnode, name_counts[name]++);

			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 (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);
	}
}

}
}