• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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