1 | //======================================================================== |
2 | // GLFW 3.4 X11 - www.glfw.org |
3 | //------------------------------------------------------------------------ |
4 | // Copyright (c) 2002-2006 Marcus Geelnard |
5 | // Copyright (c) 2006-2019 Camilla Löwy <[email protected]> |
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 | // It is fine to use C99 in this file because it will not be built with VS |
28 | //======================================================================== |
29 | |
30 | #include "internal.h" |
31 | |
32 | #include <X11/cursorfont.h> |
33 | #include <X11/Xmd.h> |
34 | |
35 | #include <sys/select.h> |
36 | |
37 | #include <string.h> |
38 | #include <stdio.h> |
39 | #include <stdlib.h> |
40 | #include <limits.h> |
41 | #include <errno.h> |
42 | #include <assert.h> |
43 | |
44 | // Action for EWMH client messages |
45 | #define _NET_WM_STATE_REMOVE 0 |
46 | #define _NET_WM_STATE_ADD 1 |
47 | #define _NET_WM_STATE_TOGGLE 2 |
48 | |
49 | // Additional mouse button names for XButtonEvent |
50 | #define Button6 6 |
51 | #define Button7 7 |
52 | |
53 | // Motif WM hints flags |
54 | #define MWM_HINTS_DECORATIONS 2 |
55 | #define MWM_DECOR_ALL 1 |
56 | |
57 | #define _GLFW_XDND_VERSION 5 |
58 | |
59 | |
60 | // Wait for data to arrive using select |
61 | // This avoids blocking other threads via the per-display Xlib lock that also |
62 | // covers GLX functions |
63 | // |
64 | static GLFWbool waitForEvent(double* timeout) |
65 | { |
66 | fd_set fds; |
67 | const int fd = ConnectionNumber(_glfw.x11.display); |
68 | int count = fd + 1; |
69 | |
70 | #if defined(__linux__) |
71 | if (_glfw.linjs.inotify > fd) |
72 | count = _glfw.linjs.inotify + 1; |
73 | #endif |
74 | for (;;) |
75 | { |
76 | FD_ZERO(&fds); |
77 | FD_SET(fd, &fds); |
78 | #if defined(__linux__) |
79 | if (_glfw.linjs.inotify > 0) |
80 | FD_SET(_glfw.linjs.inotify, &fds); |
81 | #endif |
82 | |
83 | if (timeout) |
84 | { |
85 | const long seconds = (long) *timeout; |
86 | const long microseconds = (long) ((*timeout - seconds) * 1e6); |
87 | struct timeval tv = { seconds, microseconds }; |
88 | const uint64_t base = _glfwPlatformGetTimerValue(); |
89 | |
90 | const int result = select(count, &fds, NULL, NULL, &tv); |
91 | const int error = errno; |
92 | |
93 | *timeout -= (_glfwPlatformGetTimerValue() - base) / |
94 | (double) _glfwPlatformGetTimerFrequency(); |
95 | |
96 | if (result > 0) |
97 | return GLFW_TRUE; |
98 | if ((result == -1 && error == EINTR) || *timeout <= 0.0) |
99 | return GLFW_FALSE; |
100 | } |
101 | else if (select(count, &fds, NULL, NULL, NULL) != -1 || errno != EINTR) |
102 | return GLFW_TRUE; |
103 | } |
104 | } |
105 | |
106 | // Waits until a VisibilityNotify event arrives for the specified window or the |
107 | // timeout period elapses (ICCCM section 4.2.2) |
108 | // |
109 | static GLFWbool waitForVisibilityNotify(_GLFWwindow* window) |
110 | { |
111 | XEvent dummy; |
112 | double timeout = 0.1; |
113 | |
114 | while (!XCheckTypedWindowEvent(_glfw.x11.display, |
115 | window->x11.handle, |
116 | VisibilityNotify, |
117 | &dummy)) |
118 | { |
119 | if (!waitForEvent(&timeout)) |
120 | return GLFW_FALSE; |
121 | } |
122 | |
123 | return GLFW_TRUE; |
124 | } |
125 | |
126 | // Returns whether the window is iconified |
127 | // |
128 | static int getWindowState(_GLFWwindow* window) |
129 | { |
130 | int result = WithdrawnState; |
131 | struct { |
132 | CARD32 state; |
133 | Window icon; |
134 | } *state = NULL; |
135 | |
136 | if (_glfwGetWindowPropertyX11(window->x11.handle, |
137 | _glfw.x11.WM_STATE, |
138 | _glfw.x11.WM_STATE, |
139 | (unsigned char**) &state) >= 2) |
140 | { |
141 | result = state->state; |
142 | } |
143 | |
144 | if (state) |
145 | XFree(state); |
146 | |
147 | return result; |
148 | } |
149 | |
150 | // Returns whether the event is a selection event |
151 | // |
152 | static Bool isSelectionEvent(Display* display, XEvent* event, XPointer pointer) |
153 | { |
154 | if (event->xany.window != _glfw.x11.helperWindowHandle) |
155 | return False; |
156 | |
157 | return event->type == SelectionRequest || |
158 | event->type == SelectionNotify || |
159 | event->type == SelectionClear; |
160 | } |
161 | |
162 | // Returns whether it is a _NET_FRAME_EXTENTS event for the specified window |
163 | // |
164 | static Bool isFrameExtentsEvent(Display* display, XEvent* event, XPointer pointer) |
165 | { |
166 | _GLFWwindow* window = (_GLFWwindow*) pointer; |
167 | return event->type == PropertyNotify && |
168 | event->xproperty.state == PropertyNewValue && |
169 | event->xproperty.window == window->x11.handle && |
170 | event->xproperty.atom == _glfw.x11.NET_FRAME_EXTENTS; |
171 | } |
172 | |
173 | // Returns whether it is a property event for the specified selection transfer |
174 | // |
175 | static Bool isSelPropNewValueNotify(Display* display, XEvent* event, XPointer pointer) |
176 | { |
177 | XEvent* notification = (XEvent*) pointer; |
178 | return event->type == PropertyNotify && |
179 | event->xproperty.state == PropertyNewValue && |
180 | event->xproperty.window == notification->xselection.requestor && |
181 | event->xproperty.atom == notification->xselection.property; |
182 | } |
183 | |
184 | // Translates an X event modifier state mask |
185 | // |
186 | static int translateState(int state) |
187 | { |
188 | int mods = 0; |
189 | |
190 | if (state & ShiftMask) |
191 | mods |= GLFW_MOD_SHIFT; |
192 | if (state & ControlMask) |
193 | mods |= GLFW_MOD_CONTROL; |
194 | if (state & Mod1Mask) |
195 | mods |= GLFW_MOD_ALT; |
196 | if (state & Mod4Mask) |
197 | mods |= GLFW_MOD_SUPER; |
198 | if (state & LockMask) |
199 | mods |= GLFW_MOD_CAPS_LOCK; |
200 | if (state & Mod2Mask) |
201 | mods |= GLFW_MOD_NUM_LOCK; |
202 | |
203 | return mods; |
204 | } |
205 | |
206 | // Translates an X11 key code to a GLFW key token |
207 | // |
208 | static int translateKey(int scancode) |
209 | { |
210 | // Use the pre-filled LUT (see createKeyTables() in x11_init.c) |
211 | if (scancode < 0 || scancode > 255) |
212 | return GLFW_KEY_UNKNOWN; |
213 | |
214 | return _glfw.x11.keycodes[scancode]; |
215 | } |
216 | |
217 | // Sends an EWMH or ICCCM event to the window manager |
218 | // |
219 | static void sendEventToWM(_GLFWwindow* window, Atom type, |
220 | long a, long b, long c, long d, long e) |
221 | { |
222 | XEvent event = { ClientMessage }; |
223 | event.xclient.window = window->x11.handle; |
224 | event.xclient.format = 32; // Data is 32-bit longs |
225 | event.xclient.message_type = type; |
226 | event.xclient.data.l[0] = a; |
227 | event.xclient.data.l[1] = b; |
228 | event.xclient.data.l[2] = c; |
229 | event.xclient.data.l[3] = d; |
230 | event.xclient.data.l[4] = e; |
231 | |
232 | XSendEvent(_glfw.x11.display, _glfw.x11.root, |
233 | False, |
234 | SubstructureNotifyMask | SubstructureRedirectMask, |
235 | &event); |
236 | } |
237 | |
238 | // Updates the normal hints according to the window settings |
239 | // |
240 | static void updateNormalHints(_GLFWwindow* window, int width, int height) |
241 | { |
242 | XSizeHints* hints = XAllocSizeHints(); |
243 | |
244 | if (!window->monitor) |
245 | { |
246 | if (window->resizable) |
247 | { |
248 | if (window->minwidth != GLFW_DONT_CARE && |
249 | window->minheight != GLFW_DONT_CARE) |
250 | { |
251 | hints->flags |= PMinSize; |
252 | hints->min_width = window->minwidth; |
253 | hints->min_height = window->minheight; |
254 | } |
255 | |
256 | if (window->maxwidth != GLFW_DONT_CARE && |
257 | window->maxheight != GLFW_DONT_CARE) |
258 | { |
259 | hints->flags |= PMaxSize; |
260 | hints->max_width = window->maxwidth; |
261 | hints->max_height = window->maxheight; |
262 | } |
263 | |
264 | if (window->numer != GLFW_DONT_CARE && |
265 | window->denom != GLFW_DONT_CARE) |
266 | { |
267 | hints->flags |= PAspect; |
268 | hints->min_aspect.x = hints->max_aspect.x = window->numer; |
269 | hints->min_aspect.y = hints->max_aspect.y = window->denom; |
270 | } |
271 | } |
272 | else |
273 | { |
274 | hints->flags |= (PMinSize | PMaxSize); |
275 | hints->min_width = hints->max_width = width; |
276 | hints->min_height = hints->max_height = height; |
277 | } |
278 | } |
279 | |
280 | hints->flags |= PWinGravity; |
281 | hints->win_gravity = StaticGravity; |
282 | |
283 | XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); |
284 | XFree(hints); |
285 | } |
286 | |
287 | // Updates the full screen status of the window |
288 | // |
289 | static void updateWindowMode(_GLFWwindow* window) |
290 | { |
291 | if (window->monitor) |
292 | { |
293 | if (_glfw.x11.xinerama.available && |
294 | _glfw.x11.NET_WM_FULLSCREEN_MONITORS) |
295 | { |
296 | sendEventToWM(window, |
297 | _glfw.x11.NET_WM_FULLSCREEN_MONITORS, |
298 | window->monitor->x11.index, |
299 | window->monitor->x11.index, |
300 | window->monitor->x11.index, |
301 | window->monitor->x11.index, |
302 | 0); |
303 | } |
304 | |
305 | if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_FULLSCREEN) |
306 | { |
307 | sendEventToWM(window, |
308 | _glfw.x11.NET_WM_STATE, |
309 | _NET_WM_STATE_ADD, |
310 | _glfw.x11.NET_WM_STATE_FULLSCREEN, |
311 | 0, 1, 0); |
312 | } |
313 | else |
314 | { |
315 | // This is the butcher's way of removing window decorations |
316 | // Setting the override-redirect attribute on a window makes the |
317 | // window manager ignore the window completely (ICCCM, section 4) |
318 | // The good thing is that this makes undecorated full screen windows |
319 | // easy to do; the bad thing is that we have to do everything |
320 | // manually and some things (like iconify/restore) won't work at |
321 | // all, as those are tasks usually performed by the window manager |
322 | |
323 | XSetWindowAttributes attributes; |
324 | attributes.override_redirect = True; |
325 | XChangeWindowAttributes(_glfw.x11.display, |
326 | window->x11.handle, |
327 | CWOverrideRedirect, |
328 | &attributes); |
329 | |
330 | window->x11.overrideRedirect = GLFW_TRUE; |
331 | } |
332 | |
333 | // Enable compositor bypass |
334 | if (!window->x11.transparent) |
335 | { |
336 | const unsigned long value = 1; |
337 | |
338 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
339 | _glfw.x11.NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, |
340 | PropModeReplace, (unsigned char*) &value, 1); |
341 | } |
342 | } |
343 | else |
344 | { |
345 | if (_glfw.x11.xinerama.available && |
346 | _glfw.x11.NET_WM_FULLSCREEN_MONITORS) |
347 | { |
348 | XDeleteProperty(_glfw.x11.display, window->x11.handle, |
349 | _glfw.x11.NET_WM_FULLSCREEN_MONITORS); |
350 | } |
351 | |
352 | if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_FULLSCREEN) |
353 | { |
354 | sendEventToWM(window, |
355 | _glfw.x11.NET_WM_STATE, |
356 | _NET_WM_STATE_REMOVE, |
357 | _glfw.x11.NET_WM_STATE_FULLSCREEN, |
358 | 0, 1, 0); |
359 | } |
360 | else |
361 | { |
362 | XSetWindowAttributes attributes; |
363 | attributes.override_redirect = False; |
364 | XChangeWindowAttributes(_glfw.x11.display, |
365 | window->x11.handle, |
366 | CWOverrideRedirect, |
367 | &attributes); |
368 | |
369 | window->x11.overrideRedirect = GLFW_FALSE; |
370 | } |
371 | |
372 | // Disable compositor bypass |
373 | if (!window->x11.transparent) |
374 | { |
375 | XDeleteProperty(_glfw.x11.display, window->x11.handle, |
376 | _glfw.x11.NET_WM_BYPASS_COMPOSITOR); |
377 | } |
378 | } |
379 | } |
380 | |
381 | // Splits and translates a text/uri-list into separate file paths |
382 | // NOTE: This function destroys the provided string |
383 | // |
384 | static char** parseUriList(char* text, int* count) |
385 | { |
386 | const char* prefix = "file://" ; |
387 | char** paths = NULL; |
388 | char* line; |
389 | |
390 | *count = 0; |
391 | |
392 | while ((line = strtok(text, "\r\n" ))) |
393 | { |
394 | text = NULL; |
395 | |
396 | if (line[0] == '#') |
397 | continue; |
398 | |
399 | if (strncmp(line, prefix, strlen(prefix)) == 0) |
400 | { |
401 | line += strlen(prefix); |
402 | // TODO: Validate hostname |
403 | while (*line != '/') |
404 | line++; |
405 | } |
406 | |
407 | (*count)++; |
408 | |
409 | char* path = _glfw_calloc(strlen(line) + 1, 1); |
410 | paths = _glfw_realloc(paths, *count * sizeof(char*)); |
411 | paths[*count - 1] = path; |
412 | |
413 | while (*line) |
414 | { |
415 | if (line[0] == '%' && line[1] && line[2]) |
416 | { |
417 | const char digits[3] = { line[1], line[2], '\0' }; |
418 | *path = strtol(digits, NULL, 16); |
419 | line += 2; |
420 | } |
421 | else |
422 | *path = *line; |
423 | |
424 | path++; |
425 | line++; |
426 | } |
427 | } |
428 | |
429 | return paths; |
430 | } |
431 | |
432 | // Encode a Unicode code point to a UTF-8 stream |
433 | // Based on cutef8 by Jeff Bezanson (Public Domain) |
434 | // |
435 | static size_t encodeUTF8(char* s, unsigned int ch) |
436 | { |
437 | size_t count = 0; |
438 | |
439 | if (ch < 0x80) |
440 | s[count++] = (char) ch; |
441 | else if (ch < 0x800) |
442 | { |
443 | s[count++] = (ch >> 6) | 0xc0; |
444 | s[count++] = (ch & 0x3f) | 0x80; |
445 | } |
446 | else if (ch < 0x10000) |
447 | { |
448 | s[count++] = (ch >> 12) | 0xe0; |
449 | s[count++] = ((ch >> 6) & 0x3f) | 0x80; |
450 | s[count++] = (ch & 0x3f) | 0x80; |
451 | } |
452 | else if (ch < 0x110000) |
453 | { |
454 | s[count++] = (ch >> 18) | 0xf0; |
455 | s[count++] = ((ch >> 12) & 0x3f) | 0x80; |
456 | s[count++] = ((ch >> 6) & 0x3f) | 0x80; |
457 | s[count++] = (ch & 0x3f) | 0x80; |
458 | } |
459 | |
460 | return count; |
461 | } |
462 | |
463 | // Decode a Unicode code point from a UTF-8 stream |
464 | // Based on cutef8 by Jeff Bezanson (Public Domain) |
465 | // |
466 | static unsigned int decodeUTF8(const char** s) |
467 | { |
468 | unsigned int ch = 0, count = 0; |
469 | static const unsigned int offsets[] = |
470 | { |
471 | 0x00000000u, 0x00003080u, 0x000e2080u, |
472 | 0x03c82080u, 0xfa082080u, 0x82082080u |
473 | }; |
474 | |
475 | do |
476 | { |
477 | ch = (ch << 6) + (unsigned char) **s; |
478 | (*s)++; |
479 | count++; |
480 | } while ((**s & 0xc0) == 0x80); |
481 | |
482 | assert(count <= 6); |
483 | return ch - offsets[count - 1]; |
484 | } |
485 | |
486 | // Convert the specified Latin-1 string to UTF-8 |
487 | // |
488 | static char* convertLatin1toUTF8(const char* source) |
489 | { |
490 | size_t size = 1; |
491 | const char* sp; |
492 | |
493 | for (sp = source; *sp; sp++) |
494 | size += (*sp & 0x80) ? 2 : 1; |
495 | |
496 | char* target = _glfw_calloc(size, 1); |
497 | char* tp = target; |
498 | |
499 | for (sp = source; *sp; sp++) |
500 | tp += encodeUTF8(tp, *sp); |
501 | |
502 | return target; |
503 | } |
504 | |
505 | // Updates the cursor image according to its cursor mode |
506 | // |
507 | static void updateCursorImage(_GLFWwindow* window) |
508 | { |
509 | if (window->cursorMode == GLFW_CURSOR_NORMAL) |
510 | { |
511 | if (window->cursor) |
512 | { |
513 | XDefineCursor(_glfw.x11.display, window->x11.handle, |
514 | window->cursor->x11.handle); |
515 | } |
516 | else |
517 | XUndefineCursor(_glfw.x11.display, window->x11.handle); |
518 | } |
519 | else |
520 | { |
521 | XDefineCursor(_glfw.x11.display, window->x11.handle, |
522 | _glfw.x11.hiddenCursorHandle); |
523 | } |
524 | } |
525 | |
526 | // Enable XI2 raw mouse motion events |
527 | // |
528 | static void enableRawMouseMotion(_GLFWwindow* window) |
529 | { |
530 | XIEventMask em; |
531 | unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; |
532 | |
533 | em.deviceid = XIAllMasterDevices; |
534 | em.mask_len = sizeof(mask); |
535 | em.mask = mask; |
536 | XISetMask(mask, XI_RawMotion); |
537 | |
538 | XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1); |
539 | } |
540 | |
541 | // Disable XI2 raw mouse motion events |
542 | // |
543 | static void disableRawMouseMotion(_GLFWwindow* window) |
544 | { |
545 | XIEventMask em; |
546 | unsigned char mask[] = { 0 }; |
547 | |
548 | em.deviceid = XIAllMasterDevices; |
549 | em.mask_len = sizeof(mask); |
550 | em.mask = mask; |
551 | |
552 | XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1); |
553 | } |
554 | |
555 | // Apply disabled cursor mode to a focused window |
556 | // |
557 | static void disableCursor(_GLFWwindow* window) |
558 | { |
559 | if (window->rawMouseMotion) |
560 | enableRawMouseMotion(window); |
561 | |
562 | _glfw.x11.disabledCursorWindow = window; |
563 | _glfwGetCursorPosX11(window, |
564 | &_glfw.x11.restoreCursorPosX, |
565 | &_glfw.x11.restoreCursorPosY); |
566 | updateCursorImage(window); |
567 | _glfwCenterCursorInContentArea(window); |
568 | XGrabPointer(_glfw.x11.display, window->x11.handle, True, |
569 | ButtonPressMask | ButtonReleaseMask | PointerMotionMask, |
570 | GrabModeAsync, GrabModeAsync, |
571 | window->x11.handle, |
572 | _glfw.x11.hiddenCursorHandle, |
573 | CurrentTime); |
574 | } |
575 | |
576 | // Exit disabled cursor mode for the specified window |
577 | // |
578 | static void enableCursor(_GLFWwindow* window) |
579 | { |
580 | if (window->rawMouseMotion) |
581 | disableRawMouseMotion(window); |
582 | |
583 | _glfw.x11.disabledCursorWindow = NULL; |
584 | XUngrabPointer(_glfw.x11.display, CurrentTime); |
585 | _glfwSetCursorPosX11(window, |
586 | _glfw.x11.restoreCursorPosX, |
587 | _glfw.x11.restoreCursorPosY); |
588 | updateCursorImage(window); |
589 | } |
590 | |
591 | // Clear its handle when the input context has been destroyed |
592 | // |
593 | static void inputContextDestroyCallback(XIC ic, XPointer clientData, XPointer callData) |
594 | { |
595 | _GLFWwindow* window = (_GLFWwindow*) clientData; |
596 | window->x11.ic = NULL; |
597 | } |
598 | |
599 | // Create the X11 window (and its colormap) |
600 | // |
601 | static GLFWbool createNativeWindow(_GLFWwindow* window, |
602 | const _GLFWwndconfig* wndconfig, |
603 | Visual* visual, int depth) |
604 | { |
605 | int width = wndconfig->width; |
606 | int height = wndconfig->height; |
607 | |
608 | if (wndconfig->scaleToMonitor) |
609 | { |
610 | width *= _glfw.x11.contentScaleX; |
611 | height *= _glfw.x11.contentScaleY; |
612 | } |
613 | |
614 | // Create a colormap based on the visual used by the current context |
615 | window->x11.colormap = XCreateColormap(_glfw.x11.display, |
616 | _glfw.x11.root, |
617 | visual, |
618 | AllocNone); |
619 | |
620 | window->x11.transparent = _glfwIsVisualTransparentX11(visual); |
621 | |
622 | XSetWindowAttributes wa = { 0 }; |
623 | wa.colormap = window->x11.colormap; |
624 | wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | |
625 | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | |
626 | ExposureMask | FocusChangeMask | VisibilityChangeMask | |
627 | EnterWindowMask | LeaveWindowMask | PropertyChangeMask; |
628 | |
629 | _glfwGrabErrorHandlerX11(); |
630 | |
631 | window->x11.parent = _glfw.x11.root; |
632 | window->x11.handle = XCreateWindow(_glfw.x11.display, |
633 | _glfw.x11.root, |
634 | 0, 0, // Position |
635 | width, height, |
636 | 0, // Border width |
637 | depth, // Color depth |
638 | InputOutput, |
639 | visual, |
640 | CWBorderPixel | CWColormap | CWEventMask, |
641 | &wa); |
642 | |
643 | _glfwReleaseErrorHandlerX11(); |
644 | |
645 | if (!window->x11.handle) |
646 | { |
647 | _glfwInputErrorX11(GLFW_PLATFORM_ERROR, |
648 | "X11: Failed to create window" ); |
649 | return GLFW_FALSE; |
650 | } |
651 | |
652 | XSaveContext(_glfw.x11.display, |
653 | window->x11.handle, |
654 | _glfw.x11.context, |
655 | (XPointer) window); |
656 | |
657 | if (!wndconfig->decorated) |
658 | _glfwSetWindowDecoratedX11(window, GLFW_FALSE); |
659 | |
660 | if (_glfw.x11.NET_WM_STATE && !window->monitor) |
661 | { |
662 | Atom states[3]; |
663 | int count = 0; |
664 | |
665 | if (wndconfig->floating) |
666 | { |
667 | if (_glfw.x11.NET_WM_STATE_ABOVE) |
668 | states[count++] = _glfw.x11.NET_WM_STATE_ABOVE; |
669 | } |
670 | |
671 | if (wndconfig->maximized) |
672 | { |
673 | if (_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT && |
674 | _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) |
675 | { |
676 | states[count++] = _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT; |
677 | states[count++] = _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ; |
678 | window->x11.maximized = GLFW_TRUE; |
679 | } |
680 | } |
681 | |
682 | if (count) |
683 | { |
684 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
685 | _glfw.x11.NET_WM_STATE, XA_ATOM, 32, |
686 | PropModeReplace, (unsigned char*) states, count); |
687 | } |
688 | } |
689 | |
690 | // Declare the WM protocols supported by GLFW |
691 | { |
692 | Atom protocols[] = |
693 | { |
694 | _glfw.x11.WM_DELETE_WINDOW, |
695 | _glfw.x11.NET_WM_PING |
696 | }; |
697 | |
698 | XSetWMProtocols(_glfw.x11.display, window->x11.handle, |
699 | protocols, sizeof(protocols) / sizeof(Atom)); |
700 | } |
701 | |
702 | // Declare our PID |
703 | { |
704 | const long pid = getpid(); |
705 | |
706 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
707 | _glfw.x11.NET_WM_PID, XA_CARDINAL, 32, |
708 | PropModeReplace, |
709 | (unsigned char*) &pid, 1); |
710 | } |
711 | |
712 | if (_glfw.x11.NET_WM_WINDOW_TYPE && _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL) |
713 | { |
714 | Atom type = _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL; |
715 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
716 | _glfw.x11.NET_WM_WINDOW_TYPE, XA_ATOM, 32, |
717 | PropModeReplace, (unsigned char*) &type, 1); |
718 | } |
719 | |
720 | // Set ICCCM WM_HINTS property |
721 | { |
722 | XWMHints* hints = XAllocWMHints(); |
723 | if (!hints) |
724 | { |
725 | _glfwInputError(GLFW_OUT_OF_MEMORY, |
726 | "X11: Failed to allocate WM hints" ); |
727 | return GLFW_FALSE; |
728 | } |
729 | |
730 | hints->flags = StateHint; |
731 | hints->initial_state = NormalState; |
732 | |
733 | XSetWMHints(_glfw.x11.display, window->x11.handle, hints); |
734 | XFree(hints); |
735 | } |
736 | |
737 | updateNormalHints(window, width, height); |
738 | |
739 | // Set ICCCM WM_CLASS property |
740 | { |
741 | XClassHint* hint = XAllocClassHint(); |
742 | |
743 | if (strlen(wndconfig->x11.instanceName) && |
744 | strlen(wndconfig->x11.className)) |
745 | { |
746 | hint->res_name = (char*) wndconfig->x11.instanceName; |
747 | hint->res_class = (char*) wndconfig->x11.className; |
748 | } |
749 | else |
750 | { |
751 | const char* resourceName = getenv("RESOURCE_NAME" ); |
752 | if (resourceName && strlen(resourceName)) |
753 | hint->res_name = (char*) resourceName; |
754 | else if (strlen(wndconfig->title)) |
755 | hint->res_name = (char*) wndconfig->title; |
756 | else |
757 | hint->res_name = (char*) "glfw-application" ; |
758 | |
759 | if (strlen(wndconfig->title)) |
760 | hint->res_class = (char*) wndconfig->title; |
761 | else |
762 | hint->res_class = (char*) "GLFW-Application" ; |
763 | } |
764 | |
765 | XSetClassHint(_glfw.x11.display, window->x11.handle, hint); |
766 | XFree(hint); |
767 | } |
768 | |
769 | // Announce support for Xdnd (drag and drop) |
770 | { |
771 | const Atom version = _GLFW_XDND_VERSION; |
772 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
773 | _glfw.x11.XdndAware, XA_ATOM, 32, |
774 | PropModeReplace, (unsigned char*) &version, 1); |
775 | } |
776 | |
777 | if (_glfw.x11.im) |
778 | _glfwCreateInputContextX11(window); |
779 | |
780 | _glfwSetWindowTitleX11(window, wndconfig->title); |
781 | _glfwGetWindowPosX11(window, &window->x11.xpos, &window->x11.ypos); |
782 | _glfwGetWindowSizeX11(window, &window->x11.width, &window->x11.height); |
783 | |
784 | return GLFW_TRUE; |
785 | } |
786 | |
787 | // Set the specified property to the selection converted to the requested target |
788 | // |
789 | static Atom writeTargetToProperty(const XSelectionRequestEvent* request) |
790 | { |
791 | char* selectionString = NULL; |
792 | const Atom formats[] = { _glfw.x11.UTF8_STRING, XA_STRING }; |
793 | const int formatCount = sizeof(formats) / sizeof(formats[0]); |
794 | |
795 | if (request->selection == _glfw.x11.PRIMARY) |
796 | selectionString = _glfw.x11.primarySelectionString; |
797 | else |
798 | selectionString = _glfw.x11.clipboardString; |
799 | |
800 | if (request->property == None) |
801 | { |
802 | // The requester is a legacy client (ICCCM section 2.2) |
803 | // We don't support legacy clients, so fail here |
804 | return None; |
805 | } |
806 | |
807 | if (request->target == _glfw.x11.TARGETS) |
808 | { |
809 | // The list of supported targets was requested |
810 | |
811 | const Atom targets[] = { _glfw.x11.TARGETS, |
812 | _glfw.x11.MULTIPLE, |
813 | _glfw.x11.UTF8_STRING, |
814 | XA_STRING }; |
815 | |
816 | XChangeProperty(_glfw.x11.display, |
817 | request->requestor, |
818 | request->property, |
819 | XA_ATOM, |
820 | 32, |
821 | PropModeReplace, |
822 | (unsigned char*) targets, |
823 | sizeof(targets) / sizeof(targets[0])); |
824 | |
825 | return request->property; |
826 | } |
827 | |
828 | if (request->target == _glfw.x11.MULTIPLE) |
829 | { |
830 | // Multiple conversions were requested |
831 | |
832 | Atom* targets; |
833 | const unsigned long count = |
834 | _glfwGetWindowPropertyX11(request->requestor, |
835 | request->property, |
836 | _glfw.x11.ATOM_PAIR, |
837 | (unsigned char**) &targets); |
838 | |
839 | for (unsigned long i = 0; i < count; i += 2) |
840 | { |
841 | int j; |
842 | |
843 | for (j = 0; j < formatCount; j++) |
844 | { |
845 | if (targets[i] == formats[j]) |
846 | break; |
847 | } |
848 | |
849 | if (j < formatCount) |
850 | { |
851 | XChangeProperty(_glfw.x11.display, |
852 | request->requestor, |
853 | targets[i + 1], |
854 | targets[i], |
855 | 8, |
856 | PropModeReplace, |
857 | (unsigned char *) selectionString, |
858 | strlen(selectionString)); |
859 | } |
860 | else |
861 | targets[i + 1] = None; |
862 | } |
863 | |
864 | XChangeProperty(_glfw.x11.display, |
865 | request->requestor, |
866 | request->property, |
867 | _glfw.x11.ATOM_PAIR, |
868 | 32, |
869 | PropModeReplace, |
870 | (unsigned char*) targets, |
871 | count); |
872 | |
873 | XFree(targets); |
874 | |
875 | return request->property; |
876 | } |
877 | |
878 | if (request->target == _glfw.x11.SAVE_TARGETS) |
879 | { |
880 | // The request is a check whether we support SAVE_TARGETS |
881 | // It should be handled as a no-op side effect target |
882 | |
883 | XChangeProperty(_glfw.x11.display, |
884 | request->requestor, |
885 | request->property, |
886 | _glfw.x11.NULL_, |
887 | 32, |
888 | PropModeReplace, |
889 | NULL, |
890 | 0); |
891 | |
892 | return request->property; |
893 | } |
894 | |
895 | // Conversion to a data target was requested |
896 | |
897 | for (int i = 0; i < formatCount; i++) |
898 | { |
899 | if (request->target == formats[i]) |
900 | { |
901 | // The requested target is one we support |
902 | |
903 | XChangeProperty(_glfw.x11.display, |
904 | request->requestor, |
905 | request->property, |
906 | request->target, |
907 | 8, |
908 | PropModeReplace, |
909 | (unsigned char *) selectionString, |
910 | strlen(selectionString)); |
911 | |
912 | return request->property; |
913 | } |
914 | } |
915 | |
916 | // The requested target is not supported |
917 | |
918 | return None; |
919 | } |
920 | |
921 | static void handleSelectionClear(XEvent* event) |
922 | { |
923 | if (event->xselectionclear.selection == _glfw.x11.PRIMARY) |
924 | { |
925 | _glfw_free(_glfw.x11.primarySelectionString); |
926 | _glfw.x11.primarySelectionString = NULL; |
927 | } |
928 | else |
929 | { |
930 | _glfw_free(_glfw.x11.clipboardString); |
931 | _glfw.x11.clipboardString = NULL; |
932 | } |
933 | } |
934 | |
935 | static void handleSelectionRequest(XEvent* event) |
936 | { |
937 | const XSelectionRequestEvent* request = &event->xselectionrequest; |
938 | |
939 | XEvent reply = { SelectionNotify }; |
940 | reply.xselection.property = writeTargetToProperty(request); |
941 | reply.xselection.display = request->display; |
942 | reply.xselection.requestor = request->requestor; |
943 | reply.xselection.selection = request->selection; |
944 | reply.xselection.target = request->target; |
945 | reply.xselection.time = request->time; |
946 | |
947 | XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply); |
948 | } |
949 | |
950 | static const char* getSelectionString(Atom selection) |
951 | { |
952 | char** selectionString = NULL; |
953 | const Atom targets[] = { _glfw.x11.UTF8_STRING, XA_STRING }; |
954 | const size_t targetCount = sizeof(targets) / sizeof(targets[0]); |
955 | |
956 | if (selection == _glfw.x11.PRIMARY) |
957 | selectionString = &_glfw.x11.primarySelectionString; |
958 | else |
959 | selectionString = &_glfw.x11.clipboardString; |
960 | |
961 | if (XGetSelectionOwner(_glfw.x11.display, selection) == |
962 | _glfw.x11.helperWindowHandle) |
963 | { |
964 | // Instead of doing a large number of X round-trips just to put this |
965 | // string into a window property and then read it back, just return it |
966 | return *selectionString; |
967 | } |
968 | |
969 | _glfw_free(*selectionString); |
970 | *selectionString = NULL; |
971 | |
972 | for (size_t i = 0; i < targetCount; i++) |
973 | { |
974 | char* data; |
975 | Atom actualType; |
976 | int actualFormat; |
977 | unsigned long itemCount, bytesAfter; |
978 | XEvent notification, dummy; |
979 | |
980 | XConvertSelection(_glfw.x11.display, |
981 | selection, |
982 | targets[i], |
983 | _glfw.x11.GLFW_SELECTION, |
984 | _glfw.x11.helperWindowHandle, |
985 | CurrentTime); |
986 | |
987 | while (!XCheckTypedWindowEvent(_glfw.x11.display, |
988 | _glfw.x11.helperWindowHandle, |
989 | SelectionNotify, |
990 | ¬ification)) |
991 | { |
992 | waitForEvent(NULL); |
993 | } |
994 | |
995 | if (notification.xselection.property == None) |
996 | continue; |
997 | |
998 | XCheckIfEvent(_glfw.x11.display, |
999 | &dummy, |
1000 | isSelPropNewValueNotify, |
1001 | (XPointer) ¬ification); |
1002 | |
1003 | XGetWindowProperty(_glfw.x11.display, |
1004 | notification.xselection.requestor, |
1005 | notification.xselection.property, |
1006 | 0, |
1007 | LONG_MAX, |
1008 | True, |
1009 | AnyPropertyType, |
1010 | &actualType, |
1011 | &actualFormat, |
1012 | &itemCount, |
1013 | &bytesAfter, |
1014 | (unsigned char**) &data); |
1015 | |
1016 | if (actualType == _glfw.x11.INCR) |
1017 | { |
1018 | size_t size = 1; |
1019 | char* string = NULL; |
1020 | |
1021 | for (;;) |
1022 | { |
1023 | while (!XCheckIfEvent(_glfw.x11.display, |
1024 | &dummy, |
1025 | isSelPropNewValueNotify, |
1026 | (XPointer) ¬ification)) |
1027 | { |
1028 | waitForEvent(NULL); |
1029 | } |
1030 | |
1031 | XFree(data); |
1032 | XGetWindowProperty(_glfw.x11.display, |
1033 | notification.xselection.requestor, |
1034 | notification.xselection.property, |
1035 | 0, |
1036 | LONG_MAX, |
1037 | True, |
1038 | AnyPropertyType, |
1039 | &actualType, |
1040 | &actualFormat, |
1041 | &itemCount, |
1042 | &bytesAfter, |
1043 | (unsigned char**) &data); |
1044 | |
1045 | if (itemCount) |
1046 | { |
1047 | size += itemCount; |
1048 | string = _glfw_realloc(string, size); |
1049 | string[size - itemCount - 1] = '\0'; |
1050 | strcat(string, data); |
1051 | } |
1052 | |
1053 | if (!itemCount) |
1054 | { |
1055 | if (targets[i] == XA_STRING) |
1056 | { |
1057 | *selectionString = convertLatin1toUTF8(string); |
1058 | _glfw_free(string); |
1059 | } |
1060 | else |
1061 | *selectionString = string; |
1062 | |
1063 | break; |
1064 | } |
1065 | } |
1066 | } |
1067 | else if (actualType == targets[i]) |
1068 | { |
1069 | if (targets[i] == XA_STRING) |
1070 | *selectionString = convertLatin1toUTF8(data); |
1071 | else |
1072 | *selectionString = _glfw_strdup(data); |
1073 | } |
1074 | |
1075 | XFree(data); |
1076 | |
1077 | if (*selectionString) |
1078 | break; |
1079 | } |
1080 | |
1081 | if (!*selectionString) |
1082 | { |
1083 | _glfwInputError(GLFW_FORMAT_UNAVAILABLE, |
1084 | "X11: Failed to convert selection to string" ); |
1085 | } |
1086 | |
1087 | return *selectionString; |
1088 | } |
1089 | |
1090 | // Make the specified window and its video mode active on its monitor |
1091 | // |
1092 | static void acquireMonitor(_GLFWwindow* window) |
1093 | { |
1094 | if (_glfw.x11.saver.count == 0) |
1095 | { |
1096 | // Remember old screen saver settings |
1097 | XGetScreenSaver(_glfw.x11.display, |
1098 | &_glfw.x11.saver.timeout, |
1099 | &_glfw.x11.saver.interval, |
1100 | &_glfw.x11.saver.blanking, |
1101 | &_glfw.x11.saver.exposure); |
1102 | |
1103 | // Disable screen saver |
1104 | XSetScreenSaver(_glfw.x11.display, 0, 0, DontPreferBlanking, |
1105 | DefaultExposures); |
1106 | } |
1107 | |
1108 | if (!window->monitor->window) |
1109 | _glfw.x11.saver.count++; |
1110 | |
1111 | _glfwSetVideoModeX11(window->monitor, &window->videoMode); |
1112 | |
1113 | if (window->x11.overrideRedirect) |
1114 | { |
1115 | int xpos, ypos; |
1116 | GLFWvidmode mode; |
1117 | |
1118 | // Manually position the window over its monitor |
1119 | _glfwGetMonitorPosX11(window->monitor, &xpos, &ypos); |
1120 | _glfwGetVideoModeX11(window->monitor, &mode); |
1121 | |
1122 | XMoveResizeWindow(_glfw.x11.display, window->x11.handle, |
1123 | xpos, ypos, mode.width, mode.height); |
1124 | } |
1125 | |
1126 | _glfwInputMonitorWindow(window->monitor, window); |
1127 | } |
1128 | |
1129 | // Remove the window and restore the original video mode |
1130 | // |
1131 | static void releaseMonitor(_GLFWwindow* window) |
1132 | { |
1133 | if (window->monitor->window != window) |
1134 | return; |
1135 | |
1136 | _glfwInputMonitorWindow(window->monitor, NULL); |
1137 | _glfwRestoreVideoModeX11(window->monitor); |
1138 | |
1139 | _glfw.x11.saver.count--; |
1140 | |
1141 | if (_glfw.x11.saver.count == 0) |
1142 | { |
1143 | // Restore old screen saver settings |
1144 | XSetScreenSaver(_glfw.x11.display, |
1145 | _glfw.x11.saver.timeout, |
1146 | _glfw.x11.saver.interval, |
1147 | _glfw.x11.saver.blanking, |
1148 | _glfw.x11.saver.exposure); |
1149 | } |
1150 | } |
1151 | |
1152 | // Process the specified X event |
1153 | // |
1154 | static void processEvent(XEvent *event) |
1155 | { |
1156 | int keycode = 0; |
1157 | Bool filtered = False; |
1158 | |
1159 | // HACK: Save scancode as some IMs clear the field in XFilterEvent |
1160 | if (event->type == KeyPress || event->type == KeyRelease) |
1161 | keycode = event->xkey.keycode; |
1162 | |
1163 | filtered = XFilterEvent(event, None); |
1164 | |
1165 | if (_glfw.x11.randr.available) |
1166 | { |
1167 | if (event->type == _glfw.x11.randr.eventBase + RRNotify) |
1168 | { |
1169 | XRRUpdateConfiguration(event); |
1170 | _glfwPollMonitorsX11(); |
1171 | return; |
1172 | } |
1173 | } |
1174 | |
1175 | if (_glfw.x11.xkb.available) |
1176 | { |
1177 | if (event->type == _glfw.x11.xkb.eventBase + XkbEventCode) |
1178 | { |
1179 | if (((XkbEvent*) event)->any.xkb_type == XkbStateNotify && |
1180 | (((XkbEvent*) event)->state.changed & XkbGroupStateMask)) |
1181 | { |
1182 | _glfw.x11.xkb.group = ((XkbEvent*) event)->state.group; |
1183 | } |
1184 | |
1185 | return; |
1186 | } |
1187 | } |
1188 | |
1189 | if (event->type == GenericEvent) |
1190 | { |
1191 | if (_glfw.x11.xi.available) |
1192 | { |
1193 | _GLFWwindow* window = _glfw.x11.disabledCursorWindow; |
1194 | |
1195 | if (window && |
1196 | window->rawMouseMotion && |
1197 | event->xcookie.extension == _glfw.x11.xi.majorOpcode && |
1198 | XGetEventData(_glfw.x11.display, &event->xcookie) && |
1199 | event->xcookie.evtype == XI_RawMotion) |
1200 | { |
1201 | XIRawEvent* re = event->xcookie.data; |
1202 | if (re->valuators.mask_len) |
1203 | { |
1204 | const double* values = re->raw_values; |
1205 | double xpos = window->virtualCursorPosX; |
1206 | double ypos = window->virtualCursorPosY; |
1207 | |
1208 | if (XIMaskIsSet(re->valuators.mask, 0)) |
1209 | { |
1210 | xpos += *values; |
1211 | values++; |
1212 | } |
1213 | |
1214 | if (XIMaskIsSet(re->valuators.mask, 1)) |
1215 | ypos += *values; |
1216 | |
1217 | _glfwInputCursorPos(window, xpos, ypos); |
1218 | } |
1219 | } |
1220 | |
1221 | XFreeEventData(_glfw.x11.display, &event->xcookie); |
1222 | } |
1223 | |
1224 | return; |
1225 | } |
1226 | |
1227 | if (event->type == SelectionClear) |
1228 | { |
1229 | handleSelectionClear(event); |
1230 | return; |
1231 | } |
1232 | else if (event->type == SelectionRequest) |
1233 | { |
1234 | handleSelectionRequest(event); |
1235 | return; |
1236 | } |
1237 | |
1238 | _GLFWwindow* window = NULL; |
1239 | if (XFindContext(_glfw.x11.display, |
1240 | event->xany.window, |
1241 | _glfw.x11.context, |
1242 | (XPointer*) &window) != 0) |
1243 | { |
1244 | // This is an event for a window that has already been destroyed |
1245 | return; |
1246 | } |
1247 | |
1248 | switch (event->type) |
1249 | { |
1250 | case ReparentNotify: |
1251 | { |
1252 | window->x11.parent = event->xreparent.parent; |
1253 | return; |
1254 | } |
1255 | |
1256 | case KeyPress: |
1257 | { |
1258 | const int key = translateKey(keycode); |
1259 | const int mods = translateState(event->xkey.state); |
1260 | const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); |
1261 | |
1262 | if (window->x11.ic) |
1263 | { |
1264 | // HACK: Do not report the key press events duplicated by XIM |
1265 | // Duplicate key releases are filtered out implicitly by |
1266 | // the GLFW key repeat logic in _glfwInputKey |
1267 | // A timestamp per key is used to handle simultaneous keys |
1268 | // NOTE: Always allow the first event for each key through |
1269 | // (the server never sends a timestamp of zero) |
1270 | // NOTE: Timestamp difference is compared to handle wrap-around |
1271 | Time diff = event->xkey.time - window->x11.keyPressTimes[keycode]; |
1272 | if (diff == event->xkey.time || (diff > 0 && diff < (1 << 31))) |
1273 | { |
1274 | if (keycode) |
1275 | _glfwInputKey(window, key, keycode, GLFW_PRESS, mods); |
1276 | |
1277 | window->x11.keyPressTimes[keycode] = event->xkey.time; |
1278 | } |
1279 | |
1280 | if (!filtered) |
1281 | { |
1282 | int count; |
1283 | Status status; |
1284 | char buffer[100]; |
1285 | char* chars = buffer; |
1286 | |
1287 | count = Xutf8LookupString(window->x11.ic, |
1288 | &event->xkey, |
1289 | buffer, sizeof(buffer) - 1, |
1290 | NULL, &status); |
1291 | |
1292 | if (status == XBufferOverflow) |
1293 | { |
1294 | chars = _glfw_calloc(count + 1, 1); |
1295 | count = Xutf8LookupString(window->x11.ic, |
1296 | &event->xkey, |
1297 | chars, count, |
1298 | NULL, &status); |
1299 | } |
1300 | |
1301 | if (status == XLookupChars || status == XLookupBoth) |
1302 | { |
1303 | const char* c = chars; |
1304 | chars[count] = '\0'; |
1305 | while (c - chars < count) |
1306 | _glfwInputChar(window, decodeUTF8(&c), mods, plain); |
1307 | } |
1308 | |
1309 | if (chars != buffer) |
1310 | _glfw_free(chars); |
1311 | } |
1312 | } |
1313 | else |
1314 | { |
1315 | KeySym keysym; |
1316 | XLookupString(&event->xkey, NULL, 0, &keysym, NULL); |
1317 | |
1318 | _glfwInputKey(window, key, keycode, GLFW_PRESS, mods); |
1319 | |
1320 | const long character = _glfwKeySym2Unicode(keysym); |
1321 | if (character != -1) |
1322 | _glfwInputChar(window, character, mods, plain); |
1323 | } |
1324 | |
1325 | return; |
1326 | } |
1327 | |
1328 | case KeyRelease: |
1329 | { |
1330 | const int key = translateKey(keycode); |
1331 | const int mods = translateState(event->xkey.state); |
1332 | |
1333 | if (!_glfw.x11.xkb.detectable) |
1334 | { |
1335 | // HACK: Key repeat events will arrive as KeyRelease/KeyPress |
1336 | // pairs with similar or identical time stamps |
1337 | // The key repeat logic in _glfwInputKey expects only key |
1338 | // presses to repeat, so detect and discard release events |
1339 | if (XEventsQueued(_glfw.x11.display, QueuedAfterReading)) |
1340 | { |
1341 | XEvent next; |
1342 | XPeekEvent(_glfw.x11.display, &next); |
1343 | |
1344 | if (next.type == KeyPress && |
1345 | next.xkey.window == event->xkey.window && |
1346 | next.xkey.keycode == keycode) |
1347 | { |
1348 | // HACK: The time of repeat events sometimes doesn't |
1349 | // match that of the press event, so add an |
1350 | // epsilon |
1351 | // Toshiyuki Takahashi can press a button |
1352 | // 16 times per second so it's fairly safe to |
1353 | // assume that no human is pressing the key 50 |
1354 | // times per second (value is ms) |
1355 | if ((next.xkey.time - event->xkey.time) < 20) |
1356 | { |
1357 | // This is very likely a server-generated key repeat |
1358 | // event, so ignore it |
1359 | return; |
1360 | } |
1361 | } |
1362 | } |
1363 | } |
1364 | |
1365 | _glfwInputKey(window, key, keycode, GLFW_RELEASE, mods); |
1366 | return; |
1367 | } |
1368 | |
1369 | case ButtonPress: |
1370 | { |
1371 | const int mods = translateState(event->xbutton.state); |
1372 | |
1373 | if (event->xbutton.button == Button1) |
1374 | _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, mods); |
1375 | else if (event->xbutton.button == Button2) |
1376 | _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, mods); |
1377 | else if (event->xbutton.button == Button3) |
1378 | _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, mods); |
1379 | |
1380 | // Modern X provides scroll events as mouse button presses |
1381 | else if (event->xbutton.button == Button4) |
1382 | _glfwInputScroll(window, 0.0, 1.0); |
1383 | else if (event->xbutton.button == Button5) |
1384 | _glfwInputScroll(window, 0.0, -1.0); |
1385 | else if (event->xbutton.button == Button6) |
1386 | _glfwInputScroll(window, 1.0, 0.0); |
1387 | else if (event->xbutton.button == Button7) |
1388 | _glfwInputScroll(window, -1.0, 0.0); |
1389 | |
1390 | else |
1391 | { |
1392 | // Additional buttons after 7 are treated as regular buttons |
1393 | // We subtract 4 to fill the gap left by scroll input above |
1394 | _glfwInputMouseClick(window, |
1395 | event->xbutton.button - Button1 - 4, |
1396 | GLFW_PRESS, |
1397 | mods); |
1398 | } |
1399 | |
1400 | return; |
1401 | } |
1402 | |
1403 | case ButtonRelease: |
1404 | { |
1405 | const int mods = translateState(event->xbutton.state); |
1406 | |
1407 | if (event->xbutton.button == Button1) |
1408 | { |
1409 | _glfwInputMouseClick(window, |
1410 | GLFW_MOUSE_BUTTON_LEFT, |
1411 | GLFW_RELEASE, |
1412 | mods); |
1413 | } |
1414 | else if (event->xbutton.button == Button2) |
1415 | { |
1416 | _glfwInputMouseClick(window, |
1417 | GLFW_MOUSE_BUTTON_MIDDLE, |
1418 | GLFW_RELEASE, |
1419 | mods); |
1420 | } |
1421 | else if (event->xbutton.button == Button3) |
1422 | { |
1423 | _glfwInputMouseClick(window, |
1424 | GLFW_MOUSE_BUTTON_RIGHT, |
1425 | GLFW_RELEASE, |
1426 | mods); |
1427 | } |
1428 | else if (event->xbutton.button > Button7) |
1429 | { |
1430 | // Additional buttons after 7 are treated as regular buttons |
1431 | // We subtract 4 to fill the gap left by scroll input above |
1432 | _glfwInputMouseClick(window, |
1433 | event->xbutton.button - Button1 - 4, |
1434 | GLFW_RELEASE, |
1435 | mods); |
1436 | } |
1437 | |
1438 | return; |
1439 | } |
1440 | |
1441 | case EnterNotify: |
1442 | { |
1443 | // XEnterWindowEvent is XCrossingEvent |
1444 | const int x = event->xcrossing.x; |
1445 | const int y = event->xcrossing.y; |
1446 | |
1447 | // HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise |
1448 | // ignore the defined cursor for hidden cursor mode |
1449 | if (window->cursorMode == GLFW_CURSOR_HIDDEN) |
1450 | updateCursorImage(window); |
1451 | |
1452 | _glfwInputCursorEnter(window, GLFW_TRUE); |
1453 | _glfwInputCursorPos(window, x, y); |
1454 | |
1455 | window->x11.lastCursorPosX = x; |
1456 | window->x11.lastCursorPosY = y; |
1457 | return; |
1458 | } |
1459 | |
1460 | case LeaveNotify: |
1461 | { |
1462 | _glfwInputCursorEnter(window, GLFW_FALSE); |
1463 | return; |
1464 | } |
1465 | |
1466 | case MotionNotify: |
1467 | { |
1468 | const int x = event->xmotion.x; |
1469 | const int y = event->xmotion.y; |
1470 | |
1471 | if (x != window->x11.warpCursorPosX || |
1472 | y != window->x11.warpCursorPosY) |
1473 | { |
1474 | // The cursor was moved by something other than GLFW |
1475 | |
1476 | if (window->cursorMode == GLFW_CURSOR_DISABLED) |
1477 | { |
1478 | if (_glfw.x11.disabledCursorWindow != window) |
1479 | return; |
1480 | if (window->rawMouseMotion) |
1481 | return; |
1482 | |
1483 | const int dx = x - window->x11.lastCursorPosX; |
1484 | const int dy = y - window->x11.lastCursorPosY; |
1485 | |
1486 | _glfwInputCursorPos(window, |
1487 | window->virtualCursorPosX + dx, |
1488 | window->virtualCursorPosY + dy); |
1489 | } |
1490 | else |
1491 | _glfwInputCursorPos(window, x, y); |
1492 | } |
1493 | |
1494 | window->x11.lastCursorPosX = x; |
1495 | window->x11.lastCursorPosY = y; |
1496 | return; |
1497 | } |
1498 | |
1499 | case ConfigureNotify: |
1500 | { |
1501 | if (event->xconfigure.width != window->x11.width || |
1502 | event->xconfigure.height != window->x11.height) |
1503 | { |
1504 | _glfwInputFramebufferSize(window, |
1505 | event->xconfigure.width, |
1506 | event->xconfigure.height); |
1507 | |
1508 | _glfwInputWindowSize(window, |
1509 | event->xconfigure.width, |
1510 | event->xconfigure.height); |
1511 | |
1512 | window->x11.width = event->xconfigure.width; |
1513 | window->x11.height = event->xconfigure.height; |
1514 | } |
1515 | |
1516 | int xpos = event->xconfigure.x; |
1517 | int ypos = event->xconfigure.y; |
1518 | |
1519 | // NOTE: ConfigureNotify events from the server are in local |
1520 | // coordinates, so if we are reparented we need to translate |
1521 | // the position into root (screen) coordinates |
1522 | if (!event->xany.send_event && window->x11.parent != _glfw.x11.root) |
1523 | { |
1524 | _glfwGrabErrorHandlerX11(); |
1525 | |
1526 | Window dummy; |
1527 | XTranslateCoordinates(_glfw.x11.display, |
1528 | window->x11.parent, |
1529 | _glfw.x11.root, |
1530 | xpos, ypos, |
1531 | &xpos, &ypos, |
1532 | &dummy); |
1533 | |
1534 | _glfwReleaseErrorHandlerX11(); |
1535 | if (_glfw.x11.errorCode == BadWindow) |
1536 | return; |
1537 | } |
1538 | |
1539 | if (xpos != window->x11.xpos || ypos != window->x11.ypos) |
1540 | { |
1541 | _glfwInputWindowPos(window, xpos, ypos); |
1542 | window->x11.xpos = xpos; |
1543 | window->x11.ypos = ypos; |
1544 | } |
1545 | |
1546 | return; |
1547 | } |
1548 | |
1549 | case ClientMessage: |
1550 | { |
1551 | // Custom client message, probably from the window manager |
1552 | |
1553 | if (filtered) |
1554 | return; |
1555 | |
1556 | if (event->xclient.message_type == None) |
1557 | return; |
1558 | |
1559 | if (event->xclient.message_type == _glfw.x11.WM_PROTOCOLS) |
1560 | { |
1561 | const Atom protocol = event->xclient.data.l[0]; |
1562 | if (protocol == None) |
1563 | return; |
1564 | |
1565 | if (protocol == _glfw.x11.WM_DELETE_WINDOW) |
1566 | { |
1567 | // The window manager was asked to close the window, for |
1568 | // example by the user pressing a 'close' window decoration |
1569 | // button |
1570 | _glfwInputWindowCloseRequest(window); |
1571 | } |
1572 | else if (protocol == _glfw.x11.NET_WM_PING) |
1573 | { |
1574 | // The window manager is pinging the application to ensure |
1575 | // it's still responding to events |
1576 | |
1577 | XEvent reply = *event; |
1578 | reply.xclient.window = _glfw.x11.root; |
1579 | |
1580 | XSendEvent(_glfw.x11.display, _glfw.x11.root, |
1581 | False, |
1582 | SubstructureNotifyMask | SubstructureRedirectMask, |
1583 | &reply); |
1584 | } |
1585 | } |
1586 | else if (event->xclient.message_type == _glfw.x11.XdndEnter) |
1587 | { |
1588 | // A drag operation has entered the window |
1589 | unsigned long count; |
1590 | Atom* formats = NULL; |
1591 | const GLFWbool list = event->xclient.data.l[1] & 1; |
1592 | |
1593 | _glfw.x11.xdnd.source = event->xclient.data.l[0]; |
1594 | _glfw.x11.xdnd.version = event->xclient.data.l[1] >> 24; |
1595 | _glfw.x11.xdnd.format = None; |
1596 | |
1597 | if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) |
1598 | return; |
1599 | |
1600 | if (list) |
1601 | { |
1602 | count = _glfwGetWindowPropertyX11(_glfw.x11.xdnd.source, |
1603 | _glfw.x11.XdndTypeList, |
1604 | XA_ATOM, |
1605 | (unsigned char**) &formats); |
1606 | } |
1607 | else |
1608 | { |
1609 | count = 3; |
1610 | formats = (Atom*) event->xclient.data.l + 2; |
1611 | } |
1612 | |
1613 | for (unsigned int i = 0; i < count; i++) |
1614 | { |
1615 | if (formats[i] == _glfw.x11.text_uri_list) |
1616 | { |
1617 | _glfw.x11.xdnd.format = _glfw.x11.text_uri_list; |
1618 | break; |
1619 | } |
1620 | } |
1621 | |
1622 | if (list && formats) |
1623 | XFree(formats); |
1624 | } |
1625 | else if (event->xclient.message_type == _glfw.x11.XdndDrop) |
1626 | { |
1627 | // The drag operation has finished by dropping on the window |
1628 | Time time = CurrentTime; |
1629 | |
1630 | if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) |
1631 | return; |
1632 | |
1633 | if (_glfw.x11.xdnd.format) |
1634 | { |
1635 | if (_glfw.x11.xdnd.version >= 1) |
1636 | time = event->xclient.data.l[2]; |
1637 | |
1638 | // Request the chosen format from the source window |
1639 | XConvertSelection(_glfw.x11.display, |
1640 | _glfw.x11.XdndSelection, |
1641 | _glfw.x11.xdnd.format, |
1642 | _glfw.x11.XdndSelection, |
1643 | window->x11.handle, |
1644 | time); |
1645 | } |
1646 | else if (_glfw.x11.xdnd.version >= 2) |
1647 | { |
1648 | XEvent reply = { ClientMessage }; |
1649 | reply.xclient.window = _glfw.x11.xdnd.source; |
1650 | reply.xclient.message_type = _glfw.x11.XdndFinished; |
1651 | reply.xclient.format = 32; |
1652 | reply.xclient.data.l[0] = window->x11.handle; |
1653 | reply.xclient.data.l[1] = 0; // The drag was rejected |
1654 | reply.xclient.data.l[2] = None; |
1655 | |
1656 | XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, |
1657 | False, NoEventMask, &reply); |
1658 | XFlush(_glfw.x11.display); |
1659 | } |
1660 | } |
1661 | else if (event->xclient.message_type == _glfw.x11.XdndPosition) |
1662 | { |
1663 | // The drag operation has moved over the window |
1664 | const int xabs = (event->xclient.data.l[2] >> 16) & 0xffff; |
1665 | const int yabs = (event->xclient.data.l[2]) & 0xffff; |
1666 | Window dummy; |
1667 | int xpos, ypos; |
1668 | |
1669 | if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) |
1670 | return; |
1671 | |
1672 | XTranslateCoordinates(_glfw.x11.display, |
1673 | _glfw.x11.root, |
1674 | window->x11.handle, |
1675 | xabs, yabs, |
1676 | &xpos, &ypos, |
1677 | &dummy); |
1678 | |
1679 | _glfwInputCursorPos(window, xpos, ypos); |
1680 | |
1681 | XEvent reply = { ClientMessage }; |
1682 | reply.xclient.window = _glfw.x11.xdnd.source; |
1683 | reply.xclient.message_type = _glfw.x11.XdndStatus; |
1684 | reply.xclient.format = 32; |
1685 | reply.xclient.data.l[0] = window->x11.handle; |
1686 | reply.xclient.data.l[2] = 0; // Specify an empty rectangle |
1687 | reply.xclient.data.l[3] = 0; |
1688 | |
1689 | if (_glfw.x11.xdnd.format) |
1690 | { |
1691 | // Reply that we are ready to copy the dragged data |
1692 | reply.xclient.data.l[1] = 1; // Accept with no rectangle |
1693 | if (_glfw.x11.xdnd.version >= 2) |
1694 | reply.xclient.data.l[4] = _glfw.x11.XdndActionCopy; |
1695 | } |
1696 | |
1697 | XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, |
1698 | False, NoEventMask, &reply); |
1699 | XFlush(_glfw.x11.display); |
1700 | } |
1701 | |
1702 | return; |
1703 | } |
1704 | |
1705 | case SelectionNotify: |
1706 | { |
1707 | if (event->xselection.property == _glfw.x11.XdndSelection) |
1708 | { |
1709 | // The converted data from the drag operation has arrived |
1710 | char* data; |
1711 | const unsigned long result = |
1712 | _glfwGetWindowPropertyX11(event->xselection.requestor, |
1713 | event->xselection.property, |
1714 | event->xselection.target, |
1715 | (unsigned char**) &data); |
1716 | |
1717 | if (result) |
1718 | { |
1719 | int count; |
1720 | char** paths = parseUriList(data, &count); |
1721 | |
1722 | _glfwInputDrop(window, count, (const char**) paths); |
1723 | |
1724 | for (int i = 0; i < count; i++) |
1725 | _glfw_free(paths[i]); |
1726 | _glfw_free(paths); |
1727 | } |
1728 | |
1729 | if (data) |
1730 | XFree(data); |
1731 | |
1732 | if (_glfw.x11.xdnd.version >= 2) |
1733 | { |
1734 | XEvent reply = { ClientMessage }; |
1735 | reply.xclient.window = _glfw.x11.xdnd.source; |
1736 | reply.xclient.message_type = _glfw.x11.XdndFinished; |
1737 | reply.xclient.format = 32; |
1738 | reply.xclient.data.l[0] = window->x11.handle; |
1739 | reply.xclient.data.l[1] = result; |
1740 | reply.xclient.data.l[2] = _glfw.x11.XdndActionCopy; |
1741 | |
1742 | XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, |
1743 | False, NoEventMask, &reply); |
1744 | XFlush(_glfw.x11.display); |
1745 | } |
1746 | } |
1747 | |
1748 | return; |
1749 | } |
1750 | |
1751 | case FocusIn: |
1752 | { |
1753 | if (event->xfocus.mode == NotifyGrab || |
1754 | event->xfocus.mode == NotifyUngrab) |
1755 | { |
1756 | // Ignore focus events from popup indicator windows, window menu |
1757 | // key chords and window dragging |
1758 | return; |
1759 | } |
1760 | |
1761 | if (window->cursorMode == GLFW_CURSOR_DISABLED) |
1762 | disableCursor(window); |
1763 | |
1764 | if (window->x11.ic) |
1765 | XSetICFocus(window->x11.ic); |
1766 | |
1767 | _glfwInputWindowFocus(window, GLFW_TRUE); |
1768 | return; |
1769 | } |
1770 | |
1771 | case FocusOut: |
1772 | { |
1773 | if (event->xfocus.mode == NotifyGrab || |
1774 | event->xfocus.mode == NotifyUngrab) |
1775 | { |
1776 | // Ignore focus events from popup indicator windows, window menu |
1777 | // key chords and window dragging |
1778 | return; |
1779 | } |
1780 | |
1781 | if (window->cursorMode == GLFW_CURSOR_DISABLED) |
1782 | enableCursor(window); |
1783 | |
1784 | if (window->x11.ic) |
1785 | XUnsetICFocus(window->x11.ic); |
1786 | |
1787 | if (window->monitor && window->autoIconify) |
1788 | _glfwIconifyWindowX11(window); |
1789 | |
1790 | _glfwInputWindowFocus(window, GLFW_FALSE); |
1791 | return; |
1792 | } |
1793 | |
1794 | case Expose: |
1795 | { |
1796 | _glfwInputWindowDamage(window); |
1797 | return; |
1798 | } |
1799 | |
1800 | case PropertyNotify: |
1801 | { |
1802 | if (event->xproperty.state != PropertyNewValue) |
1803 | return; |
1804 | |
1805 | if (event->xproperty.atom == _glfw.x11.WM_STATE) |
1806 | { |
1807 | const int state = getWindowState(window); |
1808 | if (state != IconicState && state != NormalState) |
1809 | return; |
1810 | |
1811 | const GLFWbool iconified = (state == IconicState); |
1812 | if (window->x11.iconified != iconified) |
1813 | { |
1814 | if (window->monitor) |
1815 | { |
1816 | if (iconified) |
1817 | releaseMonitor(window); |
1818 | else |
1819 | acquireMonitor(window); |
1820 | } |
1821 | |
1822 | window->x11.iconified = iconified; |
1823 | _glfwInputWindowIconify(window, iconified); |
1824 | } |
1825 | } |
1826 | else if (event->xproperty.atom == _glfw.x11.NET_WM_STATE) |
1827 | { |
1828 | const GLFWbool maximized = _glfwWindowMaximizedX11(window); |
1829 | if (window->x11.maximized != maximized) |
1830 | { |
1831 | window->x11.maximized = maximized; |
1832 | _glfwInputWindowMaximize(window, maximized); |
1833 | } |
1834 | } |
1835 | |
1836 | return; |
1837 | } |
1838 | |
1839 | case DestroyNotify: |
1840 | return; |
1841 | } |
1842 | } |
1843 | |
1844 | |
1845 | ////////////////////////////////////////////////////////////////////////// |
1846 | ////// GLFW internal API ////// |
1847 | ////////////////////////////////////////////////////////////////////////// |
1848 | |
1849 | // Retrieve a single window property of the specified type |
1850 | // Inspired by fghGetWindowProperty from freeglut |
1851 | // |
1852 | unsigned long _glfwGetWindowPropertyX11(Window window, |
1853 | Atom property, |
1854 | Atom type, |
1855 | unsigned char** value) |
1856 | { |
1857 | Atom actualType; |
1858 | int actualFormat; |
1859 | unsigned long itemCount, bytesAfter; |
1860 | |
1861 | XGetWindowProperty(_glfw.x11.display, |
1862 | window, |
1863 | property, |
1864 | 0, |
1865 | LONG_MAX, |
1866 | False, |
1867 | type, |
1868 | &actualType, |
1869 | &actualFormat, |
1870 | &itemCount, |
1871 | &bytesAfter, |
1872 | value); |
1873 | |
1874 | return itemCount; |
1875 | } |
1876 | |
1877 | GLFWbool _glfwIsVisualTransparentX11(Visual* visual) |
1878 | { |
1879 | if (!_glfw.x11.xrender.available) |
1880 | return GLFW_FALSE; |
1881 | |
1882 | XRenderPictFormat* pf = XRenderFindVisualFormat(_glfw.x11.display, visual); |
1883 | return pf && pf->direct.alphaMask; |
1884 | } |
1885 | |
1886 | // Push contents of our selection to clipboard manager |
1887 | // |
1888 | void _glfwPushSelectionToManagerX11(void) |
1889 | { |
1890 | XConvertSelection(_glfw.x11.display, |
1891 | _glfw.x11.CLIPBOARD_MANAGER, |
1892 | _glfw.x11.SAVE_TARGETS, |
1893 | None, |
1894 | _glfw.x11.helperWindowHandle, |
1895 | CurrentTime); |
1896 | |
1897 | for (;;) |
1898 | { |
1899 | XEvent event; |
1900 | |
1901 | while (XCheckIfEvent(_glfw.x11.display, &event, isSelectionEvent, NULL)) |
1902 | { |
1903 | switch (event.type) |
1904 | { |
1905 | case SelectionRequest: |
1906 | handleSelectionRequest(&event); |
1907 | break; |
1908 | |
1909 | case SelectionClear: |
1910 | handleSelectionClear(&event); |
1911 | break; |
1912 | |
1913 | case SelectionNotify: |
1914 | { |
1915 | if (event.xselection.target == _glfw.x11.SAVE_TARGETS) |
1916 | { |
1917 | // This means one of two things; either the selection |
1918 | // was not owned, which means there is no clipboard |
1919 | // manager, or the transfer to the clipboard manager has |
1920 | // completed |
1921 | // In either case, it means we are done here |
1922 | return; |
1923 | } |
1924 | |
1925 | break; |
1926 | } |
1927 | } |
1928 | } |
1929 | |
1930 | waitForEvent(NULL); |
1931 | } |
1932 | } |
1933 | |
1934 | void _glfwCreateInputContextX11(_GLFWwindow* window) |
1935 | { |
1936 | XIMCallback callback; |
1937 | callback.callback = (XIMProc) inputContextDestroyCallback; |
1938 | callback.client_data = (XPointer) window; |
1939 | |
1940 | window->x11.ic = XCreateIC(_glfw.x11.im, |
1941 | XNInputStyle, |
1942 | XIMPreeditNothing | XIMStatusNothing, |
1943 | XNClientWindow, |
1944 | window->x11.handle, |
1945 | XNFocusWindow, |
1946 | window->x11.handle, |
1947 | XNDestroyCallback, |
1948 | &callback, |
1949 | NULL); |
1950 | |
1951 | if (window->x11.ic) |
1952 | { |
1953 | XWindowAttributes attribs; |
1954 | XGetWindowAttributes(_glfw.x11.display, window->x11.handle, &attribs); |
1955 | |
1956 | unsigned long filter = 0; |
1957 | if (XGetICValues(window->x11.ic, XNFilterEvents, &filter, NULL) == NULL) |
1958 | { |
1959 | XSelectInput(_glfw.x11.display, |
1960 | window->x11.handle, |
1961 | attribs.your_event_mask | filter); |
1962 | } |
1963 | } |
1964 | } |
1965 | |
1966 | |
1967 | ////////////////////////////////////////////////////////////////////////// |
1968 | ////// GLFW platform API ////// |
1969 | ////////////////////////////////////////////////////////////////////////// |
1970 | |
1971 | int _glfwCreateWindowX11(_GLFWwindow* window, |
1972 | const _GLFWwndconfig* wndconfig, |
1973 | const _GLFWctxconfig* ctxconfig, |
1974 | const _GLFWfbconfig* fbconfig) |
1975 | { |
1976 | Visual* visual = NULL; |
1977 | int depth; |
1978 | |
1979 | if (ctxconfig->client != GLFW_NO_API) |
1980 | { |
1981 | if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) |
1982 | { |
1983 | if (!_glfwInitGLX()) |
1984 | return GLFW_FALSE; |
1985 | if (!_glfwChooseVisualGLX(wndconfig, ctxconfig, fbconfig, &visual, &depth)) |
1986 | return GLFW_FALSE; |
1987 | } |
1988 | else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) |
1989 | { |
1990 | if (!_glfwInitEGL()) |
1991 | return GLFW_FALSE; |
1992 | if (!_glfwChooseVisualEGL(wndconfig, ctxconfig, fbconfig, &visual, &depth)) |
1993 | return GLFW_FALSE; |
1994 | } |
1995 | else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) |
1996 | { |
1997 | if (!_glfwInitOSMesa()) |
1998 | return GLFW_FALSE; |
1999 | } |
2000 | } |
2001 | |
2002 | if (!visual) |
2003 | { |
2004 | visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen); |
2005 | depth = DefaultDepth(_glfw.x11.display, _glfw.x11.screen); |
2006 | } |
2007 | |
2008 | if (!createNativeWindow(window, wndconfig, visual, depth)) |
2009 | return GLFW_FALSE; |
2010 | |
2011 | if (ctxconfig->client != GLFW_NO_API) |
2012 | { |
2013 | if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) |
2014 | { |
2015 | if (!_glfwCreateContextGLX(window, ctxconfig, fbconfig)) |
2016 | return GLFW_FALSE; |
2017 | } |
2018 | else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) |
2019 | { |
2020 | if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) |
2021 | return GLFW_FALSE; |
2022 | } |
2023 | else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) |
2024 | { |
2025 | if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) |
2026 | return GLFW_FALSE; |
2027 | } |
2028 | } |
2029 | |
2030 | if (window->monitor) |
2031 | { |
2032 | _glfwShowWindowX11(window); |
2033 | updateWindowMode(window); |
2034 | acquireMonitor(window); |
2035 | } |
2036 | |
2037 | XFlush(_glfw.x11.display); |
2038 | return GLFW_TRUE; |
2039 | } |
2040 | |
2041 | void _glfwDestroyWindowX11(_GLFWwindow* window) |
2042 | { |
2043 | if (_glfw.x11.disabledCursorWindow == window) |
2044 | _glfw.x11.disabledCursorWindow = NULL; |
2045 | |
2046 | if (window->monitor) |
2047 | releaseMonitor(window); |
2048 | |
2049 | if (window->x11.ic) |
2050 | { |
2051 | XDestroyIC(window->x11.ic); |
2052 | window->x11.ic = NULL; |
2053 | } |
2054 | |
2055 | if (window->context.destroy) |
2056 | window->context.destroy(window); |
2057 | |
2058 | if (window->x11.handle) |
2059 | { |
2060 | XDeleteContext(_glfw.x11.display, window->x11.handle, _glfw.x11.context); |
2061 | XUnmapWindow(_glfw.x11.display, window->x11.handle); |
2062 | XDestroyWindow(_glfw.x11.display, window->x11.handle); |
2063 | window->x11.handle = (Window) 0; |
2064 | } |
2065 | |
2066 | if (window->x11.colormap) |
2067 | { |
2068 | XFreeColormap(_glfw.x11.display, window->x11.colormap); |
2069 | window->x11.colormap = (Colormap) 0; |
2070 | } |
2071 | |
2072 | XFlush(_glfw.x11.display); |
2073 | } |
2074 | |
2075 | void _glfwSetWindowTitleX11(_GLFWwindow* window, const char* title) |
2076 | { |
2077 | if (_glfw.x11.xlib.utf8) |
2078 | { |
2079 | Xutf8SetWMProperties(_glfw.x11.display, |
2080 | window->x11.handle, |
2081 | title, title, |
2082 | NULL, 0, |
2083 | NULL, NULL, NULL); |
2084 | } |
2085 | |
2086 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
2087 | _glfw.x11.NET_WM_NAME, _glfw.x11.UTF8_STRING, 8, |
2088 | PropModeReplace, |
2089 | (unsigned char*) title, strlen(title)); |
2090 | |
2091 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
2092 | _glfw.x11.NET_WM_ICON_NAME, _glfw.x11.UTF8_STRING, 8, |
2093 | PropModeReplace, |
2094 | (unsigned char*) title, strlen(title)); |
2095 | |
2096 | XFlush(_glfw.x11.display); |
2097 | } |
2098 | |
2099 | void _glfwSetWindowIconX11(_GLFWwindow* window, int count, const GLFWimage* images) |
2100 | { |
2101 | if (count) |
2102 | { |
2103 | int longCount = 0; |
2104 | |
2105 | for (int i = 0; i < count; i++) |
2106 | longCount += 2 + images[i].width * images[i].height; |
2107 | |
2108 | long* icon = _glfw_calloc(longCount, sizeof(long)); |
2109 | long* target = icon; |
2110 | |
2111 | for (int i = 0; i < count; i++) |
2112 | { |
2113 | *target++ = images[i].width; |
2114 | *target++ = images[i].height; |
2115 | |
2116 | for (int j = 0; j < images[i].width * images[i].height; j++) |
2117 | { |
2118 | *target++ = (images[i].pixels[j * 4 + 0] << 16) | |
2119 | (images[i].pixels[j * 4 + 1] << 8) | |
2120 | (images[i].pixels[j * 4 + 2] << 0) | |
2121 | (images[i].pixels[j * 4 + 3] << 24); |
2122 | } |
2123 | } |
2124 | |
2125 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
2126 | _glfw.x11.NET_WM_ICON, |
2127 | XA_CARDINAL, 32, |
2128 | PropModeReplace, |
2129 | (unsigned char*) icon, |
2130 | longCount); |
2131 | |
2132 | _glfw_free(icon); |
2133 | } |
2134 | else |
2135 | { |
2136 | XDeleteProperty(_glfw.x11.display, window->x11.handle, |
2137 | _glfw.x11.NET_WM_ICON); |
2138 | } |
2139 | |
2140 | XFlush(_glfw.x11.display); |
2141 | } |
2142 | |
2143 | void _glfwGetWindowPosX11(_GLFWwindow* window, int* xpos, int* ypos) |
2144 | { |
2145 | Window dummy; |
2146 | int x, y; |
2147 | |
2148 | XTranslateCoordinates(_glfw.x11.display, window->x11.handle, _glfw.x11.root, |
2149 | 0, 0, &x, &y, &dummy); |
2150 | |
2151 | if (xpos) |
2152 | *xpos = x; |
2153 | if (ypos) |
2154 | *ypos = y; |
2155 | } |
2156 | |
2157 | void _glfwSetWindowPosX11(_GLFWwindow* window, int xpos, int ypos) |
2158 | { |
2159 | // HACK: Explicitly setting PPosition to any value causes some WMs, notably |
2160 | // Compiz and Metacity, to honor the position of unmapped windows |
2161 | if (!_glfwWindowVisibleX11(window)) |
2162 | { |
2163 | long supplied; |
2164 | XSizeHints* hints = XAllocSizeHints(); |
2165 | |
2166 | if (XGetWMNormalHints(_glfw.x11.display, window->x11.handle, hints, &supplied)) |
2167 | { |
2168 | hints->flags |= PPosition; |
2169 | hints->x = hints->y = 0; |
2170 | |
2171 | XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); |
2172 | } |
2173 | |
2174 | XFree(hints); |
2175 | } |
2176 | |
2177 | XMoveWindow(_glfw.x11.display, window->x11.handle, xpos, ypos); |
2178 | XFlush(_glfw.x11.display); |
2179 | } |
2180 | |
2181 | void _glfwGetWindowSizeX11(_GLFWwindow* window, int* width, int* height) |
2182 | { |
2183 | XWindowAttributes attribs; |
2184 | XGetWindowAttributes(_glfw.x11.display, window->x11.handle, &attribs); |
2185 | |
2186 | if (width) |
2187 | *width = attribs.width; |
2188 | if (height) |
2189 | *height = attribs.height; |
2190 | } |
2191 | |
2192 | void _glfwSetWindowSizeX11(_GLFWwindow* window, int width, int height) |
2193 | { |
2194 | if (window->monitor) |
2195 | { |
2196 | if (window->monitor->window == window) |
2197 | acquireMonitor(window); |
2198 | } |
2199 | else |
2200 | { |
2201 | if (!window->resizable) |
2202 | updateNormalHints(window, width, height); |
2203 | |
2204 | XResizeWindow(_glfw.x11.display, window->x11.handle, width, height); |
2205 | } |
2206 | |
2207 | XFlush(_glfw.x11.display); |
2208 | } |
2209 | |
2210 | void _glfwSetWindowSizeLimitsX11(_GLFWwindow* window, |
2211 | int minwidth, int minheight, |
2212 | int maxwidth, int maxheight) |
2213 | { |
2214 | int width, height; |
2215 | _glfwGetWindowSizeX11(window, &width, &height); |
2216 | updateNormalHints(window, width, height); |
2217 | XFlush(_glfw.x11.display); |
2218 | } |
2219 | |
2220 | void _glfwSetWindowAspectRatioX11(_GLFWwindow* window, int numer, int denom) |
2221 | { |
2222 | int width, height; |
2223 | _glfwGetWindowSizeX11(window, &width, &height); |
2224 | updateNormalHints(window, width, height); |
2225 | XFlush(_glfw.x11.display); |
2226 | } |
2227 | |
2228 | void _glfwGetFramebufferSizeX11(_GLFWwindow* window, int* width, int* height) |
2229 | { |
2230 | _glfwGetWindowSizeX11(window, width, height); |
2231 | } |
2232 | |
2233 | void _glfwGetWindowFrameSizeX11(_GLFWwindow* window, |
2234 | int* left, int* top, |
2235 | int* right, int* bottom) |
2236 | { |
2237 | long* extents = NULL; |
2238 | |
2239 | if (window->monitor || !window->decorated) |
2240 | return; |
2241 | |
2242 | if (_glfw.x11.NET_FRAME_EXTENTS == None) |
2243 | return; |
2244 | |
2245 | if (!_glfwWindowVisibleX11(window) && |
2246 | _glfw.x11.NET_REQUEST_FRAME_EXTENTS) |
2247 | { |
2248 | XEvent event; |
2249 | double timeout = 0.5; |
2250 | |
2251 | // Ensure _NET_FRAME_EXTENTS is set, allowing glfwGetWindowFrameSize to |
2252 | // function before the window is mapped |
2253 | sendEventToWM(window, _glfw.x11.NET_REQUEST_FRAME_EXTENTS, |
2254 | 0, 0, 0, 0, 0); |
2255 | |
2256 | // HACK: Use a timeout because earlier versions of some window managers |
2257 | // (at least Unity, Fluxbox and Xfwm) failed to send the reply |
2258 | // They have been fixed but broken versions are still in the wild |
2259 | // If you are affected by this and your window manager is NOT |
2260 | // listed above, PLEASE report it to their and our issue trackers |
2261 | while (!XCheckIfEvent(_glfw.x11.display, |
2262 | &event, |
2263 | isFrameExtentsEvent, |
2264 | (XPointer) window)) |
2265 | { |
2266 | if (!waitForEvent(&timeout)) |
2267 | { |
2268 | _glfwInputError(GLFW_PLATFORM_ERROR, |
2269 | "X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue" ); |
2270 | return; |
2271 | } |
2272 | } |
2273 | } |
2274 | |
2275 | if (_glfwGetWindowPropertyX11(window->x11.handle, |
2276 | _glfw.x11.NET_FRAME_EXTENTS, |
2277 | XA_CARDINAL, |
2278 | (unsigned char**) &extents) == 4) |
2279 | { |
2280 | if (left) |
2281 | *left = extents[0]; |
2282 | if (top) |
2283 | *top = extents[2]; |
2284 | if (right) |
2285 | *right = extents[1]; |
2286 | if (bottom) |
2287 | *bottom = extents[3]; |
2288 | } |
2289 | |
2290 | if (extents) |
2291 | XFree(extents); |
2292 | } |
2293 | |
2294 | void _glfwGetWindowContentScaleX11(_GLFWwindow* window, float* xscale, float* yscale) |
2295 | { |
2296 | if (xscale) |
2297 | *xscale = _glfw.x11.contentScaleX; |
2298 | if (yscale) |
2299 | *yscale = _glfw.x11.contentScaleY; |
2300 | } |
2301 | |
2302 | void _glfwIconifyWindowX11(_GLFWwindow* window) |
2303 | { |
2304 | if (window->x11.overrideRedirect) |
2305 | { |
2306 | // Override-redirect windows cannot be iconified or restored, as those |
2307 | // tasks are performed by the window manager |
2308 | _glfwInputError(GLFW_PLATFORM_ERROR, |
2309 | "X11: Iconification of full screen windows requires a WM that supports EWMH full screen" ); |
2310 | return; |
2311 | } |
2312 | |
2313 | XIconifyWindow(_glfw.x11.display, window->x11.handle, _glfw.x11.screen); |
2314 | XFlush(_glfw.x11.display); |
2315 | } |
2316 | |
2317 | void _glfwRestoreWindowX11(_GLFWwindow* window) |
2318 | { |
2319 | if (window->x11.overrideRedirect) |
2320 | { |
2321 | // Override-redirect windows cannot be iconified or restored, as those |
2322 | // tasks are performed by the window manager |
2323 | _glfwInputError(GLFW_PLATFORM_ERROR, |
2324 | "X11: Iconification of full screen windows requires a WM that supports EWMH full screen" ); |
2325 | return; |
2326 | } |
2327 | |
2328 | if (_glfwWindowIconifiedX11(window)) |
2329 | { |
2330 | XMapWindow(_glfw.x11.display, window->x11.handle); |
2331 | waitForVisibilityNotify(window); |
2332 | } |
2333 | else if (_glfwWindowVisibleX11(window)) |
2334 | { |
2335 | if (_glfw.x11.NET_WM_STATE && |
2336 | _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT && |
2337 | _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) |
2338 | { |
2339 | sendEventToWM(window, |
2340 | _glfw.x11.NET_WM_STATE, |
2341 | _NET_WM_STATE_REMOVE, |
2342 | _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT, |
2343 | _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ, |
2344 | 1, 0); |
2345 | } |
2346 | } |
2347 | |
2348 | XFlush(_glfw.x11.display); |
2349 | } |
2350 | |
2351 | void _glfwMaximizeWindowX11(_GLFWwindow* window) |
2352 | { |
2353 | if (!_glfw.x11.NET_WM_STATE || |
2354 | !_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT || |
2355 | !_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) |
2356 | { |
2357 | return; |
2358 | } |
2359 | |
2360 | if (_glfwWindowVisibleX11(window)) |
2361 | { |
2362 | sendEventToWM(window, |
2363 | _glfw.x11.NET_WM_STATE, |
2364 | _NET_WM_STATE_ADD, |
2365 | _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT, |
2366 | _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ, |
2367 | 1, 0); |
2368 | } |
2369 | else |
2370 | { |
2371 | Atom* states = NULL; |
2372 | unsigned long count = |
2373 | _glfwGetWindowPropertyX11(window->x11.handle, |
2374 | _glfw.x11.NET_WM_STATE, |
2375 | XA_ATOM, |
2376 | (unsigned char**) &states); |
2377 | |
2378 | // NOTE: We don't check for failure as this property may not exist yet |
2379 | // and that's fine (and we'll create it implicitly with append) |
2380 | |
2381 | Atom missing[2] = |
2382 | { |
2383 | _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT, |
2384 | _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ |
2385 | }; |
2386 | unsigned long missingCount = 2; |
2387 | |
2388 | for (unsigned long i = 0; i < count; i++) |
2389 | { |
2390 | for (unsigned long j = 0; j < missingCount; j++) |
2391 | { |
2392 | if (states[i] == missing[j]) |
2393 | { |
2394 | missing[j] = missing[missingCount - 1]; |
2395 | missingCount--; |
2396 | } |
2397 | } |
2398 | } |
2399 | |
2400 | if (states) |
2401 | XFree(states); |
2402 | |
2403 | if (!missingCount) |
2404 | return; |
2405 | |
2406 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
2407 | _glfw.x11.NET_WM_STATE, XA_ATOM, 32, |
2408 | PropModeAppend, |
2409 | (unsigned char*) missing, |
2410 | missingCount); |
2411 | } |
2412 | |
2413 | XFlush(_glfw.x11.display); |
2414 | } |
2415 | |
2416 | void _glfwShowWindowX11(_GLFWwindow* window) |
2417 | { |
2418 | if (_glfwWindowVisibleX11(window)) |
2419 | return; |
2420 | |
2421 | XMapWindow(_glfw.x11.display, window->x11.handle); |
2422 | waitForVisibilityNotify(window); |
2423 | } |
2424 | |
2425 | void _glfwHideWindowX11(_GLFWwindow* window) |
2426 | { |
2427 | XUnmapWindow(_glfw.x11.display, window->x11.handle); |
2428 | XFlush(_glfw.x11.display); |
2429 | } |
2430 | |
2431 | void _glfwRequestWindowAttentionX11(_GLFWwindow* window) |
2432 | { |
2433 | if (!_glfw.x11.NET_WM_STATE || !_glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION) |
2434 | return; |
2435 | |
2436 | sendEventToWM(window, |
2437 | _glfw.x11.NET_WM_STATE, |
2438 | _NET_WM_STATE_ADD, |
2439 | _glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION, |
2440 | 0, 1, 0); |
2441 | } |
2442 | |
2443 | void _glfwFocusWindowX11(_GLFWwindow* window) |
2444 | { |
2445 | if (_glfw.x11.NET_ACTIVE_WINDOW) |
2446 | sendEventToWM(window, _glfw.x11.NET_ACTIVE_WINDOW, 1, 0, 0, 0, 0); |
2447 | else if (_glfwWindowVisibleX11(window)) |
2448 | { |
2449 | XRaiseWindow(_glfw.x11.display, window->x11.handle); |
2450 | XSetInputFocus(_glfw.x11.display, window->x11.handle, |
2451 | RevertToParent, CurrentTime); |
2452 | } |
2453 | |
2454 | XFlush(_glfw.x11.display); |
2455 | } |
2456 | |
2457 | void _glfwSetWindowMonitorX11(_GLFWwindow* window, |
2458 | _GLFWmonitor* monitor, |
2459 | int xpos, int ypos, |
2460 | int width, int height, |
2461 | int refreshRate) |
2462 | { |
2463 | if (window->monitor == monitor) |
2464 | { |
2465 | if (monitor) |
2466 | { |
2467 | if (monitor->window == window) |
2468 | acquireMonitor(window); |
2469 | } |
2470 | else |
2471 | { |
2472 | if (!window->resizable) |
2473 | updateNormalHints(window, width, height); |
2474 | |
2475 | XMoveResizeWindow(_glfw.x11.display, window->x11.handle, |
2476 | xpos, ypos, width, height); |
2477 | } |
2478 | |
2479 | XFlush(_glfw.x11.display); |
2480 | return; |
2481 | } |
2482 | |
2483 | if (window->monitor) |
2484 | { |
2485 | _glfwSetWindowDecoratedX11(window, window->decorated); |
2486 | _glfwSetWindowFloatingX11(window, window->floating); |
2487 | releaseMonitor(window); |
2488 | } |
2489 | |
2490 | _glfwInputWindowMonitor(window, monitor); |
2491 | updateNormalHints(window, width, height); |
2492 | |
2493 | if (window->monitor) |
2494 | { |
2495 | if (!_glfwWindowVisibleX11(window)) |
2496 | { |
2497 | XMapRaised(_glfw.x11.display, window->x11.handle); |
2498 | waitForVisibilityNotify(window); |
2499 | } |
2500 | |
2501 | updateWindowMode(window); |
2502 | acquireMonitor(window); |
2503 | } |
2504 | else |
2505 | { |
2506 | updateWindowMode(window); |
2507 | XMoveResizeWindow(_glfw.x11.display, window->x11.handle, |
2508 | xpos, ypos, width, height); |
2509 | } |
2510 | |
2511 | XFlush(_glfw.x11.display); |
2512 | } |
2513 | |
2514 | int _glfwWindowFocusedX11(_GLFWwindow* window) |
2515 | { |
2516 | Window focused; |
2517 | int state; |
2518 | |
2519 | XGetInputFocus(_glfw.x11.display, &focused, &state); |
2520 | return window->x11.handle == focused; |
2521 | } |
2522 | |
2523 | int _glfwWindowIconifiedX11(_GLFWwindow* window) |
2524 | { |
2525 | return getWindowState(window) == IconicState; |
2526 | } |
2527 | |
2528 | int _glfwWindowVisibleX11(_GLFWwindow* window) |
2529 | { |
2530 | XWindowAttributes wa; |
2531 | XGetWindowAttributes(_glfw.x11.display, window->x11.handle, &wa); |
2532 | return wa.map_state == IsViewable; |
2533 | } |
2534 | |
2535 | int _glfwWindowMaximizedX11(_GLFWwindow* window) |
2536 | { |
2537 | Atom* states; |
2538 | GLFWbool maximized = GLFW_FALSE; |
2539 | |
2540 | if (!_glfw.x11.NET_WM_STATE || |
2541 | !_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT || |
2542 | !_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) |
2543 | { |
2544 | return maximized; |
2545 | } |
2546 | |
2547 | const unsigned long count = |
2548 | _glfwGetWindowPropertyX11(window->x11.handle, |
2549 | _glfw.x11.NET_WM_STATE, |
2550 | XA_ATOM, |
2551 | (unsigned char**) &states); |
2552 | |
2553 | for (unsigned long i = 0; i < count; i++) |
2554 | { |
2555 | if (states[i] == _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT || |
2556 | states[i] == _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) |
2557 | { |
2558 | maximized = GLFW_TRUE; |
2559 | break; |
2560 | } |
2561 | } |
2562 | |
2563 | if (states) |
2564 | XFree(states); |
2565 | |
2566 | return maximized; |
2567 | } |
2568 | |
2569 | int _glfwWindowHoveredX11(_GLFWwindow* window) |
2570 | { |
2571 | Window w = _glfw.x11.root; |
2572 | while (w) |
2573 | { |
2574 | Window root; |
2575 | int rootX, rootY, childX, childY; |
2576 | unsigned int mask; |
2577 | |
2578 | _glfwGrabErrorHandlerX11(); |
2579 | |
2580 | const Bool result = XQueryPointer(_glfw.x11.display, w, |
2581 | &root, &w, &rootX, &rootY, |
2582 | &childX, &childY, &mask); |
2583 | |
2584 | _glfwReleaseErrorHandlerX11(); |
2585 | |
2586 | if (_glfw.x11.errorCode == BadWindow) |
2587 | w = _glfw.x11.root; |
2588 | else if (!result) |
2589 | return GLFW_FALSE; |
2590 | else if (w == window->x11.handle) |
2591 | return GLFW_TRUE; |
2592 | } |
2593 | |
2594 | return GLFW_FALSE; |
2595 | } |
2596 | |
2597 | int _glfwFramebufferTransparentX11(_GLFWwindow* window) |
2598 | { |
2599 | if (!window->x11.transparent) |
2600 | return GLFW_FALSE; |
2601 | |
2602 | return XGetSelectionOwner(_glfw.x11.display, _glfw.x11.NET_WM_CM_Sx) != None; |
2603 | } |
2604 | |
2605 | void _glfwSetWindowResizableX11(_GLFWwindow* window, GLFWbool enabled) |
2606 | { |
2607 | int width, height; |
2608 | _glfwGetWindowSizeX11(window, &width, &height); |
2609 | updateNormalHints(window, width, height); |
2610 | } |
2611 | |
2612 | void _glfwSetWindowDecoratedX11(_GLFWwindow* window, GLFWbool enabled) |
2613 | { |
2614 | struct |
2615 | { |
2616 | unsigned long flags; |
2617 | unsigned long functions; |
2618 | unsigned long decorations; |
2619 | long input_mode; |
2620 | unsigned long status; |
2621 | } hints = {0}; |
2622 | |
2623 | hints.flags = MWM_HINTS_DECORATIONS; |
2624 | hints.decorations = enabled ? MWM_DECOR_ALL : 0; |
2625 | |
2626 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
2627 | _glfw.x11.MOTIF_WM_HINTS, |
2628 | _glfw.x11.MOTIF_WM_HINTS, 32, |
2629 | PropModeReplace, |
2630 | (unsigned char*) &hints, |
2631 | sizeof(hints) / sizeof(long)); |
2632 | } |
2633 | |
2634 | void _glfwSetWindowFloatingX11(_GLFWwindow* window, GLFWbool enabled) |
2635 | { |
2636 | if (!_glfw.x11.NET_WM_STATE || !_glfw.x11.NET_WM_STATE_ABOVE) |
2637 | return; |
2638 | |
2639 | if (_glfwWindowVisibleX11(window)) |
2640 | { |
2641 | const long action = enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; |
2642 | sendEventToWM(window, |
2643 | _glfw.x11.NET_WM_STATE, |
2644 | action, |
2645 | _glfw.x11.NET_WM_STATE_ABOVE, |
2646 | 0, 1, 0); |
2647 | } |
2648 | else |
2649 | { |
2650 | Atom* states = NULL; |
2651 | const unsigned long count = |
2652 | _glfwGetWindowPropertyX11(window->x11.handle, |
2653 | _glfw.x11.NET_WM_STATE, |
2654 | XA_ATOM, |
2655 | (unsigned char**) &states); |
2656 | |
2657 | // NOTE: We don't check for failure as this property may not exist yet |
2658 | // and that's fine (and we'll create it implicitly with append) |
2659 | |
2660 | if (enabled) |
2661 | { |
2662 | unsigned long i; |
2663 | |
2664 | for (i = 0; i < count; i++) |
2665 | { |
2666 | if (states[i] == _glfw.x11.NET_WM_STATE_ABOVE) |
2667 | break; |
2668 | } |
2669 | |
2670 | if (i == count) |
2671 | { |
2672 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
2673 | _glfw.x11.NET_WM_STATE, XA_ATOM, 32, |
2674 | PropModeAppend, |
2675 | (unsigned char*) &_glfw.x11.NET_WM_STATE_ABOVE, |
2676 | 1); |
2677 | } |
2678 | } |
2679 | else if (states) |
2680 | { |
2681 | for (unsigned long i = 0; i < count; i++) |
2682 | { |
2683 | if (states[i] == _glfw.x11.NET_WM_STATE_ABOVE) |
2684 | { |
2685 | states[i] = states[count - 1]; |
2686 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
2687 | _glfw.x11.NET_WM_STATE, XA_ATOM, 32, |
2688 | PropModeReplace, (unsigned char*) states, count - 1); |
2689 | break; |
2690 | } |
2691 | } |
2692 | } |
2693 | |
2694 | if (states) |
2695 | XFree(states); |
2696 | } |
2697 | |
2698 | XFlush(_glfw.x11.display); |
2699 | } |
2700 | |
2701 | void _glfwSetWindowMousePassthroughX11(_GLFWwindow* window, GLFWbool enabled) |
2702 | { |
2703 | if (!_glfw.x11.xshape.available) |
2704 | return; |
2705 | |
2706 | if (enabled) |
2707 | { |
2708 | Region region = XCreateRegion(); |
2709 | XShapeCombineRegion(_glfw.x11.display, window->x11.handle, |
2710 | ShapeInput, 0, 0, region, ShapeSet); |
2711 | XDestroyRegion(region); |
2712 | } |
2713 | else |
2714 | { |
2715 | XShapeCombineMask(_glfw.x11.display, window->x11.handle, |
2716 | ShapeInput, 0, 0, None, ShapeSet); |
2717 | } |
2718 | } |
2719 | |
2720 | float _glfwGetWindowOpacityX11(_GLFWwindow* window) |
2721 | { |
2722 | float opacity = 1.f; |
2723 | |
2724 | if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.NET_WM_CM_Sx)) |
2725 | { |
2726 | CARD32* value = NULL; |
2727 | |
2728 | if (_glfwGetWindowPropertyX11(window->x11.handle, |
2729 | _glfw.x11.NET_WM_WINDOW_OPACITY, |
2730 | XA_CARDINAL, |
2731 | (unsigned char**) &value)) |
2732 | { |
2733 | opacity = (float) (*value / (double) 0xffffffffu); |
2734 | } |
2735 | |
2736 | if (value) |
2737 | XFree(value); |
2738 | } |
2739 | |
2740 | return opacity; |
2741 | } |
2742 | |
2743 | void _glfwSetWindowOpacityX11(_GLFWwindow* window, float opacity) |
2744 | { |
2745 | const CARD32 value = (CARD32) (0xffffffffu * (double) opacity); |
2746 | XChangeProperty(_glfw.x11.display, window->x11.handle, |
2747 | _glfw.x11.NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, |
2748 | PropModeReplace, (unsigned char*) &value, 1); |
2749 | } |
2750 | |
2751 | void _glfwSetRawMouseMotionX11(_GLFWwindow *window, GLFWbool enabled) |
2752 | { |
2753 | if (!_glfw.x11.xi.available) |
2754 | return; |
2755 | |
2756 | if (_glfw.x11.disabledCursorWindow != window) |
2757 | return; |
2758 | |
2759 | if (enabled) |
2760 | enableRawMouseMotion(window); |
2761 | else |
2762 | disableRawMouseMotion(window); |
2763 | } |
2764 | |
2765 | GLFWbool _glfwRawMouseMotionSupportedX11(void) |
2766 | { |
2767 | return _glfw.x11.xi.available; |
2768 | } |
2769 | |
2770 | void _glfwPollEventsX11(void) |
2771 | { |
2772 | _GLFWwindow* window; |
2773 | |
2774 | #if defined(__linux__) |
2775 | if (_glfw.joysticksInitialized) |
2776 | _glfwDetectJoystickConnectionLinux(); |
2777 | #endif |
2778 | XPending(_glfw.x11.display); |
2779 | |
2780 | while (QLength(_glfw.x11.display)) |
2781 | { |
2782 | XEvent event; |
2783 | XNextEvent(_glfw.x11.display, &event); |
2784 | processEvent(&event); |
2785 | } |
2786 | |
2787 | window = _glfw.x11.disabledCursorWindow; |
2788 | if (window) |
2789 | { |
2790 | int width, height; |
2791 | _glfwGetWindowSizeX11(window, &width, &height); |
2792 | |
2793 | // NOTE: Re-center the cursor only if it has moved since the last call, |
2794 | // to avoid breaking glfwWaitEvents with MotionNotify |
2795 | if (window->x11.lastCursorPosX != width / 2 || |
2796 | window->x11.lastCursorPosY != height / 2) |
2797 | { |
2798 | _glfwSetCursorPosX11(window, width / 2, height / 2); |
2799 | } |
2800 | } |
2801 | |
2802 | XFlush(_glfw.x11.display); |
2803 | } |
2804 | |
2805 | void _glfwWaitEventsX11(void) |
2806 | { |
2807 | while (!XPending(_glfw.x11.display)) |
2808 | waitForEvent(NULL); |
2809 | |
2810 | _glfwPollEventsX11(); |
2811 | } |
2812 | |
2813 | void _glfwWaitEventsTimeoutX11(double timeout) |
2814 | { |
2815 | while (!XPending(_glfw.x11.display)) |
2816 | { |
2817 | if (!waitForEvent(&timeout)) |
2818 | break; |
2819 | } |
2820 | |
2821 | _glfwPollEventsX11(); |
2822 | } |
2823 | |
2824 | void _glfwPostEmptyEventX11(void) |
2825 | { |
2826 | XEvent event = { ClientMessage }; |
2827 | event.xclient.window = _glfw.x11.helperWindowHandle; |
2828 | event.xclient.format = 32; // Data is 32-bit longs |
2829 | event.xclient.message_type = _glfw.x11.NULL_; |
2830 | |
2831 | XSendEvent(_glfw.x11.display, _glfw.x11.helperWindowHandle, False, 0, &event); |
2832 | XFlush(_glfw.x11.display); |
2833 | } |
2834 | |
2835 | void _glfwGetCursorPosX11(_GLFWwindow* window, double* xpos, double* ypos) |
2836 | { |
2837 | Window root, child; |
2838 | int rootX, rootY, childX, childY; |
2839 | unsigned int mask; |
2840 | |
2841 | XQueryPointer(_glfw.x11.display, window->x11.handle, |
2842 | &root, &child, |
2843 | &rootX, &rootY, &childX, &childY, |
2844 | &mask); |
2845 | |
2846 | if (xpos) |
2847 | *xpos = childX; |
2848 | if (ypos) |
2849 | *ypos = childY; |
2850 | } |
2851 | |
2852 | void _glfwSetCursorPosX11(_GLFWwindow* window, double x, double y) |
2853 | { |
2854 | // Store the new position so it can be recognized later |
2855 | window->x11.warpCursorPosX = (int) x; |
2856 | window->x11.warpCursorPosY = (int) y; |
2857 | |
2858 | XWarpPointer(_glfw.x11.display, None, window->x11.handle, |
2859 | 0,0,0,0, (int) x, (int) y); |
2860 | XFlush(_glfw.x11.display); |
2861 | } |
2862 | |
2863 | void _glfwSetCursorModeX11(_GLFWwindow* window, int mode) |
2864 | { |
2865 | if (mode == GLFW_CURSOR_DISABLED) |
2866 | { |
2867 | if (_glfwWindowFocusedX11(window)) |
2868 | disableCursor(window); |
2869 | } |
2870 | else if (_glfw.x11.disabledCursorWindow == window) |
2871 | enableCursor(window); |
2872 | else |
2873 | updateCursorImage(window); |
2874 | |
2875 | XFlush(_glfw.x11.display); |
2876 | } |
2877 | |
2878 | const char* _glfwGetScancodeNameX11(int scancode) |
2879 | { |
2880 | if (!_glfw.x11.xkb.available) |
2881 | return NULL; |
2882 | |
2883 | if (scancode < 0 || scancode > 0xff || |
2884 | _glfw.x11.keycodes[scancode] == GLFW_KEY_UNKNOWN) |
2885 | { |
2886 | _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i" , scancode); |
2887 | return NULL; |
2888 | } |
2889 | |
2890 | const int key = _glfw.x11.keycodes[scancode]; |
2891 | const KeySym keysym = XkbKeycodeToKeysym(_glfw.x11.display, |
2892 | scancode, _glfw.x11.xkb.group, 0); |
2893 | if (keysym == NoSymbol) |
2894 | return NULL; |
2895 | |
2896 | const long ch = _glfwKeySym2Unicode(keysym); |
2897 | if (ch == -1) |
2898 | return NULL; |
2899 | |
2900 | const size_t count = encodeUTF8(_glfw.x11.keynames[key], (unsigned int) ch); |
2901 | if (count == 0) |
2902 | return NULL; |
2903 | |
2904 | _glfw.x11.keynames[key][count] = '\0'; |
2905 | return _glfw.x11.keynames[key]; |
2906 | } |
2907 | |
2908 | int _glfwGetKeyScancodeX11(int key) |
2909 | { |
2910 | return _glfw.x11.scancodes[key]; |
2911 | } |
2912 | |
2913 | int _glfwCreateCursorX11(_GLFWcursor* cursor, |
2914 | const GLFWimage* image, |
2915 | int xhot, int yhot) |
2916 | { |
2917 | cursor->x11.handle = _glfwCreateNativeCursorX11(image, xhot, yhot); |
2918 | if (!cursor->x11.handle) |
2919 | return GLFW_FALSE; |
2920 | |
2921 | return GLFW_TRUE; |
2922 | } |
2923 | |
2924 | int _glfwCreateStandardCursorX11(_GLFWcursor* cursor, int shape) |
2925 | { |
2926 | if (_glfw.x11.xcursor.handle) |
2927 | { |
2928 | char* theme = XcursorGetTheme(_glfw.x11.display); |
2929 | if (theme) |
2930 | { |
2931 | const int size = XcursorGetDefaultSize(_glfw.x11.display); |
2932 | const char* name = NULL; |
2933 | |
2934 | switch (shape) |
2935 | { |
2936 | case GLFW_ARROW_CURSOR: |
2937 | name = "default" ; |
2938 | break; |
2939 | case GLFW_IBEAM_CURSOR: |
2940 | name = "text" ; |
2941 | break; |
2942 | case GLFW_CROSSHAIR_CURSOR: |
2943 | name = "crosshair" ; |
2944 | break; |
2945 | case GLFW_POINTING_HAND_CURSOR: |
2946 | name = "pointer" ; |
2947 | break; |
2948 | case GLFW_RESIZE_EW_CURSOR: |
2949 | name = "ew-resize" ; |
2950 | break; |
2951 | case GLFW_RESIZE_NS_CURSOR: |
2952 | name = "ns-resize" ; |
2953 | break; |
2954 | case GLFW_RESIZE_NWSE_CURSOR: |
2955 | name = "nwse-resize" ; |
2956 | break; |
2957 | case GLFW_RESIZE_NESW_CURSOR: |
2958 | name = "nesw-resize" ; |
2959 | break; |
2960 | case GLFW_RESIZE_ALL_CURSOR: |
2961 | name = "all-scroll" ; |
2962 | break; |
2963 | case GLFW_NOT_ALLOWED_CURSOR: |
2964 | name = "not-allowed" ; |
2965 | break; |
2966 | } |
2967 | |
2968 | XcursorImage* image = XcursorLibraryLoadImage(name, theme, size); |
2969 | if (image) |
2970 | { |
2971 | cursor->x11.handle = XcursorImageLoadCursor(_glfw.x11.display, image); |
2972 | XcursorImageDestroy(image); |
2973 | } |
2974 | } |
2975 | } |
2976 | |
2977 | if (!cursor->x11.handle) |
2978 | { |
2979 | unsigned int native = 0; |
2980 | |
2981 | switch (shape) |
2982 | { |
2983 | case GLFW_ARROW_CURSOR: |
2984 | native = XC_left_ptr; |
2985 | break; |
2986 | case GLFW_IBEAM_CURSOR: |
2987 | native = XC_xterm; |
2988 | break; |
2989 | case GLFW_CROSSHAIR_CURSOR: |
2990 | native = XC_crosshair; |
2991 | break; |
2992 | case GLFW_POINTING_HAND_CURSOR: |
2993 | native = XC_hand2; |
2994 | break; |
2995 | case GLFW_RESIZE_EW_CURSOR: |
2996 | native = XC_sb_h_double_arrow; |
2997 | break; |
2998 | case GLFW_RESIZE_NS_CURSOR: |
2999 | native = XC_sb_v_double_arrow; |
3000 | break; |
3001 | case GLFW_RESIZE_ALL_CURSOR: |
3002 | native = XC_fleur; |
3003 | break; |
3004 | default: |
3005 | _glfwInputError(GLFW_CURSOR_UNAVAILABLE, |
3006 | "X11: Standard cursor shape unavailable" ); |
3007 | return GLFW_FALSE; |
3008 | } |
3009 | |
3010 | cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, native); |
3011 | if (!cursor->x11.handle) |
3012 | { |
3013 | _glfwInputError(GLFW_PLATFORM_ERROR, |
3014 | "X11: Failed to create standard cursor" ); |
3015 | return GLFW_FALSE; |
3016 | } |
3017 | } |
3018 | |
3019 | return GLFW_TRUE; |
3020 | } |
3021 | |
3022 | void _glfwDestroyCursorX11(_GLFWcursor* cursor) |
3023 | { |
3024 | if (cursor->x11.handle) |
3025 | XFreeCursor(_glfw.x11.display, cursor->x11.handle); |
3026 | } |
3027 | |
3028 | void _glfwSetCursorX11(_GLFWwindow* window, _GLFWcursor* cursor) |
3029 | { |
3030 | if (window->cursorMode == GLFW_CURSOR_NORMAL) |
3031 | { |
3032 | updateCursorImage(window); |
3033 | XFlush(_glfw.x11.display); |
3034 | } |
3035 | } |
3036 | |
3037 | void _glfwSetClipboardStringX11(const char* string) |
3038 | { |
3039 | char* copy = _glfw_strdup(string); |
3040 | _glfw_free(_glfw.x11.clipboardString); |
3041 | _glfw.x11.clipboardString = copy; |
3042 | |
3043 | XSetSelectionOwner(_glfw.x11.display, |
3044 | _glfw.x11.CLIPBOARD, |
3045 | _glfw.x11.helperWindowHandle, |
3046 | CurrentTime); |
3047 | |
3048 | if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) != |
3049 | _glfw.x11.helperWindowHandle) |
3050 | { |
3051 | _glfwInputError(GLFW_PLATFORM_ERROR, |
3052 | "X11: Failed to become owner of clipboard selection" ); |
3053 | } |
3054 | } |
3055 | |
3056 | const char* _glfwGetClipboardStringX11(void) |
3057 | { |
3058 | return getSelectionString(_glfw.x11.CLIPBOARD); |
3059 | } |
3060 | |
3061 | EGLenum _glfwGetEGLPlatformX11(EGLint** attribs) |
3062 | { |
3063 | if (_glfw.egl.ANGLE_platform_angle) |
3064 | { |
3065 | int type = 0; |
3066 | |
3067 | if (_glfw.egl.ANGLE_platform_angle_opengl) |
3068 | { |
3069 | if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) |
3070 | type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; |
3071 | } |
3072 | |
3073 | if (_glfw.egl.ANGLE_platform_angle_vulkan) |
3074 | { |
3075 | if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_VULKAN) |
3076 | type = EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE; |
3077 | } |
3078 | |
3079 | if (type) |
3080 | { |
3081 | *attribs = _glfw_calloc(5, sizeof(EGLint)); |
3082 | (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; |
3083 | (*attribs)[1] = type; |
3084 | (*attribs)[2] = EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE; |
3085 | (*attribs)[3] = EGL_PLATFORM_X11_EXT; |
3086 | (*attribs)[4] = EGL_NONE; |
3087 | return EGL_PLATFORM_ANGLE_ANGLE; |
3088 | } |
3089 | } |
3090 | |
3091 | if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_x11) |
3092 | return EGL_PLATFORM_X11_EXT; |
3093 | |
3094 | return 0; |
3095 | } |
3096 | |
3097 | EGLNativeDisplayType _glfwGetEGLNativeDisplayX11(void) |
3098 | { |
3099 | return _glfw.x11.display; |
3100 | } |
3101 | |
3102 | EGLNativeWindowType _glfwGetEGLNativeWindowX11(_GLFWwindow* window) |
3103 | { |
3104 | if (_glfw.egl.platform) |
3105 | return &window->x11.handle; |
3106 | else |
3107 | return (EGLNativeWindowType) window->x11.handle; |
3108 | } |
3109 | |
3110 | void _glfwGetRequiredInstanceExtensionsX11(char** extensions) |
3111 | { |
3112 | if (!_glfw.vk.KHR_surface) |
3113 | return; |
3114 | |
3115 | if (!_glfw.vk.KHR_xcb_surface || !_glfw.x11.x11xcb.handle) |
3116 | { |
3117 | if (!_glfw.vk.KHR_xlib_surface) |
3118 | return; |
3119 | } |
3120 | |
3121 | extensions[0] = "VK_KHR_surface" ; |
3122 | |
3123 | // NOTE: VK_KHR_xcb_surface is preferred due to some early ICDs exposing but |
3124 | // not correctly implementing VK_KHR_xlib_surface |
3125 | if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle) |
3126 | extensions[1] = "VK_KHR_xcb_surface" ; |
3127 | else |
3128 | extensions[1] = "VK_KHR_xlib_surface" ; |
3129 | } |
3130 | |
3131 | int _glfwGetPhysicalDevicePresentationSupportX11(VkInstance instance, |
3132 | VkPhysicalDevice device, |
3133 | uint32_t queuefamily) |
3134 | { |
3135 | VisualID visualID = XVisualIDFromVisual(DefaultVisual(_glfw.x11.display, |
3136 | _glfw.x11.screen)); |
3137 | |
3138 | if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle) |
3139 | { |
3140 | PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR |
3141 | vkGetPhysicalDeviceXcbPresentationSupportKHR = |
3142 | (PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR) |
3143 | vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceXcbPresentationSupportKHR" ); |
3144 | if (!vkGetPhysicalDeviceXcbPresentationSupportKHR) |
3145 | { |
3146 | _glfwInputError(GLFW_API_UNAVAILABLE, |
3147 | "X11: Vulkan instance missing VK_KHR_xcb_surface extension" ); |
3148 | return GLFW_FALSE; |
3149 | } |
3150 | |
3151 | xcb_connection_t* connection = XGetXCBConnection(_glfw.x11.display); |
3152 | if (!connection) |
3153 | { |
3154 | _glfwInputError(GLFW_PLATFORM_ERROR, |
3155 | "X11: Failed to retrieve XCB connection" ); |
3156 | return GLFW_FALSE; |
3157 | } |
3158 | |
3159 | return vkGetPhysicalDeviceXcbPresentationSupportKHR(device, |
3160 | queuefamily, |
3161 | connection, |
3162 | visualID); |
3163 | } |
3164 | else |
3165 | { |
3166 | PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR |
3167 | vkGetPhysicalDeviceXlibPresentationSupportKHR = |
3168 | (PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR) |
3169 | vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceXlibPresentationSupportKHR" ); |
3170 | if (!vkGetPhysicalDeviceXlibPresentationSupportKHR) |
3171 | { |
3172 | _glfwInputError(GLFW_API_UNAVAILABLE, |
3173 | "X11: Vulkan instance missing VK_KHR_xlib_surface extension" ); |
3174 | return GLFW_FALSE; |
3175 | } |
3176 | |
3177 | return vkGetPhysicalDeviceXlibPresentationSupportKHR(device, |
3178 | queuefamily, |
3179 | _glfw.x11.display, |
3180 | visualID); |
3181 | } |
3182 | } |
3183 | |
3184 | VkResult _glfwCreateWindowSurfaceX11(VkInstance instance, |
3185 | _GLFWwindow* window, |
3186 | const VkAllocationCallbacks* allocator, |
3187 | VkSurfaceKHR* surface) |
3188 | { |
3189 | if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle) |
3190 | { |
3191 | VkResult err; |
3192 | VkXcbSurfaceCreateInfoKHR sci; |
3193 | PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR; |
3194 | |
3195 | xcb_connection_t* connection = XGetXCBConnection(_glfw.x11.display); |
3196 | if (!connection) |
3197 | { |
3198 | _glfwInputError(GLFW_PLATFORM_ERROR, |
3199 | "X11: Failed to retrieve XCB connection" ); |
3200 | return VK_ERROR_EXTENSION_NOT_PRESENT; |
3201 | } |
3202 | |
3203 | vkCreateXcbSurfaceKHR = (PFN_vkCreateXcbSurfaceKHR) |
3204 | vkGetInstanceProcAddr(instance, "vkCreateXcbSurfaceKHR" ); |
3205 | if (!vkCreateXcbSurfaceKHR) |
3206 | { |
3207 | _glfwInputError(GLFW_API_UNAVAILABLE, |
3208 | "X11: Vulkan instance missing VK_KHR_xcb_surface extension" ); |
3209 | return VK_ERROR_EXTENSION_NOT_PRESENT; |
3210 | } |
3211 | |
3212 | memset(&sci, 0, sizeof(sci)); |
3213 | sci.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; |
3214 | sci.connection = connection; |
3215 | sci.window = window->x11.handle; |
3216 | |
3217 | err = vkCreateXcbSurfaceKHR(instance, &sci, allocator, surface); |
3218 | if (err) |
3219 | { |
3220 | _glfwInputError(GLFW_PLATFORM_ERROR, |
3221 | "X11: Failed to create Vulkan XCB surface: %s" , |
3222 | _glfwGetVulkanResultString(err)); |
3223 | } |
3224 | |
3225 | return err; |
3226 | } |
3227 | else |
3228 | { |
3229 | VkResult err; |
3230 | VkXlibSurfaceCreateInfoKHR sci; |
3231 | PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR; |
3232 | |
3233 | vkCreateXlibSurfaceKHR = (PFN_vkCreateXlibSurfaceKHR) |
3234 | vkGetInstanceProcAddr(instance, "vkCreateXlibSurfaceKHR" ); |
3235 | if (!vkCreateXlibSurfaceKHR) |
3236 | { |
3237 | _glfwInputError(GLFW_API_UNAVAILABLE, |
3238 | "X11: Vulkan instance missing VK_KHR_xlib_surface extension" ); |
3239 | return VK_ERROR_EXTENSION_NOT_PRESENT; |
3240 | } |
3241 | |
3242 | memset(&sci, 0, sizeof(sci)); |
3243 | sci.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; |
3244 | sci.dpy = _glfw.x11.display; |
3245 | sci.window = window->x11.handle; |
3246 | |
3247 | err = vkCreateXlibSurfaceKHR(instance, &sci, allocator, surface); |
3248 | if (err) |
3249 | { |
3250 | _glfwInputError(GLFW_PLATFORM_ERROR, |
3251 | "X11: Failed to create Vulkan X11 surface: %s" , |
3252 | _glfwGetVulkanResultString(err)); |
3253 | } |
3254 | |
3255 | return err; |
3256 | } |
3257 | } |
3258 | |
3259 | |
3260 | ////////////////////////////////////////////////////////////////////////// |
3261 | ////// GLFW native API ////// |
3262 | ////////////////////////////////////////////////////////////////////////// |
3263 | |
3264 | GLFWAPI Display* glfwGetX11Display(void) |
3265 | { |
3266 | _GLFW_REQUIRE_INIT_OR_RETURN(NULL); |
3267 | |
3268 | if (_glfw.platform.platformID != GLFW_PLATFORM_X11) |
3269 | { |
3270 | _glfwInputError(GLFW_PLATFORM_UNAVAILABLE, "X11: Platform not initialized" ); |
3271 | return NULL; |
3272 | } |
3273 | |
3274 | return _glfw.x11.display; |
3275 | } |
3276 | |
3277 | GLFWAPI Window glfwGetX11Window(GLFWwindow* handle) |
3278 | { |
3279 | _GLFWwindow* window = (_GLFWwindow*) handle; |
3280 | _GLFW_REQUIRE_INIT_OR_RETURN(None); |
3281 | |
3282 | if (_glfw.platform.platformID != GLFW_PLATFORM_X11) |
3283 | { |
3284 | _glfwInputError(GLFW_PLATFORM_UNAVAILABLE, "X11: Platform not initialized" ); |
3285 | return None; |
3286 | } |
3287 | |
3288 | return window->x11.handle; |
3289 | } |
3290 | |
3291 | GLFWAPI void glfwSetX11SelectionString(const char* string) |
3292 | { |
3293 | _GLFW_REQUIRE_INIT(); |
3294 | |
3295 | if (_glfw.platform.platformID != GLFW_PLATFORM_X11) |
3296 | { |
3297 | _glfwInputError(GLFW_PLATFORM_UNAVAILABLE, "X11: Platform not initialized" ); |
3298 | return; |
3299 | } |
3300 | |
3301 | _glfw_free(_glfw.x11.primarySelectionString); |
3302 | _glfw.x11.primarySelectionString = _glfw_strdup(string); |
3303 | |
3304 | XSetSelectionOwner(_glfw.x11.display, |
3305 | _glfw.x11.PRIMARY, |
3306 | _glfw.x11.helperWindowHandle, |
3307 | CurrentTime); |
3308 | |
3309 | if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) != |
3310 | _glfw.x11.helperWindowHandle) |
3311 | { |
3312 | _glfwInputError(GLFW_PLATFORM_ERROR, |
3313 | "X11: Failed to become owner of primary selection" ); |
3314 | } |
3315 | } |
3316 | |
3317 | GLFWAPI const char* glfwGetX11SelectionString(void) |
3318 | { |
3319 | _GLFW_REQUIRE_INIT_OR_RETURN(NULL); |
3320 | |
3321 | if (_glfw.platform.platformID != GLFW_PLATFORM_X11) |
3322 | { |
3323 | _glfwInputError(GLFW_PLATFORM_UNAVAILABLE, "X11: Platform not initialized" ); |
3324 | return NULL; |
3325 | } |
3326 | |
3327 | return getSelectionString(_glfw.x11.PRIMARY); |
3328 | } |
3329 | |
3330 | |