1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org> |
4 | |
5 | This software is provided 'as-is', without any express or implied |
6 | warranty. In no event will the authors be held liable for any damages |
7 | arising from the use of this software. |
8 | |
9 | Permission is granted to anyone to use this software for any purpose, |
10 | including commercial applications, and to alter it and redistribute it |
11 | freely, subject to the following restrictions: |
12 | |
13 | 1. The origin of this software must not be misrepresented; you must not |
14 | claim that you wrote the original software. If you use this software |
15 | in a product, an acknowledgment in the product documentation would be |
16 | appreciated but is not required. |
17 | 2. Altered source versions must be plainly marked as such, and must not be |
18 | misrepresented as being the original software. |
19 | 3. This notice may not be removed or altered from any source distribution. |
20 | */ |
21 | #include "../../SDL_internal.h" |
22 | |
23 | #if SDL_VIDEO_DRIVER_X11 |
24 | |
25 | #include "SDL_x11video.h" |
26 | #include "SDL_x11xinput2.h" |
27 | #include "../../events/SDL_mouse_c.h" |
28 | #include "../../events/SDL_touch_c.h" |
29 | |
30 | #define MAX_AXIS 16 |
31 | |
32 | #if SDL_VIDEO_DRIVER_X11_XINPUT2 |
33 | static int xinput2_initialized = 0; |
34 | |
35 | #if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH |
36 | static int xinput2_multitouch_supported = 0; |
37 | #endif |
38 | |
39 | /* Opcode returned X11_XQueryExtension |
40 | * It will be used in event processing |
41 | * to know that the event came from |
42 | * this extension */ |
43 | static int xinput2_opcode; |
44 | |
45 | static void parse_valuators(const double *input_values, const unsigned char *mask,int mask_len, |
46 | double *output_values,int output_values_len) { |
47 | int i = 0,z = 0; |
48 | int top = mask_len * 8; |
49 | if (top > MAX_AXIS) |
50 | top = MAX_AXIS; |
51 | |
52 | SDL_memset(output_values,0,output_values_len * sizeof(double)); |
53 | for (; i < top && z < output_values_len; i++) { |
54 | if (XIMaskIsSet(mask, i)) { |
55 | const int value = (int) *input_values; |
56 | output_values[z] = value; |
57 | input_values++; |
58 | } |
59 | z++; |
60 | } |
61 | } |
62 | |
63 | static int |
64 | query_xinput2_version(Display *display, int major, int minor) |
65 | { |
66 | /* We don't care if this fails, so long as it sets major/minor on it's way out the door. */ |
67 | X11_XIQueryVersion(display, &major, &minor); |
68 | return ((major * 1000) + minor); |
69 | } |
70 | |
71 | static SDL_bool |
72 | xinput2_version_atleast(const int version, const int wantmajor, const int wantminor) |
73 | { |
74 | return ( version >= ((wantmajor * 1000) + wantminor) ); |
75 | } |
76 | |
77 | #if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH |
78 | static SDL_Window * |
79 | xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window) |
80 | { |
81 | int i; |
82 | for (i = 0; i < videodata->numwindows; i++) { |
83 | SDL_WindowData *d = videodata->windowlist[i]; |
84 | if (d->xwindow == window) { |
85 | return d->window; |
86 | } |
87 | } |
88 | return NULL; |
89 | } |
90 | |
91 | static void |
92 | xinput2_normalize_touch_coordinates(SDL_Window *window, double in_x, double in_y, float *out_x, float *out_y) |
93 | { |
94 | if (window) { |
95 | if (window->w == 1) { |
96 | *out_x = 0.5f; |
97 | } else { |
98 | *out_x = in_x / (window->w - 1); |
99 | } |
100 | if (window->h == 1) { |
101 | *out_y = 0.5f; |
102 | } else { |
103 | *out_y = in_y / (window->h - 1); |
104 | } |
105 | } else { |
106 | // couldn't find the window... |
107 | *out_x = in_x; |
108 | *out_y = in_y; |
109 | } |
110 | } |
111 | #endif /* SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH */ |
112 | |
113 | #endif /* SDL_VIDEO_DRIVER_X11_XINPUT2 */ |
114 | |
115 | void |
116 | X11_InitXinput2(_THIS) |
117 | { |
118 | #if SDL_VIDEO_DRIVER_X11_XINPUT2 |
119 | SDL_VideoData *data = (SDL_VideoData *) _this->driverdata; |
120 | |
121 | int version = 0; |
122 | XIEventMask eventmask; |
123 | unsigned char mask[3] = { 0,0,0 }; |
124 | int event, err; |
125 | |
126 | /* |
127 | * Initialize XInput 2 |
128 | * According to http://who-t.blogspot.com/2009/05/xi2-recipes-part-1.html its better |
129 | * to inform Xserver what version of Xinput we support.The server will store the version we support. |
130 | * "As XI2 progresses it becomes important that you use this call as the server may treat the client |
131 | * differently depending on the supported version". |
132 | * |
133 | * FIXME:event and err are not needed but if not passed X11_XQueryExtension returns SegmentationFault |
134 | */ |
135 | if (!SDL_X11_HAVE_XINPUT2 || |
136 | !X11_XQueryExtension(data->display, "XInputExtension" , &xinput2_opcode, &event, &err)) { |
137 | return; /* X server does not have XInput at all */ |
138 | } |
139 | |
140 | /* We need at least 2.2 for Multitouch, 2.0 otherwise. */ |
141 | version = query_xinput2_version(data->display, 2, 2); |
142 | if (!xinput2_version_atleast(version, 2, 0)) { |
143 | return; /* X server does not support the version we want at all. */ |
144 | } |
145 | |
146 | xinput2_initialized = 1; |
147 | |
148 | #if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH /* Multitouch needs XInput 2.2 */ |
149 | xinput2_multitouch_supported = xinput2_version_atleast(version, 2, 2); |
150 | #endif |
151 | |
152 | /* Enable Raw motion events for this display */ |
153 | eventmask.deviceid = XIAllMasterDevices; |
154 | eventmask.mask_len = sizeof(mask); |
155 | eventmask.mask = mask; |
156 | |
157 | XISetMask(mask, XI_RawMotion); |
158 | XISetMask(mask, XI_RawButtonPress); |
159 | XISetMask(mask, XI_RawButtonRelease); |
160 | |
161 | if (X11_XISelectEvents(data->display,DefaultRootWindow(data->display),&eventmask,1) != Success) { |
162 | return; |
163 | } |
164 | #endif |
165 | } |
166 | |
167 | int |
168 | X11_HandleXinput2Event(SDL_VideoData *videodata,XGenericEventCookie *cookie) |
169 | { |
170 | #if SDL_VIDEO_DRIVER_X11_XINPUT2 |
171 | if(cookie->extension != xinput2_opcode) { |
172 | return 0; |
173 | } |
174 | switch(cookie->evtype) { |
175 | case XI_RawMotion: { |
176 | const XIRawEvent *rawev = (const XIRawEvent*)cookie->data; |
177 | SDL_Mouse *mouse = SDL_GetMouse(); |
178 | double relative_coords[2]; |
179 | static Time prev_time = 0; |
180 | static double prev_rel_coords[2]; |
181 | |
182 | videodata->global_mouse_changed = SDL_TRUE; |
183 | |
184 | if (!mouse->relative_mode || mouse->relative_mode_warp) { |
185 | return 0; |
186 | } |
187 | |
188 | parse_valuators(rawev->raw_values,rawev->valuators.mask, |
189 | rawev->valuators.mask_len,relative_coords,2); |
190 | |
191 | if ((rawev->time == prev_time) && (relative_coords[0] == prev_rel_coords[0]) && (relative_coords[1] == prev_rel_coords[1])) { |
192 | return 0; /* duplicate event, drop it. */ |
193 | } |
194 | |
195 | SDL_SendMouseMotion(mouse->focus,mouse->mouseID,1,(int)relative_coords[0],(int)relative_coords[1]); |
196 | prev_rel_coords[0] = relative_coords[0]; |
197 | prev_rel_coords[1] = relative_coords[1]; |
198 | prev_time = rawev->time; |
199 | return 1; |
200 | } |
201 | break; |
202 | |
203 | case XI_RawButtonPress: |
204 | case XI_RawButtonRelease: |
205 | videodata->global_mouse_changed = SDL_TRUE; |
206 | break; |
207 | |
208 | #if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH |
209 | /* With multitouch, register to receive XI_Motion (which desctivates MotionNotify), |
210 | * so that we can distinguish real mouse motions from synthetic one. */ |
211 | case XI_Motion: { |
212 | const XIDeviceEvent *xev = (const XIDeviceEvent *) cookie->data; |
213 | int pointer_emulated = (xev->flags & XIPointerEmulated); |
214 | |
215 | if (! pointer_emulated) { |
216 | SDL_Mouse *mouse = SDL_GetMouse(); |
217 | if(!mouse->relative_mode || mouse->relative_mode_warp) { |
218 | SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); |
219 | if (window) { |
220 | SDL_SendMouseMotion(window, 0, 0, xev->event_x, xev->event_y); |
221 | } |
222 | } |
223 | } |
224 | return 1; |
225 | } |
226 | break; |
227 | |
228 | case XI_TouchBegin: { |
229 | const XIDeviceEvent *xev = (const XIDeviceEvent *) cookie->data; |
230 | float x, y; |
231 | SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); |
232 | xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); |
233 | SDL_SendTouch(xev->sourceid, xev->detail, window, SDL_TRUE, x, y, 1.0); |
234 | return 1; |
235 | } |
236 | break; |
237 | case XI_TouchEnd: { |
238 | const XIDeviceEvent *xev = (const XIDeviceEvent *) cookie->data; |
239 | float x, y; |
240 | SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); |
241 | xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); |
242 | SDL_SendTouch(xev->sourceid, xev->detail, window, SDL_FALSE, x, y, 1.0); |
243 | return 1; |
244 | } |
245 | break; |
246 | case XI_TouchUpdate: { |
247 | const XIDeviceEvent *xev = (const XIDeviceEvent *) cookie->data; |
248 | float x, y; |
249 | SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); |
250 | xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); |
251 | SDL_SendTouchMotion(xev->sourceid, xev->detail, window, x, y, 1.0); |
252 | return 1; |
253 | } |
254 | break; |
255 | #endif |
256 | } |
257 | #endif |
258 | return 0; |
259 | } |
260 | |
261 | void |
262 | X11_InitXinput2Multitouch(_THIS) |
263 | { |
264 | #if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH |
265 | SDL_VideoData *data = (SDL_VideoData *) _this->driverdata; |
266 | XIDeviceInfo *info; |
267 | int ndevices,i,j; |
268 | info = X11_XIQueryDevice(data->display, XIAllDevices, &ndevices); |
269 | |
270 | for (i = 0; i < ndevices; i++) { |
271 | XIDeviceInfo *dev = &info[i]; |
272 | for (j = 0; j < dev->num_classes; j++) { |
273 | SDL_TouchID touchId; |
274 | SDL_TouchDeviceType touchType; |
275 | XIAnyClassInfo *class = dev->classes[j]; |
276 | XITouchClassInfo *t = (XITouchClassInfo*)class; |
277 | |
278 | /* Only touch devices */ |
279 | if (class->type != XITouchClass) |
280 | continue; |
281 | |
282 | if (t->mode == XIDependentTouch) { |
283 | touchType = SDL_TOUCH_DEVICE_INDIRECT_RELATIVE; |
284 | } else { /* XIDirectTouch */ |
285 | touchType = SDL_TOUCH_DEVICE_DIRECT; |
286 | } |
287 | |
288 | touchId = t->sourceid; |
289 | SDL_AddTouch(touchId, touchType, dev->name); |
290 | } |
291 | } |
292 | X11_XIFreeDeviceInfo(info); |
293 | #endif |
294 | } |
295 | |
296 | void |
297 | X11_Xinput2SelectTouch(_THIS, SDL_Window *window) |
298 | { |
299 | #if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH |
300 | SDL_VideoData *data = NULL; |
301 | XIEventMask eventmask; |
302 | unsigned char mask[4] = { 0, 0, 0, 0 }; |
303 | SDL_WindowData *window_data = NULL; |
304 | |
305 | if (!X11_Xinput2IsMultitouchSupported()) { |
306 | return; |
307 | } |
308 | |
309 | data = (SDL_VideoData *) _this->driverdata; |
310 | window_data = (SDL_WindowData*)window->driverdata; |
311 | |
312 | eventmask.deviceid = XIAllMasterDevices; |
313 | eventmask.mask_len = sizeof(mask); |
314 | eventmask.mask = mask; |
315 | |
316 | XISetMask(mask, XI_TouchBegin); |
317 | XISetMask(mask, XI_TouchUpdate); |
318 | XISetMask(mask, XI_TouchEnd); |
319 | XISetMask(mask, XI_Motion); |
320 | |
321 | X11_XISelectEvents(data->display,window_data->xwindow,&eventmask,1); |
322 | #endif |
323 | } |
324 | |
325 | |
326 | int |
327 | X11_Xinput2IsInitialized() |
328 | { |
329 | #if SDL_VIDEO_DRIVER_X11_XINPUT2 |
330 | return xinput2_initialized; |
331 | #else |
332 | return 0; |
333 | #endif |
334 | } |
335 | |
336 | int |
337 | X11_Xinput2IsMultitouchSupported() |
338 | { |
339 | #if SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH |
340 | return xinput2_initialized && xinput2_multitouch_supported; |
341 | #else |
342 | return 0; |
343 | #endif |
344 | } |
345 | |
346 | #endif /* SDL_VIDEO_DRIVER_X11 */ |
347 | |
348 | /* vi: set ts=4 sw=4 expandtab: */ |
349 | |