Skip to main content

miniquad_ply/native/
linux_x11.rs

1// Spiritual successor of an X11 part of https://github.com/floooh/sokol/blob/master/sokol_app.h
2
3mod clipboard;
4mod drag_n_drop;
5mod glx;
6mod keycodes;
7pub mod libx11;
8mod libx11_ex;
9mod x_cursor;
10mod xi_input;
11
12use crate::{
13    event::EventHandler,
14    native::{egl, gl, module, NativeDisplayData, Request},
15    CursorIcon,
16};
17
18use libx11::*;
19
20use std::collections::HashMap;
21
22#[derive(Debug, PartialEq, Eq, Clone)]
23pub enum X11Error {
24    LibraryNotFound(module::Error),
25    GLXError(String),
26}
27impl std::fmt::Display for X11Error {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Self::LibraryNotFound(e) => write!(f, "Library not found error: {e}"),
31            Self::GLXError(msg) => write!(f, "GLX error:\n{msg}"),
32        }
33    }
34}
35impl From<module::Error> for X11Error {
36    fn from(error: module::Error) -> X11Error {
37        X11Error::LibraryNotFound(error)
38    }
39}
40impl std::error::Error for X11Error {}
41
42pub struct X11Display {
43    libx11: LibX11,
44    libxkbcommon: LibXkbCommon,
45    libxi: xi_input::LibXi,
46    display: *mut Display,
47    root: Window,
48    window: Window,
49    repeated_keycodes: [bool; 256],
50    empty_cursor: libx11::Cursor,
51    cursor_cache: HashMap<CursorIcon, libx11::Cursor>,
52    cursor_icon: CursorIcon,
53    cursor_visible: bool,
54    update_requested: bool,
55    drag_n_drop: drag_n_drop::X11DnD,
56}
57
58impl X11Display {
59    unsafe fn process_event(&mut self, event: &mut XEvent, event_handler: &mut dyn EventHandler) {
60        match event.type_0 {
61            2 => {
62                let keycode = event.xkey.keycode as libc::c_int;
63                let key = keycodes::translate_key(&mut self.libx11, self.display, keycode);
64                let repeat = self.repeated_keycodes[(keycode & 0xff) as usize];
65                self.repeated_keycodes[(keycode & 0xff) as usize] = true;
66                let mods = keycodes::translate_mod(event.xkey.state as libc::c_int);
67                let mut keysym: KeySym = 0;
68                (self.libx11.XLookupString)(
69                    &mut event.xkey,
70                    std::ptr::null_mut(),
71                    0 as libc::c_int,
72                    &mut keysym,
73                    std::ptr::null_mut(),
74                );
75                let chr = keycodes::keysym_to_unicode(&mut self.libxkbcommon, keysym);
76                if chr > 0 {
77                    if let Some(chr) = char::from_u32(chr as u32) {
78                        event_handler.char_event(chr, mods, repeat);
79                    }
80                }
81                event_handler.key_down_event(key, mods, repeat);
82            }
83            3 => {
84                let keycode = event.xkey.keycode;
85                let key = keycodes::translate_key(&mut self.libx11, self.display, keycode as _);
86                self.repeated_keycodes[(keycode & 0xff) as usize] = false;
87                let mods = keycodes::translate_mod(event.xkey.state as libc::c_int);
88                event_handler.key_up_event(key, mods);
89            }
90            4 => {
91                let btn = keycodes::translate_mouse_button(event.xbutton.button as _);
92                let x = event.xmotion.x as libc::c_float;
93                let y = event.xmotion.y as libc::c_float;
94
95                if btn != crate::event::MouseButton::Unknown {
96                    event_handler.mouse_button_down_event(btn, x, y);
97                } else {
98                    match event.xbutton.button {
99                        4 => {
100                            event_handler.mouse_wheel_event(0.0, 1.0);
101                        }
102                        5 => {
103                            event_handler.mouse_wheel_event(0.0, -1.0);
104                        }
105                        6 => {
106                            event_handler.mouse_wheel_event(1.0, 0.0);
107                        }
108                        7 => {
109                            event_handler.mouse_wheel_event(-1.0, 0.0);
110                        }
111                        _ => {}
112                    }
113                }
114            }
115            5 => {
116                let btn = keycodes::translate_mouse_button(event.xbutton.button as _);
117                let x = event.xmotion.x as libc::c_float;
118                let y = event.xmotion.y as libc::c_float;
119
120                if btn != crate::event::MouseButton::Unknown {
121                    event_handler.mouse_button_up_event(btn, x, y);
122                }
123            }
124            7 => {
125                // Mouse Enter
126            }
127            8 => {
128                // Mouse Leave
129            }
130            6 => {
131                let x = event.xmotion.x as libc::c_float;
132                let y = event.xmotion.y as libc::c_float;
133                event_handler.mouse_motion_event(x, y);
134            }
135            9 => {
136                event_handler.window_restored_event();
137            }
138            10 => {
139                event_handler.window_minimized_event();
140            }
141            22 => {
142                let mut d = crate::native_display().try_lock().unwrap();
143                let left = event.xconfigure.x;
144                let top = event.xconfigure.y;
145                d.screen_position = (left as _, top as _);
146                if event.xconfigure.width != d.screen_width
147                    || event.xconfigure.height != d.screen_height
148                {
149                    let width = event.xconfigure.width;
150                    let height = event.xconfigure.height;
151                    d.screen_width = width;
152                    d.screen_height = height;
153                    drop(d);
154                    event_handler.resize_event(width as _, height as _);
155                }
156            }
157            // ClientMessage
158            33 => match event.xclient.message_type {
159                t if t == self.libx11.extensions.wm_protocols => {
160                    let mut d = crate::native_display().try_lock().unwrap();
161                    let protocol = event.xclient.data.l[0 as libc::c_int as usize] as Atom;
162                    if protocol == self.libx11.extensions.wm_delete_window {
163                        d.quit_requested = true;
164                    }
165                }
166                t if t == self.libx11.extensions.xdnd_enter => {
167                    self.drag_n_drop.on_enter(
168                        &mut self.libx11,
169                        self.display,
170                        self.window,
171                        event.xclient.data,
172                    );
173                }
174                t if t == self.libx11.extensions.xdnd_position => {
175                    self.drag_n_drop.on_position(
176                        &mut self.libx11,
177                        self.display,
178                        self.window,
179                        event.xclient.data,
180                    );
181                }
182                t if t == self.libx11.extensions.xdnd_drop => {
183                    self.drag_n_drop.on_drop(
184                        &mut self.libx11,
185                        self.display,
186                        self.window,
187                        event.xclient.data,
188                    );
189                }
190
191                _ => (),
192            },
193            // SelectionNotify
194            31 => match event.xselection.property {
195                p if p == self.libx11.extensions.xdnd_selection => {
196                    let bytes = clipboard::get_property_bytes(
197                        &mut self.libx11,
198                        self.display,
199                        self.window,
200                        p,
201                    );
202                    if let Ok(filenames) = std::str::from_utf8(&bytes) {
203                        let mut d = crate::native_display().try_lock().unwrap();
204                        d.dropped_files = Default::default();
205                        for filename in filenames.lines() {
206                            let path = std::path::PathBuf::from(filename);
207                            if let Ok(bytes) = std::fs::read(&path) {
208                                d.dropped_files.paths.push(path);
209                                d.dropped_files.bytes.push(bytes);
210                            }
211                        }
212                        // drop d since files_dropped_event is likely to need access to it
213                        drop(d);
214                        event_handler.files_dropped_event();
215                    }
216                }
217                _ => (),
218            },
219            // SelectionRequest
220            30 => {
221                // // some other app is waiting for clibpoard content
222                // // need to make appropriate XSelectionEvent - response for this request
223                // // only UTF8_STRING request is actually supported
224                clipboard::respond_to_clipboard_request(&mut self.libx11, self.display, event);
225            }
226            // SelectionClear
227            29 => {}
228            17 => {}
229
230            // GenericEvent
231            35 if Some(event.xcookie.extension) == self.libxi.xi_extension_opcode => {
232                if event.xcookie.evtype == xi_input::XI_RawMotion {
233                    let (dx, dy) = self.libxi.read_cookie(&mut event.xcookie, self.display);
234                    event_handler.raw_mouse_motion(dx as f32, dy as f32);
235                }
236            }
237            _ => {}
238        };
239
240        let d = crate::native_display().try_lock().unwrap();
241        if d.quit_requested && !d.quit_ordered {
242            drop(d);
243            event_handler.quit_requested_event();
244            let mut d = crate::native_display().try_lock().unwrap();
245            if d.quit_requested {
246                d.quit_ordered = true
247            }
248        }
249    }
250
251    // TODO: right now it just exits early if fullscreen is false.
252    // should be able to able to go back from fullscreen to windowed instead
253    unsafe fn set_fullscreen(&mut self, window: Window, fullscreen: bool) {
254        let wm_state = (self.libx11.XInternAtom)(
255            self.display,
256            b"_NET_WM_STATE\x00" as *const u8 as *const _,
257            false as _,
258        );
259        let wm_fullscreen = (self.libx11.XInternAtom)(
260            self.display,
261            if fullscreen {
262                b"_NET_WM_STATE_FULLSCREEN\x00" as *const u8 as *const _
263            } else {
264                b"\x00" as *const u8 as *const _
265            },
266            false as _,
267        );
268
269        // this is the first method to make window fullscreen
270        // hide it, change _NET_WM_STATE_FULLSCREEN property and than show it back
271        // someone on stackoverflow mentioned that this is not working on ubuntu/unity though
272        {
273            (self.libx11.XLowerWindow)(self.display, window);
274            (self.libx11.XUnmapWindow)(self.display, window);
275            (self.libx11.XSync)(self.display, false as _);
276
277            let mut atoms: [Atom; 2] = [wm_fullscreen, 0 as _];
278            (self.libx11.XChangeProperty)(
279                self.display,
280                window,
281                wm_state,
282                4 as _,
283                32,
284                PropModeReplace,
285                atoms.as_mut_ptr() as *mut _ as *mut _,
286                1,
287            );
288            (self.libx11.XMapWindow)(self.display, window);
289            (self.libx11.XRaiseWindow)(self.display, window);
290            (self.libx11.XFlush)(self.display);
291        }
292
293        // however, this is X, so just in case - the second method
294        // send ClientMessage to the window with request to change property to fullscreen
295        {
296            let mut data = [0isize; 5];
297
298            data[0] = 1;
299            data[1] = wm_fullscreen as isize;
300            data[2] = 0;
301
302            let mut ev = XClientMessageEvent {
303                type_0: 33,
304                serial: 0,
305                send_event: true as _,
306                message_type: wm_state,
307                window,
308                display: self.display,
309                format: 32,
310                data: ClientMessageData {
311                    l: std::mem::transmute(data),
312                },
313            };
314            (self.libx11.XSendEvent)(
315                self.display as _,
316                self.root,
317                false as _,
318                (1048576 | 131072) as _,
319                &mut ev as *mut XClientMessageEvent as *mut _,
320            );
321        }
322    }
323
324    unsafe fn set_window_size(&mut self, window: Window, new_width: i32, new_height: i32) {
325        (self.libx11.XResizeWindow)(self.display, window, new_width, new_height);
326        (self.libx11.XFlush)(self.display);
327    }
328
329    /// Set the window position in screen coordinates.
330    unsafe fn set_window_position(&mut self, window: Window, new_x: i32, new_y: i32) {
331        (self.libx11.XMoveWindow)(self.display, window, new_x, new_y);
332    }
333
334    pub unsafe fn set_cursor_grab(&mut self, window: Window, grab: bool) {
335        (self.libx11.XUngrabPointer)(self.display, 0);
336
337        if grab {
338            (self.libx11.XGrabPointer)(
339                self.display,
340                window,
341                true as _,
342                (ButtonPressMask
343                    | ButtonReleaseMask
344                    | EnterWindowMask
345                    | LeaveWindowMask
346                    | PointerMotionMask
347                    | PointerMotionHintMask
348                    | Button1MotionMask
349                    | Button2MotionMask
350                    | Button3MotionMask
351                    | Button4MotionMask
352                    | Button5MotionMask
353                    | ButtonMotionMask
354                    | KeymapStateMask) as libc::c_uint,
355                GrabModeAsync,
356                GrabModeAsync,
357                window,
358                0,
359                0, // CurrentTime
360            );
361        }
362
363        (self.libx11.XFlush)(self.display);
364    }
365    pub unsafe fn set_cursor(&mut self, window: Window, cursor: Option<CursorIcon>) {
366        let libx11 = &mut self.libx11;
367        let display = self.display;
368
369        let cursor = match cursor {
370            None => self.empty_cursor,
371            Some(cursor_icon) => *self.cursor_cache.entry(cursor_icon).or_insert_with(|| {
372                (libx11.XCreateFontCursor)(
373                    display,
374                    match cursor_icon {
375                        CursorIcon::Default => libx11::XC_left_ptr,
376                        CursorIcon::Help => libx11::XC_question_arrow,
377                        CursorIcon::Pointer => libx11::XC_hand2,
378                        CursorIcon::Wait => libx11::XC_watch,
379                        CursorIcon::Crosshair => libx11::XC_crosshair,
380                        CursorIcon::Text => libx11::XC_xterm,
381                        CursorIcon::Move => libx11::XC_fleur,
382                        CursorIcon::NotAllowed => libx11::XC_pirate,
383                        CursorIcon::EWResize => libx11::XC_sb_h_double_arrow,
384                        CursorIcon::NSResize => libx11::XC_sb_v_double_arrow,
385                        CursorIcon::NESWResize => libx11::XC_top_right_corner,
386                        CursorIcon::NWSEResize => libx11::XC_top_left_corner,
387                    },
388                )
389            }),
390        };
391        (libx11.XDefineCursor)(display, window, cursor);
392    }
393
394    fn process_request(&mut self, request: Request) {
395        use Request::*;
396        unsafe {
397            match request {
398                ScheduleUpdate => {
399                    self.update_requested = true;
400                }
401                SetCursorGrab(grab) => self.set_cursor_grab(self.window, grab),
402                ShowMouse(show) => {
403                    self.cursor_visible = show;
404                    self.set_cursor(self.window, self.cursor_visible.then_some(self.cursor_icon));
405                }
406                SetMouseCursor(icon) => {
407                    self.cursor_icon = icon;
408                    self.set_cursor(self.window, self.cursor_visible.then_some(self.cursor_icon));
409                }
410                SetWindowSize {
411                    new_width,
412                    new_height,
413                } => self.set_window_size(self.window, new_width as _, new_height as _),
414                SetWindowPosition { new_x, new_y } => {
415                    self.set_window_position(self.window, new_x as _, new_y as _)
416                }
417                SetFullscreen(fullscreen) => self.set_fullscreen(self.window, fullscreen),
418                ShowKeyboard(..) => {
419                    eprintln!("Not implemented for X11")
420                }
421                SetImePosition { .. } => {
422                    // IME position control not implemented for X11 yet
423                }
424                SetImeEnabled(..) => {
425                    // IME enable/disable not implemented for X11 yet
426                }
427            }
428        }
429    }
430}
431
432unsafe fn glx_main_loop<F>(
433    mut display: X11Display,
434    conf: &crate::conf::Conf,
435    f: &mut Option<F>,
436    screen: i32,
437) -> Result<(), X11Display>
438where
439    F: 'static + FnOnce() -> Box<dyn EventHandler>,
440{
441    let mut glx = match glx::Glx::init(&mut display.libx11, display.display, screen, conf) {
442        Ok(glx) => glx,
443        _ => return Err(display),
444    };
445    let visual = glx.visual;
446    let depth = glx.depth;
447    display.window =
448        display
449            .libx11
450            .create_window(display.root, display.display, visual, depth, conf);
451
452    let (glx_context, glx_window) = glx.create_context(display.display, display.window);
453    glx.swap_interval(
454        display.display,
455        glx_window,
456        glx_context,
457        conf.platform.swap_interval.unwrap_or(1),
458    );
459    gl::load_gl_funcs(|proc| glx.libgl.get_procaddr(proc));
460
461    display.init_drag_n_drop();
462    display.libx11.show_window(display.display, display.window);
463
464    (display.libx11.XFlush)(display.display);
465
466    let (w, h) = display
467        .libx11
468        .query_window_size(display.display, display.window);
469
470    let (tx, rx) = std::sync::mpsc::channel();
471    let clipboard = Box::new(clipboard::X11Clipboard::new(
472        display.libx11.clone(),
473        display.display,
474        display.window,
475    ));
476    crate::set_display(NativeDisplayData {
477        high_dpi: conf.high_dpi,
478        dpi_scale: display.libx11.update_system_dpi(display.display),
479        blocking_event_loop: conf.platform.blocking_event_loop,
480        ..NativeDisplayData::new(w, h, tx, clipboard)
481    });
482    if conf.fullscreen {
483        display.set_fullscreen(display.window, true);
484    }
485
486    let mut event_handler = (f.take().unwrap())();
487
488    while !crate::native_display().try_lock().unwrap().quit_ordered {
489        while let Ok(request) = rx.try_recv() {
490            display.process_request(request);
491        }
492        glx.make_current(display.display, glx_window, glx_context);
493
494        let mut count = (display.libx11.XPending)(display.display);
495        let block_on_wait = conf.platform.blocking_event_loop && !display.update_requested;
496        if block_on_wait {
497            // if there are multiple events pending, it is still desired to process
498            // them all in one frame.
499            // However, when there are no events in the queue, +1 hack
500            // will block main thread and release the cpu until the new event.
501            count += 1;
502        }
503
504        for _ in 0..count {
505            let mut xevent = _XEvent { type_0: 0 };
506            (display.libx11.XNextEvent)(display.display, &mut xevent);
507            display.process_event(&mut xevent, &mut *event_handler);
508        }
509
510        if !conf.platform.blocking_event_loop || display.update_requested {
511            display.update_requested = false;
512            event_handler.update();
513            event_handler.draw();
514
515            glx.swap_buffers(display.display, glx_window);
516            (display.libx11.XFlush)(display.display);
517        }
518    }
519
520    glx.destroy_context(display.display, glx_window, glx_context);
521    (display.libx11.XUnmapWindow)(display.display, display.window);
522    (display.libx11.XDestroyWindow)(display.display, display.window);
523    (display.libx11.XCloseDisplay)(display.display);
524
525    Ok(())
526}
527
528unsafe fn egl_main_loop<F>(
529    mut display: X11Display,
530    conf: &crate::conf::Conf,
531    f: &mut Option<F>,
532) -> Result<(), X11Display>
533where
534    F: 'static + FnOnce() -> Box<dyn EventHandler>,
535{
536    let mut egl_lib = match egl::LibEgl::try_load().ok() {
537        Some(glx) => glx,
538        _ => return Err(display),
539    };
540
541    display.window =
542        display
543            .libx11
544            .create_window(display.root, display.display, std::ptr::null_mut(), 0, conf);
545
546    let (context, config, egl_display) = egl::create_egl_context(
547        &mut egl_lib,
548        display.display as *mut _,
549        conf.platform.framebuffer_alpha,
550        conf.sample_count,
551    )
552    .unwrap();
553
554    let egl_surface =
555        (egl_lib.eglCreateWindowSurface)(egl_display, config, display.window, std::ptr::null_mut());
556
557    if egl_surface.is_null() {
558        // == EGL_NO_SURFACE
559        panic!("surface creation failed");
560    }
561    if (egl_lib.eglMakeCurrent)(egl_display, egl_surface, egl_surface, context) == 0 {
562        panic!("eglMakeCurrent failed");
563    }
564
565    if (egl_lib.eglSwapInterval)(egl_display, conf.platform.swap_interval.unwrap_or(1)) == 0 {
566        eprintln!("eglSwapInterval failed");
567    }
568
569    crate::native::gl::load_gl_funcs(|proc| {
570        let name = std::ffi::CString::new(proc).unwrap();
571        (egl_lib.eglGetProcAddress)(name.as_ptr() as _)
572    });
573
574    display.init_drag_n_drop();
575    display.libx11.show_window(display.display, display.window);
576    let (w, h) = display
577        .libx11
578        .query_window_size(display.display, display.window);
579
580    let (tx, rx) = std::sync::mpsc::channel();
581    let clipboard = Box::new(clipboard::X11Clipboard::new(
582        display.libx11.clone(),
583        display.display,
584        display.window,
585    ));
586    crate::set_display(NativeDisplayData {
587        high_dpi: conf.high_dpi,
588        dpi_scale: display.libx11.update_system_dpi(display.display),
589        blocking_event_loop: conf.platform.blocking_event_loop,
590        ..NativeDisplayData::new(w, h, tx, clipboard)
591    });
592    if conf.fullscreen {
593        display.set_fullscreen(display.window, true)
594    }
595
596    (display.libx11.XFlush)(display.display);
597
598    let mut event_handler = (f.take().unwrap())();
599
600    while !crate::native_display().try_lock().unwrap().quit_ordered {
601        while let Ok(request) = rx.try_recv() {
602            display.process_request(request);
603        }
604
605        let mut count = (display.libx11.XPending)(display.display);
606        let block_on_wait = conf.platform.blocking_event_loop && !display.update_requested;
607        if block_on_wait {
608            // same thing as in glx loop, explained there
609            count += 1;
610        }
611        for _ in 0..count {
612            let mut xevent = _XEvent { type_0: 0 };
613            (display.libx11.XNextEvent)(display.display, &mut xevent);
614            display.process_event(&mut xevent, &mut *event_handler);
615        }
616
617        if !conf.platform.blocking_event_loop || display.update_requested {
618            display.update_requested = false;
619            event_handler.update();
620            event_handler.draw();
621
622            (egl_lib.eglSwapBuffers)(egl_display, egl_surface);
623            (display.libx11.XFlush)(display.display);
624        }
625    }
626
627    (display.libx11.XUnmapWindow)(display.display, display.window);
628    (display.libx11.XDestroyWindow)(display.display, display.window);
629    (display.libx11.XCloseDisplay)(display.display);
630
631    Ok(())
632}
633
634pub fn run<F>(conf: &crate::conf::Conf, f: &mut Option<F>) -> Result<(), X11Error>
635where
636    F: 'static + FnOnce() -> Box<dyn EventHandler>,
637{
638    unsafe {
639        let mut libx11 = LibX11::try_load()?;
640        let libxkbcommon = LibXkbCommon::try_load()?;
641        let libxi = xi_input::LibXi::try_load()?;
642
643        (libx11.XInitThreads)();
644        (libx11.XrmInitialize)();
645
646        let x11_display = (libx11.XOpenDisplay)(std::ptr::null());
647        if x11_display.is_null() {
648            panic!("XOpenDisplay() failed!");
649        }
650
651        // screen selection process. The place to do something about
652        // proper multi-monitor support
653        let x11_screen = (*(x11_display as _XPrivDisplay)).default_screen;
654        let x11_root = (*(*(x11_display as _XPrivDisplay))
655            .screens
656            .offset(x11_screen as isize))
657        .root;
658
659        // https://linux.die.net/man/3/xkbsetdetectableautorepeat
660        // TLDR: Xkb allows clients to request detectable auto-repeat.
661        // If a client requests and the server supports DetectableAutoRepeat,
662        // Xkb generates KeyRelease events only when the key is physically
663        // released. If DetectableAutoRepeat is not supported or has not been
664        // requested, the server synthesizes a KeyRelease event for each
665        // repeating KeyPress event it generates.
666        (libx11.XkbSetDetectableAutoRepeat)(x11_display, true as _, std::ptr::null_mut());
667
668        libx11.load_extensions(x11_display);
669        let mut display = X11Display {
670            empty_cursor: x_cursor::create_empty_cursor(x11_display, x11_root, &mut libx11),
671            display: x11_display,
672            root: x11_root,
673            window: 0,
674            libx11,
675            libxkbcommon,
676            libxi,
677            repeated_keycodes: [false; 256],
678            cursor_cache: HashMap::new(),
679            update_requested: true,
680            drag_n_drop: Default::default(),
681            cursor_icon: CursorIcon::Default,
682            cursor_visible: true,
683        };
684
685        display
686            .libxi
687            .query_xi_extension(&mut display.libx11, display.display);
688
689        match conf.platform.linux_x11_gl {
690            crate::conf::LinuxX11Gl::GLXOnly => {
691                glx_main_loop(display, conf, f, x11_screen).ok().unwrap();
692            }
693            crate::conf::LinuxX11Gl::EGLOnly => {
694                egl_main_loop(display, conf, f).ok().unwrap();
695            }
696            crate::conf::LinuxX11Gl::GLXWithEGLFallback => {
697                if let Err(display) = glx_main_loop(display, conf, f, x11_screen) {
698                    egl_main_loop(display, conf, f).ok().unwrap();
699                }
700            }
701            crate::conf::LinuxX11Gl::EGLWithGLXFallback => {
702                if let Err(display) = egl_main_loop(display, conf, f) {
703                    glx_main_loop(display, conf, f, x11_screen).ok().unwrap();
704                }
705            }
706        }
707    }
708    Ok(())
709}