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 <sys/types.h> |
26 | #include <sys/time.h> |
27 | #include <signal.h> |
28 | #include <unistd.h> |
29 | #include <limits.h> /* For INT_MAX */ |
30 | |
31 | #include "SDL_x11video.h" |
32 | #include "SDL_x11touch.h" |
33 | #include "SDL_x11xinput2.h" |
34 | #include "../../core/unix/SDL_poll.h" |
35 | #include "../../events/SDL_events_c.h" |
36 | #include "../../events/SDL_mouse_c.h" |
37 | #include "../../events/SDL_touch_c.h" |
38 | |
39 | #include "SDL_hints.h" |
40 | #include "SDL_timer.h" |
41 | #include "SDL_syswm.h" |
42 | |
43 | #include <stdio.h> |
44 | |
45 | /*#define DEBUG_XEVENTS*/ |
46 | |
47 | #ifndef _NET_WM_MOVERESIZE_SIZE_TOPLEFT |
48 | #define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 |
49 | #endif |
50 | |
51 | #ifndef _NET_WM_MOVERESIZE_SIZE_TOP |
52 | #define _NET_WM_MOVERESIZE_SIZE_TOP 1 |
53 | #endif |
54 | |
55 | #ifndef _NET_WM_MOVERESIZE_SIZE_TOPRIGHT |
56 | #define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 |
57 | #endif |
58 | |
59 | #ifndef _NET_WM_MOVERESIZE_SIZE_RIGHT |
60 | #define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 |
61 | #endif |
62 | |
63 | #ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT |
64 | #define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 |
65 | #endif |
66 | |
67 | #ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOM |
68 | #define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 |
69 | #endif |
70 | |
71 | #ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT |
72 | #define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 |
73 | #endif |
74 | |
75 | #ifndef _NET_WM_MOVERESIZE_SIZE_LEFT |
76 | #define _NET_WM_MOVERESIZE_SIZE_LEFT 7 |
77 | #endif |
78 | |
79 | #ifndef _NET_WM_MOVERESIZE_MOVE |
80 | #define _NET_WM_MOVERESIZE_MOVE 8 |
81 | #endif |
82 | |
83 | typedef struct { |
84 | unsigned char *data; |
85 | int format, count; |
86 | Atom type; |
87 | } SDL_x11Prop; |
88 | |
89 | /* Reads property |
90 | Must call X11_XFree on results |
91 | */ |
92 | static void X11_ReadProperty(SDL_x11Prop *p, Display *disp, Window w, Atom prop) |
93 | { |
94 | unsigned char *ret=NULL; |
95 | Atom type; |
96 | int fmt; |
97 | unsigned long count; |
98 | unsigned long bytes_left; |
99 | int bytes_fetch = 0; |
100 | |
101 | do { |
102 | if (ret != 0) X11_XFree(ret); |
103 | X11_XGetWindowProperty(disp, w, prop, 0, bytes_fetch, False, AnyPropertyType, &type, &fmt, &count, &bytes_left, &ret); |
104 | bytes_fetch += bytes_left; |
105 | } while (bytes_left != 0); |
106 | |
107 | p->data=ret; |
108 | p->format=fmt; |
109 | p->count=count; |
110 | p->type=type; |
111 | } |
112 | |
113 | /* Find text-uri-list in a list of targets and return it's atom |
114 | if available, else return None */ |
115 | static Atom X11_PickTarget(Display *disp, Atom list[], int list_count) |
116 | { |
117 | Atom request = None; |
118 | char *name; |
119 | int i; |
120 | for (i=0; i < list_count && request == None; i++) { |
121 | name = X11_XGetAtomName(disp, list[i]); |
122 | if ((SDL_strcmp("text/uri-list" , name) == 0) || (SDL_strcmp("text/plain" , name) == 0)) { |
123 | request = list[i]; |
124 | } |
125 | X11_XFree(name); |
126 | } |
127 | return request; |
128 | } |
129 | |
130 | /* Wrapper for X11_PickTarget for a maximum of three targets, a special |
131 | case in the Xdnd protocol */ |
132 | static Atom X11_PickTargetFromAtoms(Display *disp, Atom a0, Atom a1, Atom a2) |
133 | { |
134 | int count=0; |
135 | Atom atom[3]; |
136 | if (a0 != None) atom[count++] = a0; |
137 | if (a1 != None) atom[count++] = a1; |
138 | if (a2 != None) atom[count++] = a2; |
139 | return X11_PickTarget(disp, atom, count); |
140 | } |
141 | |
142 | struct KeyRepeatCheckData |
143 | { |
144 | XEvent *event; |
145 | SDL_bool found; |
146 | }; |
147 | |
148 | static Bool X11_KeyRepeatCheckIfEvent(Display *display, XEvent *chkev, |
149 | XPointer arg) |
150 | { |
151 | struct KeyRepeatCheckData *d = (struct KeyRepeatCheckData *) arg; |
152 | if (chkev->type == KeyPress && |
153 | chkev->xkey.keycode == d->event->xkey.keycode && |
154 | chkev->xkey.time - d->event->xkey.time < 2) |
155 | d->found = SDL_TRUE; |
156 | return False; |
157 | } |
158 | |
159 | /* Check to see if this is a repeated key. |
160 | (idea shamelessly lifted from GII -- thanks guys! :) |
161 | */ |
162 | static SDL_bool X11_KeyRepeat(Display *display, XEvent *event) |
163 | { |
164 | XEvent dummyev; |
165 | struct KeyRepeatCheckData d; |
166 | d.event = event; |
167 | d.found = SDL_FALSE; |
168 | if (X11_XPending(display)) |
169 | X11_XCheckIfEvent(display, &dummyev, X11_KeyRepeatCheckIfEvent, |
170 | (XPointer) &d); |
171 | return d.found; |
172 | } |
173 | |
174 | static SDL_bool |
175 | X11_IsWheelEvent(Display * display,XEvent * event,int * xticks,int * yticks) |
176 | { |
177 | /* according to the xlib docs, no specific mouse wheel events exist. |
178 | However, the defacto standard is that the vertical wheel is X buttons |
179 | 4 (up) and 5 (down) and a horizontal wheel is 6 (left) and 7 (right). */ |
180 | |
181 | /* Xlib defines "Button1" through 5, so we just use literals here. */ |
182 | switch (event->xbutton.button) { |
183 | case 4: *yticks = 1; return SDL_TRUE; |
184 | case 5: *yticks = -1; return SDL_TRUE; |
185 | case 6: *xticks = 1; return SDL_TRUE; |
186 | case 7: *xticks = -1; return SDL_TRUE; |
187 | default: break; |
188 | } |
189 | return SDL_FALSE; |
190 | } |
191 | |
192 | /* Decodes URI escape sequences in string buf of len bytes |
193 | (excluding the terminating NULL byte) in-place. Since |
194 | URI-encoded characters take three times the space of |
195 | normal characters, this should not be an issue. |
196 | |
197 | Returns the number of decoded bytes that wound up in |
198 | the buffer, excluding the terminating NULL byte. |
199 | |
200 | The buffer is guaranteed to be NULL-terminated but |
201 | may contain embedded NULL bytes. |
202 | |
203 | On error, -1 is returned. |
204 | */ |
205 | static int X11_URIDecode(char *buf, int len) { |
206 | int ri, wi, di; |
207 | char decode = '\0'; |
208 | if (buf == NULL || len < 0) { |
209 | errno = EINVAL; |
210 | return -1; |
211 | } |
212 | if (len == 0) { |
213 | len = SDL_strlen(buf); |
214 | } |
215 | for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) { |
216 | if (di == 0) { |
217 | /* start decoding */ |
218 | if (buf[ri] == '%') { |
219 | decode = '\0'; |
220 | di += 1; |
221 | continue; |
222 | } |
223 | /* normal write */ |
224 | buf[wi] = buf[ri]; |
225 | wi += 1; |
226 | continue; |
227 | } else if (di == 1 || di == 2) { |
228 | char off = '\0'; |
229 | char isa = buf[ri] >= 'a' && buf[ri] <= 'f'; |
230 | char isA = buf[ri] >= 'A' && buf[ri] <= 'F'; |
231 | char isn = buf[ri] >= '0' && buf[ri] <= '9'; |
232 | if (!(isa || isA || isn)) { |
233 | /* not a hexadecimal */ |
234 | int sri; |
235 | for (sri = ri - di; sri <= ri; sri += 1) { |
236 | buf[wi] = buf[sri]; |
237 | wi += 1; |
238 | } |
239 | di = 0; |
240 | continue; |
241 | } |
242 | /* itsy bitsy magicsy */ |
243 | if (isn) { |
244 | off = 0 - '0'; |
245 | } else if (isa) { |
246 | off = 10 - 'a'; |
247 | } else if (isA) { |
248 | off = 10 - 'A'; |
249 | } |
250 | decode |= (buf[ri] + off) << (2 - di) * 4; |
251 | if (di == 2) { |
252 | buf[wi] = decode; |
253 | wi += 1; |
254 | di = 0; |
255 | } else { |
256 | di += 1; |
257 | } |
258 | continue; |
259 | } |
260 | } |
261 | buf[wi] = '\0'; |
262 | return wi; |
263 | } |
264 | |
265 | /* Convert URI to local filename |
266 | return filename if possible, else NULL |
267 | */ |
268 | static char* X11_URIToLocal(char* uri) { |
269 | char *file = NULL; |
270 | SDL_bool local; |
271 | |
272 | if (memcmp(uri,"file:/" ,6) == 0) uri += 6; /* local file? */ |
273 | else if (strstr(uri,":/" ) != NULL) return file; /* wrong scheme */ |
274 | |
275 | local = uri[0] != '/' || (uri[0] != '\0' && uri[1] == '/'); |
276 | |
277 | /* got a hostname? */ |
278 | if (!local && uri[0] == '/' && uri[2] != '/') { |
279 | char* hostname_end = strchr(uri+1, '/'); |
280 | if (hostname_end != NULL) { |
281 | char hostname[ 257 ]; |
282 | if (gethostname(hostname, 255) == 0) { |
283 | hostname[ 256 ] = '\0'; |
284 | if (memcmp(uri+1, hostname, hostname_end - (uri+1)) == 0) { |
285 | uri = hostname_end + 1; |
286 | local = SDL_TRUE; |
287 | } |
288 | } |
289 | } |
290 | } |
291 | if (local) { |
292 | file = uri; |
293 | /* Convert URI escape sequences to real characters */ |
294 | X11_URIDecode(file, 0); |
295 | if (uri[1] == '/') { |
296 | file++; |
297 | } else { |
298 | file--; |
299 | } |
300 | } |
301 | return file; |
302 | } |
303 | |
304 | #if SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS |
305 | static void X11_HandleGenericEvent(SDL_VideoData *videodata, XEvent *xev) |
306 | { |
307 | /* event is a union, so cookie == &event, but this is type safe. */ |
308 | XGenericEventCookie *cookie = &xev->xcookie; |
309 | if (X11_XGetEventData(videodata->display, cookie)) { |
310 | X11_HandleXinput2Event(videodata, cookie); |
311 | |
312 | /* Send a SDL_SYSWMEVENT if the application wants them. |
313 | * Since event data is only available until XFreeEventData is called, |
314 | * the *only* way for an application to access it is to register an event filter/watcher |
315 | * and do all the processing on the SDL_SYSWMEVENT inside the callback. */ |
316 | if (SDL_GetEventState(SDL_SYSWMEVENT) == SDL_ENABLE) { |
317 | SDL_SysWMmsg wmmsg; |
318 | |
319 | SDL_VERSION(&wmmsg.version); |
320 | wmmsg.subsystem = SDL_SYSWM_X11; |
321 | wmmsg.msg.x11.event = *xev; |
322 | SDL_SendSysWMEvent(&wmmsg); |
323 | } |
324 | |
325 | X11_XFreeEventData(videodata->display, cookie); |
326 | } |
327 | } |
328 | #endif /* SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS */ |
329 | |
330 | static unsigned |
331 | X11_GetNumLockModifierMask(_THIS) |
332 | { |
333 | SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata; |
334 | Display *display = viddata->display; |
335 | unsigned num_mask = 0; |
336 | int i, j; |
337 | XModifierKeymap *xmods; |
338 | unsigned n; |
339 | |
340 | xmods = X11_XGetModifierMapping(display); |
341 | n = xmods->max_keypermod; |
342 | for(i = 3; i < 8; i++) { |
343 | for(j = 0; j < n; j++) { |
344 | KeyCode kc = xmods->modifiermap[i * n + j]; |
345 | if (viddata->key_layout[kc] == SDL_SCANCODE_NUMLOCKCLEAR) { |
346 | num_mask = 1 << i; |
347 | break; |
348 | } |
349 | } |
350 | } |
351 | X11_XFreeModifiermap(xmods); |
352 | |
353 | return num_mask; |
354 | } |
355 | |
356 | static void |
357 | X11_ReconcileKeyboardState(_THIS) |
358 | { |
359 | SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata; |
360 | Display *display = viddata->display; |
361 | char keys[32]; |
362 | int keycode; |
363 | Window junk_window; |
364 | int x, y; |
365 | unsigned int mask; |
366 | const Uint8 *keyboardState; |
367 | |
368 | X11_XQueryKeymap(display, keys); |
369 | |
370 | /* Sync up the keyboard modifier state */ |
371 | if (X11_XQueryPointer(display, DefaultRootWindow(display), &junk_window, &junk_window, &x, &y, &x, &y, &mask)) { |
372 | SDL_ToggleModState(KMOD_CAPS, (mask & LockMask) != 0); |
373 | SDL_ToggleModState(KMOD_NUM, (mask & X11_GetNumLockModifierMask(_this)) != 0); |
374 | } |
375 | |
376 | keyboardState = SDL_GetKeyboardState(0); |
377 | for (keycode = 0; keycode < 256; ++keycode) { |
378 | SDL_Scancode scancode = viddata->key_layout[keycode]; |
379 | SDL_bool x11KeyPressed = (keys[keycode / 8] & (1 << (keycode % 8))) != 0; |
380 | SDL_bool sdlKeyPressed = keyboardState[scancode] == SDL_PRESSED; |
381 | |
382 | if (x11KeyPressed && !sdlKeyPressed) { |
383 | SDL_SendKeyboardKey(SDL_PRESSED, scancode); |
384 | } else if (!x11KeyPressed && sdlKeyPressed) { |
385 | SDL_SendKeyboardKey(SDL_RELEASED, scancode); |
386 | } |
387 | } |
388 | } |
389 | |
390 | |
391 | static void |
392 | X11_DispatchFocusIn(_THIS, SDL_WindowData *data) |
393 | { |
394 | #ifdef DEBUG_XEVENTS |
395 | printf("window %p: Dispatching FocusIn\n" , data); |
396 | #endif |
397 | SDL_SetKeyboardFocus(data->window); |
398 | X11_ReconcileKeyboardState(_this); |
399 | #ifdef X_HAVE_UTF8_STRING |
400 | if (data->ic) { |
401 | X11_XSetICFocus(data->ic); |
402 | } |
403 | #endif |
404 | #ifdef SDL_USE_IME |
405 | SDL_IME_SetFocus(SDL_TRUE); |
406 | #endif |
407 | } |
408 | |
409 | static void |
410 | X11_DispatchFocusOut(_THIS, SDL_WindowData *data) |
411 | { |
412 | #ifdef DEBUG_XEVENTS |
413 | printf("window %p: Dispatching FocusOut\n" , data); |
414 | #endif |
415 | /* If another window has already processed a focus in, then don't try to |
416 | * remove focus here. Doing so will incorrectly remove focus from that |
417 | * window, and the focus lost event for this window will have already |
418 | * been dispatched anyway. */ |
419 | if (data->window == SDL_GetKeyboardFocus()) { |
420 | SDL_SetKeyboardFocus(NULL); |
421 | } |
422 | #ifdef X_HAVE_UTF8_STRING |
423 | if (data->ic) { |
424 | X11_XUnsetICFocus(data->ic); |
425 | } |
426 | #endif |
427 | #ifdef SDL_USE_IME |
428 | SDL_IME_SetFocus(SDL_FALSE); |
429 | #endif |
430 | } |
431 | |
432 | static void |
433 | X11_DispatchMapNotify(SDL_WindowData *data) |
434 | { |
435 | SDL_Window *window = data->window; |
436 | SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESTORED, 0, 0); |
437 | SDL_SendWindowEvent(window, SDL_WINDOWEVENT_SHOWN, 0, 0); |
438 | if (!(window->flags & SDL_WINDOW_HIDDEN) && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { |
439 | SDL_UpdateWindowGrab(window); |
440 | } |
441 | } |
442 | |
443 | static void |
444 | X11_DispatchUnmapNotify(SDL_WindowData *data) |
445 | { |
446 | SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_HIDDEN, 0, 0); |
447 | SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_MINIMIZED, 0, 0); |
448 | } |
449 | |
450 | static void |
451 | InitiateWindowMove(_THIS, const SDL_WindowData *data, const SDL_Point *point) |
452 | { |
453 | SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata; |
454 | SDL_Window* window = data->window; |
455 | Display *display = viddata->display; |
456 | XEvent evt; |
457 | |
458 | /* !!! FIXME: we need to regrab this if necessary when the drag is done. */ |
459 | X11_XUngrabPointer(display, 0L); |
460 | X11_XFlush(display); |
461 | |
462 | evt.xclient.type = ClientMessage; |
463 | evt.xclient.window = data->xwindow; |
464 | evt.xclient.message_type = X11_XInternAtom(display, "_NET_WM_MOVERESIZE" , True); |
465 | evt.xclient.format = 32; |
466 | evt.xclient.data.l[0] = window->x + point->x; |
467 | evt.xclient.data.l[1] = window->y + point->y; |
468 | evt.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE; |
469 | evt.xclient.data.l[3] = Button1; |
470 | evt.xclient.data.l[4] = 0; |
471 | X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &evt); |
472 | |
473 | X11_XSync(display, 0); |
474 | } |
475 | |
476 | static void |
477 | InitiateWindowResize(_THIS, const SDL_WindowData *data, const SDL_Point *point, int direction) |
478 | { |
479 | SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata; |
480 | SDL_Window* window = data->window; |
481 | Display *display = viddata->display; |
482 | XEvent evt; |
483 | |
484 | if (direction < _NET_WM_MOVERESIZE_SIZE_TOPLEFT || direction > _NET_WM_MOVERESIZE_SIZE_LEFT) |
485 | return; |
486 | |
487 | /* !!! FIXME: we need to regrab this if necessary when the drag is done. */ |
488 | X11_XUngrabPointer(display, 0L); |
489 | X11_XFlush(display); |
490 | |
491 | evt.xclient.type = ClientMessage; |
492 | evt.xclient.window = data->xwindow; |
493 | evt.xclient.message_type = X11_XInternAtom(display, "_NET_WM_MOVERESIZE" , True); |
494 | evt.xclient.format = 32; |
495 | evt.xclient.data.l[0] = window->x + point->x; |
496 | evt.xclient.data.l[1] = window->y + point->y; |
497 | evt.xclient.data.l[2] = direction; |
498 | evt.xclient.data.l[3] = Button1; |
499 | evt.xclient.data.l[4] = 0; |
500 | X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &evt); |
501 | |
502 | X11_XSync(display, 0); |
503 | } |
504 | |
505 | static SDL_bool |
506 | ProcessHitTest(_THIS, const SDL_WindowData *data, const XEvent *xev) |
507 | { |
508 | SDL_Window *window = data->window; |
509 | |
510 | if (window->hit_test) { |
511 | const SDL_Point point = { xev->xbutton.x, xev->xbutton.y }; |
512 | const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data); |
513 | static const int directions[] = { |
514 | _NET_WM_MOVERESIZE_SIZE_TOPLEFT, _NET_WM_MOVERESIZE_SIZE_TOP, |
515 | _NET_WM_MOVERESIZE_SIZE_TOPRIGHT, _NET_WM_MOVERESIZE_SIZE_RIGHT, |
516 | _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT, _NET_WM_MOVERESIZE_SIZE_BOTTOM, |
517 | _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT, _NET_WM_MOVERESIZE_SIZE_LEFT |
518 | }; |
519 | |
520 | switch (rc) { |
521 | case SDL_HITTEST_DRAGGABLE: |
522 | InitiateWindowMove(_this, data, &point); |
523 | return SDL_TRUE; |
524 | |
525 | case SDL_HITTEST_RESIZE_TOPLEFT: |
526 | case SDL_HITTEST_RESIZE_TOP: |
527 | case SDL_HITTEST_RESIZE_TOPRIGHT: |
528 | case SDL_HITTEST_RESIZE_RIGHT: |
529 | case SDL_HITTEST_RESIZE_BOTTOMRIGHT: |
530 | case SDL_HITTEST_RESIZE_BOTTOM: |
531 | case SDL_HITTEST_RESIZE_BOTTOMLEFT: |
532 | case SDL_HITTEST_RESIZE_LEFT: |
533 | InitiateWindowResize(_this, data, &point, directions[rc - SDL_HITTEST_RESIZE_TOPLEFT]); |
534 | return SDL_TRUE; |
535 | |
536 | default: return SDL_FALSE; |
537 | } |
538 | } |
539 | |
540 | return SDL_FALSE; |
541 | } |
542 | |
543 | static void |
544 | X11_UpdateUserTime(SDL_WindowData *data, const unsigned long latest) |
545 | { |
546 | if (latest && (latest != data->user_time)) { |
547 | SDL_VideoData *videodata = data->videodata; |
548 | Display *display = videodata->display; |
549 | X11_XChangeProperty(display, data->xwindow, videodata->_NET_WM_USER_TIME, |
550 | XA_CARDINAL, 32, PropModeReplace, |
551 | (const unsigned char *) &latest, 1); |
552 | #ifdef DEBUG_XEVENTS |
553 | printf("window %p: updating _NET_WM_USER_TIME to %lu\n" , data, latest); |
554 | #endif |
555 | data->user_time = latest; |
556 | } |
557 | } |
558 | |
559 | static void |
560 | X11_HandleClipboardEvent(_THIS, const XEvent *xevent) |
561 | { |
562 | SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata; |
563 | Display *display = videodata->display; |
564 | |
565 | SDL_assert(videodata->clipboard_window != None); |
566 | SDL_assert(xevent->xany.window == videodata->clipboard_window); |
567 | |
568 | switch (xevent->type) { |
569 | /* Copy the selection from our own CUTBUFFER to the requested property */ |
570 | case SelectionRequest: { |
571 | const XSelectionRequestEvent *req = &xevent->xselectionrequest; |
572 | XEvent sevent; |
573 | int seln_format; |
574 | unsigned long nbytes; |
575 | unsigned long overflow; |
576 | unsigned char *seln_data; |
577 | |
578 | #ifdef DEBUG_XEVENTS |
579 | printf("window CLIPBOARD: SelectionRequest (requestor = %ld, target = %ld)\n" , |
580 | req->requestor, req->target); |
581 | #endif |
582 | |
583 | SDL_zero(sevent); |
584 | sevent.xany.type = SelectionNotify; |
585 | sevent.xselection.selection = req->selection; |
586 | sevent.xselection.target = None; |
587 | sevent.xselection.property = None; /* tell them no by default */ |
588 | sevent.xselection.requestor = req->requestor; |
589 | sevent.xselection.time = req->time; |
590 | |
591 | /* !!! FIXME: We were probably storing this on the root window |
592 | because an SDL window might go away...? but we don't have to do |
593 | this now (or ever, really). */ |
594 | if (X11_XGetWindowProperty(display, DefaultRootWindow(display), |
595 | X11_GetSDLCutBufferClipboardType(display), 0, INT_MAX/4, False, req->target, |
596 | &sevent.xselection.target, &seln_format, &nbytes, |
597 | &overflow, &seln_data) == Success) { |
598 | /* !!! FIXME: cache atoms */ |
599 | Atom XA_TARGETS = X11_XInternAtom(display, "TARGETS" , 0); |
600 | if (sevent.xselection.target == req->target) { |
601 | X11_XChangeProperty(display, req->requestor, req->property, |
602 | sevent.xselection.target, seln_format, PropModeReplace, |
603 | seln_data, nbytes); |
604 | sevent.xselection.property = req->property; |
605 | } else if (XA_TARGETS == req->target) { |
606 | Atom SupportedFormats[] = { XA_TARGETS, sevent.xselection.target }; |
607 | X11_XChangeProperty(display, req->requestor, req->property, |
608 | XA_ATOM, 32, PropModeReplace, |
609 | (unsigned char*)SupportedFormats, |
610 | SDL_arraysize(SupportedFormats)); |
611 | sevent.xselection.property = req->property; |
612 | sevent.xselection.target = XA_TARGETS; |
613 | } |
614 | X11_XFree(seln_data); |
615 | } |
616 | X11_XSendEvent(display, req->requestor, False, 0, &sevent); |
617 | X11_XSync(display, False); |
618 | } |
619 | break; |
620 | |
621 | case SelectionNotify: { |
622 | #ifdef DEBUG_XEVENTS |
623 | printf("window CLIPBOARD: SelectionNotify (requestor = %ld, target = %ld)\n" , |
624 | xevent->xselection.requestor, xevent->xselection.target); |
625 | #endif |
626 | videodata->selection_waiting = SDL_FALSE; |
627 | } |
628 | break; |
629 | |
630 | case SelectionClear: { |
631 | /* !!! FIXME: cache atoms */ |
632 | Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD" , 0); |
633 | |
634 | #ifdef DEBUG_XEVENTS |
635 | printf("window CLIPBOARD: SelectionClear (requestor = %ld, target = %ld)\n" , |
636 | xevent->xselection.requestor, xevent->xselection.target); |
637 | #endif |
638 | |
639 | if (xevent->xselectionclear.selection == XA_PRIMARY || |
640 | (XA_CLIPBOARD != None && xevent->xselectionclear.selection == XA_CLIPBOARD)) { |
641 | SDL_SendClipboardUpdate(); |
642 | } |
643 | } |
644 | break; |
645 | } |
646 | } |
647 | |
648 | static Bool |
649 | isMapNotify(Display *display, XEvent *ev, XPointer arg) |
650 | { |
651 | XUnmapEvent *unmap; |
652 | |
653 | unmap = (XUnmapEvent*) arg; |
654 | |
655 | return ev->type == MapNotify && |
656 | ev->xmap.window == unmap->window && |
657 | ev->xmap.serial == unmap->serial; |
658 | } |
659 | |
660 | static Bool |
661 | isReparentNotify(Display *display, XEvent *ev, XPointer arg) |
662 | { |
663 | XUnmapEvent *unmap; |
664 | |
665 | unmap = (XUnmapEvent*) arg; |
666 | |
667 | return ev->type == ReparentNotify && |
668 | ev->xreparent.window == unmap->window && |
669 | ev->xreparent.serial == unmap->serial; |
670 | } |
671 | |
672 | static void |
673 | X11_DispatchEvent(_THIS) |
674 | { |
675 | SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata; |
676 | Display *display; |
677 | SDL_WindowData *data; |
678 | XEvent xevent; |
679 | int orig_event_type; |
680 | KeyCode orig_keycode; |
681 | XClientMessageEvent m; |
682 | int i; |
683 | |
684 | if (!videodata) { |
685 | return; |
686 | } |
687 | display = videodata->display; |
688 | |
689 | SDL_zero(xevent); /* valgrind fix. --ryan. */ |
690 | X11_XNextEvent(display, &xevent); |
691 | |
692 | /* Save the original keycode for dead keys, which are filtered out by |
693 | the XFilterEvent() call below. |
694 | */ |
695 | orig_event_type = xevent.type; |
696 | if (orig_event_type == KeyPress || orig_event_type == KeyRelease) { |
697 | orig_keycode = xevent.xkey.keycode; |
698 | } else { |
699 | orig_keycode = 0; |
700 | } |
701 | |
702 | /* filter events catchs XIM events and sends them to the correct handler */ |
703 | if (X11_XFilterEvent(&xevent, None) == True) { |
704 | #if 0 |
705 | printf("Filtered event type = %d display = %d window = %d\n" , |
706 | xevent.type, xevent.xany.display, xevent.xany.window); |
707 | #endif |
708 | /* Make sure dead key press/release events are sent */ |
709 | /* But only if we're using one of the DBus IMEs, otherwise |
710 | some XIM IMEs will generate duplicate events */ |
711 | if (orig_keycode) { |
712 | #if defined(HAVE_IBUS_IBUS_H) || defined(HAVE_FCITX) |
713 | SDL_Scancode scancode = videodata->key_layout[orig_keycode]; |
714 | videodata->filter_code = orig_keycode; |
715 | videodata->filter_time = xevent.xkey.time; |
716 | |
717 | if (orig_event_type == KeyPress) { |
718 | SDL_SendKeyboardKey(SDL_PRESSED, scancode); |
719 | } else { |
720 | SDL_SendKeyboardKey(SDL_RELEASED, scancode); |
721 | } |
722 | #endif |
723 | } |
724 | return; |
725 | } |
726 | |
727 | #if SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS |
728 | if(xevent.type == GenericEvent) { |
729 | X11_HandleGenericEvent(videodata, &xevent); |
730 | return; |
731 | } |
732 | #endif |
733 | |
734 | /* Send a SDL_SYSWMEVENT if the application wants them */ |
735 | if (SDL_GetEventState(SDL_SYSWMEVENT) == SDL_ENABLE) { |
736 | SDL_SysWMmsg wmmsg; |
737 | |
738 | SDL_VERSION(&wmmsg.version); |
739 | wmmsg.subsystem = SDL_SYSWM_X11; |
740 | wmmsg.msg.x11.event = xevent; |
741 | SDL_SendSysWMEvent(&wmmsg); |
742 | } |
743 | |
744 | #if 0 |
745 | printf("type = %d display = %d window = %d\n" , |
746 | xevent.type, xevent.xany.display, xevent.xany.window); |
747 | #endif |
748 | |
749 | if ((videodata->clipboard_window != None) && |
750 | (videodata->clipboard_window == xevent.xany.window)) { |
751 | X11_HandleClipboardEvent(_this, &xevent); |
752 | return; |
753 | } |
754 | |
755 | data = NULL; |
756 | if (videodata && videodata->windowlist) { |
757 | for (i = 0; i < videodata->numwindows; ++i) { |
758 | if ((videodata->windowlist[i] != NULL) && |
759 | (videodata->windowlist[i]->xwindow == xevent.xany.window)) { |
760 | data = videodata->windowlist[i]; |
761 | break; |
762 | } |
763 | } |
764 | } |
765 | if (!data) { |
766 | /* The window for KeymapNotify, etc events is 0 */ |
767 | if (xevent.type == KeymapNotify) { |
768 | if (SDL_GetKeyboardFocus() != NULL) { |
769 | X11_ReconcileKeyboardState(_this); |
770 | } |
771 | } else if (xevent.type == MappingNotify) { |
772 | /* Has the keyboard layout changed? */ |
773 | const int request = xevent.xmapping.request; |
774 | |
775 | #ifdef DEBUG_XEVENTS |
776 | printf("window %p: MappingNotify!\n" , data); |
777 | #endif |
778 | if ((request == MappingKeyboard) || (request == MappingModifier)) { |
779 | X11_XRefreshKeyboardMapping(&xevent.xmapping); |
780 | } |
781 | |
782 | X11_UpdateKeymap(_this); |
783 | SDL_SendKeymapChangedEvent(); |
784 | } |
785 | return; |
786 | } |
787 | |
788 | switch (xevent.type) { |
789 | |
790 | /* Gaining mouse coverage? */ |
791 | case EnterNotify:{ |
792 | SDL_Mouse *mouse = SDL_GetMouse(); |
793 | #ifdef DEBUG_XEVENTS |
794 | printf("window %p: EnterNotify! (%d,%d,%d)\n" , data, |
795 | xevent.xcrossing.x, |
796 | xevent.xcrossing.y, |
797 | xevent.xcrossing.mode); |
798 | if (xevent.xcrossing.mode == NotifyGrab) |
799 | printf("Mode: NotifyGrab\n" ); |
800 | if (xevent.xcrossing.mode == NotifyUngrab) |
801 | printf("Mode: NotifyUngrab\n" ); |
802 | #endif |
803 | SDL_SetMouseFocus(data->window); |
804 | |
805 | mouse->last_x = xevent.xcrossing.x; |
806 | mouse->last_y = xevent.xcrossing.y; |
807 | |
808 | if (!mouse->relative_mode) { |
809 | SDL_SendMouseMotion(data->window, 0, 0, xevent.xcrossing.x, xevent.xcrossing.y); |
810 | } |
811 | |
812 | /* We ungrab in LeaveNotify, so we may need to grab again here */ |
813 | SDL_UpdateWindowGrab(data->window); |
814 | } |
815 | break; |
816 | /* Losing mouse coverage? */ |
817 | case LeaveNotify:{ |
818 | #ifdef DEBUG_XEVENTS |
819 | printf("window %p: LeaveNotify! (%d,%d,%d)\n" , data, |
820 | xevent.xcrossing.x, |
821 | xevent.xcrossing.y, |
822 | xevent.xcrossing.mode); |
823 | if (xevent.xcrossing.mode == NotifyGrab) |
824 | printf("Mode: NotifyGrab\n" ); |
825 | if (xevent.xcrossing.mode == NotifyUngrab) |
826 | printf("Mode: NotifyUngrab\n" ); |
827 | #endif |
828 | if (!SDL_GetMouse()->relative_mode) { |
829 | SDL_SendMouseMotion(data->window, 0, 0, xevent.xcrossing.x, xevent.xcrossing.y); |
830 | } |
831 | |
832 | if (xevent.xcrossing.mode != NotifyGrab && |
833 | xevent.xcrossing.mode != NotifyUngrab && |
834 | xevent.xcrossing.detail != NotifyInferior) { |
835 | |
836 | /* In order for interaction with the window decorations and menu to work properly |
837 | on Mutter, we need to ungrab the keyboard when the the mouse leaves. */ |
838 | if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) { |
839 | X11_SetWindowKeyboardGrab(_this, data->window, SDL_FALSE); |
840 | } |
841 | |
842 | SDL_SetMouseFocus(NULL); |
843 | } |
844 | } |
845 | break; |
846 | |
847 | /* Gaining input focus? */ |
848 | case FocusIn:{ |
849 | if (xevent.xfocus.mode == NotifyGrab || xevent.xfocus.mode == NotifyUngrab) { |
850 | /* Someone is handling a global hotkey, ignore it */ |
851 | #ifdef DEBUG_XEVENTS |
852 | printf("window %p: FocusIn (NotifyGrab/NotifyUngrab, ignoring)\n" , data); |
853 | #endif |
854 | break; |
855 | } |
856 | |
857 | if (xevent.xfocus.detail == NotifyInferior || xevent.xfocus.detail == NotifyPointer) { |
858 | #ifdef DEBUG_XEVENTS |
859 | printf("window %p: FocusIn (NotifyInferior/NotifyPointer, ignoring)\n" , data); |
860 | #endif |
861 | break; |
862 | } |
863 | #ifdef DEBUG_XEVENTS |
864 | printf("window %p: FocusIn!\n" , data); |
865 | #endif |
866 | if (!videodata->last_mode_change_deadline) /* no recent mode changes */ |
867 | { |
868 | data->pending_focus = PENDING_FOCUS_NONE; |
869 | data->pending_focus_time = 0; |
870 | X11_DispatchFocusIn(_this, data); |
871 | } |
872 | else |
873 | { |
874 | data->pending_focus = PENDING_FOCUS_IN; |
875 | data->pending_focus_time = SDL_GetTicks() + PENDING_FOCUS_TIME; |
876 | } |
877 | data->last_focus_event_time = SDL_GetTicks(); |
878 | } |
879 | break; |
880 | |
881 | /* Losing input focus? */ |
882 | case FocusOut:{ |
883 | if (xevent.xfocus.mode == NotifyGrab || xevent.xfocus.mode == NotifyUngrab) { |
884 | /* Someone is handling a global hotkey, ignore it */ |
885 | #ifdef DEBUG_XEVENTS |
886 | printf("window %p: FocusOut (NotifyGrab/NotifyUngrab, ignoring)\n" , data); |
887 | #endif |
888 | break; |
889 | } |
890 | if (xevent.xfocus.detail == NotifyInferior || xevent.xfocus.detail == NotifyPointer) { |
891 | /* We still have focus if a child gets focus. We also don't |
892 | care about the position of the pointer when the keyboard |
893 | focus changed. */ |
894 | #ifdef DEBUG_XEVENTS |
895 | printf("window %p: FocusOut (NotifyInferior/NotifyPointer, ignoring)\n" , data); |
896 | #endif |
897 | break; |
898 | } |
899 | #ifdef DEBUG_XEVENTS |
900 | printf("window %p: FocusOut!\n" , data); |
901 | #endif |
902 | if (!videodata->last_mode_change_deadline) /* no recent mode changes */ |
903 | { |
904 | data->pending_focus = PENDING_FOCUS_NONE; |
905 | data->pending_focus_time = 0; |
906 | X11_DispatchFocusOut(_this, data); |
907 | } |
908 | else |
909 | { |
910 | data->pending_focus = PENDING_FOCUS_OUT; |
911 | data->pending_focus_time = SDL_GetTicks() + PENDING_FOCUS_TIME; |
912 | } |
913 | } |
914 | break; |
915 | |
916 | /* Key press? */ |
917 | case KeyPress:{ |
918 | KeyCode keycode = xevent.xkey.keycode; |
919 | KeySym keysym = NoSymbol; |
920 | char text[SDL_TEXTINPUTEVENT_TEXT_SIZE]; |
921 | Status status = 0; |
922 | SDL_bool handled_by_ime = SDL_FALSE; |
923 | |
924 | #ifdef DEBUG_XEVENTS |
925 | printf("window %p: KeyPress (X11 keycode = 0x%X)\n" , data, xevent.xkey.keycode); |
926 | #endif |
927 | #if 1 |
928 | if (videodata->key_layout[keycode] == SDL_SCANCODE_UNKNOWN && keycode) { |
929 | int min_keycode, max_keycode; |
930 | X11_XDisplayKeycodes(display, &min_keycode, &max_keycode); |
931 | keysym = X11_KeyCodeToSym(_this, keycode, xevent.xkey.state >> 13); |
932 | fprintf(stderr, |
933 | "The key you just pressed is not recognized by SDL. To help get this fixed, please report this to the SDL forums/mailing list <https://discourse.libsdl.org/> X11 KeyCode %d (%d), X11 KeySym 0x%lX (%s).\n" , |
934 | keycode, keycode - min_keycode, keysym, |
935 | X11_XKeysymToString(keysym)); |
936 | } |
937 | #endif |
938 | /* */ |
939 | SDL_zeroa(text); |
940 | #ifdef X_HAVE_UTF8_STRING |
941 | if (data->ic) { |
942 | X11_Xutf8LookupString(data->ic, &xevent.xkey, text, sizeof(text), |
943 | &keysym, &status); |
944 | } else { |
945 | X11_XLookupString(&xevent.xkey, text, sizeof(text), &keysym, NULL); |
946 | } |
947 | #else |
948 | X11_XLookupString(&xevent.xkey, text, sizeof(text), &keysym, NULL); |
949 | #endif |
950 | |
951 | #ifdef SDL_USE_IME |
952 | if(SDL_GetEventState(SDL_TEXTINPUT) == SDL_ENABLE){ |
953 | handled_by_ime = SDL_IME_ProcessKeyEvent(keysym, keycode); |
954 | } |
955 | #endif |
956 | if (!handled_by_ime) { |
957 | /* Don't send the key if it looks like a duplicate of a filtered key sent by an IME */ |
958 | if (xevent.xkey.keycode != videodata->filter_code || xevent.xkey.time != videodata->filter_time) { |
959 | SDL_SendKeyboardKey(SDL_PRESSED, videodata->key_layout[keycode]); |
960 | } |
961 | if(*text) { |
962 | SDL_SendKeyboardText(text); |
963 | } |
964 | } |
965 | |
966 | X11_UpdateUserTime(data, xevent.xkey.time); |
967 | } |
968 | break; |
969 | |
970 | /* Key release? */ |
971 | case KeyRelease:{ |
972 | KeyCode keycode = xevent.xkey.keycode; |
973 | |
974 | #ifdef DEBUG_XEVENTS |
975 | printf("window %p: KeyRelease (X11 keycode = 0x%X)\n" , data, xevent.xkey.keycode); |
976 | #endif |
977 | if (X11_KeyRepeat(display, &xevent)) { |
978 | /* We're about to get a repeated key down, ignore the key up */ |
979 | break; |
980 | } |
981 | SDL_SendKeyboardKey(SDL_RELEASED, videodata->key_layout[keycode]); |
982 | } |
983 | break; |
984 | |
985 | /* Have we been iconified? */ |
986 | case UnmapNotify:{ |
987 | XEvent ev; |
988 | |
989 | #ifdef DEBUG_XEVENTS |
990 | printf("window %p: UnmapNotify!\n" , data); |
991 | #endif |
992 | |
993 | if (X11_XCheckIfEvent(display, &ev, &isReparentNotify, (XPointer)&xevent.xunmap)) { |
994 | X11_XCheckIfEvent(display, &ev, &isMapNotify, (XPointer)&xevent.xunmap); |
995 | } else { |
996 | X11_DispatchUnmapNotify(data); |
997 | } |
998 | } |
999 | break; |
1000 | |
1001 | /* Have we been restored? */ |
1002 | case MapNotify:{ |
1003 | #ifdef DEBUG_XEVENTS |
1004 | printf("window %p: MapNotify!\n" , data); |
1005 | #endif |
1006 | X11_DispatchMapNotify(data); |
1007 | } |
1008 | break; |
1009 | |
1010 | /* Have we been resized or moved? */ |
1011 | case ConfigureNotify:{ |
1012 | #ifdef DEBUG_XEVENTS |
1013 | printf("window %p: ConfigureNotify! (position: %d,%d, size: %dx%d)\n" , data, |
1014 | xevent.xconfigure.x, xevent.xconfigure.y, |
1015 | xevent.xconfigure.width, xevent.xconfigure.height); |
1016 | #endif |
1017 | /* Real configure notify events are relative to the parent, synthetic events are absolute. */ |
1018 | if (!xevent.xconfigure.send_event) { |
1019 | unsigned int NumChildren; |
1020 | Window ChildReturn, Root, Parent; |
1021 | Window * Children; |
1022 | /* Translate these coodinates back to relative to root */ |
1023 | X11_XQueryTree(data->videodata->display, xevent.xconfigure.window, &Root, &Parent, &Children, &NumChildren); |
1024 | X11_XTranslateCoordinates(xevent.xconfigure.display, |
1025 | Parent, DefaultRootWindow(xevent.xconfigure.display), |
1026 | xevent.xconfigure.x, xevent.xconfigure.y, |
1027 | &xevent.xconfigure.x, &xevent.xconfigure.y, |
1028 | &ChildReturn); |
1029 | } |
1030 | |
1031 | if (xevent.xconfigure.x != data->last_xconfigure.x || |
1032 | xevent.xconfigure.y != data->last_xconfigure.y) { |
1033 | SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_MOVED, |
1034 | xevent.xconfigure.x, xevent.xconfigure.y); |
1035 | #ifdef SDL_USE_IME |
1036 | if(SDL_GetEventState(SDL_TEXTINPUT) == SDL_ENABLE){ |
1037 | /* Update IME candidate list position */ |
1038 | SDL_IME_UpdateTextRect(NULL); |
1039 | } |
1040 | #endif |
1041 | } |
1042 | if (xevent.xconfigure.width != data->last_xconfigure.width || |
1043 | xevent.xconfigure.height != data->last_xconfigure.height) { |
1044 | SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_RESIZED, |
1045 | xevent.xconfigure.width, |
1046 | xevent.xconfigure.height); |
1047 | } |
1048 | data->last_xconfigure = xevent.xconfigure; |
1049 | } |
1050 | break; |
1051 | |
1052 | /* Have we been requested to quit (or another client message?) */ |
1053 | case ClientMessage:{ |
1054 | |
1055 | static int xdnd_version=0; |
1056 | |
1057 | if (xevent.xclient.message_type == videodata->XdndEnter) { |
1058 | |
1059 | SDL_bool use_list = xevent.xclient.data.l[1] & 1; |
1060 | data->xdnd_source = xevent.xclient.data.l[0]; |
1061 | xdnd_version = (xevent.xclient.data.l[1] >> 24); |
1062 | #ifdef DEBUG_XEVENTS |
1063 | printf("XID of source window : %ld\n" , data->xdnd_source); |
1064 | printf("Protocol version to use : %d\n" , xdnd_version); |
1065 | printf("More then 3 data types : %d\n" , (int) use_list); |
1066 | #endif |
1067 | |
1068 | if (use_list) { |
1069 | /* fetch conversion targets */ |
1070 | SDL_x11Prop p; |
1071 | X11_ReadProperty(&p, display, data->xdnd_source, videodata->XdndTypeList); |
1072 | /* pick one */ |
1073 | data->xdnd_req = X11_PickTarget(display, (Atom*)p.data, p.count); |
1074 | X11_XFree(p.data); |
1075 | } else { |
1076 | /* pick from list of three */ |
1077 | data->xdnd_req = X11_PickTargetFromAtoms(display, xevent.xclient.data.l[2], xevent.xclient.data.l[3], xevent.xclient.data.l[4]); |
1078 | } |
1079 | } |
1080 | else if (xevent.xclient.message_type == videodata->XdndPosition) { |
1081 | |
1082 | #ifdef DEBUG_XEVENTS |
1083 | Atom act= videodata->XdndActionCopy; |
1084 | if(xdnd_version >= 2) { |
1085 | act = xevent.xclient.data.l[4]; |
1086 | } |
1087 | printf("Action requested by user is : %s\n" , X11_XGetAtomName(display , act)); |
1088 | #endif |
1089 | |
1090 | |
1091 | /* reply with status */ |
1092 | memset(&m, 0, sizeof(XClientMessageEvent)); |
1093 | m.type = ClientMessage; |
1094 | m.display = xevent.xclient.display; |
1095 | m.window = xevent.xclient.data.l[0]; |
1096 | m.message_type = videodata->XdndStatus; |
1097 | m.format=32; |
1098 | m.data.l[0] = data->xwindow; |
1099 | m.data.l[1] = (data->xdnd_req != None); |
1100 | m.data.l[2] = 0; /* specify an empty rectangle */ |
1101 | m.data.l[3] = 0; |
1102 | m.data.l[4] = videodata->XdndActionCopy; /* we only accept copying anyway */ |
1103 | |
1104 | X11_XSendEvent(display, xevent.xclient.data.l[0], False, NoEventMask, (XEvent*)&m); |
1105 | X11_XFlush(display); |
1106 | } |
1107 | else if(xevent.xclient.message_type == videodata->XdndDrop) { |
1108 | if (data->xdnd_req == None) { |
1109 | /* say again - not interested! */ |
1110 | memset(&m, 0, sizeof(XClientMessageEvent)); |
1111 | m.type = ClientMessage; |
1112 | m.display = xevent.xclient.display; |
1113 | m.window = xevent.xclient.data.l[0]; |
1114 | m.message_type = videodata->XdndFinished; |
1115 | m.format=32; |
1116 | m.data.l[0] = data->xwindow; |
1117 | m.data.l[1] = 0; |
1118 | m.data.l[2] = None; /* fail! */ |
1119 | X11_XSendEvent(display, xevent.xclient.data.l[0], False, NoEventMask, (XEvent*)&m); |
1120 | } else { |
1121 | /* convert */ |
1122 | if(xdnd_version >= 1) { |
1123 | X11_XConvertSelection(display, videodata->XdndSelection, data->xdnd_req, videodata->PRIMARY, data->xwindow, xevent.xclient.data.l[2]); |
1124 | } else { |
1125 | X11_XConvertSelection(display, videodata->XdndSelection, data->xdnd_req, videodata->PRIMARY, data->xwindow, CurrentTime); |
1126 | } |
1127 | } |
1128 | } |
1129 | else if ((xevent.xclient.message_type == videodata->WM_PROTOCOLS) && |
1130 | (xevent.xclient.format == 32) && |
1131 | (xevent.xclient.data.l[0] == videodata->_NET_WM_PING)) { |
1132 | Window root = DefaultRootWindow(display); |
1133 | |
1134 | #ifdef DEBUG_XEVENTS |
1135 | printf("window %p: _NET_WM_PING\n" , data); |
1136 | #endif |
1137 | xevent.xclient.window = root; |
1138 | X11_XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &xevent); |
1139 | break; |
1140 | } |
1141 | |
1142 | else if ((xevent.xclient.message_type == videodata->WM_PROTOCOLS) && |
1143 | (xevent.xclient.format == 32) && |
1144 | (xevent.xclient.data.l[0] == videodata->WM_DELETE_WINDOW)) { |
1145 | |
1146 | #ifdef DEBUG_XEVENTS |
1147 | printf("window %p: WM_DELETE_WINDOW\n" , data); |
1148 | #endif |
1149 | SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_CLOSE, 0, 0); |
1150 | break; |
1151 | } |
1152 | else if ((xevent.xclient.message_type == videodata->WM_PROTOCOLS) && |
1153 | (xevent.xclient.format == 32) && |
1154 | (xevent.xclient.data.l[0] == videodata->WM_TAKE_FOCUS)) { |
1155 | |
1156 | #ifdef DEBUG_XEVENTS |
1157 | printf("window %p: WM_TAKE_FOCUS\n" , data); |
1158 | #endif |
1159 | SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_TAKE_FOCUS, 0, 0); |
1160 | break; |
1161 | } |
1162 | } |
1163 | break; |
1164 | |
1165 | /* Do we need to refresh ourselves? */ |
1166 | case Expose:{ |
1167 | #ifdef DEBUG_XEVENTS |
1168 | printf("window %p: Expose (count = %d)\n" , data, xevent.xexpose.count); |
1169 | #endif |
1170 | SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_EXPOSED, 0, 0); |
1171 | } |
1172 | break; |
1173 | |
1174 | case MotionNotify:{ |
1175 | SDL_Mouse *mouse = SDL_GetMouse(); |
1176 | if(!mouse->relative_mode || mouse->relative_mode_warp) { |
1177 | #ifdef DEBUG_MOTION |
1178 | printf("window %p: X11 motion: %d,%d\n" , data, xevent.xmotion.x, xevent.xmotion.y); |
1179 | #endif |
1180 | |
1181 | SDL_SendMouseMotion(data->window, 0, 0, xevent.xmotion.x, xevent.xmotion.y); |
1182 | } |
1183 | } |
1184 | break; |
1185 | |
1186 | case ButtonPress:{ |
1187 | int xticks = 0, yticks = 0; |
1188 | #ifdef DEBUG_XEVENTS |
1189 | printf("window %p: ButtonPress (X11 button = %d)\n" , data, xevent.xbutton.button); |
1190 | #endif |
1191 | if (X11_IsWheelEvent(display,&xevent,&xticks, &yticks)) { |
1192 | SDL_SendMouseWheel(data->window, 0, (float) xticks, (float) yticks, SDL_MOUSEWHEEL_NORMAL); |
1193 | } else { |
1194 | SDL_bool ignore_click = SDL_FALSE; |
1195 | int button = xevent.xbutton.button; |
1196 | if(button == Button1) { |
1197 | if (ProcessHitTest(_this, data, &xevent)) { |
1198 | SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_HIT_TEST, 0, 0); |
1199 | break; /* don't pass this event on to app. */ |
1200 | } |
1201 | } |
1202 | else if(button > 7) { |
1203 | /* X button values 4-7 are used for scrolling, so X1 is 8, X2 is 9, ... |
1204 | => subtract (8-SDL_BUTTON_X1) to get value SDL expects */ |
1205 | button -= (8-SDL_BUTTON_X1); |
1206 | } |
1207 | if (data->last_focus_event_time) { |
1208 | const int X11_FOCUS_CLICK_TIMEOUT = 10; |
1209 | if (!SDL_TICKS_PASSED(SDL_GetTicks(), data->last_focus_event_time + X11_FOCUS_CLICK_TIMEOUT)) { |
1210 | ignore_click = !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE); |
1211 | } |
1212 | data->last_focus_event_time = 0; |
1213 | } |
1214 | if (!ignore_click) { |
1215 | SDL_SendMouseButton(data->window, 0, SDL_PRESSED, button); |
1216 | } |
1217 | } |
1218 | X11_UpdateUserTime(data, xevent.xbutton.time); |
1219 | } |
1220 | break; |
1221 | |
1222 | case ButtonRelease:{ |
1223 | int button = xevent.xbutton.button; |
1224 | /* The X server sends a Release event for each Press for wheels. Ignore them. */ |
1225 | int xticks = 0, yticks = 0; |
1226 | #ifdef DEBUG_XEVENTS |
1227 | printf("window %p: ButtonRelease (X11 button = %d)\n" , data, xevent.xbutton.button); |
1228 | #endif |
1229 | if (!X11_IsWheelEvent(display, &xevent, &xticks, &yticks)) { |
1230 | if (button > 7) { |
1231 | /* see explanation at case ButtonPress */ |
1232 | button -= (8-SDL_BUTTON_X1); |
1233 | } |
1234 | SDL_SendMouseButton(data->window, 0, SDL_RELEASED, button); |
1235 | } |
1236 | } |
1237 | break; |
1238 | |
1239 | case PropertyNotify:{ |
1240 | #ifdef DEBUG_XEVENTS |
1241 | unsigned char *propdata; |
1242 | int status, real_format; |
1243 | Atom real_type; |
1244 | unsigned long items_read, items_left; |
1245 | |
1246 | char *name = X11_XGetAtomName(display, xevent.xproperty.atom); |
1247 | if (name) { |
1248 | printf("window %p: PropertyNotify: %s %s time=%lu\n" , data, name, (xevent.xproperty.state == PropertyDelete) ? "deleted" : "changed" , xevent.xproperty.time); |
1249 | X11_XFree(name); |
1250 | } |
1251 | |
1252 | status = X11_XGetWindowProperty(display, data->xwindow, xevent.xproperty.atom, 0L, 8192L, False, AnyPropertyType, &real_type, &real_format, &items_read, &items_left, &propdata); |
1253 | if (status == Success && items_read > 0) { |
1254 | if (real_type == XA_INTEGER) { |
1255 | int *values = (int *)propdata; |
1256 | |
1257 | printf("{" ); |
1258 | for (i = 0; i < items_read; i++) { |
1259 | printf(" %d" , values[i]); |
1260 | } |
1261 | printf(" }\n" ); |
1262 | } else if (real_type == XA_CARDINAL) { |
1263 | if (real_format == 32) { |
1264 | Uint32 *values = (Uint32 *)propdata; |
1265 | |
1266 | printf("{" ); |
1267 | for (i = 0; i < items_read; i++) { |
1268 | printf(" %d" , values[i]); |
1269 | } |
1270 | printf(" }\n" ); |
1271 | } else if (real_format == 16) { |
1272 | Uint16 *values = (Uint16 *)propdata; |
1273 | |
1274 | printf("{" ); |
1275 | for (i = 0; i < items_read; i++) { |
1276 | printf(" %d" , values[i]); |
1277 | } |
1278 | printf(" }\n" ); |
1279 | } else if (real_format == 8) { |
1280 | Uint8 *values = (Uint8 *)propdata; |
1281 | |
1282 | printf("{" ); |
1283 | for (i = 0; i < items_read; i++) { |
1284 | printf(" %d" , values[i]); |
1285 | } |
1286 | printf(" }\n" ); |
1287 | } |
1288 | } else if (real_type == XA_STRING || |
1289 | real_type == videodata->UTF8_STRING) { |
1290 | printf("{ \"%s\" }\n" , propdata); |
1291 | } else if (real_type == XA_ATOM) { |
1292 | Atom *atoms = (Atom *)propdata; |
1293 | |
1294 | printf("{" ); |
1295 | for (i = 0; i < items_read; i++) { |
1296 | char *atomname = X11_XGetAtomName(display, atoms[i]); |
1297 | if (atomname) { |
1298 | printf(" %s" , atomname); |
1299 | X11_XFree(atomname); |
1300 | } |
1301 | } |
1302 | printf(" }\n" ); |
1303 | } else { |
1304 | char *atomname = X11_XGetAtomName(display, real_type); |
1305 | printf("Unknown type: %ld (%s)\n" , real_type, atomname ? atomname : "UNKNOWN" ); |
1306 | if (atomname) { |
1307 | X11_XFree(atomname); |
1308 | } |
1309 | } |
1310 | } |
1311 | if (status == Success) { |
1312 | X11_XFree(propdata); |
1313 | } |
1314 | #endif /* DEBUG_XEVENTS */ |
1315 | |
1316 | /* Take advantage of this moment to make sure user_time has a |
1317 | valid timestamp from the X server, so if we later try to |
1318 | raise/restore this window, _NET_ACTIVE_WINDOW can have a |
1319 | non-zero timestamp, even if there's never been a mouse or |
1320 | key press to this window so far. Note that we don't try to |
1321 | set _NET_WM_USER_TIME here, though. That's only for legit |
1322 | user interaction with the window. */ |
1323 | if (!data->user_time) { |
1324 | data->user_time = xevent.xproperty.time; |
1325 | } |
1326 | |
1327 | if (xevent.xproperty.atom == data->videodata->_NET_WM_STATE) { |
1328 | /* Get the new state from the window manager. |
1329 | Compositing window managers can alter visibility of windows |
1330 | without ever mapping / unmapping them, so we handle that here, |
1331 | because they use the NETWM protocol to notify us of changes. |
1332 | */ |
1333 | const Uint32 flags = X11_GetNetWMState(_this, xevent.xproperty.window); |
1334 | const Uint32 changed = flags ^ data->window->flags; |
1335 | |
1336 | if ((changed & SDL_WINDOW_HIDDEN) || (changed & SDL_WINDOW_FULLSCREEN)) { |
1337 | if (flags & SDL_WINDOW_HIDDEN) { |
1338 | X11_DispatchUnmapNotify(data); |
1339 | } else { |
1340 | X11_DispatchMapNotify(data); |
1341 | } |
1342 | } |
1343 | |
1344 | if (changed & SDL_WINDOW_MAXIMIZED) { |
1345 | if (flags & SDL_WINDOW_MAXIMIZED) { |
1346 | SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_MAXIMIZED, 0, 0); |
1347 | } else { |
1348 | SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_RESTORED, 0, 0); |
1349 | } |
1350 | } |
1351 | } else if (xevent.xproperty.atom == videodata->XKLAVIER_STATE) { |
1352 | /* Hack for Ubuntu 12.04 (etc) that doesn't send MappingNotify |
1353 | events when the keyboard layout changes (for example, |
1354 | changing from English to French on the menubar's keyboard |
1355 | icon). Since it changes the XKLAVIER_STATE property, we |
1356 | notice and reinit our keymap here. This might not be the |
1357 | right approach, but it seems to work. */ |
1358 | X11_UpdateKeymap(_this); |
1359 | SDL_SendKeymapChangedEvent(); |
1360 | } else if (xevent.xproperty.atom == videodata->_NET_FRAME_EXTENTS) { |
1361 | Atom type; |
1362 | int format; |
1363 | unsigned long nitems, bytes_after; |
1364 | unsigned char *property; |
1365 | if (X11_XGetWindowProperty(display, data->xwindow, videodata->_NET_FRAME_EXTENTS, 0, 16, 0, XA_CARDINAL, &type, &format, &nitems, &bytes_after, &property) == Success) { |
1366 | if (type != None && nitems == 4) { |
1367 | data->border_left = (int) ((long*)property)[0]; |
1368 | data->border_right = (int) ((long*)property)[1]; |
1369 | data->border_top = (int) ((long*)property)[2]; |
1370 | data->border_bottom = (int) ((long*)property)[3]; |
1371 | } |
1372 | X11_XFree(property); |
1373 | |
1374 | #ifdef DEBUG_XEVENTS |
1375 | printf("New _NET_FRAME_EXTENTS: left=%d right=%d, top=%d, bottom=%d\n" , data->border_left, data->border_right, data->border_top, data->border_bottom); |
1376 | #endif |
1377 | } |
1378 | } |
1379 | } |
1380 | break; |
1381 | |
1382 | case SelectionNotify: { |
1383 | Atom target = xevent.xselection.target; |
1384 | #ifdef DEBUG_XEVENTS |
1385 | printf("window %p: SelectionNotify (requestor = %ld, target = %ld)\n" , data, |
1386 | xevent.xselection.requestor, xevent.xselection.target); |
1387 | #endif |
1388 | if (target == data->xdnd_req) { |
1389 | /* read data */ |
1390 | SDL_x11Prop p; |
1391 | X11_ReadProperty(&p, display, data->xwindow, videodata->PRIMARY); |
1392 | |
1393 | if (p.format == 8) { |
1394 | char *saveptr = NULL; |
1395 | char *name = X11_XGetAtomName(display, target); |
1396 | if (name) { |
1397 | char *token = SDL_strtokr((char *) p.data, "\r\n" , &saveptr); |
1398 | while (token != NULL) { |
1399 | if (SDL_strcmp("text/plain" , name) == 0) { |
1400 | SDL_SendDropText(data->window, token); |
1401 | } else if (SDL_strcmp("text/uri-list" , name) == 0) { |
1402 | char *fn = X11_URIToLocal(token); |
1403 | if (fn) { |
1404 | SDL_SendDropFile(data->window, fn); |
1405 | } |
1406 | } |
1407 | token = SDL_strtokr(NULL, "\r\n" , &saveptr); |
1408 | } |
1409 | X11_XFree(name); |
1410 | } |
1411 | SDL_SendDropComplete(data->window); |
1412 | } |
1413 | X11_XFree(p.data); |
1414 | |
1415 | /* send reply */ |
1416 | SDL_memset(&m, 0, sizeof(XClientMessageEvent)); |
1417 | m.type = ClientMessage; |
1418 | m.display = display; |
1419 | m.window = data->xdnd_source; |
1420 | m.message_type = videodata->XdndFinished; |
1421 | m.format = 32; |
1422 | m.data.l[0] = data->xwindow; |
1423 | m.data.l[1] = 1; |
1424 | m.data.l[2] = videodata->XdndActionCopy; |
1425 | X11_XSendEvent(display, data->xdnd_source, False, NoEventMask, (XEvent*)&m); |
1426 | |
1427 | X11_XSync(display, False); |
1428 | } |
1429 | } |
1430 | break; |
1431 | |
1432 | default:{ |
1433 | #ifdef DEBUG_XEVENTS |
1434 | printf("window %p: Unhandled event %d\n" , data, xevent.type); |
1435 | #endif |
1436 | } |
1437 | break; |
1438 | } |
1439 | } |
1440 | |
1441 | static void |
1442 | X11_HandleFocusChanges(_THIS) |
1443 | { |
1444 | SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata; |
1445 | int i; |
1446 | |
1447 | if (videodata && videodata->windowlist) { |
1448 | for (i = 0; i < videodata->numwindows; ++i) { |
1449 | SDL_WindowData *data = videodata->windowlist[i]; |
1450 | if (data && data->pending_focus != PENDING_FOCUS_NONE) { |
1451 | Uint32 now = SDL_GetTicks(); |
1452 | if (SDL_TICKS_PASSED(now, data->pending_focus_time)) { |
1453 | if (data->pending_focus == PENDING_FOCUS_IN) { |
1454 | X11_DispatchFocusIn(_this, data); |
1455 | } else { |
1456 | X11_DispatchFocusOut(_this, data); |
1457 | } |
1458 | data->pending_focus = PENDING_FOCUS_NONE; |
1459 | } |
1460 | } |
1461 | } |
1462 | } |
1463 | } |
1464 | /* Ack! X11_XPending() actually performs a blocking read if no events available */ |
1465 | static int |
1466 | X11_Pending(Display * display) |
1467 | { |
1468 | /* Flush the display connection and look to see if events are queued */ |
1469 | X11_XFlush(display); |
1470 | if (X11_XEventsQueued(display, QueuedAlready)) { |
1471 | return (1); |
1472 | } |
1473 | |
1474 | /* More drastic measures are required -- see if X is ready to talk */ |
1475 | if (SDL_IOReady(ConnectionNumber(display), SDL_FALSE, 0)) { |
1476 | return (X11_XPending(display)); |
1477 | } |
1478 | |
1479 | /* Oh well, nothing is ready .. */ |
1480 | return (0); |
1481 | } |
1482 | |
1483 | void |
1484 | X11_PumpEvents(_THIS) |
1485 | { |
1486 | SDL_VideoData *data = (SDL_VideoData *) _this->driverdata; |
1487 | |
1488 | if (data->last_mode_change_deadline) { |
1489 | if (SDL_TICKS_PASSED(SDL_GetTicks(), data->last_mode_change_deadline)) { |
1490 | data->last_mode_change_deadline = 0; /* assume we're done. */ |
1491 | } |
1492 | } |
1493 | |
1494 | /* Update activity every 30 seconds to prevent screensaver */ |
1495 | if (_this->suspend_screensaver) { |
1496 | const Uint32 now = SDL_GetTicks(); |
1497 | if (!data->screensaver_activity || |
1498 | SDL_TICKS_PASSED(now, data->screensaver_activity + 30000)) { |
1499 | X11_XResetScreenSaver(data->display); |
1500 | |
1501 | #if SDL_USE_LIBDBUS |
1502 | SDL_DBus_ScreensaverTickle(); |
1503 | #endif |
1504 | |
1505 | data->screensaver_activity = now; |
1506 | } |
1507 | } |
1508 | |
1509 | /* Keep processing pending events */ |
1510 | while (X11_Pending(data->display)) { |
1511 | X11_DispatchEvent(_this); |
1512 | } |
1513 | |
1514 | #ifdef SDL_USE_IME |
1515 | if(SDL_GetEventState(SDL_TEXTINPUT) == SDL_ENABLE){ |
1516 | SDL_IME_PumpEvents(); |
1517 | } |
1518 | #endif |
1519 | |
1520 | /* FIXME: Only need to do this when there are pending focus changes */ |
1521 | X11_HandleFocusChanges(_this); |
1522 | } |
1523 | |
1524 | |
1525 | void |
1526 | X11_SuspendScreenSaver(_THIS) |
1527 | { |
1528 | #if SDL_VIDEO_DRIVER_X11_XSCRNSAVER |
1529 | SDL_VideoData *data = (SDL_VideoData *) _this->driverdata; |
1530 | int dummy; |
1531 | int major_version, minor_version; |
1532 | #endif /* SDL_VIDEO_DRIVER_X11_XSCRNSAVER */ |
1533 | |
1534 | #if SDL_USE_LIBDBUS |
1535 | if (SDL_DBus_ScreensaverInhibit(_this->suspend_screensaver)) { |
1536 | return; |
1537 | } |
1538 | |
1539 | if (_this->suspend_screensaver) { |
1540 | SDL_DBus_ScreensaverTickle(); |
1541 | } |
1542 | #endif |
1543 | |
1544 | #if SDL_VIDEO_DRIVER_X11_XSCRNSAVER |
1545 | if (SDL_X11_HAVE_XSS) { |
1546 | /* X11_XScreenSaverSuspend was introduced in MIT-SCREEN-SAVER 1.1 */ |
1547 | if (!X11_XScreenSaverQueryExtension(data->display, &dummy, &dummy) || |
1548 | !X11_XScreenSaverQueryVersion(data->display, |
1549 | &major_version, &minor_version) || |
1550 | major_version < 1 || (major_version == 1 && minor_version < 1)) { |
1551 | return; |
1552 | } |
1553 | |
1554 | X11_XScreenSaverSuspend(data->display, _this->suspend_screensaver); |
1555 | X11_XResetScreenSaver(data->display); |
1556 | } |
1557 | #endif |
1558 | } |
1559 | |
1560 | #endif /* SDL_VIDEO_DRIVER_X11 */ |
1561 | |
1562 | /* vi: set ts=4 sw=4 expandtab: */ |
1563 | |