// stdlib #include #include #include #include #include // wayland #include #include // OGL #include #include // X11 #include // linux #include #include // generated header using wayland-scanner #include "xdg-shell-client-protocol.h" #include "zwp-text-input-unstable-v3-protocol.h" // common headers for platform layer and application layer #include "input.h" struct wl_compositor *compositor = NULL; struct wl_registry *registry = NULL; struct wl_surface *surface = NULL; struct xdg_wm_base *wm_base = NULL; struct xdg_surface *xdg_surface = NULL; struct xdg_toplevel *xdg_toplevel = NULL; struct wl_egl_window *window = NULL; /* Input API */ struct wl_seat *wl_seat = NULL; struct wl_keyboard *wl_keyboard = NULL; struct wl_pointer *wl_pointer = NULL; /* ZWP text input extension */ struct zwp_text_input_v3 *zwp_text_input_v3 = NULL; struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3 = NULL; /* XKB library */ struct xkb_context *xkb_context = NULL; struct xkb_state *xkb_state = NULL; struct xkb_keymap *xkb_keymap = NULL; /* Misc. global state */ EGLBoolean errcode = 0; bool running = true; input_t user_input = {}; char *egl_s(int code) { switch (code) { case (intptr_t)EGL_NO_SURFACE: return "EGL_NO_SURFACE"; case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT"; case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE"; case EGL_BAD_MATCH: return "EGL_BAD_MATCH"; case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW"; case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC"; case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST"; case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED"; case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY"; case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG"; case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE"; default: return "UNKNOWN"; } } void registry_handle_global( void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { fprintf(stdout, "Wayland interface: '%s', version: %d, name: %d\n", interface, version, name); if (!strcmp(interface, wl_compositor_interface.name)) { fprintf(stdout, "Registering compositor...\n"); compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); } if (!strcmp(interface, xdg_wm_base_interface.name)) { fprintf(stdout, "Registering XDG WM base interface...\n"); wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); } /* NOTE: Since wl_seat v5, pointer events are grouped into "frames", which do not appear to be useful for non-touch applications and also create more events which are more complicated to dispatch. */ if (!strcmp(interface, wl_seat_interface.name)) { fprintf(stdout, "Registering WL seat interface...\n"); wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 8); } if (!strcmp(interface, zwp_text_input_manager_v3_interface.name)) { zwp_text_input_manager_v3 = wl_registry_bind(registry, name, &zwp_text_input_manager_v3_interface, 1); } } void zwp_text_input_v3_enter_callback(void *data, struct zwp_text_input_v3 *text_input, struct wl_surface *surface) { fprintf(stdout, "ZWP text input v3 enter callback\n"); zwp_text_input_v3 = text_input; zwp_text_input_v3_enable(zwp_text_input_v3); zwp_text_input_v3_commit(zwp_text_input_v3); } void zwp_text_input_v3_leave_callback(void *data, struct zwp_text_input_v3 *text_input, struct wl_surface *surface) { fprintf(stdout, "ZWP text input v3 leave callback\n"); zwp_text_input_v3_disable(zwp_text_input_v3); zwp_text_input_v3_commit(zwp_text_input_v3); } void zwp_text_input_v3_preedit_string_callback(void *data, struct zwp_text_input_v3 *text_input, const char *text, int32_t cursor_begin, int32_t cursor_end) { fprintf(stdout, "ZWP text input v3 preedit string callback with text %s\n", text); user_input.keyboard_state &= KEYBOARD_STATE_IME; } void zwp_text_input_v3_commit_string_callback(void *data, struct zwp_text_input_v3 *text_input, const char *text) { fprintf(stdout, "ZWP text input v3 commit string callback with text %s\n", text); user_input.keyboard_state &= ~KEYBOARD_STATE_IME; const char *s = text; int i = 0; for (; i < IME_STR_MAX - 1 && s; ++i) user_input.ime_str[i] = *s++; user_input.ime_str[i] = '\0'; fprintf(stdout, "Copied string %s\n", text); } void zwp_text_input_v3_delete_surrounding_text_callback(void *data, struct zwp_text_input_v3 *text_input, uint32_t before_length, uint32_t after_length) { fprintf(stdout, "ZWP text input v3 delete surrounding text callback\n"); } void zwp_text_input_v3_done_callback(void *data, struct zwp_text_input_v3 *text_input, uint32_t surface) { fprintf(stdout, "ZWP text input v3 done callback\n"); } void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { // This space deliberately left blank } static const struct wl_registry_listener registry_listener = { .global = registry_handle_global, .global_remove = registry_handle_global_remove, }; /* Window size change callbacks */ void xdg_toplevel_configure(void *data, struct xdg_toplevel *t, int32_t width, int32_t height, struct wl_array *states) { if (!width || !height) return; // compositor is deferring to us fprintf(stdout, "Trying to resize window to %d W %d H\n", width, height); wl_egl_window_resize(window, width, height, 0, 0); glViewport(0, 0, width, height); } /* Window close callback */ void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = false; } /* Basic wayland method to check if our program is responsive still */ void wayland_ping(void *data, struct xdg_wm_base *base, uint32_t serial) { xdg_wm_base_pong(base, serial); } void wl_seat_capabilities_callback(void *data, struct wl_seat *wl_seat, uint capabilities) { fprintf(stdout, "Capabilities changed for WL seat\n"); } void keymap_callback(void *data, struct wl_keyboard *kb, uint format, int fd, uint size) { // TODO: Check for XKB compatibility and use xkbcommon to extract fprintf(stdout, "KBMAP of size %d detected\n", size); char *mem = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); fprintf(stdout, "XKBMAP desc:\n\t%s\n", mem); xkb_keymap = xkb_keymap_new_from_string(xkb_context, mem, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap(mem, size); close(fd); if (!(xkb_state = xkb_state_new(xkb_keymap))) fprintf(stderr, "Could not initialize XKB state machine\n"); } void enter_callback(void *data, struct wl_keyboard* kb, uint serial, struct wl_surface* surface, struct wl_array *keys) { fprintf(stdout, "KBMAP with serial %d entered surface\n", serial); user_input.keyboard_state = KEYBOARD_STATE_UNFOCUSED; } void leave_callback(void *data, struct wl_keyboard* kb, uint serial, struct wl_surface* surface) { fprintf(stdout, "KBMAP with serial %d left surface\n", serial); user_input.keyboard_state = KEYBOARD_STATE_FOCUSED; } void key_callback(void *data, struct wl_keyboard *kb, uint serial, uint time, uint key, uint state) { /* NOTE: libxkbcommon provides the correct glyphs if: 1. The user is using the system layout without an IME, or 2. The user is using an IME, but the current key is the same for both languages. If none of them are true, wayback will either use text-input-v3's callbacks instead, or call both functions. For example, if the user is using spanish IME on US keymap and inputs the letter A, key_callback will be called normally. However trying to write ñ or à won't call this function at all. */ key += 8; uint32_t layout = xkb_state_key_get_layout(xkb_state, key); if (layout >= xkb_keymap_num_layouts_for_key(xkb_keymap, key)) fprintf(stderr, "Layout %d exceeds number of available key layouts\n", layout); xkb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state, key); char keyname[128]; xkb_keysym_get_name(sym, keyname, sizeof(keyname)); char keyutf8[128]; xkb_state_key_get_utf8(xkb_state, key, keyutf8, sizeof(keyutf8)); fprintf(stdout, "%s key %d layout %d (KEYSYM %s) (UTF8 %s)\n", (state == WL_KEYBOARD_KEY_STATE_PRESSED ? "Pressed" : "Released"), key, layout, keyname, keyutf8); // TODO: Test this user_input.keys[key] = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? KEY_STATE_PRESSED : KEY_STATE_RELEASED); } void modifier_callback(void *data, struct wl_keyboard *kb, uint serial, uint mods_depressed, uint mods_latched, uint mods_locked, uint group) { fprintf(stdout, "Keyboard modifier keys changed\n"); xkb_state_update_mask(xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); user_input.modifiers = (mods_depressed | mods_latched | mods_locked); } void repeat_info_callback(void *data, struct wl_keyboard *kb, int rate, int delay) { fprintf(stdout, "Keyboard repeat/delay rate changed\n"); } void pointer_enter_callback(void *data, struct wl_pointer *pointer, uint serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y) { //fprintf(stdout, "Pointer entered WL surface on coordinates %d %d\n", x, y); user_input.mouse_state = MOUSE_STATE_FOCUSED; } void pointer_leave_callback(void *data, struct wl_pointer *pointer, uint serial, struct wl_surface *surface) { //fprintf(stdout, "Pointer left WL surface\n"); user_input.mouse_state = MOUSE_STATE_UNFOCUSED; } void pointer_motion_callback(void *data, struct wl_pointer *pointer, uint time, wl_fixed_t x, wl_fixed_t y) { //fprintf(stdout, "Pointer moved to %d %d\n", x, y); user_input.mouse_x = x; user_input.mouse_y = y; } void pointer_button_callback(void *data, struct wl_pointer *pointer, uint serial, uint time, uint button, uint state) { fprintf(stdout, "Pointer button %d changed state\n", button); mouse_keysym_t idx = 0; switch (button) { case BTN_LEFT: idx = MOUSE_KEY_LEFT; break; case BTN_RIGHT: idx = MOUSE_KEY_RIGHT; break; case BTN_MIDDLE: idx = MOUSE_KEY_MIDDLE; break; case BTN_SIDE: idx = MOUSE_KEY_SIDE; break; case BTN_EXTRA: idx = MOUSE_KEY_EXTRA; break; case BTN_FORWARD: idx = MOUSE_KEY_FORWARD; break; case BTN_TASK: idx = MOUSE_KEY_TASK; break; case BTN_0: idx = MOUSE_KEY_EXTRA_0; break; case BTN_1: idx = MOUSE_KEY_EXTRA_1; break; case BTN_2: idx = MOUSE_KEY_EXTRA_2; break; case BTN_3: idx = MOUSE_KEY_EXTRA_3; break; case BTN_4: idx = MOUSE_KEY_EXTRA_4; break; case BTN_5: idx = MOUSE_KEY_EXTRA_5; break; case BTN_6: idx = MOUSE_KEY_EXTRA_6; break; case BTN_7: idx = MOUSE_KEY_EXTRA_7; break; case BTN_8: idx = MOUSE_KEY_EXTRA_8; break; case BTN_9: idx = MOUSE_KEY_EXTRA_9; break; default: fprintf(stderr, "Unknown wayland button identifier: 0x%x\n", button); } user_input.mouse_keys[idx] = (state == WL_POINTER_BUTTON_STATE_PRESSED) ? KEY_STATE_PRESSED : KEY_STATE_RELEASED; } void pointer_axis_callback(void *data, struct wl_pointer *pointer, uint time, uint axis, wl_fixed_t value) { //fprintf(stdout, "Pointer scroll on axis %d value %d\n", axis, value); } /* NOTE: *some* events are logically grouped together. The frame event marks the boundary between groups of them. More info: https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_pointer */ void pointer_frame_callback(void *data, struct wl_pointer *frame) { //fprintf(stdout, "Pointer frame ended\n"); } /* NOTE: This event is also used to group pointer events together. It specifies info about all events in its frame */ void pointer_axis_source_callback(void *data, struct wl_pointer *pointer, uint axis_source) { //fprintf(stdout, "Pointer axis source: %d\n", axis_source); } /* Optional event to implement kinetic scrolling */ void pointer_axis_stop_callback(void *data, struct wl_pointer *pointer, uint time, uint axis) { //fprintf(stdout, "Pointer axis stopped: %d\n", axis); } /* Optional event. Carries discrete scroll info after an 'axis' event */ void pointer_axis_discrete_callback(void *data, struct wl_pointer *pointer, uint axis, int discrete) { //fprintf(stdout, "Pointer axis discrete step: %d\n", discrete); } void pointer_axis_value120_callback(void *data, struct wl_pointer *pointer, uint axis, int value120) { fprintf(stdout, "Pointer axis value120: %d\n", value120); } void pointer_axis_relative_direction_callback(void *data, struct wl_pointer *pointer, uint axis, uint direction) { fprintf(stdout, "Pointer axis direction: %d\n", direction); } int main() { fprintf(stdout, "Starting platform layer...\n"); struct wl_display *display = wl_display_connect(NULL); if (!display) { fprintf(stderr, "Cannot retrieve wayland display!\n"); } registry = wl_display_get_registry(display); if (!registry) { fprintf(stderr, "Cannot retrieve wayland registry!\n"); } fprintf(stdout, "Linking registry listener...\n"); wl_registry_add_listener(registry, ®istry_listener, NULL); wl_display_dispatch(display); wl_display_roundtrip(display); surface = wl_compositor_create_surface(compositor); if (!surface) { fprintf(stderr, "Can't create surface!\n"); } xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, surface); xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); xdg_toplevel_set_title(xdg_toplevel, "Hi"); /* XKB library initialization */ xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = xdg_toplevel_configure, .close = xdg_toplevel_close }; const struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = wayland_ping }; if (xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL)) fprintf(stderr, "Could not set XDG toplevel listener\n"); if (xdg_wm_base_add_listener(wm_base, &xdg_wm_base_listener, NULL)) fprintf(stderr, "Could not set XDG WM base listener\n"); if (!(wl_keyboard = wl_seat_get_keyboard(wl_seat))) { fprintf(stderr, "Could not get XKB keyboard\n"); user_input.keyboard_state = KEYBOARD_STATE_UNPLUGGED; } if (!(wl_pointer = wl_seat_get_pointer(wl_seat))) fprintf(stderr, "Could not get WL pointer\n"); if (!zwp_text_input_manager_v3) fprintf(stderr, "Could not get ZWP text input manager\n"); else if (!(zwp_text_input_v3 = zwp_text_input_manager_v3_get_text_input(zwp_text_input_manager_v3, wl_seat))) fprintf(stderr, "Could not get ZWP text input handle\n"); const struct zwp_text_input_v3_listener zwp_text_input_v3_listener = { .enter = zwp_text_input_v3_enter_callback, .leave = zwp_text_input_v3_leave_callback, .preedit_string = zwp_text_input_v3_preedit_string_callback, .commit_string = zwp_text_input_v3_commit_string_callback, .delete_surrounding_text = zwp_text_input_v3_delete_surrounding_text_callback, .done = zwp_text_input_v3_done_callback }; if (zwp_text_input_v3_add_listener(zwp_text_input_v3, &zwp_text_input_v3_listener, NULL)) fprintf(stderr, "Could not add ZWP text input callback\n"); const struct wl_seat_listener wl_seat_listener = { .capabilities = wl_seat_capabilities_callback }; if (wl_seat_add_listener(wl_seat, &wl_seat_listener, NULL)) fprintf(stderr, "Could not set WL seat listener\n"); /* NOTE: We have to define all available callbacks for the keyboard listener even if we are not interested in them: https://gitlab.freedesktop.org/wayland/wayland/-/issues/160 */ const struct wl_keyboard_listener wl_keyboard_listener = { .keymap = keymap_callback, .enter = enter_callback, .leave = leave_callback, .key = key_callback, .modifiers = modifier_callback, .repeat_info = repeat_info_callback, }; if (wl_keyboard_add_listener(wl_keyboard, &wl_keyboard_listener, NULL)) fprintf(stderr, "Could not set XKB keyboard listener\n"); /* NOTE: Apparently wl_pointer added 'axis_value120' with v8 and 'axis_relative_direction' events with v9. Failing to provide callbacks for them crashes the client in runtime */ const struct wl_pointer_listener wl_pointer_listener = { .enter = pointer_enter_callback, .leave = pointer_leave_callback, .motion = pointer_motion_callback, .button = pointer_button_callback, .axis = pointer_axis_callback, .frame = pointer_frame_callback, .axis_source = pointer_axis_source_callback, .axis_stop = pointer_axis_stop_callback, .axis_discrete = pointer_axis_discrete_callback, .axis_value120 = pointer_axis_value120_callback, .axis_relative_direction = pointer_axis_relative_direction_callback, }; if (wl_pointer_add_listener(wl_pointer, &wl_pointer_listener, NULL)) fprintf(stderr, "Could not set WL pointer listener\n"); /* WL EGL initialization */ window = wl_egl_window_create(surface, 480, 460); EGLDisplay egl_display = eglGetDisplay((EGLNativeDisplayType) display); if (egl_display == EGL_NO_DISPLAY) { fprintf(stderr, "Could not retrieve EGL display from wayland display\n"); } errcode = eglInitialize(egl_display, NULL, NULL); if (errcode != EGL_TRUE) fprintf(stderr, "ERR %s: Could not initialize EGL display\n", egl_s(errcode)); int config_count; errcode = eglGetConfigs(egl_display, NULL, 0, &config_count); if (errcode != EGL_TRUE) fprintf(stderr, "ERR %s: Could not retrieve EGL config number\n", egl_s(errcode)); EGLConfig *configs = calloc(config_count, sizeof(EGLConfig)); EGLint attr_list[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_RENDERABLE_TYPE, (EGL_OPENGL_BIT | EGL_OPENGL_ES_BIT | EGL_OPENGL_ES2_BIT), // better be safe than sorry with our compatibility bitmask EGL_NONE }; int chosen_config_count; errcode = eglChooseConfig(egl_display, attr_list, configs, config_count * sizeof(EGLConfig), &chosen_config_count); if (errcode != EGL_TRUE) fprintf(stderr, "ERR %s: Could not choose EGL config parameters\n", egl_s(errcode)); int chosen_config_id = 0; EGLConfig *chosen_config = configs[0]; fprintf(stdout, "Found %d possible configurations:\n", chosen_config_count); for (int i = 0; i < chosen_config_count; ++i) { fprintf(stdout, "\tCONFIG %d:\n", i); int attr; #define PRINT_ATTR(attr_name) \ errcode = eglGetConfigAttrib(egl_display, configs[i], attr_name, &attr); \ int attr_##attr_name = -1; \ do { \ if (errcode == EGL_TRUE) { \ fprintf(stdout, "\t\t" #attr_name ": %d (%#0x)\n", attr, attr); \ attr_##attr_name = attr; \ } \ } while (0) PRINT_ATTR(EGL_ALPHA_SIZE); PRINT_ATTR(EGL_ALPHA_MASK_SIZE); PRINT_ATTR(EGL_BIND_TO_TEXTURE_RGB); PRINT_ATTR(EGL_BIND_TO_TEXTURE_RGBA); PRINT_ATTR(EGL_BLUE_SIZE); PRINT_ATTR(EGL_BUFFER_SIZE); PRINT_ATTR(EGL_COLOR_BUFFER_TYPE); PRINT_ATTR(EGL_CONFIG_CAVEAT); PRINT_ATTR(EGL_CONFIG_ID); PRINT_ATTR(EGL_CONFORMANT); PRINT_ATTR(EGL_DEPTH_SIZE); PRINT_ATTR(EGL_GREEN_SIZE); PRINT_ATTR(EGL_LEVEL); PRINT_ATTR(EGL_LUMINANCE_SIZE); PRINT_ATTR(EGL_MAX_PBUFFER_WIDTH); PRINT_ATTR(EGL_MAX_PBUFFER_HEIGHT); PRINT_ATTR(EGL_MAX_PBUFFER_PIXELS); PRINT_ATTR(EGL_MAX_SWAP_INTERVAL); PRINT_ATTR(EGL_NATIVE_RENDERABLE); PRINT_ATTR(EGL_NATIVE_VISUAL_ID); PRINT_ATTR(EGL_NATIVE_VISUAL_TYPE); PRINT_ATTR(EGL_RED_SIZE); PRINT_ATTR(EGL_RENDERABLE_TYPE); PRINT_ATTR(EGL_SAMPLE_BUFFERS); PRINT_ATTR(EGL_SAMPLES); PRINT_ATTR(EGL_STENCIL_SIZE); PRINT_ATTR(EGL_SURFACE_TYPE); PRINT_ATTR(EGL_TRANSPARENT_TYPE); PRINT_ATTR(EGL_TRANSPARENT_RED_VALUE); PRINT_ATTR(EGL_TRANSPARENT_GREEN_VALUE); PRINT_ATTR(EGL_TRANSPARENT_BLUE_VALUE); // Try to at least choose an RGBA 32bit config if (attr_EGL_ALPHA_SIZE == 8 && attr_EGL_BLUE_SIZE == 8 && attr_EGL_RED_SIZE == 8 && attr_EGL_GREEN_SIZE == 8 && attr_EGL_BUFFER_SIZE == 32 && attr_EGL_COLOR_BUFFER_TYPE == EGL_RGB_BUFFER && attr_EGL_SAMPLE_BUFFERS == 0) // with no multisampling { chosen_config_id = i; } } #undef PRINT_ATTR chosen_config = configs[chosen_config_id]; fprintf(stdout, "Chosen config #%d\n", chosen_config_id); EGLSurface egl_surface = eglCreateWindowSurface(egl_display, chosen_config, (EGLNativeWindowType)window, NULL); switch ((intptr_t)egl_surface) { case (intptr_t)EGL_NO_SURFACE: case EGL_BAD_DISPLAY: case EGL_NOT_INITIALIZED: case EGL_BAD_CONFIG: case EGL_BAD_NATIVE_WINDOW: case EGL_BAD_ATTRIBUTE: case EGL_BAD_ALLOC: case EGL_BAD_MATCH: fprintf(stderr, "ERR %s: Could not create EGL window surface\n", egl_s((intptr_t)egl_surface)); break; } /* Even if we ask for a particular OGL version, the driver is likely to choose the latest one, so we do not even bother */ static const EGLint context_attribs[] = { #ifdef DEBUG EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE, #endif EGL_NONE }; /* Change from OpenGL ES to regular OpenGL */ errcode = eglBindAPI(EGL_OPENGL_API); if (errcode != EGL_TRUE) fprintf(stderr, "ERR %s: Could not bind OpenGL API to EGL\n", egl_s(errcode)); // Since we already bound desktop OGL, this is actually making a OGL context instead of an OGL ES one EGLContext egl_context = eglCreateContext(egl_display, chosen_config, EGL_NO_CONTEXT, context_attribs); errcode = eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); if (errcode != EGL_TRUE) fprintf(stderr, "ERR %s: could not make EGL context current\n", egl_s(errcode)); /* NOTE: It would seem that having a non-zero swap interval can hang the Mesa driver forever, according to this SDL GH issue: https://github.com/libsdl-org/SDL/issues/4335#issuecomment-829789881 */ if (eglSwapInterval(egl_display, 0) != EGL_TRUE) fprintf(stderr, "Could not set EGL swap interval\n"); // necessary before our first eglSwapBuffers wl_surface_commit(surface); // Main event loop int framecount = 0; while (running) { wl_display_dispatch_pending(display); //fprintf(stdout, "Frame %d\n", framecount); glClearColor(0.5, 0.3, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glFlush(); eglSwapBuffers(egl_display, egl_surface); ++framecount; } wl_display_disconnect(display); return 0; }