1// LAF OS Library
2// Copyright (C) 2020-2022 Igara Studio S.A.
3//
4// This file is released under the terms of the MIT license.
5// Read LICENSE.txt for more information.
6
7#ifdef HAVE_CONFIG_H
8#include "config.h"
9#endif
10
11#include "os/x11/xinput.h"
12
13#include "base/log.h"
14#include "base/string.h"
15#include "os/x11/x11.h"
16
17#include <cstring>
18
19#pragma push_macro("None")
20#undef None // Undefine the X11 None macro
21
22namespace os {
23
24XInput::~XInput()
25{
26 if (m_xi) {
27 base::unload_dll(m_xi);
28 m_xi = nullptr;
29 }
30}
31
32void XInput::load(::Display* display)
33{
34 int majorOpcode;
35 int firstEvent;
36 int firstError;
37
38 // Check that the XInputExtension is available.
39 if (!XQueryExtension(display, "XInputExtension",
40 &majorOpcode,
41 &firstEvent,
42 &firstError))
43 return;
44
45 m_xi = base::load_dll("libXi.so");
46 if (!m_xi) m_xi = base::load_dll("libXi.so.6");
47 if (!m_xi) {
48 LOG("XI: Error loading libXi.so library\n");
49 return;
50 }
51
52 XListInputDevices = base::get_dll_proc<XListInputDevices_Func>(m_xi, "XListInputDevices");
53 XFreeDeviceList = base::get_dll_proc<XFreeDeviceList_Func>(m_xi, "XFreeDeviceList");
54 XOpenDevice = base::get_dll_proc<XOpenDevice_Func>(m_xi, "XOpenDevice");
55 XCloseDevice = base::get_dll_proc<XCloseDevice_Func>(m_xi, "XCloseDevice");
56 XSelectExtensionEvent = base::get_dll_proc<XSelectExtensionEvent_Func>(m_xi, "XSelectExtensionEvent");
57
58 if (!XListInputDevices ||
59 !XFreeDeviceList ||
60 !XOpenDevice ||
61 !XCloseDevice ||
62 !XSelectExtensionEvent) {
63 base::unload_dll(m_xi);
64 m_xi = nullptr;
65
66 LOG("XI: Error loading functions from libXi.so\n");
67 return;
68 }
69
70 int ndevices = 0;
71 auto devices = XListInputDevices(display, &ndevices);
72 if (!devices)
73 return;
74
75 std::string userDefinedTablet = X11::instance()->userDefinedTablet();
76 if (!userDefinedTablet.empty())
77 userDefinedTablet = base::string_to_lower(userDefinedTablet);
78
79 std::string devName;
80 for (int i=0; i<ndevices; ++i) {
81 XDeviceInfo* devInfo = devices+i;
82 if (!devInfo->name)
83 continue;
84
85 // Some devices has "stylus" and others "STYLUS".
86 devName = base::string_to_lower(devInfo->name);
87
88 PointerType pointerType;
89 if (std::strstr(devName.c_str(), "stylus") ||
90 // Some devices has "Tablet Pen", others "PenTablet Pen",
91 // this case cover both:
92 std::strstr(devName.c_str(), "tablet pen") ||
93 // Generic driver for stylus with external monitors?
94 std::strstr(devName.c_str(), "tablet monitor pen") ||
95 // Detect old Wacom Bamboo devices
96 std::strstr(devName.c_str(), "wacom bamboo connect pen pen") ||
97 // Detect user-defined strings
98 (!userDefinedTablet.empty() &&
99 std::strstr(devName.c_str(), userDefinedTablet.c_str()))) {
100 pointerType = PointerType::Pen;
101 }
102 // It can be "eraser", or "Tablet Eraser", or "PenTablet
103 // Eraser", etc. Anything with "eraser" word should work.
104 else if (std::strstr(devName.c_str(), "eraser")) {
105 pointerType = PointerType::Eraser;
106 }
107 else
108 continue;
109
110 auto p = (uint8_t*)devInfo->inputclassinfo;
111 for (int j=0; j<devInfo->num_classes; ++j, p+=((XAnyClassPtr)p)->length) {
112 if (((XAnyClassPtr)p)->c_class != ValuatorClass)
113 continue;
114
115 auto valuator = (XValuatorInfoPtr)p;
116 // Only for devices with 3 or more axes (axis 0 is X, 1 is Y,
117 // and 2 is the pressure).
118 if (valuator->num_axes < 3)
119 continue;
120
121 Info info;
122 info.pointerType = pointerType;
123 info.minPressure = valuator->axes[2].min_value;
124 info.maxPressure = valuator->axes[2].max_value;
125
126 XDevice* device = XOpenDevice(display, devInfo->id);
127 if (!device)
128 continue;
129
130 XEventClass eventClass;
131 int eventType;
132
133 DeviceButtonPress(device, eventType, eventClass);
134 addEvent(eventType, eventClass, Event::MouseDown);
135
136 DeviceButtonRelease(device, eventType, eventClass);
137 addEvent(eventType, eventClass, Event::MouseUp);
138
139 DeviceMotionNotify(device, eventType, eventClass);
140 addEvent(eventType, eventClass, Event::MouseMove);
141
142 m_info[device->device_id] = info;
143 m_openDevices.push_back(device);
144 }
145 }
146
147 XFreeDeviceList(devices);
148}
149
150void XInput::unload(::Display* display)
151{
152 if (!m_xi)
153 return;
154
155 for (XDevice* dev : m_openDevices)
156 XCloseDevice(display, dev);
157 m_openDevices.clear();
158}
159
160void XInput::selectExtensionEvents(::Display* display, ::Window window)
161{
162 if (!m_xi)
163 return;
164
165 ASSERT(XSelectExtensionEvent);
166 XSelectExtensionEvent(display, window,
167 m_eventClasses.data(),
168 int(m_eventClasses.size()));
169}
170
171bool XInput::handleExtensionEvent(const XEvent& xevent)
172{
173 return (xevent.type >= 0 &&
174 xevent.type < int(m_eventTypes.size()) &&
175 m_eventTypes[xevent.type] != Event::None);
176}
177
178void XInput::convertExtensionEvent(const XEvent& xevent,
179 Event& ev,
180 int scale,
181 Time& time)
182{
183 ev.setType(m_eventTypes[xevent.type]);
184
185 gfx::Point pos;
186 KeyModifiers modifiers = kKeyNoneModifier;
187 Event::MouseButton button = Event::NoneButton;
188 XID deviceid;
189 int pressure;
190
191 switch (ev.type()) {
192
193 case Event::MouseDown:
194 case Event::MouseUp: {
195 auto button = (const XDeviceButtonEvent*)&xevent;
196 time = button->time;
197 deviceid = button->deviceid;
198 pos.x = button->x / scale;
199 pos.y = button->y / scale;
200 modifiers = get_modifiers_from_x(button->state);
201 pressure = button->axis_data[2];
202 ev.setButton(get_mouse_button_from_x(button->button));
203 break;
204 }
205
206 case Event::MouseMove: {
207 auto motion = (const XDeviceMotionEvent*)&xevent;
208 time = motion->time;
209 deviceid = motion->deviceid;
210 pos.x = motion->x / scale;
211 pos.y = motion->y / scale;
212 modifiers = get_modifiers_from_x(motion->state);
213 pressure = motion->axis_data[2];
214 break;
215 }
216
217 default:
218 ASSERT(false);
219 break;
220 }
221
222 ev.setModifiers(modifiers);
223 ev.setPosition(pos);
224
225 auto it = m_info.find(deviceid);
226 ASSERT(it != m_info.end());
227 if (it != m_info.end()) {
228 const auto& info = it->second;
229 if (info.minPressure != info.maxPressure) {
230 ev.setPressure(
231 float(pressure - info.minPressure) /
232 float(info.maxPressure - info.minPressure));
233 }
234 ev.setPointerType(info.pointerType);
235 }
236}
237
238void XInput::addEvent(int type, XEventClass eventClass, Event::Type ourEventype)
239{
240 if (!type || !eventClass)
241 return;
242
243 m_eventClasses.push_back(eventClass);
244
245 if (type >= 0 && type < 256) {
246 if (type >= m_eventTypes.size())
247 m_eventTypes.resize(type+1, Event::None);
248 m_eventTypes[type] = ourEventype;
249 }
250}
251
252} // namespace os
253
254#pragma pop_macro("None")
255