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 | |
22 | namespace os { |
23 | |
24 | XInput::~XInput() |
25 | { |
26 | if (m_xi) { |
27 | base::unload_dll(m_xi); |
28 | m_xi = nullptr; |
29 | } |
30 | } |
31 | |
32 | void 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 | |
150 | void 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 | |
160 | void 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 | |
171 | bool 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 | |
178 | void 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 | |
238 | void 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 | |