1 | //======================================================================== |
2 | // GLFW 3.2 X11 - www.glfw.org |
3 | //------------------------------------------------------------------------ |
4 | // Copyright (c) 2002-2006 Marcus Geelnard |
5 | // Copyright (c) 2006-2016 Camilla Berglund <elmindreda@glfw.org> |
6 | // |
7 | // This software is provided 'as-is', without any express or implied |
8 | // warranty. In no event will the authors be held liable for any damages |
9 | // arising from the use of this software. |
10 | // |
11 | // Permission is granted to anyone to use this software for any purpose, |
12 | // including commercial applications, and to alter it and redistribute it |
13 | // freely, subject to the following restrictions: |
14 | // |
15 | // 1. The origin of this software must not be misrepresented; you must not |
16 | // claim that you wrote the original software. If you use this software |
17 | // in a product, an acknowledgment in the product documentation would |
18 | // be appreciated but is not required. |
19 | // |
20 | // 2. Altered source versions must be plainly marked as such, and must not |
21 | // be misrepresented as being the original software. |
22 | // |
23 | // 3. This notice may not be removed or altered from any source |
24 | // distribution. |
25 | // |
26 | //======================================================================== |
27 | |
28 | #include "internal.h" |
29 | |
30 | #include <X11/cursorfont.h> |
31 | #include <X11/Xmd.h> |
32 | |
33 | #include <sys/select.h> |
34 | |
35 | #include <string.h> |
36 | #include <stdio.h> |
37 | #include <stdlib.h> |
38 | #include <limits.h> |
39 | #include <errno.h> |
40 | #include <assert.h> |
41 | |
42 | // Action for EWMH client messages |
43 | #define _NET_WM_STATE_REMOVE 0 |
44 | #define _NET_WM_STATE_ADD 1 |
45 | #define _NET_WM_STATE_TOGGLE 2 |
46 | |
47 | // Additional mouse button names for XButtonEvent |
48 | #define Button6 6 |
49 | #define Button7 7 |
50 | |
51 | |
52 | // Wait for data to arrive using select |
53 | // This avoids blocking other threads via the per-display Xlib lock that also |
54 | // covers GLX functions |
55 | // |
56 | static GLFWbool waitForEvent(double* timeout) |
57 | { |
58 | fd_set fds; |
59 | const int fd = ConnectionNumber(_glfw.x11.display); |
60 | int count = fd + 1; |
61 | |
62 | FD_ZERO(&fds); |
63 | FD_SET(fd, &fds); |
64 | #if defined(__linux__) |
65 | FD_SET(_glfw.linux_js.inotify, &fds); |
66 | |
67 | if (fd < _glfw.linux_js.inotify) |
68 | count = _glfw.linux_js.inotify + 1; |
69 | #endif |
70 | for (;;) |
71 | { |
72 | if (timeout) |
73 | { |
74 | const long seconds = (long) *timeout; |
75 | const long microseconds = (long) ((*timeout - seconds) * 1e6); |
76 | struct timeval tv = { seconds, microseconds }; |
77 | const uint64_t base = _glfwPlatformGetTimerValue(); |
78 | |
79 | const int result = select(count, &fds, NULL, NULL, &tv); |
80 | const int error = errno; |
81 | |
82 | *timeout -= (_glfwPlatformGetTimerValue() - base) / |
83 | (double) _glfwPlatformGetTimerFrequency(); |
84 | |
85 | if (result > 0) |
86 | return GLFW_TRUE; |
87 | if ((result == -1 && error == EINTR) || *timeout <= 0.0) |
88 | return GLFW_FALSE; |
89 | } |
90 | else if (select(count, &fds, NULL, NULL, NULL) != -1 || errno != EINTR) |
91 | return GLFW_TRUE; |
92 | } |
93 | } |
94 | |
95 | // Waits until a VisibilityNotify event arrives for the specified window or the |
96 | // timeout period elapses (ICCCM section 4.2.2) |
97 | // |
98 | static GLFWbool waitForVisibilityNotify(_GLFWwindow* window) |
99 | { |
100 | XEvent dummy; |
101 | double timeout = 0.1; |
102 | |
103 | while (!XCheckTypedWindowEvent(_glfw.x11.display, |
104 | window->x11.handle, |
105 | VisibilityNotify, |
106 | &dummy)) |
107 | { |
108 | if (!waitForEvent(&timeout)) |
109 | return GLFW_FALSE; |
110 | } |
111 | |
112 | return GLFW_TRUE; |
113 | } |
114 | |
115 | // Returns whether the window is iconified |
116 | // |
117 | static int getWindowState(_GLFWwindow* window) |
118 | { |
119 | int result = WithdrawnState; |
120 | struct { |
121 | CARD32 state; |
122 | Window icon; |
123 | } *state = NULL; |
124 | |
125 | if (_glfwGetWindowPropertyX11(window->x11.handle, |
126 | _glfw.x11.WM_STATE, |
127 | _glfw.x11.WM_STATE, |
128 | (unsigned char**) &state) >= 2) |
129 | { |
130 | result = state->state; |
131 | } |
132 | |
133 | XFree(state); |
134 | return result; |
135 | } |
136 | |
137 | // Returns whether the event is a selection event |
138 | // |
139 | static Bool isSelectionEvent(Display* display, XEvent* event, XPointer pointer) |
140 | { |
141 | return event->type == SelectionRequest || |
142 | event->type == SelectionNotify || |
143 | event->type == SelectionClear; |
144 | } |
145 | |
146 | // Returns whether it is a _NET_FRAME_EXTENTS event for the specified window |
147 | // |
148 | static Bool isFrameExtentsEvent(Display* display, XEvent* event, XPointer pointer) |
149 | { |
150 | _GLFWwindow* window = (_GLFWwindow*) pointer; |
151 | return event->type == PropertyNotify && |
152 | event->xproperty.state == PropertyNewValue && |
153 | event->xproperty.window == window->x11.handle && |
154 | event->xproperty.atom == _glfw.x11.NET_FRAME_EXTENTS; |
155 | } |
156 | |
157 | // Translates a GLFW standard cursor to a font cursor shape |
158 | // |
159 | static int translateCursorShape(int shape) |
160 | { |
161 | switch (shape) |
162 | { |
163 | case GLFW_ARROW_CURSOR: |
164 | return XC_left_ptr; |
165 | case GLFW_IBEAM_CURSOR: |
166 | return XC_xterm; |
167 | case GLFW_CROSSHAIR_CURSOR: |
168 | return XC_crosshair; |
169 | case GLFW_HAND_CURSOR: |
170 | return XC_hand1; |
171 | case GLFW_HRESIZE_CURSOR: |
172 | return XC_sb_h_double_arrow; |
173 | case GLFW_VRESIZE_CURSOR: |
174 | return XC_sb_v_double_arrow; |
175 | } |
176 | |
177 | return 0; |
178 | } |
179 | |
180 | // Translates an X event modifier state mask |
181 | // |
182 | static int translateState(int state) |
183 | { |
184 | int mods = 0; |
185 | |
186 | if (state & ShiftMask) |
187 | mods |= GLFW_MOD_SHIFT; |
188 | if (state & ControlMask) |
189 | mods |= GLFW_MOD_CONTROL; |
190 | if (state & Mod1Mask) |
191 | mods |= GLFW_MOD_ALT; |
192 | if (state & Mod4Mask) |
193 | mods |= GLFW_MOD_SUPER; |
194 | |
195 | return mods; |
196 | } |
197 | |
198 | // Translates an X11 key code to a GLFW key token |
199 | // |
200 | static int translateKey(int scancode) |
201 | { |
202 | // Use the pre-filled LUT (see createKeyTables() in x11_init.c) |
203 | if (scancode < 0 || scancode > 255) |
204 | return GLFW_KEY_UNKNOWN; |
205 | |
206 | return _glfw.x11.publicKeys[scancode]; |
207 | } |
208 | |
209 | // Return the GLFW window corresponding to the specified X11 window |
210 | // |
211 | static _GLFWwindow* findWindowByHandle(Window handle) |
212 | { |
213 | _GLFWwindow* window; |
214 | |
215 | if (XFindContext(_glfw.x11.display, |
216 | handle, |
217 | _glfw.x11.context, |
218 | (XPointer*) &window) != 0) |
219 | { |
220 | return NULL; |
221 | } |
222 | |
223 | return window; |
224 | } |
225 | |
226 | // Sends an EWMH or ICCCM event to the window manager |
227 | // |
228 | static void sendEventToWM(_GLFWwindow* window, Atom type, |
229 | long a, long b, long c, long d, long e) |
230 | { |
231 | XEvent event; |
232 | memset(&event, 0, sizeof(event)); |
233 | |
234 | event.type = ClientMessage; |
235 | event.xclient.window = window->x11.handle; |
236 | event.xclient.format = 32; // Data is 32-bit longs |
237 | event.xclient.message_type = type; |
238 | event.xclient.data.l[0] = a; |
239 | event.xclient.data.l[1] = b; |
240 | event.xclient.data.l[2] = c; |
241 | event.xclient.data.l[3] = d; |
242 | event.xclient.data.l[4] = e; |
243 | |
244 | XSendEvent(_glfw.x11.display, _glfw.x11.root, |
245 | False, |
246 | SubstructureNotifyMask | SubstructureRedirectMask, |
247 | &event); |
248 | } |
249 | |
250 | // Updates the normal hints according to the window settings |
251 | // |
252 | static void updateNormalHints(_GLFWwindow* window, int width, int height) |
253 | { |
254 | XSizeHints* hints = XAllocSizeHints(); |
255 | |
256 | if (!window->monitor) |
257 | { |
258 | if (window->resizable) |
259 | { |
260 | if (window->minwidth != GLFW_DONT_CARE && |
261 | window->minheight != GLFW_DONT_CARE) |
262 | { |
263 | hints->flags |= PMinSize; |
264 | hints->min_width = window->minwidth; |
265 | hints->min_height = window->minheight; |
266 | } |
267 | |
268 | if (window->maxwidth != GLFW_DONT_CARE && |
269 | window->maxheight != GLFW_DONT_CARE) |
270 | { |
271 | hints->flags |= PMaxSize; |
272 | hints->max_width = window->maxwidth; |
273 | hints->max_height = window->maxheight; |
274 | } |
275 | |
276 | if (window->numer != GLFW_DONT_CARE && |
277 | window->denom != GLFW_DONT_CARE) |
278 | { |
279 | hints->flags |= PAspect; |
280 | hints->min_aspect.x = hints->max_aspect.x = window->numer; |
281 | hints->min_aspect.y = hints->max_aspect.y = window->denom; |
282 | } |
283 | } |
284 | else |
285 | { |
286 | hints->flags |= (PMinSize | PMaxSize); |
287 | hints->min_width = hints->max_width = width; |
288 | hints->min_height = hints->max_height = height; |
289 | } |
290 | } |
291 | |
292 | hints->flags |= PWinGravity; |
293 | hints->win_gravity = StaticGravity; |
294 | |
295 | XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); |
296 | XFree(hints); |
297 | } |
298 | |
299 | // Updates the full screen status of the window |
300 | // |
301 | static void updateWindowMode(_GLFWwindow* window) |
302 | { |
303 | if (window->monitor) |
304 | { |
305 | if (_glfw.x11.xinerama.available && |
306 | _glfw.x11.NET_WM_FULLSCREEN_MONITORS) |
307 | { |
308 | sendEventToWM(window, |
309 | _glfw.x11.NET_WM_FULLSCREEN_MONITORS, |
310 | window->monitor->x11.index, |
311 | window->monitor->x11.index, |
312 | window->monitor->x11.index, |
313 | window->monitor->x11.index, |
314 | 0); |
315 | } |
316 | |
317 | if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_FULLSCREEN) |
318 | { |
319 | sendEventToWM(window, |
320 | _glfw.x11.NET_WM_STATE, |
321 | _NET_WM_STATE_ADD, |
322 | _glfw.x11.NET_WM_STATE_FULLSCREEN, |
323 | 0, 1, 0); |
324 | } |
325 | else |
326 | { |
327 | // This is the butcher's way of removing window decorations |
328 | // Setting the override-redirect attribute on a window makes the |
329 | // window manager ignore the window completely (ICCCM, section 4) |
330 | // The good thing is that this makes undecorated full screen windows |
331 | // easy to do; the bad thing is that we have to do everything |
332 | // manually and some things (like iconify/restore) won't work at |
333 | // all, as those are tasks usually performed by the window manager |
334 | |
335 | XSetWindowAttributes attributes; |
336 | attributes.override_redirect = True; |
337 | XChangeWindowAttributes(_glfw.x11.display, |
338 | window->x11.handle, |
339 | CWOverrideRedirect, |
340 | &attributes); |
341 | |
342 | window->x11.overrideRedirect = GLFW_TRUE; |
343 | } |
344 | |
345 | // Enable compositor bypass |
346 | { |
347 | const unsigned long value = 1; |
348 | |
349 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
350 | _glfw.x11.NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, |
351 | PropModeReplace, (unsigned char*) &value, 1); |
352 | } |
353 | } |
354 | else |
355 | { |
356 | if (_glfw.x11.xinerama.available && |
357 | _glfw.x11.NET_WM_FULLSCREEN_MONITORS) |
358 | { |
359 | XDeleteProperty(_glfw.x11.display, window->x11.handle, |
360 | _glfw.x11.NET_WM_FULLSCREEN_MONITORS); |
361 | } |
362 | |
363 | if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_FULLSCREEN) |
364 | { |
365 | sendEventToWM(window, |
366 | _glfw.x11.NET_WM_STATE, |
367 | _NET_WM_STATE_REMOVE, |
368 | _glfw.x11.NET_WM_STATE_FULLSCREEN, |
369 | 0, 1, 0); |
370 | } |
371 | else |
372 | { |
373 | XSetWindowAttributes attributes; |
374 | attributes.override_redirect = False; |
375 | XChangeWindowAttributes(_glfw.x11.display, |
376 | window->x11.handle, |
377 | CWOverrideRedirect, |
378 | &attributes); |
379 | |
380 | window->x11.overrideRedirect = GLFW_FALSE; |
381 | } |
382 | |
383 | // Disable compositor bypass |
384 | { |
385 | XDeleteProperty(_glfw.x11.display, window->x11.handle, |
386 | _glfw.x11.NET_WM_BYPASS_COMPOSITOR); |
387 | } |
388 | } |
389 | } |
390 | |
391 | // Splits and translates a text/uri-list into separate file paths |
392 | // NOTE: This function destroys the provided string |
393 | // |
394 | static char** parseUriList(char* text, int* count) |
395 | { |
396 | const char* prefix = "file://" ; |
397 | char** paths = NULL; |
398 | char* line; |
399 | |
400 | *count = 0; |
401 | |
402 | while ((line = strtok(text, "\r\n" ))) |
403 | { |
404 | text = NULL; |
405 | |
406 | if (line[0] == '#') |
407 | continue; |
408 | |
409 | if (strncmp(line, prefix, strlen(prefix)) == 0) |
410 | line += strlen(prefix); |
411 | |
412 | (*count)++; |
413 | |
414 | char* path = calloc(strlen(line) + 1, 1); |
415 | paths = realloc(paths, *count * sizeof(char*)); |
416 | paths[*count - 1] = path; |
417 | |
418 | while (*line) |
419 | { |
420 | if (line[0] == '%' && line[1] && line[2]) |
421 | { |
422 | const char digits[3] = { line[1], line[2], '\0' }; |
423 | *path = strtol(digits, NULL, 16); |
424 | line += 2; |
425 | } |
426 | else |
427 | *path = *line; |
428 | |
429 | path++; |
430 | line++; |
431 | } |
432 | } |
433 | |
434 | return paths; |
435 | } |
436 | |
437 | // Centers the cursor over the window client area |
438 | // |
439 | static void centerCursor(_GLFWwindow* window) |
440 | { |
441 | int width, height; |
442 | _glfwPlatformGetWindowSize(window, &width, &height); |
443 | _glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0); |
444 | } |
445 | |
446 | // Updates the cursor image according to its cursor mode |
447 | // |
448 | static void updateCursorImage(_GLFWwindow* window) |
449 | { |
450 | if (window->cursorMode == GLFW_CURSOR_NORMAL) |
451 | { |
452 | if (window->cursor) |
453 | { |
454 | XDefineCursor(_glfw.x11.display, window->x11.handle, |
455 | window->cursor->x11.handle); |
456 | } |
457 | else |
458 | XUndefineCursor(_glfw.x11.display, window->x11.handle); |
459 | } |
460 | else |
461 | XDefineCursor(_glfw.x11.display, window->x11.handle, _glfw.x11.cursor); |
462 | } |
463 | |
464 | // Create the X11 window (and its colormap) |
465 | // |
466 | static GLFWbool createNativeWindow(_GLFWwindow* window, |
467 | const _GLFWwndconfig* wndconfig, |
468 | Visual* visual, int depth) |
469 | { |
470 | // Create a colormap based on the visual used by the current context |
471 | window->x11.colormap = XCreateColormap(_glfw.x11.display, |
472 | _glfw.x11.root, |
473 | visual, |
474 | AllocNone); |
475 | |
476 | // Create the actual window |
477 | { |
478 | XSetWindowAttributes wa; |
479 | const unsigned long wamask = CWBorderPixel | CWColormap | CWEventMask; |
480 | |
481 | wa.colormap = window->x11.colormap; |
482 | wa.border_pixel = 0; |
483 | wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | |
484 | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | |
485 | ExposureMask | FocusChangeMask | VisibilityChangeMask | |
486 | EnterWindowMask | LeaveWindowMask | PropertyChangeMask; |
487 | |
488 | _glfwGrabErrorHandlerX11(); |
489 | |
490 | window->x11.handle = XCreateWindow(_glfw.x11.display, |
491 | _glfw.x11.root, |
492 | 0, 0, |
493 | wndconfig->width, wndconfig->height, |
494 | 0, // Border width |
495 | depth, // Color depth |
496 | InputOutput, |
497 | visual, |
498 | wamask, |
499 | &wa); |
500 | |
501 | _glfwReleaseErrorHandlerX11(); |
502 | |
503 | if (!window->x11.handle) |
504 | { |
505 | _glfwInputErrorX11(GLFW_PLATFORM_ERROR, |
506 | "X11: Failed to create window" ); |
507 | return GLFW_FALSE; |
508 | } |
509 | |
510 | XSaveContext(_glfw.x11.display, |
511 | window->x11.handle, |
512 | _glfw.x11.context, |
513 | (XPointer) window); |
514 | } |
515 | |
516 | if (!wndconfig->decorated) |
517 | { |
518 | struct |
519 | { |
520 | unsigned long flags; |
521 | unsigned long functions; |
522 | unsigned long decorations; |
523 | long input_mode; |
524 | unsigned long status; |
525 | } hints; |
526 | |
527 | hints.flags = 2; // Set decorations |
528 | hints.decorations = 0; // No decorations |
529 | |
530 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
531 | _glfw.x11.MOTIF_WM_HINTS, |
532 | _glfw.x11.MOTIF_WM_HINTS, 32, |
533 | PropModeReplace, |
534 | (unsigned char*) &hints, |
535 | sizeof(hints) / sizeof(long)); |
536 | } |
537 | |
538 | if (_glfw.x11.NET_WM_STATE && !window->monitor) |
539 | { |
540 | Atom states[3]; |
541 | int count = 0; |
542 | |
543 | if (wndconfig->floating) |
544 | { |
545 | if (_glfw.x11.NET_WM_STATE_ABOVE) |
546 | states[count++] = _glfw.x11.NET_WM_STATE_ABOVE; |
547 | } |
548 | |
549 | if (wndconfig->maximized) |
550 | { |
551 | if (_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT && |
552 | _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) |
553 | { |
554 | states[count++] = _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT; |
555 | states[count++] = _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ; |
556 | } |
557 | } |
558 | |
559 | if (count) |
560 | { |
561 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
562 | _glfw.x11.NET_WM_STATE, XA_ATOM, 32, |
563 | PropModeReplace, (unsigned char*) &states, count); |
564 | } |
565 | } |
566 | |
567 | // Declare the WM protocols supported by GLFW |
568 | { |
569 | Atom protocols[] = |
570 | { |
571 | _glfw.x11.WM_DELETE_WINDOW, |
572 | _glfw.x11.NET_WM_PING |
573 | }; |
574 | |
575 | XSetWMProtocols(_glfw.x11.display, window->x11.handle, |
576 | protocols, sizeof(protocols) / sizeof(Atom)); |
577 | } |
578 | |
579 | // Declare our PID |
580 | { |
581 | const pid_t pid = getpid(); |
582 | |
583 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
584 | _glfw.x11.NET_WM_PID, XA_CARDINAL, 32, |
585 | PropModeReplace, |
586 | (unsigned char*) &pid, 1); |
587 | } |
588 | |
589 | if (_glfw.x11.NET_WM_WINDOW_TYPE && _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL) |
590 | { |
591 | Atom type = _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL; |
592 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
593 | _glfw.x11.NET_WM_WINDOW_TYPE, XA_ATOM, 32, |
594 | PropModeReplace, (unsigned char*) &type, 1); |
595 | } |
596 | |
597 | // Set ICCCM WM_HINTS property |
598 | { |
599 | XWMHints* hints = XAllocWMHints(); |
600 | if (!hints) |
601 | { |
602 | _glfwInputError(GLFW_OUT_OF_MEMORY, |
603 | "X11: Failed to allocate WM hints" ); |
604 | return GLFW_FALSE; |
605 | } |
606 | |
607 | hints->flags = StateHint; |
608 | hints->initial_state = NormalState; |
609 | |
610 | XSetWMHints(_glfw.x11.display, window->x11.handle, hints); |
611 | XFree(hints); |
612 | } |
613 | |
614 | updateNormalHints(window, wndconfig->width, wndconfig->height); |
615 | |
616 | // Set ICCCM WM_CLASS property |
617 | // HACK: Until a mechanism for specifying the application name is added, the |
618 | // initial window title is used as the window class name |
619 | if (strlen(wndconfig->title)) |
620 | { |
621 | XClassHint* hint = XAllocClassHint(); |
622 | hint->res_name = (char*) wndconfig->title; |
623 | hint->res_class = (char*) wndconfig->title; |
624 | |
625 | XSetClassHint(_glfw.x11.display, window->x11.handle, hint); |
626 | XFree(hint); |
627 | } |
628 | |
629 | if (_glfw.x11.XdndAware) |
630 | { |
631 | // Announce support for Xdnd (drag and drop) |
632 | const Atom version = 5; |
633 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
634 | _glfw.x11.XdndAware, XA_ATOM, 32, |
635 | PropModeReplace, (unsigned char*) &version, 1); |
636 | } |
637 | |
638 | _glfwPlatformSetWindowTitle(window, wndconfig->title); |
639 | |
640 | if (_glfw.x11.im) |
641 | { |
642 | window->x11.ic = XCreateIC(_glfw.x11.im, |
643 | XNInputStyle, |
644 | XIMPreeditNothing | XIMStatusNothing, |
645 | XNClientWindow, |
646 | window->x11.handle, |
647 | XNFocusWindow, |
648 | window->x11.handle, |
649 | NULL); |
650 | } |
651 | |
652 | _glfwPlatformGetWindowPos(window, &window->x11.xpos, &window->x11.ypos); |
653 | _glfwPlatformGetWindowSize(window, &window->x11.width, &window->x11.height); |
654 | |
655 | return GLFW_TRUE; |
656 | } |
657 | |
658 | // Set the specified property to the selection converted to the requested target |
659 | // |
660 | static Atom writeTargetToProperty(const XSelectionRequestEvent* request) |
661 | { |
662 | int i; |
663 | const Atom formats[] = { _glfw.x11.UTF8_STRING, |
664 | _glfw.x11.COMPOUND_STRING, |
665 | XA_STRING }; |
666 | const int formatCount = sizeof(formats) / sizeof(formats[0]); |
667 | |
668 | if (request->property == None) |
669 | { |
670 | // The requester is a legacy client (ICCCM section 2.2) |
671 | // We don't support legacy clients, so fail here |
672 | return None; |
673 | } |
674 | |
675 | if (request->target == _glfw.x11.TARGETS) |
676 | { |
677 | // The list of supported targets was requested |
678 | |
679 | const Atom targets[] = { _glfw.x11.TARGETS, |
680 | _glfw.x11.MULTIPLE, |
681 | _glfw.x11.UTF8_STRING, |
682 | _glfw.x11.COMPOUND_STRING, |
683 | XA_STRING }; |
684 | |
685 | XChangeProperty(_glfw.x11.display, |
686 | request->requestor, |
687 | request->property, |
688 | XA_ATOM, |
689 | 32, |
690 | PropModeReplace, |
691 | (unsigned char*) targets, |
692 | sizeof(targets) / sizeof(targets[0])); |
693 | |
694 | return request->property; |
695 | } |
696 | |
697 | if (request->target == _glfw.x11.MULTIPLE) |
698 | { |
699 | // Multiple conversions were requested |
700 | |
701 | Atom* targets; |
702 | unsigned long i, count; |
703 | |
704 | count = _glfwGetWindowPropertyX11(request->requestor, |
705 | request->property, |
706 | _glfw.x11.ATOM_PAIR, |
707 | (unsigned char**) &targets); |
708 | |
709 | for (i = 0; i < count; i += 2) |
710 | { |
711 | int j; |
712 | |
713 | for (j = 0; j < formatCount; j++) |
714 | { |
715 | if (targets[i] == formats[j]) |
716 | break; |
717 | } |
718 | |
719 | if (j < formatCount) |
720 | { |
721 | XChangeProperty(_glfw.x11.display, |
722 | request->requestor, |
723 | targets[i + 1], |
724 | targets[i], |
725 | 8, |
726 | PropModeReplace, |
727 | (unsigned char*) _glfw.x11.clipboardString, |
728 | strlen(_glfw.x11.clipboardString)); |
729 | } |
730 | else |
731 | targets[i + 1] = None; |
732 | } |
733 | |
734 | XChangeProperty(_glfw.x11.display, |
735 | request->requestor, |
736 | request->property, |
737 | _glfw.x11.ATOM_PAIR, |
738 | 32, |
739 | PropModeReplace, |
740 | (unsigned char*) targets, |
741 | count); |
742 | |
743 | XFree(targets); |
744 | |
745 | return request->property; |
746 | } |
747 | |
748 | if (request->target == _glfw.x11.SAVE_TARGETS) |
749 | { |
750 | // The request is a check whether we support SAVE_TARGETS |
751 | // It should be handled as a no-op side effect target |
752 | |
753 | XChangeProperty(_glfw.x11.display, |
754 | request->requestor, |
755 | request->property, |
756 | _glfw.x11.NULL_, |
757 | 32, |
758 | PropModeReplace, |
759 | NULL, |
760 | 0); |
761 | |
762 | return request->property; |
763 | } |
764 | |
765 | // Conversion to a data target was requested |
766 | |
767 | for (i = 0; i < formatCount; i++) |
768 | { |
769 | if (request->target == formats[i]) |
770 | { |
771 | // The requested target is one we support |
772 | |
773 | XChangeProperty(_glfw.x11.display, |
774 | request->requestor, |
775 | request->property, |
776 | request->target, |
777 | 8, |
778 | PropModeReplace, |
779 | (unsigned char*) _glfw.x11.clipboardString, |
780 | strlen(_glfw.x11.clipboardString)); |
781 | |
782 | return request->property; |
783 | } |
784 | } |
785 | |
786 | // The requested target is not supported |
787 | |
788 | return None; |
789 | } |
790 | |
791 | static void handleSelectionClear(XEvent* event) |
792 | { |
793 | free(_glfw.x11.clipboardString); |
794 | _glfw.x11.clipboardString = NULL; |
795 | } |
796 | |
797 | static void handleSelectionRequest(XEvent* event) |
798 | { |
799 | const XSelectionRequestEvent* request = &event->xselectionrequest; |
800 | |
801 | XEvent reply; |
802 | memset(&reply, 0, sizeof(reply)); |
803 | |
804 | reply.xselection.property = writeTargetToProperty(request); |
805 | reply.xselection.type = SelectionNotify; |
806 | reply.xselection.display = request->display; |
807 | reply.xselection.requestor = request->requestor; |
808 | reply.xselection.selection = request->selection; |
809 | reply.xselection.target = request->target; |
810 | reply.xselection.time = request->time; |
811 | |
812 | XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply); |
813 | } |
814 | |
815 | static void pushSelectionToManager(_GLFWwindow* window) |
816 | { |
817 | XConvertSelection(_glfw.x11.display, |
818 | _glfw.x11.CLIPBOARD_MANAGER, |
819 | _glfw.x11.SAVE_TARGETS, |
820 | None, |
821 | window->x11.handle, |
822 | CurrentTime); |
823 | |
824 | for (;;) |
825 | { |
826 | XEvent event; |
827 | |
828 | while (XCheckIfEvent(_glfw.x11.display, &event, isSelectionEvent, NULL)) |
829 | { |
830 | switch (event.type) |
831 | { |
832 | case SelectionRequest: |
833 | handleSelectionRequest(&event); |
834 | break; |
835 | |
836 | case SelectionClear: |
837 | handleSelectionClear(&event); |
838 | break; |
839 | |
840 | case SelectionNotify: |
841 | { |
842 | if (event.xselection.target == _glfw.x11.SAVE_TARGETS) |
843 | { |
844 | // This means one of two things; either the selection was |
845 | // not owned, which means there is no clipboard manager, or |
846 | // the transfer to the clipboard manager has completed |
847 | // In either case, it means we are done here |
848 | return; |
849 | } |
850 | |
851 | break; |
852 | } |
853 | } |
854 | } |
855 | |
856 | waitForEvent(NULL); |
857 | } |
858 | } |
859 | |
860 | // Make the specified window and its video mode active on its monitor |
861 | // |
862 | static GLFWbool acquireMonitor(_GLFWwindow* window) |
863 | { |
864 | GLFWbool status; |
865 | |
866 | if (_glfw.x11.saver.count == 0) |
867 | { |
868 | // Remember old screen saver settings |
869 | XGetScreenSaver(_glfw.x11.display, |
870 | &_glfw.x11.saver.timeout, |
871 | &_glfw.x11.saver.interval, |
872 | &_glfw.x11.saver.blanking, |
873 | &_glfw.x11.saver.exposure); |
874 | |
875 | // Disable screen saver |
876 | XSetScreenSaver(_glfw.x11.display, 0, 0, DontPreferBlanking, |
877 | DefaultExposures); |
878 | } |
879 | |
880 | if (!window->monitor->window) |
881 | _glfw.x11.saver.count++; |
882 | |
883 | status = _glfwSetVideoModeX11(window->monitor, &window->videoMode); |
884 | |
885 | if (window->x11.overrideRedirect) |
886 | { |
887 | int xpos, ypos; |
888 | GLFWvidmode mode; |
889 | |
890 | // Manually position the window over its monitor |
891 | _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos); |
892 | _glfwPlatformGetVideoMode(window->monitor, &mode); |
893 | |
894 | XMoveResizeWindow(_glfw.x11.display, window->x11.handle, |
895 | xpos, ypos, mode.width, mode.height); |
896 | } |
897 | |
898 | _glfwInputMonitorWindowChange(window->monitor, window); |
899 | return status; |
900 | } |
901 | |
902 | // Remove the window and restore the original video mode |
903 | // |
904 | static void releaseMonitor(_GLFWwindow* window) |
905 | { |
906 | if (window->monitor->window != window) |
907 | return; |
908 | |
909 | _glfwInputMonitorWindowChange(window->monitor, NULL); |
910 | _glfwRestoreVideoModeX11(window->monitor); |
911 | |
912 | _glfw.x11.saver.count--; |
913 | |
914 | if (_glfw.x11.saver.count == 0) |
915 | { |
916 | // Restore old screen saver settings |
917 | XSetScreenSaver(_glfw.x11.display, |
918 | _glfw.x11.saver.timeout, |
919 | _glfw.x11.saver.interval, |
920 | _glfw.x11.saver.blanking, |
921 | _glfw.x11.saver.exposure); |
922 | } |
923 | } |
924 | |
925 | // Decode a Unicode code point from a UTF-8 stream |
926 | // Based on cutef8 by Jeff Bezanson (Public Domain) |
927 | // |
928 | #if defined(X_HAVE_UTF8_STRING) |
929 | static unsigned int decodeUTF8(const char** s) |
930 | { |
931 | unsigned int ch = 0, count = 0; |
932 | static const unsigned int offsets[] = |
933 | { |
934 | 0x00000000u, 0x00003080u, 0x000e2080u, |
935 | 0x03c82080u, 0xfa082080u, 0x82082080u |
936 | }; |
937 | |
938 | do |
939 | { |
940 | ch = (ch << 6) + (unsigned char) **s; |
941 | (*s)++; |
942 | count++; |
943 | } while ((**s & 0xc0) == 0x80); |
944 | |
945 | assert(count <= 6); |
946 | return ch - offsets[count - 1]; |
947 | } |
948 | #endif /*X_HAVE_UTF8_STRING*/ |
949 | |
950 | // Process the specified X event |
951 | // |
952 | static void processEvent(XEvent *event) |
953 | { |
954 | _GLFWwindow* window = NULL; |
955 | int keycode = 0; |
956 | Bool filtered = False; |
957 | |
958 | // HACK: Save scancode as some IMs clear the field in XFilterEvent |
959 | if (event->type == KeyPress || event->type == KeyRelease) |
960 | keycode = event->xkey.keycode; |
961 | |
962 | if (_glfw.x11.im) |
963 | filtered = XFilterEvent(event, None); |
964 | |
965 | if (_glfw.x11.randr.available) |
966 | { |
967 | if (event->type == _glfw.x11.randr.eventBase + RRNotify) |
968 | { |
969 | XRRUpdateConfiguration(event); |
970 | _glfwInputMonitorChange(); |
971 | return; |
972 | } |
973 | } |
974 | |
975 | if (event->type != GenericEvent) |
976 | { |
977 | window = findWindowByHandle(event->xany.window); |
978 | if (window == NULL) |
979 | { |
980 | // This is an event for a window that has already been destroyed |
981 | return; |
982 | } |
983 | } |
984 | |
985 | switch (event->type) |
986 | { |
987 | case KeyPress: |
988 | { |
989 | const int key = translateKey(keycode); |
990 | const int mods = translateState(event->xkey.state); |
991 | const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); |
992 | |
993 | if (window->x11.ic) |
994 | { |
995 | // HACK: Ignore duplicate key press events generated by ibus |
996 | // Corresponding release events are filtered out by the |
997 | // GLFW key repeat logic |
998 | if (window->x11.lastKeyCode != keycode || |
999 | window->x11.lastKeyTime != event->xkey.time) |
1000 | { |
1001 | if (keycode) |
1002 | _glfwInputKey(window, key, keycode, GLFW_PRESS, mods); |
1003 | } |
1004 | |
1005 | window->x11.lastKeyCode = keycode; |
1006 | window->x11.lastKeyTime = event->xkey.time; |
1007 | |
1008 | if (!filtered) |
1009 | { |
1010 | int count; |
1011 | Status status; |
1012 | #if defined(X_HAVE_UTF8_STRING) |
1013 | char buffer[100]; |
1014 | char* chars = buffer; |
1015 | |
1016 | count = Xutf8LookupString(window->x11.ic, |
1017 | &event->xkey, |
1018 | buffer, sizeof(buffer) - 1, |
1019 | NULL, &status); |
1020 | |
1021 | if (status == XBufferOverflow) |
1022 | { |
1023 | chars = calloc(count + 1, 1); |
1024 | count = Xutf8LookupString(window->x11.ic, |
1025 | &event->xkey, |
1026 | chars, count, |
1027 | NULL, &status); |
1028 | } |
1029 | |
1030 | if (status == XLookupChars || status == XLookupBoth) |
1031 | { |
1032 | const char* c = chars; |
1033 | chars[count] = '\0'; |
1034 | while (c - chars < count) |
1035 | _glfwInputChar(window, decodeUTF8(&c), mods, plain); |
1036 | } |
1037 | #else /*X_HAVE_UTF8_STRING*/ |
1038 | wchar_t buffer[16]; |
1039 | wchar_t* chars = buffer; |
1040 | |
1041 | count = XwcLookupString(window->x11.ic, |
1042 | &event->xkey, |
1043 | buffer, sizeof(buffer) / sizeof(wchar_t), |
1044 | NULL, &status); |
1045 | |
1046 | if (status == XBufferOverflow) |
1047 | { |
1048 | chars = calloc(count, sizeof(wchar_t)); |
1049 | count = XwcLookupString(window->x11.ic, |
1050 | &event->xkey, |
1051 | chars, count, |
1052 | NULL, &status); |
1053 | } |
1054 | |
1055 | if (status == XLookupChars || status == XLookupBoth) |
1056 | { |
1057 | int i; |
1058 | for (i = 0; i < count; i++) |
1059 | _glfwInputChar(window, chars[i], mods, plain); |
1060 | } |
1061 | #endif /*X_HAVE_UTF8_STRING*/ |
1062 | |
1063 | if (chars != buffer) |
1064 | free(chars); |
1065 | } |
1066 | } |
1067 | else |
1068 | { |
1069 | KeySym keysym; |
1070 | XLookupString(&event->xkey, NULL, 0, &keysym, NULL); |
1071 | |
1072 | _glfwInputKey(window, key, keycode, GLFW_PRESS, mods); |
1073 | |
1074 | const long character = _glfwKeySym2Unicode(keysym); |
1075 | if (character != -1) |
1076 | _glfwInputChar(window, character, mods, plain); |
1077 | } |
1078 | |
1079 | return; |
1080 | } |
1081 | |
1082 | case KeyRelease: |
1083 | { |
1084 | const int key = translateKey(keycode); |
1085 | const int mods = translateState(event->xkey.state); |
1086 | |
1087 | if (!_glfw.x11.xkb.detectable) |
1088 | { |
1089 | // HACK: Key repeat events will arrive as KeyRelease/KeyPress |
1090 | // pairs with similar or identical time stamps |
1091 | // The key repeat logic in _glfwInputKey expects only key |
1092 | // presses to repeat, so detect and discard release events |
1093 | if (XEventsQueued(_glfw.x11.display, QueuedAfterReading)) |
1094 | { |
1095 | XEvent next; |
1096 | XPeekEvent(_glfw.x11.display, &next); |
1097 | |
1098 | if (next.type == KeyPress && |
1099 | next.xkey.window == event->xkey.window && |
1100 | next.xkey.keycode == keycode) |
1101 | { |
1102 | // HACK: The time of repeat events sometimes doesn't |
1103 | // match that of the press event, so add an |
1104 | // epsilon |
1105 | // Toshiyuki Takahashi can press a button |
1106 | // 16 times per second so it's fairly safe to |
1107 | // assume that no human is pressing the key 50 |
1108 | // times per second (value is ms) |
1109 | if ((next.xkey.time - event->xkey.time) < 20) |
1110 | { |
1111 | // This is very likely a server-generated key repeat |
1112 | // event, so ignore it |
1113 | return; |
1114 | } |
1115 | } |
1116 | } |
1117 | } |
1118 | |
1119 | _glfwInputKey(window, key, keycode, GLFW_RELEASE, mods); |
1120 | return; |
1121 | } |
1122 | |
1123 | case ButtonPress: |
1124 | { |
1125 | const int mods = translateState(event->xbutton.state); |
1126 | |
1127 | if (event->xbutton.button == Button1) |
1128 | _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, mods); |
1129 | else if (event->xbutton.button == Button2) |
1130 | _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, mods); |
1131 | else if (event->xbutton.button == Button3) |
1132 | _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, mods); |
1133 | |
1134 | // Modern X provides scroll events as mouse button presses |
1135 | else if (event->xbutton.button == Button4) |
1136 | _glfwInputScroll(window, 0.0, 1.0); |
1137 | else if (event->xbutton.button == Button5) |
1138 | _glfwInputScroll(window, 0.0, -1.0); |
1139 | else if (event->xbutton.button == Button6) |
1140 | _glfwInputScroll(window, 1.0, 0.0); |
1141 | else if (event->xbutton.button == Button7) |
1142 | _glfwInputScroll(window, -1.0, 0.0); |
1143 | |
1144 | else |
1145 | { |
1146 | // Additional buttons after 7 are treated as regular buttons |
1147 | // We subtract 4 to fill the gap left by scroll input above |
1148 | _glfwInputMouseClick(window, |
1149 | event->xbutton.button - Button1 - 4, |
1150 | GLFW_PRESS, |
1151 | mods); |
1152 | } |
1153 | |
1154 | return; |
1155 | } |
1156 | |
1157 | case ButtonRelease: |
1158 | { |
1159 | const int mods = translateState(event->xbutton.state); |
1160 | |
1161 | if (event->xbutton.button == Button1) |
1162 | { |
1163 | _glfwInputMouseClick(window, |
1164 | GLFW_MOUSE_BUTTON_LEFT, |
1165 | GLFW_RELEASE, |
1166 | mods); |
1167 | } |
1168 | else if (event->xbutton.button == Button2) |
1169 | { |
1170 | _glfwInputMouseClick(window, |
1171 | GLFW_MOUSE_BUTTON_MIDDLE, |
1172 | GLFW_RELEASE, |
1173 | mods); |
1174 | } |
1175 | else if (event->xbutton.button == Button3) |
1176 | { |
1177 | _glfwInputMouseClick(window, |
1178 | GLFW_MOUSE_BUTTON_RIGHT, |
1179 | GLFW_RELEASE, |
1180 | mods); |
1181 | } |
1182 | else if (event->xbutton.button > Button7) |
1183 | { |
1184 | // Additional buttons after 7 are treated as regular buttons |
1185 | // We subtract 4 to fill the gap left by scroll input above |
1186 | _glfwInputMouseClick(window, |
1187 | event->xbutton.button - Button1 - 4, |
1188 | GLFW_RELEASE, |
1189 | mods); |
1190 | } |
1191 | |
1192 | return; |
1193 | } |
1194 | |
1195 | case EnterNotify: |
1196 | { |
1197 | // HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise |
1198 | // ignore the defined cursor for hidden cursor mode |
1199 | if (window->cursorMode == GLFW_CURSOR_HIDDEN) |
1200 | _glfwPlatformSetCursorMode(window, GLFW_CURSOR_HIDDEN); |
1201 | |
1202 | _glfwInputCursorEnter(window, GLFW_TRUE); |
1203 | return; |
1204 | } |
1205 | |
1206 | case LeaveNotify: |
1207 | { |
1208 | _glfwInputCursorEnter(window, GLFW_FALSE); |
1209 | return; |
1210 | } |
1211 | |
1212 | case MotionNotify: |
1213 | { |
1214 | const int x = event->xmotion.x; |
1215 | const int y = event->xmotion.y; |
1216 | |
1217 | if (x != window->x11.warpCursorPosX || y != window->x11.warpCursorPosY) |
1218 | { |
1219 | // The cursor was moved by something other than GLFW |
1220 | |
1221 | if (window->cursorMode == GLFW_CURSOR_DISABLED) |
1222 | { |
1223 | if (_glfw.x11.disabledCursorWindow != window) |
1224 | return; |
1225 | |
1226 | const int dx = x - window->x11.lastCursorPosX; |
1227 | const int dy = y - window->x11.lastCursorPosY; |
1228 | |
1229 | _glfwInputCursorPos(window, |
1230 | window->virtualCursorPosX + dx, |
1231 | window->virtualCursorPosY + dy); |
1232 | } |
1233 | else |
1234 | _glfwInputCursorPos(window, x, y); |
1235 | } |
1236 | |
1237 | window->x11.lastCursorPosX = x; |
1238 | window->x11.lastCursorPosY = y; |
1239 | return; |
1240 | } |
1241 | |
1242 | case ConfigureNotify: |
1243 | { |
1244 | if (event->xconfigure.width != window->x11.width || |
1245 | event->xconfigure.height != window->x11.height) |
1246 | { |
1247 | _glfwInputFramebufferSize(window, |
1248 | event->xconfigure.width, |
1249 | event->xconfigure.height); |
1250 | |
1251 | _glfwInputWindowSize(window, |
1252 | event->xconfigure.width, |
1253 | event->xconfigure.height); |
1254 | |
1255 | window->x11.width = event->xconfigure.width; |
1256 | window->x11.height = event->xconfigure.height; |
1257 | } |
1258 | |
1259 | if (event->xconfigure.x != window->x11.xpos || |
1260 | event->xconfigure.y != window->x11.ypos) |
1261 | { |
1262 | if (window->x11.overrideRedirect || event->xany.send_event) |
1263 | { |
1264 | _glfwInputWindowPos(window, |
1265 | event->xconfigure.x, |
1266 | event->xconfigure.y); |
1267 | |
1268 | window->x11.xpos = event->xconfigure.x; |
1269 | window->x11.ypos = event->xconfigure.y; |
1270 | } |
1271 | } |
1272 | |
1273 | return; |
1274 | } |
1275 | |
1276 | case ClientMessage: |
1277 | { |
1278 | // Custom client message, probably from the window manager |
1279 | |
1280 | if (filtered) |
1281 | return; |
1282 | |
1283 | if (event->xclient.message_type == None) |
1284 | return; |
1285 | |
1286 | if (event->xclient.message_type == _glfw.x11.WM_PROTOCOLS) |
1287 | { |
1288 | const Atom protocol = event->xclient.data.l[0]; |
1289 | if (protocol == None) |
1290 | return; |
1291 | |
1292 | if (protocol == _glfw.x11.WM_DELETE_WINDOW) |
1293 | { |
1294 | // The window manager was asked to close the window, for example by |
1295 | // the user pressing a 'close' window decoration button |
1296 | _glfwInputWindowCloseRequest(window); |
1297 | } |
1298 | else if (protocol == _glfw.x11.NET_WM_PING) |
1299 | { |
1300 | // The window manager is pinging the application to ensure it's |
1301 | // still responding to events |
1302 | |
1303 | XEvent reply = *event; |
1304 | reply.xclient.window = _glfw.x11.root; |
1305 | |
1306 | XSendEvent(_glfw.x11.display, _glfw.x11.root, |
1307 | False, |
1308 | SubstructureNotifyMask | SubstructureRedirectMask, |
1309 | &reply); |
1310 | } |
1311 | } |
1312 | else if (event->xclient.message_type == _glfw.x11.XdndEnter) |
1313 | { |
1314 | // A drag operation has entered the window |
1315 | // TODO: Check if UTF-8 string is supported by the source |
1316 | } |
1317 | else if (event->xclient.message_type == _glfw.x11.XdndDrop) |
1318 | { |
1319 | // The drag operation has finished dropping on |
1320 | // the window, ask to convert it to a UTF-8 string |
1321 | _glfw.x11.xdnd.source = event->xclient.data.l[0]; |
1322 | XConvertSelection(_glfw.x11.display, |
1323 | _glfw.x11.XdndSelection, |
1324 | _glfw.x11.UTF8_STRING, |
1325 | _glfw.x11.XdndSelection, |
1326 | window->x11.handle, CurrentTime); |
1327 | } |
1328 | else if (event->xclient.message_type == _glfw.x11.XdndPosition) |
1329 | { |
1330 | // The drag operation has moved over the window |
1331 | const int absX = (event->xclient.data.l[2] >> 16) & 0xFFFF; |
1332 | const int absY = (event->xclient.data.l[2]) & 0xFFFF; |
1333 | int x, y; |
1334 | |
1335 | _glfwPlatformGetWindowPos(window, &x, &y); |
1336 | _glfwInputCursorPos(window, absX - x, absY - y); |
1337 | |
1338 | // Reply that we are ready to copy the dragged data |
1339 | XEvent reply; |
1340 | memset(&reply, 0, sizeof(reply)); |
1341 | |
1342 | reply.type = ClientMessage; |
1343 | reply.xclient.window = event->xclient.data.l[0]; |
1344 | reply.xclient.message_type = _glfw.x11.XdndStatus; |
1345 | reply.xclient.format = 32; |
1346 | reply.xclient.data.l[0] = window->x11.handle; |
1347 | reply.xclient.data.l[1] = 1; // Always accept the dnd with no rectangle |
1348 | reply.xclient.data.l[2] = 0; // Specify an empty rectangle |
1349 | reply.xclient.data.l[3] = 0; |
1350 | reply.xclient.data.l[4] = _glfw.x11.XdndActionCopy; |
1351 | |
1352 | XSendEvent(_glfw.x11.display, event->xclient.data.l[0], |
1353 | False, NoEventMask, &reply); |
1354 | XFlush(_glfw.x11.display); |
1355 | } |
1356 | |
1357 | return; |
1358 | } |
1359 | |
1360 | case SelectionNotify: |
1361 | { |
1362 | if (event->xselection.property) |
1363 | { |
1364 | // The converted data from the drag operation has arrived |
1365 | char* data; |
1366 | const int result = |
1367 | _glfwGetWindowPropertyX11(event->xselection.requestor, |
1368 | event->xselection.property, |
1369 | event->xselection.target, |
1370 | (unsigned char**) &data); |
1371 | |
1372 | if (result) |
1373 | { |
1374 | int i, count; |
1375 | char** paths = parseUriList(data, &count); |
1376 | |
1377 | _glfwInputDrop(window, count, (const char**) paths); |
1378 | |
1379 | for (i = 0; i < count; i++) |
1380 | free(paths[i]); |
1381 | free(paths); |
1382 | } |
1383 | |
1384 | XFree(data); |
1385 | |
1386 | XEvent reply; |
1387 | memset(&reply, 0, sizeof(reply)); |
1388 | |
1389 | reply.type = ClientMessage; |
1390 | reply.xclient.window = _glfw.x11.xdnd.source; |
1391 | reply.xclient.message_type = _glfw.x11.XdndFinished; |
1392 | reply.xclient.format = 32; |
1393 | reply.xclient.data.l[0] = window->x11.handle; |
1394 | reply.xclient.data.l[1] = result; |
1395 | reply.xclient.data.l[2] = _glfw.x11.XdndActionCopy; |
1396 | |
1397 | // Reply that all is well |
1398 | XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, |
1399 | False, NoEventMask, &reply); |
1400 | XFlush(_glfw.x11.display); |
1401 | } |
1402 | |
1403 | return; |
1404 | } |
1405 | |
1406 | case FocusIn: |
1407 | { |
1408 | if (window->cursorMode == GLFW_CURSOR_DISABLED) |
1409 | _glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED); |
1410 | |
1411 | if (event->xfocus.mode == NotifyGrab || |
1412 | event->xfocus.mode == NotifyUngrab) |
1413 | { |
1414 | // Ignore focus events from popup indicator windows, window menu |
1415 | // key chords and window dragging |
1416 | return; |
1417 | } |
1418 | |
1419 | if (window->x11.ic) |
1420 | XSetICFocus(window->x11.ic); |
1421 | |
1422 | _glfwInputWindowFocus(window, GLFW_TRUE); |
1423 | return; |
1424 | } |
1425 | |
1426 | case FocusOut: |
1427 | { |
1428 | if (window->cursorMode == GLFW_CURSOR_DISABLED) |
1429 | _glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL); |
1430 | |
1431 | if (event->xfocus.mode == NotifyGrab || |
1432 | event->xfocus.mode == NotifyUngrab) |
1433 | { |
1434 | // Ignore focus events from popup indicator windows, window menu |
1435 | // key chords and window dragging |
1436 | return; |
1437 | } |
1438 | |
1439 | if (window->x11.ic) |
1440 | XUnsetICFocus(window->x11.ic); |
1441 | |
1442 | if (window->monitor && window->autoIconify) |
1443 | _glfwPlatformIconifyWindow(window); |
1444 | |
1445 | _glfwInputWindowFocus(window, GLFW_FALSE); |
1446 | return; |
1447 | } |
1448 | |
1449 | case Expose: |
1450 | { |
1451 | _glfwInputWindowDamage(window); |
1452 | return; |
1453 | } |
1454 | |
1455 | case PropertyNotify: |
1456 | { |
1457 | if (event->xproperty.atom == _glfw.x11.WM_STATE && |
1458 | event->xproperty.state == PropertyNewValue) |
1459 | { |
1460 | const int state = getWindowState(window); |
1461 | if (state == IconicState) |
1462 | { |
1463 | if (window->monitor) |
1464 | releaseMonitor(window); |
1465 | |
1466 | _glfwInputWindowIconify(window, GLFW_TRUE); |
1467 | } |
1468 | else if (state == NormalState) |
1469 | { |
1470 | if (window->monitor) |
1471 | acquireMonitor(window); |
1472 | |
1473 | _glfwInputWindowIconify(window, GLFW_FALSE); |
1474 | } |
1475 | } |
1476 | |
1477 | return; |
1478 | } |
1479 | |
1480 | case SelectionClear: |
1481 | { |
1482 | handleSelectionClear(event); |
1483 | return; |
1484 | } |
1485 | |
1486 | case SelectionRequest: |
1487 | { |
1488 | handleSelectionRequest(event); |
1489 | return; |
1490 | } |
1491 | |
1492 | case DestroyNotify: |
1493 | return; |
1494 | } |
1495 | } |
1496 | |
1497 | |
1498 | ////////////////////////////////////////////////////////////////////////// |
1499 | ////// GLFW internal API ////// |
1500 | ////////////////////////////////////////////////////////////////////////// |
1501 | |
1502 | // Retrieve a single window property of the specified type |
1503 | // Inspired by fghGetWindowProperty from freeglut |
1504 | // |
1505 | unsigned long _glfwGetWindowPropertyX11(Window window, |
1506 | Atom property, |
1507 | Atom type, |
1508 | unsigned char** value) |
1509 | { |
1510 | Atom actualType; |
1511 | int actualFormat; |
1512 | unsigned long itemCount, bytesAfter; |
1513 | |
1514 | XGetWindowProperty(_glfw.x11.display, |
1515 | window, |
1516 | property, |
1517 | 0, |
1518 | LONG_MAX, |
1519 | False, |
1520 | type, |
1521 | &actualType, |
1522 | &actualFormat, |
1523 | &itemCount, |
1524 | &bytesAfter, |
1525 | value); |
1526 | |
1527 | if (type != AnyPropertyType && actualType != type) |
1528 | return 0; |
1529 | |
1530 | return itemCount; |
1531 | } |
1532 | |
1533 | |
1534 | ////////////////////////////////////////////////////////////////////////// |
1535 | ////// GLFW platform API ////// |
1536 | ////////////////////////////////////////////////////////////////////////// |
1537 | |
1538 | int _glfwPlatformCreateWindow(_GLFWwindow* window, |
1539 | const _GLFWwndconfig* wndconfig, |
1540 | const _GLFWctxconfig* ctxconfig, |
1541 | const _GLFWfbconfig* fbconfig) |
1542 | { |
1543 | Visual* visual; |
1544 | int depth; |
1545 | |
1546 | if (ctxconfig->client == GLFW_NO_API) |
1547 | { |
1548 | visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen); |
1549 | depth = DefaultDepth(_glfw.x11.display, _glfw.x11.screen); |
1550 | } |
1551 | else |
1552 | { |
1553 | if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) |
1554 | { |
1555 | if (!_glfwInitGLX()) |
1556 | return GLFW_FALSE; |
1557 | if (!_glfwChooseVisualGLX(ctxconfig, fbconfig, &visual, &depth)) |
1558 | return GLFW_FALSE; |
1559 | } |
1560 | else |
1561 | { |
1562 | if (!_glfwInitEGL()) |
1563 | return GLFW_FALSE; |
1564 | if (!_glfwChooseVisualEGL(ctxconfig, fbconfig, &visual, &depth)) |
1565 | return GLFW_FALSE; |
1566 | } |
1567 | } |
1568 | |
1569 | if (!createNativeWindow(window, wndconfig, visual, depth)) |
1570 | return GLFW_FALSE; |
1571 | |
1572 | if (ctxconfig->client != GLFW_NO_API) |
1573 | { |
1574 | if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) |
1575 | { |
1576 | if (!_glfwCreateContextGLX(window, ctxconfig, fbconfig)) |
1577 | return GLFW_FALSE; |
1578 | } |
1579 | else |
1580 | { |
1581 | if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) |
1582 | return GLFW_FALSE; |
1583 | } |
1584 | } |
1585 | |
1586 | if (window->monitor) |
1587 | { |
1588 | _glfwPlatformShowWindow(window); |
1589 | updateWindowMode(window); |
1590 | if (!acquireMonitor(window)) |
1591 | return GLFW_FALSE; |
1592 | |
1593 | centerCursor(window); |
1594 | } |
1595 | |
1596 | XFlush(_glfw.x11.display); |
1597 | return GLFW_TRUE; |
1598 | } |
1599 | |
1600 | void _glfwPlatformDestroyWindow(_GLFWwindow* window) |
1601 | { |
1602 | if (_glfw.x11.disabledCursorWindow == window) |
1603 | _glfw.x11.disabledCursorWindow = NULL; |
1604 | |
1605 | if (window->monitor) |
1606 | releaseMonitor(window); |
1607 | |
1608 | if (window->x11.ic) |
1609 | { |
1610 | XDestroyIC(window->x11.ic); |
1611 | window->x11.ic = NULL; |
1612 | } |
1613 | |
1614 | if (window->context.destroy) |
1615 | window->context.destroy(window); |
1616 | |
1617 | if (window->x11.handle) |
1618 | { |
1619 | if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) == |
1620 | window->x11.handle) |
1621 | { |
1622 | pushSelectionToManager(window); |
1623 | } |
1624 | |
1625 | XDeleteContext(_glfw.x11.display, window->x11.handle, _glfw.x11.context); |
1626 | XUnmapWindow(_glfw.x11.display, window->x11.handle); |
1627 | XDestroyWindow(_glfw.x11.display, window->x11.handle); |
1628 | window->x11.handle = (Window) 0; |
1629 | } |
1630 | |
1631 | if (window->x11.colormap) |
1632 | { |
1633 | XFreeColormap(_glfw.x11.display, window->x11.colormap); |
1634 | window->x11.colormap = (Colormap) 0; |
1635 | } |
1636 | |
1637 | XFlush(_glfw.x11.display); |
1638 | } |
1639 | |
1640 | void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) |
1641 | { |
1642 | #if defined(X_HAVE_UTF8_STRING) |
1643 | Xutf8SetWMProperties(_glfw.x11.display, |
1644 | window->x11.handle, |
1645 | title, title, |
1646 | NULL, 0, |
1647 | NULL, NULL, NULL); |
1648 | #else |
1649 | // This may be a slightly better fallback than using XStoreName and |
1650 | // XSetIconName, which always store their arguments using STRING |
1651 | XmbSetWMProperties(_glfw.x11.display, |
1652 | window->x11.handle, |
1653 | title, title, |
1654 | NULL, 0, |
1655 | NULL, NULL, NULL); |
1656 | #endif |
1657 | |
1658 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
1659 | _glfw.x11.NET_WM_NAME, _glfw.x11.UTF8_STRING, 8, |
1660 | PropModeReplace, |
1661 | (unsigned char*) title, strlen(title)); |
1662 | |
1663 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
1664 | _glfw.x11.NET_WM_ICON_NAME, _glfw.x11.UTF8_STRING, 8, |
1665 | PropModeReplace, |
1666 | (unsigned char*) title, strlen(title)); |
1667 | |
1668 | XFlush(_glfw.x11.display); |
1669 | } |
1670 | |
1671 | void _glfwPlatformSetWindowIcon(_GLFWwindow* window, |
1672 | int count, const GLFWimage* images) |
1673 | { |
1674 | if (count) |
1675 | { |
1676 | int i, j, longCount = 0; |
1677 | |
1678 | for (i = 0; i < count; i++) |
1679 | longCount += 2 + images[i].width * images[i].height; |
1680 | |
1681 | long* icon = calloc(longCount, sizeof(long)); |
1682 | long* target = icon; |
1683 | |
1684 | for (i = 0; i < count; i++) |
1685 | { |
1686 | *target++ = images[i].width; |
1687 | *target++ = images[i].height; |
1688 | |
1689 | for (j = 0; j < images[i].width * images[i].height; j++) |
1690 | { |
1691 | *target++ = (images[i].pixels[j * 4 + 0] << 16) | |
1692 | (images[i].pixels[j * 4 + 1] << 8) | |
1693 | (images[i].pixels[j * 4 + 2] << 0) | |
1694 | (images[i].pixels[j * 4 + 3] << 24); |
1695 | } |
1696 | } |
1697 | |
1698 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
1699 | _glfw.x11.NET_WM_ICON, |
1700 | XA_CARDINAL, 32, |
1701 | PropModeReplace, |
1702 | (unsigned char*) icon, |
1703 | longCount); |
1704 | |
1705 | free(icon); |
1706 | } |
1707 | else |
1708 | { |
1709 | XDeleteProperty(_glfw.x11.display, window->x11.handle, |
1710 | _glfw.x11.NET_WM_ICON); |
1711 | } |
1712 | |
1713 | XFlush(_glfw.x11.display); |
1714 | } |
1715 | |
1716 | void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) |
1717 | { |
1718 | Window dummy; |
1719 | int x, y; |
1720 | |
1721 | XTranslateCoordinates(_glfw.x11.display, window->x11.handle, _glfw.x11.root, |
1722 | 0, 0, &x, &y, &dummy); |
1723 | |
1724 | if (xpos) |
1725 | *xpos = x; |
1726 | if (ypos) |
1727 | *ypos = y; |
1728 | } |
1729 | |
1730 | void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos) |
1731 | { |
1732 | // HACK: Explicitly setting PPosition to any value causes some WMs, notably |
1733 | // Compiz and Metacity, to honor the position of unmapped windows |
1734 | if (!_glfwPlatformWindowVisible(window)) |
1735 | { |
1736 | long supplied; |
1737 | XSizeHints* hints = XAllocSizeHints(); |
1738 | |
1739 | if (XGetWMNormalHints(_glfw.x11.display, window->x11.handle, hints, &supplied)) |
1740 | { |
1741 | hints->flags |= PPosition; |
1742 | hints->x = hints->y = 0; |
1743 | |
1744 | XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); |
1745 | } |
1746 | |
1747 | XFree(hints); |
1748 | } |
1749 | |
1750 | XMoveWindow(_glfw.x11.display, window->x11.handle, xpos, ypos); |
1751 | XFlush(_glfw.x11.display); |
1752 | } |
1753 | |
1754 | void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) |
1755 | { |
1756 | XWindowAttributes attribs; |
1757 | XGetWindowAttributes(_glfw.x11.display, window->x11.handle, &attribs); |
1758 | |
1759 | if (width) |
1760 | *width = attribs.width; |
1761 | if (height) |
1762 | *height = attribs.height; |
1763 | } |
1764 | |
1765 | void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) |
1766 | { |
1767 | if (window->monitor) |
1768 | { |
1769 | if (window->monitor->window == window) |
1770 | acquireMonitor(window); |
1771 | } |
1772 | else |
1773 | { |
1774 | if (!window->resizable) |
1775 | updateNormalHints(window, width, height); |
1776 | |
1777 | XResizeWindow(_glfw.x11.display, window->x11.handle, width, height); |
1778 | } |
1779 | |
1780 | XFlush(_glfw.x11.display); |
1781 | } |
1782 | |
1783 | void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, |
1784 | int minwidth, int minheight, |
1785 | int maxwidth, int maxheight) |
1786 | { |
1787 | int width, height; |
1788 | _glfwPlatformGetWindowSize(window, &width, &height); |
1789 | updateNormalHints(window, width, height); |
1790 | XFlush(_glfw.x11.display); |
1791 | } |
1792 | |
1793 | void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) |
1794 | { |
1795 | int width, height; |
1796 | _glfwPlatformGetWindowSize(window, &width, &height); |
1797 | updateNormalHints(window, width, height); |
1798 | XFlush(_glfw.x11.display); |
1799 | } |
1800 | |
1801 | void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) |
1802 | { |
1803 | _glfwPlatformGetWindowSize(window, width, height); |
1804 | } |
1805 | |
1806 | void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, |
1807 | int* left, int* top, |
1808 | int* right, int* bottom) |
1809 | { |
1810 | long* extents = NULL; |
1811 | |
1812 | if (window->monitor || !window->decorated) |
1813 | return; |
1814 | |
1815 | if (_glfw.x11.NET_FRAME_EXTENTS == None) |
1816 | return; |
1817 | |
1818 | if (!_glfwPlatformWindowVisible(window) && |
1819 | _glfw.x11.NET_REQUEST_FRAME_EXTENTS) |
1820 | { |
1821 | XEvent event; |
1822 | double timeout = 0.5; |
1823 | |
1824 | // Ensure _NET_FRAME_EXTENTS is set, allowing glfwGetWindowFrameSize to |
1825 | // function before the window is mapped |
1826 | sendEventToWM(window, _glfw.x11.NET_REQUEST_FRAME_EXTENTS, |
1827 | 0, 0, 0, 0, 0); |
1828 | |
1829 | // HACK: Use a timeout because earlier versions of some window managers |
1830 | // (at least Unity, Fluxbox and Xfwm) failed to send the reply |
1831 | // They have been fixed but broken versions are still in the wild |
1832 | // If you are affected by this and your window manager is NOT |
1833 | // listed above, PLEASE report it to their and our issue trackers |
1834 | while (!XCheckIfEvent(_glfw.x11.display, |
1835 | &event, |
1836 | isFrameExtentsEvent, |
1837 | (XPointer) window)) |
1838 | { |
1839 | if (!waitForEvent(&timeout)) |
1840 | { |
1841 | _glfwInputError(GLFW_PLATFORM_ERROR, |
1842 | "X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue" ); |
1843 | return; |
1844 | } |
1845 | } |
1846 | } |
1847 | |
1848 | if (_glfwGetWindowPropertyX11(window->x11.handle, |
1849 | _glfw.x11.NET_FRAME_EXTENTS, |
1850 | XA_CARDINAL, |
1851 | (unsigned char**) &extents) == 4) |
1852 | { |
1853 | if (left) |
1854 | *left = extents[0]; |
1855 | if (top) |
1856 | *top = extents[2]; |
1857 | if (right) |
1858 | *right = extents[1]; |
1859 | if (bottom) |
1860 | *bottom = extents[3]; |
1861 | } |
1862 | |
1863 | if (extents) |
1864 | XFree(extents); |
1865 | } |
1866 | |
1867 | void _glfwPlatformIconifyWindow(_GLFWwindow* window) |
1868 | { |
1869 | if (window->x11.overrideRedirect) |
1870 | { |
1871 | // Override-redirect windows cannot be iconified or restored, as those |
1872 | // tasks are performed by the window manager |
1873 | _glfwInputError(GLFW_PLATFORM_ERROR, |
1874 | "X11: Iconification of full screen windows requires a WM that supports EWMH full screen" ); |
1875 | return; |
1876 | } |
1877 | |
1878 | XIconifyWindow(_glfw.x11.display, window->x11.handle, _glfw.x11.screen); |
1879 | XFlush(_glfw.x11.display); |
1880 | } |
1881 | |
1882 | void _glfwPlatformRestoreWindow(_GLFWwindow* window) |
1883 | { |
1884 | if (window->x11.overrideRedirect) |
1885 | { |
1886 | // Override-redirect windows cannot be iconified or restored, as those |
1887 | // tasks are performed by the window manager |
1888 | _glfwInputError(GLFW_PLATFORM_ERROR, |
1889 | "X11: Iconification of full screen windows requires a WM that supports EWMH full screen" ); |
1890 | return; |
1891 | } |
1892 | |
1893 | if (_glfwPlatformWindowIconified(window)) |
1894 | { |
1895 | XMapWindow(_glfw.x11.display, window->x11.handle); |
1896 | waitForVisibilityNotify(window); |
1897 | } |
1898 | else if (_glfwPlatformWindowVisible(window)) |
1899 | { |
1900 | if (_glfw.x11.NET_WM_STATE && |
1901 | _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT && |
1902 | _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) |
1903 | { |
1904 | sendEventToWM(window, |
1905 | _glfw.x11.NET_WM_STATE, |
1906 | _NET_WM_STATE_REMOVE, |
1907 | _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT, |
1908 | _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ, |
1909 | 1, 0); |
1910 | } |
1911 | } |
1912 | |
1913 | XFlush(_glfw.x11.display); |
1914 | } |
1915 | |
1916 | void _glfwPlatformMaximizeWindow(_GLFWwindow* window) |
1917 | { |
1918 | if (_glfw.x11.NET_WM_STATE && |
1919 | _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT && |
1920 | _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) |
1921 | { |
1922 | sendEventToWM(window, |
1923 | _glfw.x11.NET_WM_STATE, |
1924 | _NET_WM_STATE_ADD, |
1925 | _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT, |
1926 | _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ, |
1927 | 1, 0); |
1928 | XFlush(_glfw.x11.display); |
1929 | } |
1930 | } |
1931 | |
1932 | void _glfwPlatformShowWindow(_GLFWwindow* window) |
1933 | { |
1934 | if (_glfwPlatformWindowVisible(window)) |
1935 | return; |
1936 | |
1937 | XMapWindow(_glfw.x11.display, window->x11.handle); |
1938 | waitForVisibilityNotify(window); |
1939 | } |
1940 | |
1941 | void _glfwPlatformHideWindow(_GLFWwindow* window) |
1942 | { |
1943 | XUnmapWindow(_glfw.x11.display, window->x11.handle); |
1944 | XFlush(_glfw.x11.display); |
1945 | } |
1946 | |
1947 | void _glfwPlatformFocusWindow(_GLFWwindow* window) |
1948 | { |
1949 | if (_glfw.x11.NET_ACTIVE_WINDOW) |
1950 | sendEventToWM(window, _glfw.x11.NET_ACTIVE_WINDOW, 1, 0, 0, 0, 0); |
1951 | else |
1952 | { |
1953 | XRaiseWindow(_glfw.x11.display, window->x11.handle); |
1954 | XSetInputFocus(_glfw.x11.display, window->x11.handle, |
1955 | RevertToParent, CurrentTime); |
1956 | } |
1957 | |
1958 | XFlush(_glfw.x11.display); |
1959 | } |
1960 | |
1961 | void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, |
1962 | _GLFWmonitor* monitor, |
1963 | int xpos, int ypos, |
1964 | int width, int height, |
1965 | int refreshRate) |
1966 | { |
1967 | if (window->monitor == monitor) |
1968 | { |
1969 | if (monitor) |
1970 | { |
1971 | if (monitor->window == window) |
1972 | acquireMonitor(window); |
1973 | } |
1974 | else |
1975 | { |
1976 | XMoveResizeWindow(_glfw.x11.display, window->x11.handle, |
1977 | xpos, ypos, width, height); |
1978 | } |
1979 | |
1980 | return; |
1981 | } |
1982 | |
1983 | if (window->monitor) |
1984 | releaseMonitor(window); |
1985 | |
1986 | _glfwInputWindowMonitorChange(window, monitor); |
1987 | updateNormalHints(window, width, height); |
1988 | updateWindowMode(window); |
1989 | |
1990 | if (window->monitor) |
1991 | { |
1992 | XMapRaised(_glfw.x11.display, window->x11.handle); |
1993 | if (waitForVisibilityNotify(window)) |
1994 | acquireMonitor(window); |
1995 | } |
1996 | else |
1997 | { |
1998 | XMoveResizeWindow(_glfw.x11.display, window->x11.handle, |
1999 | xpos, ypos, width, height); |
2000 | } |
2001 | |
2002 | XFlush(_glfw.x11.display); |
2003 | } |
2004 | |
2005 | int _glfwPlatformWindowFocused(_GLFWwindow* window) |
2006 | { |
2007 | Window focused; |
2008 | int state; |
2009 | |
2010 | XGetInputFocus(_glfw.x11.display, &focused, &state); |
2011 | return window->x11.handle == focused; |
2012 | } |
2013 | |
2014 | int _glfwPlatformWindowIconified(_GLFWwindow* window) |
2015 | { |
2016 | return getWindowState(window) == IconicState; |
2017 | } |
2018 | |
2019 | int _glfwPlatformWindowVisible(_GLFWwindow* window) |
2020 | { |
2021 | XWindowAttributes wa; |
2022 | XGetWindowAttributes(_glfw.x11.display, window->x11.handle, &wa); |
2023 | return wa.map_state == IsViewable; |
2024 | } |
2025 | |
2026 | int _glfwPlatformWindowMaximized(_GLFWwindow* window) |
2027 | { |
2028 | Atom* states; |
2029 | unsigned long i; |
2030 | GLFWbool maximized = GLFW_FALSE; |
2031 | const unsigned long count = |
2032 | _glfwGetWindowPropertyX11(window->x11.handle, |
2033 | _glfw.x11.NET_WM_STATE, |
2034 | XA_ATOM, |
2035 | (unsigned char**) &states); |
2036 | |
2037 | for (i = 0; i < count; i++) |
2038 | { |
2039 | if (states[i] == _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT || |
2040 | states[i] == _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) |
2041 | { |
2042 | maximized = GLFW_TRUE; |
2043 | break; |
2044 | } |
2045 | } |
2046 | |
2047 | XFree(states); |
2048 | return maximized; |
2049 | } |
2050 | |
2051 | void _glfwPlatformPollEvents(void) |
2052 | { |
2053 | _glfwPollJoystickEvents(); |
2054 | |
2055 | int count = XPending(_glfw.x11.display); |
2056 | while (count--) |
2057 | { |
2058 | XEvent event; |
2059 | XNextEvent(_glfw.x11.display, &event); |
2060 | processEvent(&event); |
2061 | } |
2062 | |
2063 | if (_glfw.x11.disabledCursorWindow) |
2064 | centerCursor(_glfw.x11.disabledCursorWindow); |
2065 | |
2066 | XFlush(_glfw.x11.display); |
2067 | } |
2068 | |
2069 | void _glfwPlatformWaitEvents(void) |
2070 | { |
2071 | while (!XPending(_glfw.x11.display)) |
2072 | waitForEvent(NULL); |
2073 | |
2074 | _glfwPlatformPollEvents(); |
2075 | } |
2076 | |
2077 | void _glfwPlatformWaitEventsTimeout(double timeout) |
2078 | { |
2079 | while (!XPending(_glfw.x11.display)) |
2080 | { |
2081 | if (!waitForEvent(&timeout)) |
2082 | break; |
2083 | } |
2084 | |
2085 | _glfwPlatformPollEvents(); |
2086 | } |
2087 | |
2088 | void _glfwPlatformPostEmptyEvent(void) |
2089 | { |
2090 | XEvent event; |
2091 | _GLFWwindow* window = _glfw.windowListHead; |
2092 | |
2093 | memset(&event, 0, sizeof(event)); |
2094 | event.type = ClientMessage; |
2095 | event.xclient.window = window->x11.handle; |
2096 | event.xclient.format = 32; // Data is 32-bit longs |
2097 | event.xclient.message_type = _glfw.x11.NULL_; |
2098 | |
2099 | XSendEvent(_glfw.x11.display, window->x11.handle, False, 0, &event); |
2100 | XFlush(_glfw.x11.display); |
2101 | } |
2102 | |
2103 | void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) |
2104 | { |
2105 | Window root, child; |
2106 | int rootX, rootY, childX, childY; |
2107 | unsigned int mask; |
2108 | |
2109 | XQueryPointer(_glfw.x11.display, window->x11.handle, |
2110 | &root, &child, |
2111 | &rootX, &rootY, &childX, &childY, |
2112 | &mask); |
2113 | |
2114 | if (xpos) |
2115 | *xpos = childX; |
2116 | if (ypos) |
2117 | *ypos = childY; |
2118 | } |
2119 | |
2120 | void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) |
2121 | { |
2122 | // Store the new position so it can be recognized later |
2123 | window->x11.warpCursorPosX = (int) x; |
2124 | window->x11.warpCursorPosY = (int) y; |
2125 | |
2126 | XWarpPointer(_glfw.x11.display, None, window->x11.handle, |
2127 | 0,0,0,0, (int) x, (int) y); |
2128 | XFlush(_glfw.x11.display); |
2129 | } |
2130 | |
2131 | void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) |
2132 | { |
2133 | if (mode == GLFW_CURSOR_DISABLED) |
2134 | { |
2135 | _glfw.x11.disabledCursorWindow = window; |
2136 | _glfwPlatformGetCursorPos(window, |
2137 | &_glfw.x11.restoreCursorPosX, |
2138 | &_glfw.x11.restoreCursorPosY); |
2139 | centerCursor(window); |
2140 | XGrabPointer(_glfw.x11.display, window->x11.handle, True, |
2141 | ButtonPressMask | ButtonReleaseMask | PointerMotionMask, |
2142 | GrabModeAsync, GrabModeAsync, |
2143 | window->x11.handle, _glfw.x11.cursor, CurrentTime); |
2144 | } |
2145 | else if (_glfw.x11.disabledCursorWindow == window) |
2146 | { |
2147 | _glfw.x11.disabledCursorWindow = NULL; |
2148 | XUngrabPointer(_glfw.x11.display, CurrentTime); |
2149 | _glfwPlatformSetCursorPos(window, |
2150 | _glfw.x11.restoreCursorPosX, |
2151 | _glfw.x11.restoreCursorPosY); |
2152 | } |
2153 | |
2154 | updateCursorImage(window); |
2155 | XFlush(_glfw.x11.display); |
2156 | } |
2157 | |
2158 | const char* _glfwPlatformGetKeyName(int key, int scancode) |
2159 | { |
2160 | KeySym keysym; |
2161 | int ; |
2162 | |
2163 | if (!_glfw.x11.xkb.available) |
2164 | return NULL; |
2165 | |
2166 | if (key != GLFW_KEY_UNKNOWN) |
2167 | scancode = _glfw.x11.nativeKeys[key]; |
2168 | |
2169 | if (!_glfwIsPrintable(_glfw.x11.publicKeys[scancode])) |
2170 | return NULL; |
2171 | |
2172 | keysym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 0); |
2173 | if (keysym == NoSymbol) |
2174 | return NULL; |
2175 | |
2176 | XkbTranslateKeySym(_glfw.x11.display, &keysym, 0, |
2177 | _glfw.x11.keyName, sizeof(_glfw.x11.keyName), |
2178 | &extra); |
2179 | |
2180 | if (!strlen(_glfw.x11.keyName)) |
2181 | return NULL; |
2182 | |
2183 | return _glfw.x11.keyName; |
2184 | } |
2185 | |
2186 | int _glfwPlatformCreateCursor(_GLFWcursor* cursor, |
2187 | const GLFWimage* image, |
2188 | int xhot, int yhot) |
2189 | { |
2190 | cursor->x11.handle = _glfwCreateCursorX11(image, xhot, yhot); |
2191 | if (!cursor->x11.handle) |
2192 | return GLFW_FALSE; |
2193 | |
2194 | return GLFW_TRUE; |
2195 | } |
2196 | |
2197 | int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) |
2198 | { |
2199 | cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, |
2200 | translateCursorShape(shape)); |
2201 | if (!cursor->x11.handle) |
2202 | { |
2203 | _glfwInputError(GLFW_PLATFORM_ERROR, |
2204 | "X11: Failed to create standard cursor" ); |
2205 | return GLFW_FALSE; |
2206 | } |
2207 | |
2208 | return GLFW_TRUE; |
2209 | } |
2210 | |
2211 | void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) |
2212 | { |
2213 | if (cursor->x11.handle) |
2214 | XFreeCursor(_glfw.x11.display, cursor->x11.handle); |
2215 | } |
2216 | |
2217 | void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) |
2218 | { |
2219 | if (window->cursorMode == GLFW_CURSOR_NORMAL) |
2220 | { |
2221 | updateCursorImage(window); |
2222 | XFlush(_glfw.x11.display); |
2223 | } |
2224 | } |
2225 | |
2226 | void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string) |
2227 | { |
2228 | free(_glfw.x11.clipboardString); |
2229 | _glfw.x11.clipboardString = strdup(string); |
2230 | |
2231 | XSetSelectionOwner(_glfw.x11.display, |
2232 | _glfw.x11.CLIPBOARD, |
2233 | window->x11.handle, CurrentTime); |
2234 | |
2235 | if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) != |
2236 | window->x11.handle) |
2237 | { |
2238 | _glfwInputError(GLFW_PLATFORM_ERROR, |
2239 | "X11: Failed to become owner of clipboard selection" ); |
2240 | } |
2241 | } |
2242 | |
2243 | const char* _glfwPlatformGetClipboardString(_GLFWwindow* window) |
2244 | { |
2245 | size_t i; |
2246 | const Atom formats[] = { _glfw.x11.UTF8_STRING, |
2247 | _glfw.x11.COMPOUND_STRING, |
2248 | XA_STRING }; |
2249 | const size_t formatCount = sizeof(formats) / sizeof(formats[0]); |
2250 | |
2251 | if (findWindowByHandle(XGetSelectionOwner(_glfw.x11.display, |
2252 | _glfw.x11.CLIPBOARD))) |
2253 | { |
2254 | // Instead of doing a large number of X round-trips just to put this |
2255 | // string into a window property and then read it back, just return it |
2256 | return _glfw.x11.clipboardString; |
2257 | } |
2258 | |
2259 | free(_glfw.x11.clipboardString); |
2260 | _glfw.x11.clipboardString = NULL; |
2261 | |
2262 | for (i = 0; i < formatCount; i++) |
2263 | { |
2264 | char* data; |
2265 | XEvent event; |
2266 | |
2267 | XConvertSelection(_glfw.x11.display, |
2268 | _glfw.x11.CLIPBOARD, |
2269 | formats[i], |
2270 | _glfw.x11.GLFW_SELECTION, |
2271 | window->x11.handle, CurrentTime); |
2272 | |
2273 | while (!XCheckTypedEvent(_glfw.x11.display, SelectionNotify, &event)) |
2274 | waitForEvent(NULL); |
2275 | |
2276 | if (event.xselection.property == None) |
2277 | continue; |
2278 | |
2279 | if (_glfwGetWindowPropertyX11(event.xselection.requestor, |
2280 | event.xselection.property, |
2281 | event.xselection.target, |
2282 | (unsigned char**) &data)) |
2283 | { |
2284 | _glfw.x11.clipboardString = strdup(data); |
2285 | } |
2286 | |
2287 | XFree(data); |
2288 | |
2289 | XDeleteProperty(_glfw.x11.display, |
2290 | event.xselection.requestor, |
2291 | event.xselection.property); |
2292 | |
2293 | if (_glfw.x11.clipboardString) |
2294 | break; |
2295 | } |
2296 | |
2297 | if (_glfw.x11.clipboardString == NULL) |
2298 | { |
2299 | _glfwInputError(GLFW_FORMAT_UNAVAILABLE, |
2300 | "X11: Failed to convert clipboard to string" ); |
2301 | } |
2302 | |
2303 | return _glfw.x11.clipboardString; |
2304 | } |
2305 | |
2306 | char** _glfwPlatformGetRequiredInstanceExtensions(uint32_t* count) |
2307 | { |
2308 | char** extensions; |
2309 | |
2310 | *count = 0; |
2311 | |
2312 | if (!_glfw.vk.KHR_xcb_surface || !_glfw.x11.x11xcb.handle) |
2313 | { |
2314 | if (!_glfw.vk.KHR_xlib_surface) |
2315 | return NULL; |
2316 | } |
2317 | |
2318 | extensions = calloc(2, sizeof(char*)); |
2319 | extensions[0] = strdup("VK_KHR_surface" ); |
2320 | |
2321 | if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle) |
2322 | extensions[1] = strdup("VK_KHR_xcb_surface" ); |
2323 | else |
2324 | extensions[1] = strdup("VK_KHR_xlib_surface" ); |
2325 | |
2326 | *count = 2; |
2327 | return extensions; |
2328 | } |
2329 | |
2330 | int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, |
2331 | VkPhysicalDevice device, |
2332 | uint32_t queuefamily) |
2333 | { |
2334 | VisualID visualID = XVisualIDFromVisual(DefaultVisual(_glfw.x11.display, |
2335 | _glfw.x11.screen)); |
2336 | |
2337 | if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle) |
2338 | { |
2339 | PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR = |
2340 | (PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR) |
2341 | vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceXcbPresentationSupportKHR" ); |
2342 | if (!vkGetPhysicalDeviceXcbPresentationSupportKHR) |
2343 | { |
2344 | _glfwInputError(GLFW_API_UNAVAILABLE, |
2345 | "X11: Vulkan instance missing VK_KHR_xcb_surface extension" ); |
2346 | return GLFW_FALSE; |
2347 | } |
2348 | |
2349 | xcb_connection_t* connection = |
2350 | _glfw.x11.x11xcb.XGetXCBConnection(_glfw.x11.display); |
2351 | if (!connection) |
2352 | { |
2353 | _glfwInputError(GLFW_PLATFORM_ERROR, |
2354 | "X11: Failed to retrieve XCB connection" ); |
2355 | return GLFW_FALSE; |
2356 | } |
2357 | |
2358 | return vkGetPhysicalDeviceXcbPresentationSupportKHR(device, |
2359 | queuefamily, |
2360 | connection, |
2361 | visualID); |
2362 | } |
2363 | else |
2364 | { |
2365 | PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR = |
2366 | (PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR) |
2367 | vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceXlibPresentationSupportKHR" ); |
2368 | if (!vkGetPhysicalDeviceXlibPresentationSupportKHR) |
2369 | { |
2370 | _glfwInputError(GLFW_API_UNAVAILABLE, |
2371 | "X11: Vulkan instance missing VK_KHR_xlib_surface extension" ); |
2372 | return GLFW_FALSE; |
2373 | } |
2374 | |
2375 | return vkGetPhysicalDeviceXlibPresentationSupportKHR(device, |
2376 | queuefamily, |
2377 | _glfw.x11.display, |
2378 | visualID); |
2379 | } |
2380 | } |
2381 | |
2382 | VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, |
2383 | _GLFWwindow* window, |
2384 | const VkAllocationCallbacks* allocator, |
2385 | VkSurfaceKHR* surface) |
2386 | { |
2387 | if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle) |
2388 | { |
2389 | VkResult err; |
2390 | VkXcbSurfaceCreateInfoKHR sci; |
2391 | PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR; |
2392 | |
2393 | xcb_connection_t* connection = |
2394 | _glfw.x11.x11xcb.XGetXCBConnection(_glfw.x11.display); |
2395 | if (!connection) |
2396 | { |
2397 | _glfwInputError(GLFW_PLATFORM_ERROR, |
2398 | "X11: Failed to retrieve XCB connection" ); |
2399 | return VK_ERROR_EXTENSION_NOT_PRESENT; |
2400 | } |
2401 | |
2402 | vkCreateXcbSurfaceKHR = (PFN_vkCreateXcbSurfaceKHR) |
2403 | vkGetInstanceProcAddr(instance, "vkCreateXcbSurfaceKHR" ); |
2404 | if (!vkCreateXcbSurfaceKHR) |
2405 | { |
2406 | _glfwInputError(GLFW_API_UNAVAILABLE, |
2407 | "X11: Vulkan instance missing VK_KHR_xcb_surface extension" ); |
2408 | return VK_ERROR_EXTENSION_NOT_PRESENT; |
2409 | } |
2410 | |
2411 | memset(&sci, 0, sizeof(sci)); |
2412 | sci.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; |
2413 | sci.connection = connection; |
2414 | sci.window = window->x11.handle; |
2415 | |
2416 | err = vkCreateXcbSurfaceKHR(instance, &sci, allocator, surface); |
2417 | if (err) |
2418 | { |
2419 | _glfwInputError(GLFW_PLATFORM_ERROR, |
2420 | "X11: Failed to create Vulkan XCB surface: %s" , |
2421 | _glfwGetVulkanResultString(err)); |
2422 | } |
2423 | |
2424 | return err; |
2425 | } |
2426 | else |
2427 | { |
2428 | VkResult err; |
2429 | VkXlibSurfaceCreateInfoKHR sci; |
2430 | PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR; |
2431 | |
2432 | vkCreateXlibSurfaceKHR = (PFN_vkCreateXlibSurfaceKHR) |
2433 | vkGetInstanceProcAddr(instance, "vkCreateXlibSurfaceKHR" ); |
2434 | if (!vkCreateXlibSurfaceKHR) |
2435 | { |
2436 | _glfwInputError(GLFW_API_UNAVAILABLE, |
2437 | "X11: Vulkan instance missing VK_KHR_xlib_surface extension" ); |
2438 | return VK_ERROR_EXTENSION_NOT_PRESENT; |
2439 | } |
2440 | |
2441 | memset(&sci, 0, sizeof(sci)); |
2442 | sci.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; |
2443 | sci.dpy = _glfw.x11.display; |
2444 | sci.window = window->x11.handle; |
2445 | |
2446 | err = vkCreateXlibSurfaceKHR(instance, &sci, allocator, surface); |
2447 | if (err) |
2448 | { |
2449 | _glfwInputError(GLFW_PLATFORM_ERROR, |
2450 | "X11: Failed to create Vulkan X11 surface: %s" , |
2451 | _glfwGetVulkanResultString(err)); |
2452 | } |
2453 | |
2454 | return err; |
2455 | } |
2456 | } |
2457 | |
2458 | |
2459 | ////////////////////////////////////////////////////////////////////////// |
2460 | ////// GLFW native API ////// |
2461 | ////////////////////////////////////////////////////////////////////////// |
2462 | |
2463 | GLFWAPI Display* glfwGetX11Display(void) |
2464 | { |
2465 | _GLFW_REQUIRE_INIT_OR_RETURN(NULL); |
2466 | return _glfw.x11.display; |
2467 | } |
2468 | |
2469 | GLFWAPI Window glfwGetX11Window(GLFWwindow* handle) |
2470 | { |
2471 | _GLFWwindow* window = (_GLFWwindow*) handle; |
2472 | _GLFW_REQUIRE_INIT_OR_RETURN(None); |
2473 | return window->x11.handle; |
2474 | } |
2475 | |
2476 | |