Skip to content

Commit 1422bd1

Browse files
committed
[protocols] Implement direct evdev support for reading raw keyboard, mouse, etc.. input on linux
1 parent 4df69ed commit 1422bd1

11 files changed

Lines changed: 1782 additions & 0 deletions

src/plugins/score-plugin-protocols/CMakeLists.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,21 @@ set(GPS_SRCS
234234
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/GPS/GPSSpecificSettingsSerialization.cpp"
235235
)
236236

237+
set(EVDEV_HDRS
238+
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Evdev/EvdevDevice.hpp"
239+
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Evdev/EvdevProtocolFactory.hpp"
240+
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Evdev/EvdevProtocolSettingsWidget.hpp"
241+
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Evdev/EvdevSpecificSettings.hpp"
242+
)
243+
244+
set(EVDEV_SRCS
245+
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Evdev/EvdevDevice.cpp"
246+
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Evdev/EvdevProtocolFactory.cpp"
247+
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Evdev/EvdevProtocolSettingsWidget.cpp"
248+
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Evdev/EvdevSpecificSettingsSerialization.cpp"
249+
)
250+
251+
237252
set(MAPPER_SRCS
238253
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Mapper/MapperDevice.hpp"
239254
"${CMAKE_CURRENT_SOURCE_DIR}/Protocols/Mapper/MapperDevice.cpp"
@@ -346,6 +361,13 @@ if(GPS_HEADER)
346361
list(APPEND SCORE_FEATURES_LIST protocol_gps)
347362
endif()
348363

364+
if(LINUX)
365+
target_sources(${PROJECT_NAME} PRIVATE ${EVDEV_HDRS} ${EVDEV_SRCS})
366+
target_include_directories(${PROJECT_NAME} PRIVATE ${EVDEV_HEADER})
367+
target_compile_definitions(${PROJECT_NAME} PRIVATE OSSIA_PROTOCOL_EVDEV)
368+
list(APPEND SCORE_FEATURES_LIST protocol_evdev)
369+
endif()
370+
349371
target_link_libraries(${PROJECT_NAME}
350372
PUBLIC
351373
${QT_PREFIX}::Core ${QT_PREFIX}::Widgets ${QT_PREFIX}::Network
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
#if defined(OSSIA_PROTOCOL_EVDEV)
2+
#include "EvdevDevice.hpp"
3+
4+
#include "EvdevEvents.cpp"
5+
#include "EvdevSpecificSettings.hpp"
6+
7+
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
8+
9+
#include <score/document/DocumentContext.hpp>
10+
11+
#include <ossia/detail/config.hpp>
12+
13+
#include <ossia/detail/dylib_loader.hpp>
14+
#include <ossia/detail/timer.hpp>
15+
#include <ossia/network/base/protocol.hpp>
16+
#include <ossia/network/common/complex_type.hpp>
17+
#include <ossia/network/context.hpp>
18+
#include <ossia/network/generic/generic_device.hpp>
19+
20+
#include <boost/asio/placeholders.hpp>
21+
#include <boost/asio/posix/stream_descriptor.hpp>
22+
#include <boost/bind.hpp>
23+
#include <boost/date_time/posix_time/posix_time.hpp>
24+
#include <boost/range/adaptor/sliced.hpp>
25+
26+
#include <QDebug>
27+
28+
#include <linux/input.h>
29+
#include <sys/poll.h>
30+
31+
#include <fcntl.h>
32+
#include <stdio.h>
33+
#include <unistd.h>
34+
#include <wobjectimpl.h>
35+
W_OBJECT_IMPL(Protocols::EvdevDevice)
36+
namespace ossia::net
37+
{
38+
39+
struct evdev_protocol : public ossia::net::protocol_base
40+
{
41+
public:
42+
evdev_protocol(
43+
const Protocols::EvdevSpecificSettings& set, ossia::net::network_context_ptr ctx)
44+
: protocol_base{flags{}}
45+
, m_context{std::move(ctx)}
46+
, m_stream(
47+
m_context->context,
48+
open(
49+
QString("/dev/input/%1").arg(set.handler).toStdString().c_str(),
50+
O_RDONLY | O_NONBLOCK))
51+
{
52+
}
53+
54+
~evdev_protocol() { m_stream.close(); }
55+
56+
static inline const char* codename(unsigned int type, unsigned int code)
57+
{
58+
using namespace evdev_input_names;
59+
return (type <= EV_MAX && code <= unsigned(maxval[type]) && names[type]
60+
&& names[type][code])
61+
? names[type][code]
62+
: "?";
63+
}
64+
65+
void set_device(ossia::net::device_base& dev) override
66+
{
67+
m_device = &dev;
68+
69+
auto& root = m_device->get_root_node();
70+
71+
params.event = ossia::create_parameter(root, "/event", "list");
72+
73+
auto info = this->device_info(m_stream.native_handle());
74+
for(const auto& type : info)
75+
{
76+
ossia::net::node_base* node{};
77+
switch(type.type)
78+
{
79+
case EV_KEY: {
80+
node = &ossia::net::create_node(root, "/event/key");
81+
params.key = node->create_parameter(ossia::val_type::INT);
82+
auto& k = params.categorized[EV_KEY];
83+
for(int code : type.event_codes)
84+
k.emplace(
85+
code, ossia::create_parameter(*node, codename(EV_KEY, code), "int"));
86+
break;
87+
}
88+
case EV_REL: {
89+
node = &ossia::net::create_node(root, "/event/relative");
90+
auto& k = params.categorized[EV_REL];
91+
92+
for(int code : type.event_codes)
93+
k.emplace(
94+
code, ossia::create_parameter(*node, codename(EV_REL, code), "int"));
95+
break;
96+
}
97+
case EV_ABS: {
98+
node = &ossia::net::create_node(root, "/event/absolute");
99+
auto& k = params.categorized[EV_ABS];
100+
for(int code : type.event_codes)
101+
k.emplace(
102+
code, ossia::create_parameter(*node, codename(EV_ABS, code), "int"));
103+
break;
104+
}
105+
case EV_MSC: {
106+
node = &ossia::net::create_node(root, "/event/misc");
107+
auto& k = params.categorized[EV_MSC];
108+
for(int code : type.event_codes)
109+
k.emplace(
110+
code, ossia::create_parameter(*node, codename(EV_MSC, code), "int"));
111+
break;
112+
}
113+
case EV_SW: {
114+
node = &ossia::net::create_node(root, "/event/switch");
115+
auto& k = params.categorized[EV_SW];
116+
for(int code : type.event_codes)
117+
k.emplace(
118+
code, ossia::create_parameter(*node, codename(EV_SW, code), "int"));
119+
break;
120+
}
121+
}
122+
}
123+
post_read();
124+
}
125+
126+
struct supported_event_type
127+
{
128+
unsigned int type{};
129+
std::vector<int> event_codes;
130+
};
131+
132+
static std::vector<supported_event_type> device_info(int fd)
133+
{
134+
std::vector<supported_event_type> ret;
135+
136+
#define BITS_PER_LONG (sizeof(long) * 8)
137+
#define NBITS(x) ((((x) - 1) / BITS_PER_LONG) + 1)
138+
#define OFF(x) ((x) % BITS_PER_LONG)
139+
#define BIT(x) (1UL << OFF(x))
140+
#define LONG(x) ((x) / BITS_PER_LONG)
141+
#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
142+
143+
unsigned long bit[EV_MAX][NBITS(KEY_MAX)];
144+
145+
// Read device metadata
146+
int version;
147+
if(ioctl(fd, EVIOCGVERSION, &version))
148+
{
149+
return {};
150+
}
151+
152+
unsigned short id[4];
153+
ioctl(fd, EVIOCGID, id);
154+
155+
char name[256] = "Unknown";
156+
ioctl(fd, EVIOCGNAME(sizeof(name)), name);
157+
158+
memset(bit, 0, sizeof(bit));
159+
ioctl(fd, EVIOCGBIT(0, EV_MAX), bit[0]);
160+
161+
// Look for all the supported events
162+
for(unsigned int type = 0; type < EV_MAX; type++)
163+
{
164+
if(test_bit(type, bit[0]) && type != EV_REP)
165+
{
166+
//int have_state = (get_state(fd, type, state, sizeof(state)) == 0);
167+
if(type == EV_SYN)
168+
continue;
169+
supported_event_type t{.type = type, .event_codes = {}};
170+
171+
ioctl(fd, EVIOCGBIT(type, KEY_MAX), bit[type]);
172+
for(unsigned int code = 0; code < KEY_MAX; code++)
173+
if(test_bit(code, bit[type]))
174+
t.event_codes.push_back(code);
175+
ret.push_back(std::move(t));
176+
}
177+
}
178+
179+
return ret;
180+
}
181+
182+
void push_to_param(int type, int code)
183+
{
184+
185+
if(auto k_it = this->params.categorized.find(type);
186+
k_it != this->params.categorized.end())
187+
{
188+
auto& k = *k_it;
189+
if(auto c_it = k.second.find(code); c_it != k.second.end())
190+
{
191+
c_it->second->push_value(ossia::impulse{});
192+
}
193+
}
194+
}
195+
void push_to_param(int type, int code, int value)
196+
{
197+
if(auto k_it = this->params.categorized.find(type);
198+
k_it != this->params.categorized.end())
199+
{
200+
auto& k = *k_it;
201+
if(auto c_it = k.second.find(code); c_it != k.second.end())
202+
{
203+
c_it->second->push_value(value);
204+
}
205+
}
206+
}
207+
208+
void post_read()
209+
{
210+
events.resize(32);
211+
m_stream.async_read_some(
212+
boost::asio::buffer(events),
213+
[this](boost::system::error_code ec, size_t bytes_transferred) {
214+
if(ec)
215+
return;
216+
217+
auto const n = bytes_transferred / sizeof(::input_event);
218+
for(auto& ev : events | boost::adaptors::sliced(0, n))
219+
{
220+
switch(ev.type)
221+
{
222+
case EV_SYN:
223+
break;
224+
case EV_KEY: // raw keyboard input
225+
this->params.event->push_value(
226+
std::vector<ossia::value>{ev.type, ev.code, ev.value});
227+
if(this->params.key)
228+
this->params.key->push_value(ev.code);
229+
push_to_param(ev.type, ev.code, ev.value);
230+
break;
231+
232+
case EV_REL: // relative, mouse-like
233+
this->params.event->push_value(
234+
std::vector<ossia::value>{ev.type, ev.code, ev.value});
235+
push_to_param(ev.type, ev.code, ev.value);
236+
break;
237+
238+
case EV_ABS: // absolute, tablet-like
239+
this->params.event->push_value(
240+
std::vector<ossia::value>{ev.type, ev.code, ev.value});
241+
push_to_param(ev.type, ev.code, ev.value);
242+
break;
243+
case EV_MSC: // misc
244+
this->params.event->push_value(
245+
std::vector<ossia::value>{ev.type, ev.code, ev.value});
246+
push_to_param(ev.type, ev.code, ev.value);
247+
break;
248+
case EV_SW: // switch
249+
this->params.event->push_value(
250+
std::vector<ossia::value>{ev.type, ev.code, ev.value});
251+
push_to_param(ev.type, ev.code, ev.value);
252+
break;
253+
case EV_LED:
254+
break;
255+
case EV_SND:
256+
break;
257+
case EV_REP:
258+
break;
259+
case EV_FF:
260+
break;
261+
case EV_PWR:
262+
break;
263+
}
264+
}
265+
266+
std::cout << "\n";
267+
268+
post_read();
269+
});
270+
}
271+
272+
bool pull(parameter_base& v) override { return false; }
273+
274+
bool push(const parameter_base& p, const value& v) override { return false; }
275+
bool push_raw(const full_parameter_data&) override { return false; }
276+
bool observe(parameter_base&, bool) override { return false; }
277+
bool update(node_base& node_base) override { return false; }
278+
279+
ossia::net::network_context_ptr m_context;
280+
ossia::net::device_base* m_device{};
281+
struct
282+
{
283+
ossia::net::parameter_base* event{};
284+
ossia::net::parameter_base* key{};
285+
ossia::net::parameter_base* mouse_xy{};
286+
ossia::flat_map<int, ossia::flat_map<int, ossia::net::parameter_base*>> categorized;
287+
} params;
288+
289+
boost::asio::posix::stream_descriptor m_stream;
290+
std::vector<::input_event> events;
291+
};
292+
}
293+
namespace Protocols
294+
{
295+
296+
EvdevDevice::EvdevDevice(
297+
const Device::DeviceSettings& settings, const ossia::net::network_context_ptr& ctx)
298+
: OwningDeviceInterface{settings}
299+
, m_ctx{ctx}
300+
{
301+
m_capas.canRefreshTree = true;
302+
m_capas.canAddNode = false;
303+
m_capas.canRemoveNode = false;
304+
m_capas.canRenameNode = false;
305+
m_capas.canSetProperties = false;
306+
m_capas.canSerialize = false;
307+
}
308+
309+
EvdevDevice::~EvdevDevice() { }
310+
311+
bool EvdevDevice::reconnect()
312+
{
313+
disconnect();
314+
315+
try
316+
{
317+
const auto& set = m_settings.deviceSpecificSettings.value<EvdevSpecificSettings>();
318+
{
319+
auto pproto = std::make_unique<ossia::net::evdev_protocol>(set, m_ctx);
320+
auto dev = std::make_unique<ossia::net::generic_device>(
321+
std::move(pproto), settings().name.toStdString());
322+
m_dev = std::move(dev);
323+
}
324+
deviceChanged(nullptr, m_dev.get());
325+
}
326+
catch(const std::runtime_error& e)
327+
{
328+
qDebug() << "Evdev error: " << e.what();
329+
}
330+
catch(...)
331+
{
332+
qDebug() << "Evdev error";
333+
}
334+
335+
return connected();
336+
}
337+
338+
void EvdevDevice::disconnect()
339+
{
340+
OwningDeviceInterface::disconnect();
341+
}
342+
}
343+
#endif

0 commit comments

Comments
 (0)