#if 0 if [ "$1" == "meta" ]; then mkdir -p wayland wayland-scanner client-header /usr/share/wayland/wayland.xml wayland/wayland-client-protocol.h wayland-scanner private-code /usr/share/wayland/wayland.xml wayland/wayland-client-protocol.c wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland/xdg-shell-client-protocol.h wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland/xdg-shell-client-protocol.c else mkdir -p build clang -o build/demo -g wayland_xdg_egl.c -lwayland-client -lwayland-egl -lEGL fi exit 0 #endif /* ** Reading From: ** (1) Wayland Docs https://wayland.freedesktop.org/docs/html/ ** (2) XDG shell Docs https://wayland.app/protocols/xdg-shell#xdg_wm_base ** (egl) EGL spec https://registry.khronos.org/EGL/sdk/docs/man/ ** ** (?) I cannot find any documentation for wl_egl_ except for headers ** and example usage code. :( */ /* [1] IMPORTANT NOTE ** ** (1) /ch04.html#sect-Protocol-Code-Generation ** " The interfaces, requests and events are defined in ** protocol/wayland.xml. This xml is used to generate the function ** prototypes that can be used by clients and compositors. " ** ** (1) /ch04.html#sect-Protocol-Basic-Principles ** " The Wayland protocol is an asynchronous object oriented protocol. ** All requests are method invocations on some object. ** ... ** The protocol is message-based. A message sent by a client to ** the server is called request. A message from the server to a ** client is called event. " ** **~ **~ I have yet to see any place where it is *defined* how the xml **~ is transformed into function prototypes. So I'm putting notes of **~ my own inferences here. **~ **~ From the client's perspective: **~ **~ A request `R(...)` on an object interface `O` becomes the **~ function `O_R(O *obj, ...)`. **~ ! Sometimes the arguments for `R(...)` include an argument that **~ doesn't appear to have a documented type (at least in (1)). **~ We can also refer to the xml and see such arguments have **~ the type "new_id". These seem to actually be a return value **~ that does not appear in the generated signature as an id, **~ but instead is translated into a typed return pointer. **~ If the documentation does explicitly write the "new_id" type **~ that also gets rewritten to a typed return. **~ **~ An event `E(...)` on an object interface `O` will becomes a **~ callback with the signature `(*)(void *data, O *obj, ...)`. **~ **~ The collection of event callbacks on an object interface become **~ `struct wl_O_listener` using the names `E` of the events as the **~ field names. **~ **~ A function for adding the listener to the object is also generated **~ `wl_O_add_listener(O *obj, struct wl_O_listener *listener, void *data)` **~ **~ Each object interface `O` becomes a `wl_O_interface` which (I think) **~ is a global variable vtable representation of the interface. **~ */ #include "wayland/wayland-client-protocol.h" #include "wayland/xdg-shell-client-protocol.h" #include /*~ NOTE: wayland-egl.h *before* EGL/ */ #include #include #include #include #include #include "wayland/wayland-client-protocol.c" #include "wayland/xdg-shell-client-protocol.c" // X(N:name,R:return,P:params) #define GL_FUNCS_XLIST(X)\ X(glDrawBuffer, void, (GLenum buf)) \ X(glViewport, void, (GLint x, GLint y, GLsizei w, GLsizei h)) \ X(glClear, void, (GLbitfield mask)) \ X(glClearColor, void, (GLfloat r, GLfloat g, GLfloat b, GLfloat a)) #define X(N,R,P) R (*N)P = 0; GL_FUNCS_XLIST(X) #undef X typedef struct Ctx{ /* globals */ struct wl_display *wl_display; struct wl_registry *wl_registry; struct wl_compositor *wl_compositor; struct xdg_wm_base *xdg_wm_base; /* window */ struct wl_surface *wl_surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct wl_region *wl_region; struct wl_egl_window *wl_egl_window; EGLDisplay egl_display; EGLContext egl_context; EGLSurface egl_surface; int close_signal; } Ctx; static Ctx ctx = {0}; /* (2) xdg_wm_base::ping */ static void wlevent__xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial){ xdg_wm_base_pong(xdg_wm_base, serial); } const struct xdg_wm_base_listener xdg_wm_base_listener = { wlevent__xdg_wm_base_ping, }; /* (2) xdg_surface::configure ** " marks the end of a configure sequence ... ** Clients should arrange their surface for the new states, and then ** send an ack_configure request with the serial sent in this ** configure event at some point before committing the new surface. " */ static void wlevent__xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial){ xdg_surface_ack_configure(xdg_surface, serial); wl_surface_commit(ctx.wl_surface); } const struct xdg_surface_listener xdg_surface_listener = { wlevent__xdg_surface_configure, }; /* (2) xdg_toplevel::configure ** " This configure event asks the client to resize its toplevel surface ** or to change its state. The configured state should not be applied ** immediately. See xdg_surface.configure for details. " ** @see:(xdg_surface::configure) */ static void wlevent__xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states){ if (width > 0 && height > 0){ /* (?) */ wl_egl_window_resize(ctx.wl_egl_window, width, height, 0, 0); } } /* (2) xdg_toplevel::close ** " sent by the compositor when the user wants the surface to be closed. " */ static void wlevent__xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel){ ctx.close_signal = 1; } /* (2) xdg_toplevel::configure_bounds ** " sent [...] to communicate the bounds a window geometry size is ** recommended to constrain to " **~ NOTE: basically "tells you the monitor size". ** @see:xdg_surface.configure */ static void wlevent__xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height){ } /* (2) xdg_toplevel::wm_capabilities ** " advertises the capabilities supported by the compositor " **~ NOTE: basically "tells you the monitor size". ** @see:xdg_surface.configure */ static void wlevent__xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities){ } const struct xdg_toplevel_listener xdg_toplevel_listener = { wlevent__xdg_toplevel_configure, wlevent__xdg_toplevel_close, wlevent__xdg_toplevel_configure_bounds, wlevent__xdg_toplevel_wm_capabilities, }; /* (1) Appendix A: wl_registry::global ** " The event notifies the client that a global object with the given ** name is now available " */ static void wlevent__wl_registry_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version){ /* (1) Appendix A: wl_registry::bind ** " Binds a new, client-created object to the server " */ if (strcmp(interface, "wl_compositor") == 0){ ctx.wl_compositor = (struct wl_compositor*) wl_registry_bind(registry, name, &wl_compositor_interface, 1); } else if (strcmp(interface, "xdg_wm_base") == 0){ ctx.xdg_wm_base = (struct xdg_wm_base*) wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); /* [1] */ xdg_wm_base_add_listener(ctx.xdg_wm_base, &xdg_wm_base_listener, 0); } } /* (1) Appendix A: wl_registry::global_remove */ static void wlevent__wl_registry_global_remove(void *data, struct wl_registry *registry, uint32_t name){} const struct wl_registry_listener wl_registry_listener = { wlevent__wl_registry_global, wlevent__wl_registry_global_remove, }; int main(){ /* (1) Appendix B: wl_display_connect ** " Connect to a Wayland display. " */ ctx.wl_display = wl_display_connect(0); if (ctx.wl_display == 0){ printf("wl_display_connect failed\n"); } /* (1) Appendix A: wl_display::get_registry ** " creates a registry object that allows the client to list ** and bind the global objects available from the compositor " */ if (ctx.wl_display != 0){ ctx.wl_registry = wl_display_get_registry(ctx.wl_display); if (ctx.wl_registry == 0){ printf("wl_display_get_registry failed\n"); } } if (ctx.wl_registry != 0){ /* [1] */ wl_registry_add_listener(ctx.wl_registry, &wl_registry_listener, 0); /* (1) Appendix B: wl_display::dispatch ** " Dispatch events on the default event queue. If the default ** event queue is empty, this function blocks until there are ** events to be read from the display fd. " */ wl_display_dispatch(ctx.wl_display); /* (1) Appendix B: wl_display_roundtrip ** " Block until all pending request are processed by the server " */ wl_display_roundtrip(ctx.wl_display); if (ctx.wl_compositor == 0){ printf("failed to get wl_compositor\n"); } } int opengl_load_success = 0; if (ctx.wl_display != 0 && ctx.wl_compositor != 0){ /* (egl) eglGetDisplay ** " obtains the EGL display connection for the native display " */ ctx.egl_display = eglGetDisplay(ctx.wl_display); if (ctx.egl_display == 0){ printf("eglGetDisplay failed\n"); } /* (egl) eglInitialize ** " initializes* the EGL display connection obtained with eglGetDisplay " */ int egl_init_success = 0; EGLint major = 0, minor = 0; if (ctx.egl_display != 0){ egl_init_success = eglInitialize(ctx.egl_display, &major, &minor); if (!egl_init_success){ printf("eglInitialize failed\n"); } } // print version if (egl_init_success){ printf("EGL version %d.%d\n", major, minor); if (major < 1 || (major == 1 && minor < 5)){ printf("version 1.5 or higher\n"); egl_init_success = 0; } } /* (egl) eglBindAPI ** " defines the current rendering API for EGL in the thread it is ** called from " */ EGLBoolean bind_api_success = 0; if (egl_init_success){ bind_api_success = eglBindAPI(EGL_OPENGL_API); } /*~ NOTE: **~ Create OpenGL context & link OpenGL procedures */ /* (egl) eglCreateContext ** " creates an EGL rendering context for the current rendering API ** (as set with eglBindAPI) and returns a handle to the context " */ if (bind_api_success){ EGLint attr[] = { EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, EGL_NONE, }; ctx.egl_context = eglCreateContext(ctx.egl_display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attr); if (ctx.egl_context == EGL_NO_CONTEXT){ printf("eglCreateContext failed\n"); } } /* (egl) eglMakeCurrent ** " binds context to the current rendering thread " */ EGLBoolean make_current_success = 0; if (ctx.egl_context != 0){ make_current_success = eglMakeCurrent(ctx.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx.egl_context); if (!make_current_success){ printf("eglMakeCurrent failed\n"); } } /* (egl) eglGetProcAddress ** " returns the address of the client API or EGL function " */ if (make_current_success){ opengl_load_success = 1; #define X(N,R,P) N = (R(*)P)(eglGetProcAddress(#N)); if (N == 0) opengl_load_success = 0; GL_FUNCS_XLIST(X) #undef X if (!opengl_load_success){ printf("GL procedure loading failed\n"); } } } if (opengl_load_success){ /* (1) Appendix A: wl_compositor::create_surface ** " create new surface " */ ctx.wl_surface = wl_compositor_create_surface(ctx.wl_compositor); if (ctx.wl_surface == 0){ printf("wl_compositor_create_surface failed\n"); } } if (ctx.wl_surface != 0){ /* (2) xdg_wm_base::get_xdg_surface ** " creates an xdg_surface for the given surface " */ ctx.xdg_surface = xdg_wm_base_get_xdg_surface(ctx.xdg_wm_base, ctx.wl_surface); if (ctx.xdg_surface == 0){ printf("xdg_wm_base_get_xdg_surface failed\n"); } } if (ctx.xdg_surface != 0){ /* [1] */ xdg_surface_add_listener(ctx.xdg_surface, &xdg_surface_listener, 0); /* (2) xdg_surface::get_toplevel ** " creates an xdg_toplevel object for the given xdg_surface and gives ** the associated wl_surface the xdg_toplevel role. " */ ctx.xdg_toplevel = xdg_surface_get_toplevel(ctx.xdg_surface); if (ctx.xdg_toplevel == 0){ printf("xdg_surface_get_toplevel\n"); } } if (ctx.xdg_toplevel != 0){ /* (2) xdg_toplevel::set_title " ** " set a short title for the surface " */ xdg_toplevel_set_title(ctx.xdg_toplevel, "Wayland EGL example"); /* [1] */ xdg_toplevel_add_listener(ctx.xdg_toplevel, &xdg_toplevel_listener, NULL); } if (ctx.xdg_surface != 0){ /* (1) Appendix A: wl_compositor::create_region ** " create new region " */ ctx.wl_region = wl_compositor_create_region(ctx.wl_compositor); if (ctx.wl_region == 0){ printf("wl_compositor_create_region failed\n"); } if (ctx.wl_surface != 0 && ctx.wl_region != 0){ /* (1) Appendix A: wl_region::add ** " add rectangle to region " */ wl_region_add(ctx.wl_region, 0, 0, 640, 480); /* (1) Appendix A: wl_region::set_opaque_region ** " The opaque region is an optimization hint for the compositor ** that lets it optimize out redrawing of content behind opaque ** regions. " */ wl_surface_set_opaque_region(ctx.wl_surface, ctx.wl_region); /* (?) */ ctx.wl_egl_window = wl_egl_window_create(ctx.wl_surface, 640, 480); if (ctx.wl_egl_window == EGL_NO_SURFACE){ printf("wl_egl_window_create failed\n"); } } } if (ctx.wl_egl_window != EGL_NO_SURFACE){ /* (egl) eglChooseConfig ** " returns in configs a list of all EGL frame buffer configurations ** that match the attributes specified in attrib_list " */ EGLConfig configs[64]; EGLint config_cap = sizeof(configs)/sizeof(*configs); EGLint config_count = 0; { EGLint attributes[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_CONFORMANT, EGL_OPENGL_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8, EGL_NONE, }; if (!eglChooseConfig(ctx.egl_display, attributes, configs, config_cap, &config_count)){ printf("eglChooseConfig failed\n"); config_count = 0; } } /* (egl) eglCreateWindowSurface ** " creates an on-screen EGL window surface and returns a handle to it " */ { EGLint attributes[] = { EGL_RENDER_BUFFER, EGL_BACK_BUFFER, EGL_NONE, }; for (EGLint i = 0; i < config_count; i += 1){ ctx.egl_surface = eglCreateWindowSurface(ctx.egl_display, configs[i], ctx.wl_egl_window, attributes); if (ctx.egl_surface != EGL_NO_SURFACE){ break; } } if (ctx.egl_surface == EGL_NO_SURFACE){ printf("eglCreateWindowSurface failed\n"); } } } /* (egl) eglMakeCurrent */ EGLBoolean make_current_success2 = 0; if (ctx.egl_surface != EGL_NO_SURFACE){ make_current_success2 = eglMakeCurrent(ctx.egl_display, ctx.egl_surface, ctx.egl_surface, ctx.egl_context); } /* (egl) eglSwapInterval ** " Specifies the minimum number of video frames that are displayed before ** a buffer swap will occur " */ EGLBoolean swap_interval_success = 0; if (make_current_success2){ swap_interval_success = eglSwapInterval(ctx.egl_display, 1); } /*~ NOTE: Main loop */ int exit_loop = 0; for (;!exit_loop;){ /* (1) Appendix B: wl_display_dispatch_pending ** " This function dispatches events on the main event queue. ** ... it doesn't block." */ wl_display_dispatch_pending(ctx.wl_display); if (ctx.close_signal){ exit_loop = 1; } /*~ NOTE: render */ { glDrawBuffer(GL_BACK); glViewport(0, 0, 640, 480); glClearColor(0.40f, 0.90f, 0.15f, 1.f); glClear(GL_COLOR_BUFFER_BIT); } /* (egl) eglSwapBuffers ** " back-buffered window surface, then the color buffer is copied ** (posted) to the native window associated with that surface " */ EGLBoolean swap_success = eglSwapBuffers(ctx.egl_display, ctx.egl_surface); if (!swap_success){ printf("eglSwapBuffers failed\n"); } } /* (1) #Client-classwl__display_1a9150a7e3213a58b469a6966e60a9f108 ** " Close the connection to display " */ if (ctx.wl_display != 0){ wl_display_disconnect(ctx.wl_display); } return(0); }