{"slug": "simple-example-of-a-wayland-client-with-xdg-shell-and-egl", "title": "Simple example of a wayland client with xdg-shell and EGL", "summary": "This article provides a C code example demonstrating how to create a Wayland client that uses the xdg-shell protocol and EGL for OpenGL rendering. The code includes setup for the Wayland display, registry, compositor, and xdg-shell components, along with EGL surface and context initialization. It also implements listeners for handling window configuration, resizing, and close events.", "body_md": "Last active\nMay 23, 2026 23:52\n\n-\n-\nSave lmarz/1059f7c4101a15e2a04d6991d7b7b3d1 to your computer and use it in GitHub Desktop.\n\nSimple example of a wayland client with xdg-shell and EGL\n\nThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.\n\n[Learn more about bidirectional Unicode characters](https://github.co/hiddenchars)| /* Compile with: | |\n| * wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell.c | |\n| * wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell.h | |\n| * gcc -c xdg-shell.c | |\n| * gcc -c egl-on-wayland-xdg.c | |\n| * gcc -o egl-on-wayland-xdg xdg-shell.o egl-on-wayland-xdg.o -lwayland-egl -lwayland-client -lEGL -lGL | |\n| */ | |\n| #include <stdio.h> | |\n| #include <stdlib.h> | |\n| #include <string.h> | |\n| #include <wayland-egl.h> | |\n| #include <EGL/egl.h> | |\n| #include <GL/gl.h> | |\n| #include \"xdg-shell.h\" | |\n| struct client_state { | |\n| struct wl_display *display; | |\n| struct wl_registry *registry; | |\n| struct wl_compositor *compositor; | |\n| struct xdg_wm_base *xdg_wm_base; | |\n| struct wl_surface *surface; | |\n| struct xdg_surface *xdg_surface; | |\n| struct xdg_toplevel *xdg_toplevel; | |\n| struct wl_egl_window *egl_window; | |\n| EGLDisplay egl_display; | |\n| EGLConfig egl_config; | |\n| EGLContext egl_context; | |\n| EGLSurface egl_surface; | |\n| int32_t width; | |\n| int32_t height; | |\n| uint8_t running; | |\n| }; | |\n| /******************************/ | |\n| /*********** Registry***********/ | |\n| /******************************/ | |\n| static void global_registry(void *data, struct wl_registry *wl_registry, | |\n| uint32_t name, const char *interface, uint32_t version) { | |\n| struct client_state *state = data; | |\n| if(!strcmp(interface, wl_compositor_interface.name)) { | |\n| state->compositor = wl_registry_bind(wl_registry, name, | |\n| &wl_compositor_interface, version); | |\n| } else if(!strcmp(interface, xdg_wm_base_interface.name)) { | |\n| state->xdg_wm_base = wl_registry_bind(wl_registry, name, | |\n| &xdg_wm_base_interface, version); | |\n| } | |\n| } | |\n| static void global_remove(void *data, struct wl_registry *wl_registry, | |\n| uint32_t name) { | |\n| (void) data; | |\n| (void) wl_registry; | |\n| (void) name; | |\n| } | |\n| static const struct wl_registry_listener registry_listener = { | |\n| global_registry, | |\n| global_remove | |\n| }; | |\n| /******************************/ | |\n| /****** XDG Window Manager******/ | |\n| /******************************/ | |\n| static void wm_ping(void *data, struct xdg_wm_base *xdg_wm_base, | |\n| uint32_t serial) { | |\n| (void) data; | |\n| xdg_wm_base_pong(xdg_wm_base, serial); | |\n| } | |\n| static const struct xdg_wm_base_listener wm_base_listener = { | |\n| wm_ping | |\n| }; | |\n| /******************************/ | |\n| /********* XDG Surface**********/ | |\n| /******************************/ | |\n| static void surface_configure(void *data, struct xdg_surface *xdg_surface, | |\n| uint32_t serial) { | |\n| (void) data; | |\n| xdg_surface_ack_configure(xdg_surface, serial); | |\n| } | |\n| static const struct xdg_surface_listener surface_listener = { | |\n| surface_configure | |\n| }; | |\n| /******************************/ | |\n| /******** XDG Toplevel**********/ | |\n| /******************************/ | |\n| static void toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, | |\n| int32_t width, int32_t height, struct wl_array *states) { | |\n| struct client_state *state = data; | |\n| (void) xdg_toplevel; | |\n| (void) states; | |\n| if(!width && !height) return; | |\n| if(state->width != width || state->height != height) { | |\n| state->width = width; | |\n| state->height = height; | |\n| wl_egl_window_resize(state->egl_window, width, height, 0, 0); | |\n| wl_surface_commit(state->surface); | |\n| } | |\n| } | |\n| static void toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { | |\n| (void) xdg_toplevel; | |\n| struct client_state *state = data; | |\n| state->running = 0; | |\n| } | |\n| static const struct xdg_toplevel_listener toplevel_listener = { | |\n| toplevel_configure, | |\n| toplevel_close | |\n| }; | |\n| /******************************/ | |\n| /******************************/ | |\n| /******************************/ | |\n| static void wayland_connect(struct client_state *state) { | |\n| state->display = wl_display_connect(NULL); | |\n| if(!state->display) { | |\n| fprintf(stderr, \"Couldn't connect to wayland display\\n\"); | |\n| exit(EXIT_FAILURE); | |\n| } | |\n| state->registry = wl_display_get_registry(state->display); | |\n| wl_registry_add_listener(state->registry, ®istry_listener, state); | |\n| wl_display_roundtrip(state->display); | |\n| if(!state->compositor || !state->xdg_wm_base) { | |\n| fprintf(stderr, \"Couldn't find compositor or xdg shell\\n\"); | |\n| exit(EXIT_FAILURE); | |\n| } | |\n| xdg_wm_base_add_listener(state->xdg_wm_base, &wm_base_listener, NULL); | |\n| state->surface = wl_compositor_create_surface(state->compositor); | |\n| state->xdg_surface = xdg_wm_base_get_xdg_surface(state->xdg_wm_base, | |\n| state->surface); | |\n| xdg_surface_add_listener(state->xdg_surface, &surface_listener, NULL); | |\n| state->xdg_toplevel = xdg_surface_get_toplevel(state->xdg_surface); | |\n| xdg_toplevel_set_title(state->xdg_toplevel, \"Hello World\"); | |\n| xdg_toplevel_add_listener(state->xdg_toplevel, &toplevel_listener, state); | |\n| wl_surface_commit(state->surface); | |\n| } | |\n| static void egl_init(struct client_state *state) { | |\n| EGLint major; | |\n| EGLint minor; | |\n| EGLint num_configs; | |\n| EGLint attribs[] = { | |\n| EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, | |\n| EGL_NONE | |\n| }; | |\n| state->egl_window = wl_egl_window_create(state->surface, state->width, | |\n| state->height); | |\n| state->egl_display = eglGetDisplay((EGLNativeDisplayType) state->display); | |\n| if(state->display == EGL_NO_DISPLAY) { | |\n| fprintf(stderr, \"Couldn't get EGL display\\n\"); | |\n| exit(EXIT_FAILURE); | |\n| } | |\n| if(eglInitialize(state->egl_display, &major, &minor) != EGL_TRUE) { | |\n| fprintf(stderr, \"Couldnt initialize EGL\\n\"); | |\n| exit(EXIT_FAILURE); | |\n| } | |\n| if(eglChooseConfig(state->egl_display, attribs, &state->egl_config, 1, | |\n| &num_configs) != EGL_TRUE) { | |\n| fprintf(stderr, \"CouldnÄt find matching EGL config\\n\"); | |\n| exit(EXIT_FAILURE); | |\n| } | |\n| state->egl_surface = eglCreateWindowSurface(state->egl_display, | |\n| state->egl_config, | |\n| (EGLNativeWindowType) state->egl_window, NULL); | |\n| if(state->egl_surface == EGL_NO_SURFACE) { | |\n| fprintf(stderr, \"Couldn't create EGL surface\\n\"); | |\n| exit(EXIT_FAILURE); | |\n| } | |\n| state->egl_context = eglCreateContext(state->egl_display, state->egl_config, | |\n| EGL_NO_CONTEXT, NULL); | |\n| if(state->egl_context == EGL_NO_CONTEXT) { | |\n| fprintf(stderr, \"Couldn't create EGL context\\n\"); | |\n| exit(EXIT_FAILURE); | |\n| } | |\n| if(!eglMakeCurrent(state->egl_display, state->egl_surface, | |\n| state->egl_surface, state->egl_context)) { | |\n| fprintf(stderr, \"Couldn't make EGL context current\\n\"); | |\n| exit(EXIT_FAILURE); | |\n| } | |\n| } | |\n| static void draw(struct client_state *state) { | |\n| glClearColor(0.0/255, 79.0/255, 158.0/255, 1.0); | |\n| glClear(GL_COLOR_BUFFER_BIT); | |\n| eglSwapBuffers(state->egl_display, state->egl_surface); | |\n| } | |\n| int main(int argc, char const *argv[]) { | |\n| struct client_state state; | |\n| state.width = 800; | |\n| state.height = 600; | |\n| state.running = 1; | |\n| wayland_connect(&state); | |\n| egl_init(&state); | |\n| while(state.running) { | |\n| wl_display_dispatch_pending(state.display); | |\n| draw(&state); | |\n| } | |\n| eglDestroySurface(state.egl_display, state.egl_surface); | |\n| eglDestroyContext(state.egl_display, state.egl_context); | |\n| wl_egl_window_destroy(state.egl_window); | |\n| xdg_toplevel_destroy(state.xdg_toplevel); | |\n| xdg_surface_destroy(state.xdg_surface); | |\n| wl_surface_destroy(state.surface); | |\n| wl_display_disconnect(state.display); | |\n| return 0; | |\n| } |\n\nI just want to add my 2 cents:\n\nOn a current Archlinux System with Gnome (mutter compositor), I see two problems:\n\n`Couldn't create EGL context`\n\nThis is solved by adding\n\n```\neglBindAPI(EGL_OPENGL_API);\n```\n\nbefore creating the EGL context, e.g. in line [#184](https://gist.github.com/lmarz/1059f7c4101a15e2a04d6991d7b7b3d1#file-egl-on-wayland-xdg-c-L184).\n\n# listener function for opcode 2/3 of xdg_toplevel is NULL\n\nAdd the appropriate listener functions to [#126](https://gist.github.com/lmarz/1059f7c4101a15e2a04d6991d7b7b3d1#file-egl-on-wayland-xdg-c-L126):\n\n```\nstatic void toplevel_configure_bounds(void *data,\n\t\t\t\t struct xdg_toplevel *xdg_toplevel,\n\t\t\t\t int32_t width,\n\t\t\t\t int32_t height)\n{\n}\n\nstatic void toplevel_wm_capabilities(void *data,\n\t\t\t\tstruct xdg_toplevel *xdg_toplevel,\n\t\t\t\tstruct wl_array *capabilities)\n{\n}\n\nstatic const struct xdg_toplevel_listener toplevel_listener = {\n\ttoplevel_configure,\n\ttoplevel_close,\n\ttoplevel_configure_bounds,\n\ttoplevel_wm_capabilities\n};\n```\n\nFor me it was enough to simply implement them with an empty body.", "url": "https://wpnews.pro/news/simple-example-of-a-wayland-client-with-xdg-shell-and-egl", "canonical_source": "https://gist.github.com/lmarz/1059f7c4101a15e2a04d6991d7b7b3d1", "published_at": "2021-07-17 13:53:36+00:00", "updated_at": "2026-05-24 00:04:36.660319+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Wayland", "EGL", "OpenGL", "Xdg-shell"], "alternates": {"html": "https://wpnews.pro/news/simple-example-of-a-wayland-client-with-xdg-shell-and-egl", "markdown": "https://wpnews.pro/news/simple-example-of-a-wayland-client-with-xdg-shell-and-egl.md", "text": "https://wpnews.pro/news/simple-example-of-a-wayland-client-with-xdg-shell-and-egl.txt", "jsonld": "https://wpnews.pro/news/simple-example-of-a-wayland-client-with-xdg-shell-and-egl.jsonld"}}