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