xref: /aosp_15_r20/external/angle/util/linux/x11/X11Window.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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