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