-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathvirtualdevice.cc
145 lines (122 loc) · 5.05 KB
/
virtualdevice.cc
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
// This file is part of Projecteur - https://github.com/jahnf/projecteur
// - See LICENSE.md and README.md
#include "virtualdevice.h"
#include "logging.h"
#include <fcntl.h>
#include <linux/uinput.h>
#include <linux/input-event-codes.h>
#include <unistd.h>
#include <QFileInfo>
LOGGING_CATEGORY(virtualdevice, "virtualdevice")
// KEY_MACRO1 is only defined in newer linux versions
#ifndef KEY_MACRO1
#define KEY_MACRO1 0x290
#endif
namespace {
class VirtualDevice_ : public QObject {}; // for i18n and logging
} // end anonymous namespace
struct VirtualDevice::Token {};
// -------------------------------------------------------------------------------------------------
VirtualDevice::VirtualDevice(Token /* token */, int fd, const char* name, const char* sysfs_name)
: m_uinpFd(fd)
, m_userName(name)
, m_deviceName(sysfs_name)
{}
// -------------------------------------------------------------------------------------------------
VirtualDevice::~VirtualDevice()
{
if (m_uinpFd >= 0)
{
ioctl(m_uinpFd, UI_DEV_DESTROY);
::close(m_uinpFd);
logDebug(virtualdevice)
<< VirtualDevice_::tr("uinput Device Closed (%1; %2)").arg(m_userName, m_deviceName);
}
}
// -------------------------------------------------------------------------------------------------
// Setup a uinput device that can send mouse or keyboard events.
std::shared_ptr<VirtualDevice> VirtualDevice::create(Type deviceType,
const char* name,
uint16_t virtualVendorId,
uint16_t virtualProductId,
uint16_t virtualVersionId,
const char* location)
{
const QFileInfo fi(location);
if (!fi.exists()) {
logWarn(virtualdevice) << VirtualDevice_::tr("File not found: %1").arg(location);
logWarn(virtualdevice) << VirtualDevice_::tr("Please check if uinput kernel module is loaded");
return std::shared_ptr<VirtualDevice>();
}
const int fd = ::open(location, O_WRONLY | O_NDELAY);
if (fd < 0) {
logWarn(virtualdevice) << VirtualDevice_::tr("Unable to open: %1").arg(location);
logWarn(virtualdevice) << VirtualDevice_::tr("Please check if current user has write access");
return std::shared_ptr<VirtualDevice>();
}
struct uinput_user_dev uinp {};
snprintf(uinp.name, sizeof(uinp.name), "%s", name);
uinp.id.bustype = BUS_USB;
uinp.id.vendor = virtualVendorId;
uinp.id.product = virtualProductId;
uinp.id.version = virtualVersionId;
// Setup the uinput device
// (see all in Linux's input-event-codes.h)
ioctl(fd, UI_SET_EVBIT, EV_SYN);
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_EVBIT, EV_REL);
// Set all relative event code bits on virtual device
for (int i = 0; i < REL_CNT; ++i) {
ioctl(fd, UI_SET_RELBIT, i);
}
// Thank's to Matthias Blümel / https://github.com/Blaimi
// for the detailed investigation on the uinput issue on newer
// Linux distributions.
// See https://github.com/jahnf/Projecteur/issues/175#issuecomment-1432112896
if (deviceType == Type::Mouse) {
// Set key code bits for a virtual mouse
for (int i = BTN_MISC; i < KEY_OK; ++i) {
ioctl(fd, UI_SET_KEYBIT, i);
}
} else if (deviceType == Type::Keyboard) {
// Set key code bits for a virtual keyboard
for (int i = 1; i < BTN_MISC; ++i) {
ioctl(fd, UI_SET_KEYBIT, i);
}
for (int i = KEY_OK; i < KEY_MACRO1; ++i) {
ioctl(fd, UI_SET_KEYBIT, i);
}
// will set key bits from i = KEY_MACRO1 to i < KEY_CNT also work?
}
// Create input device into input sub-system
const auto bytesWritten = write(fd, &uinp, sizeof(uinp));
if ((bytesWritten != sizeof(uinp)) || (ioctl(fd, UI_DEV_CREATE)))
{
::close(fd);
logWarn(virtualdevice) << VirtualDevice_::tr("Unable to create Virtual (UINPUT) device.");
return std::unique_ptr<VirtualDevice>();
}
// Log the device name
char sysfs_device_name[16]{};
ioctl(fd, UI_GET_SYSNAME(sizeof(sysfs_device_name)), sysfs_device_name);
logInfo(virtualdevice) << VirtualDevice_::tr("Created uinput device: %1")
.arg(QString("%1; /sys/devices/virtual/input/%2")
.arg(name, sysfs_device_name));
return std::make_shared<VirtualDevice>(Token{}, fd, name, sysfs_device_name);
}
// -------------------------------------------------------------------------------------------------
void VirtualDevice::emitEvents(const struct input_event input_events[], size_t num)
{
if (!num) { return; }
if (const ssize_t sz = sizeof(input_event) * num) {
const auto bytesWritten = write(m_uinpFd, input_events, sz);
if (bytesWritten != sz) {
logError(virtualdevice) << VirtualDevice_::tr("Error while writing to virtual device.");
}
}
}
// -------------------------------------------------------------------------------------------------
void VirtualDevice::emitEvents(const std::vector<struct input_event>& events)
{
emitEvents(events.data(), events.size());
}