#include #include #include #include #include #include #include #include #include #include #include // Generated header using wayland-scanner #include "xdg-shell-client-protocol.h" #include "zwp-text-input-unstable-v3-protocol.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; 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\n"); } 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\n"); } 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); } 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); } void key_callback(void *data, struct wl_keyboard *kb, uint serial, uint time, uint key, uint state) { /* TODO: This does *not* respect the system's language defaults. At least not in KDE. * Wayland's own (terrible) documentation does not go into detail about languages: * https://wayland-book.com/seat/xkb.html */ 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); } 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); } 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); } void pointer_leave_callback(void *data, struct wl_pointer *pointer, uint serial, struct wl_surface *surface) { //fprintf(stdout, "Pointer left WL surface\n"); } 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); } 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); } 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); } 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"); 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"); // TODO: Apparently we are not setting opcode 10's callback? 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 }; 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; }