1 //
2 // Copyright 2015 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6
7 // X11Window.cpp: Implementation of OSWindow for X11
8
9 #include "util/linux/x11/X11Window.h"
10
11 #include "common/debug.h"
12 #include "util/Timer.h"
13 #include "util/test_utils.h"
14
15 namespace
16 {
17
WaitForMapNotify(Display * dpy,XEvent * event,XPointer window)18 Bool WaitForMapNotify(Display *dpy, XEvent *event, XPointer window)
19 {
20 return event->type == MapNotify && event->xmap.window == reinterpret_cast<Window>(window);
21 }
22
X11CodeToKey(Display * display,unsigned int scancode)23 static Key X11CodeToKey(Display *display, unsigned int scancode)
24 {
25 int temp;
26 KeySym *keySymbols;
27 keySymbols = XGetKeyboardMapping(display, scancode, 1, &temp);
28
29 KeySym keySymbol = keySymbols[0];
30 XFree(keySymbols);
31
32 switch (keySymbol)
33 {
34 case XK_Shift_L:
35 return KEY_LSHIFT;
36 case XK_Shift_R:
37 return KEY_RSHIFT;
38 case XK_Alt_L:
39 return KEY_LALT;
40 case XK_Alt_R:
41 return KEY_RALT;
42 case XK_Control_L:
43 return KEY_LCONTROL;
44 case XK_Control_R:
45 return KEY_RCONTROL;
46 case XK_Super_L:
47 return KEY_LSYSTEM;
48 case XK_Super_R:
49 return KEY_RSYSTEM;
50 case XK_Menu:
51 return KEY_MENU;
52
53 case XK_semicolon:
54 return KEY_SEMICOLON;
55 case XK_slash:
56 return KEY_SLASH;
57 case XK_equal:
58 return KEY_EQUAL;
59 case XK_minus:
60 return KEY_DASH;
61 case XK_bracketleft:
62 return KEY_LBRACKET;
63 case XK_bracketright:
64 return KEY_RBRACKET;
65 case XK_comma:
66 return KEY_COMMA;
67 case XK_period:
68 return KEY_PERIOD;
69 case XK_backslash:
70 return KEY_BACKSLASH;
71 case XK_asciitilde:
72 return KEY_TILDE;
73 case XK_Escape:
74 return KEY_ESCAPE;
75 case XK_space:
76 return KEY_SPACE;
77 case XK_Return:
78 return KEY_RETURN;
79 case XK_BackSpace:
80 return KEY_BACK;
81 case XK_Tab:
82 return KEY_TAB;
83 case XK_Page_Up:
84 return KEY_PAGEUP;
85 case XK_Page_Down:
86 return KEY_PAGEDOWN;
87 case XK_End:
88 return KEY_END;
89 case XK_Home:
90 return KEY_HOME;
91 case XK_Insert:
92 return KEY_INSERT;
93 case XK_Delete:
94 return KEY_DELETE;
95 case XK_KP_Add:
96 return KEY_ADD;
97 case XK_KP_Subtract:
98 return KEY_SUBTRACT;
99 case XK_KP_Multiply:
100 return KEY_MULTIPLY;
101 case XK_KP_Divide:
102 return KEY_DIVIDE;
103 case XK_Pause:
104 return KEY_PAUSE;
105
106 case XK_F1:
107 return KEY_F1;
108 case XK_F2:
109 return KEY_F2;
110 case XK_F3:
111 return KEY_F3;
112 case XK_F4:
113 return KEY_F4;
114 case XK_F5:
115 return KEY_F5;
116 case XK_F6:
117 return KEY_F6;
118 case XK_F7:
119 return KEY_F7;
120 case XK_F8:
121 return KEY_F8;
122 case XK_F9:
123 return KEY_F9;
124 case XK_F10:
125 return KEY_F10;
126 case XK_F11:
127 return KEY_F11;
128 case XK_F12:
129 return KEY_F12;
130 case XK_F13:
131 return KEY_F13;
132 case XK_F14:
133 return KEY_F14;
134 case XK_F15:
135 return KEY_F15;
136
137 case XK_Left:
138 return KEY_LEFT;
139 case XK_Right:
140 return KEY_RIGHT;
141 case XK_Down:
142 return KEY_DOWN;
143 case XK_Up:
144 return KEY_UP;
145
146 case XK_KP_Insert:
147 return KEY_NUMPAD0;
148 case XK_KP_End:
149 return KEY_NUMPAD1;
150 case XK_KP_Down:
151 return KEY_NUMPAD2;
152 case XK_KP_Page_Down:
153 return KEY_NUMPAD3;
154 case XK_KP_Left:
155 return KEY_NUMPAD4;
156 case XK_KP_5:
157 return KEY_NUMPAD5;
158 case XK_KP_Right:
159 return KEY_NUMPAD6;
160 case XK_KP_Home:
161 return KEY_NUMPAD7;
162 case XK_KP_Up:
163 return KEY_NUMPAD8;
164 case XK_KP_Page_Up:
165 return KEY_NUMPAD9;
166
167 case XK_a:
168 return KEY_A;
169 case XK_b:
170 return KEY_B;
171 case XK_c:
172 return KEY_C;
173 case XK_d:
174 return KEY_D;
175 case XK_e:
176 return KEY_E;
177 case XK_f:
178 return KEY_F;
179 case XK_g:
180 return KEY_G;
181 case XK_h:
182 return KEY_H;
183 case XK_i:
184 return KEY_I;
185 case XK_j:
186 return KEY_J;
187 case XK_k:
188 return KEY_K;
189 case XK_l:
190 return KEY_L;
191 case XK_m:
192 return KEY_M;
193 case XK_n:
194 return KEY_N;
195 case XK_o:
196 return KEY_O;
197 case XK_p:
198 return KEY_P;
199 case XK_q:
200 return KEY_Q;
201 case XK_r:
202 return KEY_R;
203 case XK_s:
204 return KEY_S;
205 case XK_t:
206 return KEY_T;
207 case XK_u:
208 return KEY_U;
209 case XK_v:
210 return KEY_V;
211 case XK_w:
212 return KEY_W;
213 case XK_x:
214 return KEY_X;
215 case XK_y:
216 return KEY_Y;
217 case XK_z:
218 return KEY_Z;
219
220 case XK_1:
221 return KEY_NUM1;
222 case XK_2:
223 return KEY_NUM2;
224 case XK_3:
225 return KEY_NUM3;
226 case XK_4:
227 return KEY_NUM4;
228 case XK_5:
229 return KEY_NUM5;
230 case XK_6:
231 return KEY_NUM6;
232 case XK_7:
233 return KEY_NUM7;
234 case XK_8:
235 return KEY_NUM8;
236 case XK_9:
237 return KEY_NUM9;
238 case XK_0:
239 return KEY_NUM0;
240 }
241
242 return Key(0);
243 }
244
AddX11KeyStateToEvent(Event * event,unsigned int state)245 static void AddX11KeyStateToEvent(Event *event, unsigned int state)
246 {
247 event->Key.Shift = state & ShiftMask;
248 event->Key.Control = state & ControlMask;
249 event->Key.Alt = state & Mod1Mask;
250 event->Key.System = state & Mod4Mask;
251 }
252
setWindowSizeHints(Display * display,Window window,int width,int height)253 void setWindowSizeHints(Display *display, Window window, int width, int height)
254 {
255 // Set PMinSize and PMaxSize on XSizeHints so windows larger than the screen do not get adjusted
256 // to screen size
257 XSizeHints *sizeHints = XAllocSizeHints();
258 sizeHints->flags = PMinSize | PMaxSize;
259 sizeHints->min_width = width;
260 sizeHints->min_height = height;
261 sizeHints->max_width = width;
262 sizeHints->max_height = height;
263
264 XSetWMNormalHints(display, window, sizeHints);
265
266 XFree(sizeHints);
267 }
268
269 } // namespace
270
X11Window()271 X11Window::X11Window()
272 : WM_DELETE_WINDOW(None),
273 WM_PROTOCOLS(None),
274 TEST_EVENT(None),
275 mDisplay(nullptr),
276 mWindow(0),
277 mRequestedVisualId(-1),
278 mVisible(false)
279 {}
280
X11Window(int visualId)281 X11Window::X11Window(int visualId)
282 : WM_DELETE_WINDOW(None),
283 WM_PROTOCOLS(None),
284 TEST_EVENT(None),
285 mDisplay(nullptr),
286 mWindow(0),
287 mRequestedVisualId(visualId),
288 mVisible(false)
289 {}
290
~X11Window()291 X11Window::~X11Window()
292 {
293 destroy();
294 }
295
initializeImpl(const std::string & name,int width,int height)296 bool X11Window::initializeImpl(const std::string &name, int width, int height)
297 {
298 destroy();
299
300 mDisplay = XOpenDisplay(nullptr);
301 if (!mDisplay)
302 {
303 return false;
304 }
305
306 {
307 int screen = DefaultScreen(mDisplay);
308 Window root = RootWindow(mDisplay, screen);
309
310 Visual *visual;
311 if (mRequestedVisualId == -1)
312 {
313 visual = DefaultVisual(mDisplay, screen);
314 }
315 else
316 {
317 XVisualInfo visualTemplate;
318 visualTemplate.visualid = mRequestedVisualId;
319
320 int numVisuals = 0;
321 XVisualInfo *visuals =
322 XGetVisualInfo(mDisplay, VisualIDMask, &visualTemplate, &numVisuals);
323 if (numVisuals <= 0)
324 {
325 return false;
326 }
327 ASSERT(numVisuals == 1);
328
329 visual = visuals[0].visual;
330 XFree(visuals);
331 }
332
333 int depth = DefaultDepth(mDisplay, screen);
334 Colormap colormap = XCreateColormap(mDisplay, root, visual, AllocNone);
335
336 XSetWindowAttributes attributes;
337 unsigned long attributeMask = CWBorderPixel | CWColormap | CWEventMask;
338
339 attributes.event_mask = StructureNotifyMask | PointerMotionMask | ButtonPressMask |
340 ButtonReleaseMask | FocusChangeMask | EnterWindowMask |
341 LeaveWindowMask | KeyPressMask | KeyReleaseMask;
342 attributes.border_pixel = 0;
343 attributes.colormap = colormap;
344
345 mWindow = XCreateWindow(mDisplay, root, 0, 0, width, height, 0, depth, InputOutput, visual,
346 attributeMask, &attributes);
347 XFreeColormap(mDisplay, colormap);
348 }
349
350 if (!mWindow)
351 {
352 destroy();
353 return false;
354 }
355
356 // Tell the window manager to notify us when the user wants to close the
357 // window so we can do it ourselves.
358 WM_DELETE_WINDOW = XInternAtom(mDisplay, "WM_DELETE_WINDOW", False);
359 WM_PROTOCOLS = XInternAtom(mDisplay, "WM_PROTOCOLS", False);
360 if (WM_DELETE_WINDOW == None || WM_PROTOCOLS == None)
361 {
362 destroy();
363 return false;
364 }
365
366 if (XSetWMProtocols(mDisplay, mWindow, &WM_DELETE_WINDOW, 1) == 0)
367 {
368 destroy();
369 return false;
370 }
371
372 // Create an atom to identify our test event
373 TEST_EVENT = XInternAtom(mDisplay, "ANGLE_TEST_EVENT", False);
374 if (TEST_EVENT == None)
375 {
376 destroy();
377 return false;
378 }
379
380 setWindowSizeHints(mDisplay, mWindow, width, height);
381
382 XFlush(mDisplay);
383
384 mX = 0;
385 mY = 0;
386 mWidth = width;
387 mHeight = height;
388
389 return true;
390 }
391
disableErrorMessageDialog()392 void X11Window::disableErrorMessageDialog() {}
393
destroy()394 void X11Window::destroy()
395 {
396 if (mWindow)
397 {
398 XDestroyWindow(mDisplay, mWindow);
399 XFlush(mDisplay);
400 // There appears to be a race condition where XDestroyWindow+XCreateWindow ignores
401 // the new size (the same window normally gets reused but this only happens sometimes on
402 // some X11 versions). Wait until we get the destroy notification.
403 mWindow = 0; // Set before messageLoop() to avoid a race in processEvent().
404 while (!mDestroyed)
405 {
406 messageLoop();
407 angle::Sleep(10);
408 }
409 }
410 if (mDisplay)
411 {
412 XCloseDisplay(mDisplay);
413 mDisplay = nullptr;
414 }
415 WM_DELETE_WINDOW = None;
416 WM_PROTOCOLS = None;
417 }
418
resetNativeWindow()419 void X11Window::resetNativeWindow() {}
420
getNativeWindow() const421 EGLNativeWindowType X11Window::getNativeWindow() const
422 {
423 return mWindow;
424 }
425
getPlatformExtension()426 void *X11Window::getPlatformExtension()
427 {
428 // X11 native window for eglCreateSurfacePlatformEXT is Window*
429 return &mWindow;
430 }
431
getNativeDisplay() const432 EGLNativeDisplayType X11Window::getNativeDisplay() const
433 {
434 return reinterpret_cast<EGLNativeDisplayType>(mDisplay);
435 }
436
messageLoop()437 void X11Window::messageLoop()
438 {
439 int eventCount = XPending(mDisplay);
440 while (eventCount--)
441 {
442 XEvent event;
443 XNextEvent(mDisplay, &event);
444 processEvent(event);
445 }
446 }
447
setMousePosition(int x,int y)448 void X11Window::setMousePosition(int x, int y)
449 {
450 XWarpPointer(mDisplay, None, mWindow, 0, 0, 0, 0, x, y);
451 }
452
setOrientation(int width,int height)453 bool X11Window::setOrientation(int width, int height)
454 {
455 UNIMPLEMENTED();
456 return false;
457 }
458
setPosition(int x,int y)459 bool X11Window::setPosition(int x, int y)
460 {
461 XMoveWindow(mDisplay, mWindow, x, y);
462 XFlush(mDisplay);
463 return true;
464 }
465
resize(int width,int height)466 bool X11Window::resize(int width, int height)
467 {
468 setWindowSizeHints(mDisplay, mWindow, width, height);
469 XResizeWindow(mDisplay, mWindow, width, height);
470
471 XFlush(mDisplay);
472
473 Timer timer;
474 timer.start();
475
476 // Wait until the window has actually been resized so that the code calling resize
477 // can assume the window has been resized.
478 const double kResizeWaitDelay = 0.2;
479 while ((mHeight != height || mWidth != width) &&
480 timer.getElapsedWallClockTime() < kResizeWaitDelay)
481 {
482 messageLoop();
483 angle::Sleep(10);
484 }
485
486 return true;
487 }
488
setVisible(bool isVisible)489 void X11Window::setVisible(bool isVisible)
490 {
491 if (mVisible == isVisible)
492 {
493 return;
494 }
495
496 if (isVisible)
497 {
498 XMapWindow(mDisplay, mWindow);
499
500 // Wait until we get an event saying this window is mapped so that the
501 // code calling setVisible can assume the window is visible.
502 // This is important when creating a framebuffer as the framebuffer content
503 // is undefined when the window is not visible.
504 XEvent placeholderEvent;
505 XIfEvent(mDisplay, &placeholderEvent, WaitForMapNotify,
506 reinterpret_cast<XPointer>(mWindow));
507 }
508 else
509 {
510 XUnmapWindow(mDisplay, mWindow);
511 XFlush(mDisplay);
512 }
513
514 // Block until we get ConfigureNotify to set up fully before returning.
515 mConfigured = false;
516 while (!mConfigured)
517 {
518 messageLoop();
519 angle::Sleep(10);
520 }
521
522 mVisible = isVisible;
523 }
524
signalTestEvent()525 void X11Window::signalTestEvent()
526 {
527 XEvent event;
528 event.type = ClientMessage;
529 event.xclient.message_type = TEST_EVENT;
530 // Format needs to be valid or a BadValue is generated
531 event.xclient.format = 32;
532
533 // Hijack StructureNotifyMask as we know we will be listening for it.
534 XSendEvent(mDisplay, mWindow, False, StructureNotifyMask, &event);
535
536 // For test events, the tests want to check that it really did arrive, and they don't wait
537 // long. XSync here makes sure the event is sent by the time the messageLoop() is called.
538 XSync(mDisplay, false);
539 }
540
processEvent(const XEvent & xEvent)541 void X11Window::processEvent(const XEvent &xEvent)
542 {
543 // TODO(cwallez) text events
544 switch (xEvent.type)
545 {
546 case ButtonPress:
547 {
548 Event event;
549 MouseButton button = MOUSEBUTTON_UNKNOWN;
550 int wheelY = 0;
551
552 // The mouse wheel updates are sent via button events.
553 switch (xEvent.xbutton.button)
554 {
555 case Button4:
556 wheelY = 1;
557 break;
558 case Button5:
559 wheelY = -1;
560 break;
561 case 6:
562 break;
563 case 7:
564 break;
565
566 case Button1:
567 button = MOUSEBUTTON_LEFT;
568 break;
569 case Button2:
570 button = MOUSEBUTTON_MIDDLE;
571 break;
572 case Button3:
573 button = MOUSEBUTTON_RIGHT;
574 break;
575 case 8:
576 button = MOUSEBUTTON_BUTTON4;
577 break;
578 case 9:
579 button = MOUSEBUTTON_BUTTON5;
580 break;
581
582 default:
583 break;
584 }
585
586 if (wheelY != 0)
587 {
588 event.Type = Event::EVENT_MOUSE_WHEEL_MOVED;
589 event.MouseWheel.Delta = wheelY;
590 pushEvent(event);
591 }
592
593 if (button != MOUSEBUTTON_UNKNOWN)
594 {
595 event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
596 event.MouseButton.Button = button;
597 event.MouseButton.X = xEvent.xbutton.x;
598 event.MouseButton.Y = xEvent.xbutton.y;
599 pushEvent(event);
600 }
601 }
602 break;
603
604 case ButtonRelease:
605 {
606 Event event;
607 MouseButton button = MOUSEBUTTON_UNKNOWN;
608
609 switch (xEvent.xbutton.button)
610 {
611 case Button1:
612 button = MOUSEBUTTON_LEFT;
613 break;
614 case Button2:
615 button = MOUSEBUTTON_MIDDLE;
616 break;
617 case Button3:
618 button = MOUSEBUTTON_RIGHT;
619 break;
620 case 8:
621 button = MOUSEBUTTON_BUTTON4;
622 break;
623 case 9:
624 button = MOUSEBUTTON_BUTTON5;
625 break;
626
627 default:
628 break;
629 }
630
631 if (button != MOUSEBUTTON_UNKNOWN)
632 {
633 event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
634 event.MouseButton.Button = button;
635 event.MouseButton.X = xEvent.xbutton.x;
636 event.MouseButton.Y = xEvent.xbutton.y;
637 pushEvent(event);
638 }
639 }
640 break;
641
642 case KeyPress:
643 {
644 Event event;
645 event.Type = Event::EVENT_KEY_PRESSED;
646 event.Key.Code = X11CodeToKey(mDisplay, xEvent.xkey.keycode);
647 AddX11KeyStateToEvent(&event, xEvent.xkey.state);
648 pushEvent(event);
649 }
650 break;
651
652 case KeyRelease:
653 {
654 Event event;
655 event.Type = Event::EVENT_KEY_RELEASED;
656 event.Key.Code = X11CodeToKey(mDisplay, xEvent.xkey.keycode);
657 AddX11KeyStateToEvent(&event, xEvent.xkey.state);
658 pushEvent(event);
659 }
660 break;
661
662 case EnterNotify:
663 {
664 Event event;
665 event.Type = Event::EVENT_MOUSE_ENTERED;
666 pushEvent(event);
667 }
668 break;
669
670 case LeaveNotify:
671 {
672 Event event;
673 event.Type = Event::EVENT_MOUSE_LEFT;
674 pushEvent(event);
675 }
676 break;
677
678 case MotionNotify:
679 {
680 Event event;
681 event.Type = Event::EVENT_MOUSE_MOVED;
682 event.MouseMove.X = xEvent.xmotion.x;
683 event.MouseMove.Y = xEvent.xmotion.y;
684 pushEvent(event);
685 }
686 break;
687
688 case ConfigureNotify:
689 {
690 mConfigured = true;
691 if (mWindow == 0)
692 {
693 break;
694 }
695 if (xEvent.xconfigure.width != mWidth || xEvent.xconfigure.height != mHeight)
696 {
697 Event event;
698 event.Type = Event::EVENT_RESIZED;
699 event.Size.Width = xEvent.xconfigure.width;
700 event.Size.Height = xEvent.xconfigure.height;
701 pushEvent(event);
702 }
703 if (xEvent.xconfigure.x != mX || xEvent.xconfigure.y != mY)
704 {
705 // Sometimes, the window manager reparents our window (for example
706 // when resizing) then the X and Y coordinates will be with respect to
707 // the new parent and not what the user wants to know. Use
708 // XTranslateCoordinates to get the coordinates on the screen.
709 int screen = DefaultScreen(mDisplay);
710 Window root = RootWindow(mDisplay, screen);
711
712 int x, y;
713 Window child;
714 XTranslateCoordinates(mDisplay, mWindow, root, 0, 0, &x, &y, &child);
715
716 if (x != mX || y != mY)
717 {
718 Event event;
719 event.Type = Event::EVENT_MOVED;
720 event.Move.X = x;
721 event.Move.Y = y;
722 pushEvent(event);
723 }
724 }
725 }
726 break;
727
728 case FocusIn:
729 if (xEvent.xfocus.mode == NotifyNormal || xEvent.xfocus.mode == NotifyWhileGrabbed)
730 {
731 Event event;
732 event.Type = Event::EVENT_GAINED_FOCUS;
733 pushEvent(event);
734 }
735 break;
736
737 case FocusOut:
738 if (xEvent.xfocus.mode == NotifyNormal || xEvent.xfocus.mode == NotifyWhileGrabbed)
739 {
740 Event event;
741 event.Type = Event::EVENT_LOST_FOCUS;
742 pushEvent(event);
743 }
744 break;
745
746 case DestroyNotify:
747 // Note: we already received WM_DELETE_WINDOW
748 mDestroyed = true;
749 break;
750
751 case ClientMessage:
752 if (xEvent.xclient.message_type == WM_PROTOCOLS &&
753 static_cast<Atom>(xEvent.xclient.data.l[0]) == WM_DELETE_WINDOW)
754 {
755 Event event;
756 event.Type = Event::EVENT_CLOSED;
757 pushEvent(event);
758 }
759 else if (xEvent.xclient.message_type == TEST_EVENT)
760 {
761 Event event;
762 event.Type = Event::EVENT_TEST;
763 pushEvent(event);
764 }
765 break;
766 }
767 }
768
IsX11WindowAvailable()769 bool IsX11WindowAvailable()
770 {
771 Display *display = XOpenDisplay(nullptr);
772 if (!display)
773 {
774 return false;
775 }
776 XCloseDisplay(display);
777 return true;
778 }
779