476 lines
18 KiB
C
476 lines
18 KiB
C
#include <stdio.h>
|
|
#include <wayland-client.h>
|
|
#include <wayland-egl.h>
|
|
#include <EGL/egl.h>
|
|
#include <GL/gl.h>
|
|
#include <xkbcommon/xkbcommon.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
// Generated header using wayland-scanner
|
|
#include "xdg-shell-client-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;
|
|
|
|
/* 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, 9);
|
|
}
|
|
}
|
|
|
|
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");
|
|
|
|
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;
|
|
}
|