move wayland experiments here
This commit is contained in:
parent
4b925dcb0d
commit
85e5323ee1
6 changed files with 662 additions and 0 deletions
19
audiovisual/wayland/README.md
Normal file
19
audiovisual/wayland/README.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Wayland experiments
|
||||||
|
Just a bunch of programs I wrote to teach myself about Wayland using the [Wayland book](https://wayland-book.com/) as reference.
|
||||||
|
|
||||||
|
Building this requires [Meson](https://mesonbuild.com/).
|
||||||
|
The build script will take care of creating the interfacing and glue code for you.
|
||||||
|
|
||||||
|
```
|
||||||
|
meson setup --buildtype=release build
|
||||||
|
meson compile -C build
|
||||||
|
```
|
||||||
|
|
||||||
|
## client.c
|
||||||
|
A Wayland client that draws a barberpole-like animation; implementing the XDG shell protocol, interfaces with
|
||||||
|
`wl_seat` and `wl_keyboard` too, so it will listen keyboard events and print them on the console.
|
||||||
|
Because of this, libxkbcommon is required.
|
||||||
|
|
||||||
|
You can close it by pressing the escape key or just how you would close any window (see below).
|
||||||
|
|
||||||
|
This client also implements the XDG decoration protocol and uses server-side decorations.
|
512
audiovisual/wayland/client.c
Normal file
512
audiovisual/wayland/client.c
Normal file
|
@ -0,0 +1,512 @@
|
||||||
|
// Wayland client example, made following the https://wayland-book.com/
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <err.h>
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <wayland-client-core.h>
|
||||||
|
#include <wayland-client-protocol.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include <wayland-util.h>
|
||||||
|
|
||||||
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
#include "shm_helper.h"
|
||||||
|
#include "xdg-shell-client-protocol.h"
|
||||||
|
#include "xdg-decoration-unstable-v1-client-protocol.h"
|
||||||
|
#define _DBGPRINT_
|
||||||
|
#include "dbgpri.h"
|
||||||
|
|
||||||
|
#define COMPOSITOR_INTERFACE_VER 4u
|
||||||
|
#define TOPLEVEL_TITLE "Fubuland"
|
||||||
|
#define TOPLEVEL_APP_ID "net.tocariimaa.fubuland"
|
||||||
|
|
||||||
|
struct ClientState
|
||||||
|
{
|
||||||
|
struct wl_display *display;
|
||||||
|
struct wl_compositor *compositor;
|
||||||
|
struct wl_registry *registry;
|
||||||
|
struct wl_shm *shm;
|
||||||
|
struct wl_seat *seat;
|
||||||
|
struct wl_keyboard *keyboard;
|
||||||
|
struct xdg_wm_base *xdg_wm_base;
|
||||||
|
|
||||||
|
struct wl_surface *wl_surface;
|
||||||
|
struct xdg_surface *xdg_surface;
|
||||||
|
struct xdg_toplevel *xdg_toplevel;
|
||||||
|
struct zxdg_decoration_manager_v1 *xdg_decoration_mgr;
|
||||||
|
struct zxdg_toplevel_decoration_v1 *xdg_toplevel_deco;
|
||||||
|
|
||||||
|
float offset;
|
||||||
|
uint32_t last_frame;
|
||||||
|
int32_t width, height;
|
||||||
|
bool closed;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct wl_buffer *buffer;
|
||||||
|
uint32_t *pool_data;
|
||||||
|
size_t pool_size;
|
||||||
|
} memory;
|
||||||
|
struct {
|
||||||
|
struct xkb_context *ctx;
|
||||||
|
struct xkb_state *state;
|
||||||
|
struct xkb_keymap *keymap;
|
||||||
|
} xkb;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void draw_frame(struct ClientState *state);
|
||||||
|
static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time);
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
draw_frame(struct ClientState *state)
|
||||||
|
{
|
||||||
|
// Macro helper to index the 1D buffer as a 2D lattice (buffer 2D index)
|
||||||
|
#define b2di(data, width, y, x) (data[y * width + x])
|
||||||
|
const int bar1_color = 0xe7f5c5;
|
||||||
|
const int bar2_color = 0xa2dd99;
|
||||||
|
const int bar1_thickness = 64; // px
|
||||||
|
const int bar2_thickness = 64;
|
||||||
|
|
||||||
|
int offset = (int)state->offset % bar2_thickness;
|
||||||
|
for (int y = 0; y < state->height; ++y) {
|
||||||
|
for (int x = 0; x < state->width; ++x) {
|
||||||
|
if (((x + offset) + (y + offset)) % bar1_thickness < bar2_thickness / 2)
|
||||||
|
b2di(state->memory.pool_data, state->width, y, x) = bar1_color;
|
||||||
|
else
|
||||||
|
b2di(state->memory.pool_data, state->width, y, x) = bar2_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef b2di
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
create_memory_pool(struct ClientState *state)
|
||||||
|
{
|
||||||
|
int width = state->width, height = state->height;
|
||||||
|
int stride = width * 4; // 4 bytes per pixel; for WL_SHM_FORMAT_XRGB8888
|
||||||
|
int pool_size = height * stride;
|
||||||
|
const int32_t offset = 0;
|
||||||
|
|
||||||
|
int shm_fd;
|
||||||
|
if ((shm_fd = allocate_shm_file(pool_size)) < 0)
|
||||||
|
err(EXIT_FAILURE, "couldn't create shared memory object");
|
||||||
|
|
||||||
|
uint32_t *pool_data = mmap(NULL, pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
|
||||||
|
if (pool_data == MAP_FAILED) {
|
||||||
|
warn("mmap failed");
|
||||||
|
close(shm_fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef __linux__
|
||||||
|
// Request the kernel to use hugepages for shared memory if possible, improves performance by reducing page faults.
|
||||||
|
// This only will work (on Linux) if transparent hugepages are enabled for shmem on your system.
|
||||||
|
// To see/enable this check /sys/kernel/mm/transparent_hugepage/shmem_enabled.
|
||||||
|
// Related: https://stackoverflow.com/questions/73278608/can-mmaps-performance-be-improved-for-shared-memory
|
||||||
|
if (madvise(pool_data, pool_size, MADV_HUGEPAGE) < 0)
|
||||||
|
warn("madvise hugepage");
|
||||||
|
#endif
|
||||||
|
// Might help...
|
||||||
|
if (madvise(pool_data, pool_size, MADV_WILLNEED) < 0)
|
||||||
|
warn("madvise willneed");
|
||||||
|
|
||||||
|
//dbgpri("w: %d h: %d\n", state->width, state->height);
|
||||||
|
struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, shm_fd, pool_size);
|
||||||
|
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, offset, width, height, stride, WL_SHM_FORMAT_XRGB8888);
|
||||||
|
wl_shm_pool_destroy(pool);
|
||||||
|
close(shm_fd);
|
||||||
|
|
||||||
|
state->memory.pool_data = pool_data;
|
||||||
|
state->memory.pool_size = pool_size;
|
||||||
|
state->memory.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static void
|
||||||
|
buffer_release(void *data, struct wl_buffer *wl_buf)
|
||||||
|
{
|
||||||
|
(void)data;
|
||||||
|
dbgpri("buffer release event");
|
||||||
|
wl_buffer_destroy(wl_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_buffer_listener buffer_listener = {
|
||||||
|
.release = buffer_release,
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void
|
||||||
|
destroy_memory_pool(struct ClientState *state)
|
||||||
|
{
|
||||||
|
munmap(state->memory.pool_data, state->memory.pool_size);
|
||||||
|
state->memory.pool_data = NULL;
|
||||||
|
wl_buffer_destroy(state->memory.buffer);
|
||||||
|
/* This event never gets called for some reason, leaking memory, so I just call wl_buffer_destroy manually */
|
||||||
|
/* wl_buffer_add_listener(state->memory.buffer, &buffer_listener, NULL); */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
xdg_toplevel_configure(void *data, struct xdg_toplevel *xdgtpl, int32_t width, int32_t height, struct wl_array *states)
|
||||||
|
{
|
||||||
|
(void)xdgtpl;
|
||||||
|
// the compositor is asking us to give it an initial size
|
||||||
|
if (width == 0 || height == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct ClientState *state = data;
|
||||||
|
state->width = width;
|
||||||
|
state->height = height;
|
||||||
|
dbgpri("XDG toplevel configure: ");
|
||||||
|
|
||||||
|
enum xdg_toplevel_state *xdg_state;
|
||||||
|
wl_array_for_each(xdg_state, states) {
|
||||||
|
putchar('\t');
|
||||||
|
switch (*xdg_state) {
|
||||||
|
case XDG_TOPLEVEL_STATE_MAXIMIZED:
|
||||||
|
puts("maximized");
|
||||||
|
break;
|
||||||
|
case XDG_TOPLEVEL_STATE_FULLSCREEN:
|
||||||
|
puts("fullscreen");
|
||||||
|
break;
|
||||||
|
case XDG_TOPLEVEL_STATE_RESIZING:
|
||||||
|
puts("resizing");
|
||||||
|
break;
|
||||||
|
case XDG_TOPLEVEL_STATE_ACTIVATED:
|
||||||
|
puts("activated");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("xdgstate: %d\n", *xdg_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
xdg_toplevel_close(void *data, struct xdg_toplevel *xdgtpl)
|
||||||
|
{
|
||||||
|
(void)xdgtpl;
|
||||||
|
((struct ClientState *)data)->closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
|
||||||
|
{
|
||||||
|
struct ClientState *state = data;
|
||||||
|
// Acknowledge to the server that we've configured the surface
|
||||||
|
xdg_surface_ack_configure(xdg_surface, serial);
|
||||||
|
dbgpri("XDG surface configure event");
|
||||||
|
|
||||||
|
if (state->memory.pool_data != NULL)
|
||||||
|
destroy_memory_pool(state);
|
||||||
|
// The surface has a new size now, so create a new memory pool with the proper size
|
||||||
|
// TODO: we can optimize for the case of the new surface size being smaller than the current
|
||||||
|
// buffer, avoiding creating a new buffer.
|
||||||
|
create_memory_pool(state);
|
||||||
|
|
||||||
|
// We have to draw here as well, otherwise we will send an empty frame
|
||||||
|
draw_frame(state);
|
||||||
|
wl_surface_attach(state->wl_surface, state->memory.buffer, 0, 0);
|
||||||
|
wl_surface_commit(state->wl_surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reply to a ping event from the compositor, so it does not think that we're dead.
|
||||||
|
static void
|
||||||
|
xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
|
||||||
|
{
|
||||||
|
(void)data;
|
||||||
|
xdg_wm_base_pong(xdg_wm_base, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct xdg_surface_listener xdg_surface_listener = {
|
||||||
|
.configure = xdg_surface_configure,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
|
||||||
|
.ping = xdg_wm_base_ping,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
||||||
|
.configure = xdg_toplevel_configure,
|
||||||
|
.close = xdg_toplevel_close,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct wl_callback_listener wl_surface_frame_listener = {
|
||||||
|
.done = surface_frame_done,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This function will be called each time the server requests a frame to be drawn
|
||||||
|
static void
|
||||||
|
surface_frame_done(void *data, struct wl_callback *cb, uint32_t time)
|
||||||
|
{
|
||||||
|
struct ClientState *state = data;
|
||||||
|
|
||||||
|
wl_callback_destroy(cb);
|
||||||
|
// Add this function itself to be called on the next frame event
|
||||||
|
cb = wl_surface_frame(state->wl_surface);
|
||||||
|
wl_callback_add_listener(cb, &wl_surface_frame_listener, state);
|
||||||
|
|
||||||
|
if (state->last_frame != 0) {
|
||||||
|
int elapsed = time - state->last_frame;
|
||||||
|
state->offset += elapsed / 1000.0 * 60; // move 60px per second
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_frame(state);
|
||||||
|
wl_surface_attach(state->wl_surface, state->memory.buffer, 0, 0);
|
||||||
|
// To "damage" a surface means to update it
|
||||||
|
wl_surface_damage_buffer(state->wl_surface, 0, 0, state->width, state->height);
|
||||||
|
// We're done with this frame, send it
|
||||||
|
wl_surface_commit(state->wl_surface);
|
||||||
|
state->last_frame = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keyboard_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size)
|
||||||
|
{
|
||||||
|
(void)keyboard;
|
||||||
|
assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1);
|
||||||
|
struct ClientState *state = data;
|
||||||
|
|
||||||
|
// Release previous xkb state, if any
|
||||||
|
xkb_keymap_unref(state->xkb.keymap);
|
||||||
|
xkb_state_unref(state->xkb.state);
|
||||||
|
|
||||||
|
// must be mapped as MAP_PRIVATE since wl_keyboard version 7
|
||||||
|
char *map_shm = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||||
|
if (map_shm == MAP_FAILED) {
|
||||||
|
warn("mmap keyboard keymap");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state->xkb.keymap = xkb_keymap_new_from_string(
|
||||||
|
state->xkb.ctx, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||||
|
munmap(map_shm, size);
|
||||||
|
close(fd);
|
||||||
|
state->xkb.state = xkb_state_new(state->xkb.keymap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keyboard_decode_key(struct ClientState *state, uint32_t keycode, uint32_t kstate)
|
||||||
|
{
|
||||||
|
const char *key_state = kstate == WL_KEYBOARD_KEY_STATE_PRESSED ? "press" : "release";
|
||||||
|
char key_name[255], keyutf8[255];
|
||||||
|
|
||||||
|
xkb_keysym_t sym = xkb_state_key_get_one_sym(state->xkb.state, keycode);
|
||||||
|
xkb_keysym_get_name(sym, key_name, sizeof(key_name));
|
||||||
|
xkb_state_key_get_utf8(state->xkb.state, keycode, keyutf8, sizeof(keyutf8));
|
||||||
|
dbgpri("Key %s: name: %s; code: %d; utf8: %s", key_state, key_name, sym, keyutf8);
|
||||||
|
|
||||||
|
if (sym == XKB_KEY_Escape)
|
||||||
|
state->closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keyboard_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface,
|
||||||
|
struct wl_array *keys)
|
||||||
|
{
|
||||||
|
(void)keyboard, (void)serial, (void)surface;
|
||||||
|
dbgpri("Got keyboard enter event");
|
||||||
|
uint32_t *key;
|
||||||
|
wl_array_for_each(key, keys) {
|
||||||
|
keyboard_decode_key(data, *key + 8, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keyboard_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface)
|
||||||
|
{
|
||||||
|
dbgpri("Keyboard focus leave event");
|
||||||
|
(void)data, (void)keyboard, (void)serial, (void)surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keyboard_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time,
|
||||||
|
uint32_t key, uint32_t kstate)
|
||||||
|
{
|
||||||
|
(void)keyboard, (void)serial, (void)time;
|
||||||
|
|
||||||
|
const uint32_t keycode = key + 8; // evdev codes to xkb codes
|
||||||
|
keyboard_decode_key(data, keycode, kstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keyboard_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mod_pressed,
|
||||||
|
uint32_t mod_latchd, uint32_t mod_lockd, uint32_t group)
|
||||||
|
{
|
||||||
|
(void)serial, (void)keyboard;
|
||||||
|
struct ClientState *state = data;
|
||||||
|
xkb_state_update_mask(state->xkb.state, mod_pressed, mod_latchd, mod_lockd,
|
||||||
|
0, 0, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
keyboard_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay)
|
||||||
|
{
|
||||||
|
(void)data, (void)keyboard, (void)rate, (void)delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_keyboard_listener wl_keyboard_listener = {
|
||||||
|
.keymap = keyboard_keymap,
|
||||||
|
.enter = keyboard_enter,
|
||||||
|
.leave = keyboard_leave,
|
||||||
|
.key = keyboard_key,
|
||||||
|
.modifiers = keyboard_modifiers,
|
||||||
|
.repeat_info = keyboard_repeat_info,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
seat_capabilities(void *data, struct wl_seat *seat, uint32_t capabilities)
|
||||||
|
{
|
||||||
|
struct ClientState *state = data;
|
||||||
|
bool keycap = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
|
||||||
|
if (keycap && state->keyboard == NULL) {
|
||||||
|
dbgpri("we have a keyboard");
|
||||||
|
state->keyboard = wl_seat_get_keyboard(seat);
|
||||||
|
wl_keyboard_add_listener(state->keyboard, &wl_keyboard_listener, data);
|
||||||
|
} else if (!keycap && state->keyboard != NULL) {
|
||||||
|
wl_keyboard_release(state->keyboard);
|
||||||
|
state->keyboard = NULL;
|
||||||
|
}
|
||||||
|
if (capabilities & WL_SEAT_CAPABILITY_POINTER)
|
||||||
|
dbgpri("we have a pointer");
|
||||||
|
if (capabilities & WL_SEAT_CAPABILITY_TOUCH)
|
||||||
|
dbgpri("we have a touchscreen");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
seat_name(void *data, struct wl_seat *seat, const char *name)
|
||||||
|
{
|
||||||
|
(void)data, (void)seat;
|
||||||
|
dbgpri("seat id: %s", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_seat_listener wl_seat_listener = {
|
||||||
|
.capabilities = seat_capabilities,
|
||||||
|
.name = seat_name,
|
||||||
|
};
|
||||||
|
|
||||||
|
void xdg_toplevel_deco_cfg(void *data, struct zxdg_toplevel_decoration_v1 *deco, uint32_t mode)
|
||||||
|
{
|
||||||
|
dbgpri("Deco cfg: %d", mode);
|
||||||
|
/* We're supposed to send an ack_configure, however, where do we get the serial?
|
||||||
|
* xdg_surface_ack_configure(((struct ClientState *)data)->xdg_surface, ?);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zxdg_toplevel_decoration_v1_listener xdg_deco_listener = {
|
||||||
|
.configure = xdg_toplevel_deco_cfg,
|
||||||
|
};
|
||||||
|
|
||||||
|
// On this callback we bind objects with an interface
|
||||||
|
void reg_handle_global(void *data, struct wl_registry *reg, uint32_t name, const char *interface, uint32_t ver)
|
||||||
|
{
|
||||||
|
(void)ver;
|
||||||
|
struct ClientState *state = data;
|
||||||
|
// dbgpri("Interface: '%s': ver.: %u, name: %u", interface, ver, name);
|
||||||
|
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
||||||
|
dbgpri("Bound compositor interface: %s", interface);
|
||||||
|
state->compositor = wl_registry_bind(reg, name, &wl_compositor_interface, COMPOSITOR_INTERFACE_VER);
|
||||||
|
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||||
|
dbgpri("Bound shared memory interface");
|
||||||
|
state->shm = wl_registry_bind(reg, name, &wl_shm_interface, 1);
|
||||||
|
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
|
||||||
|
dbgpri("Bound XDG WM base interface");
|
||||||
|
state->xdg_wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface, 1);
|
||||||
|
xdg_wm_base_add_listener(state->xdg_wm_base, &xdg_wm_base_listener, state);
|
||||||
|
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
||||||
|
dbgpri("Bound seat interface");
|
||||||
|
state->seat = wl_registry_bind(reg, name, &wl_seat_interface, 8);
|
||||||
|
wl_seat_add_listener(state->seat, &wl_seat_listener, state);
|
||||||
|
} else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
|
||||||
|
dbgpri("Bound XDG decoration protocol");
|
||||||
|
state->xdg_decoration_mgr = wl_registry_bind(reg, name, &zxdg_decoration_manager_v1_interface, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reg_handle_global_rm(void *data, struct wl_registry *reg, uint32_t name)
|
||||||
|
{
|
||||||
|
(void)data, (void)reg, (void)name;
|
||||||
|
dbgpri("unbind event: %u", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_registry_listener reg_listener = {
|
||||||
|
.global = reg_handle_global, // object bind event
|
||||||
|
.global_remove = reg_handle_global_rm, // '' removal ''
|
||||||
|
};
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
struct ClientState state = {.width = 640, .height = 480};
|
||||||
|
|
||||||
|
state.display = wl_display_connect(NULL);
|
||||||
|
if (state.display == NULL)
|
||||||
|
errx(EXIT_FAILURE, "could not open display");
|
||||||
|
|
||||||
|
dbgpri("Connected to Wayland server; display fd: %d", wl_display_get_fd(state.display));
|
||||||
|
|
||||||
|
// get the object registry
|
||||||
|
state.registry = wl_display_get_registry(state.display);
|
||||||
|
wl_registry_add_listener(state.registry, ®_listener, &state);
|
||||||
|
// Do a roundtrip, block until the server replies to all of our requests
|
||||||
|
wl_display_roundtrip(state.display);
|
||||||
|
|
||||||
|
if (state.compositor == NULL || state.shm == NULL || state.registry == NULL)
|
||||||
|
errx(EXIT_FAILURE, "Failed to retrieve essential interfaces (comp shm reg)");
|
||||||
|
|
||||||
|
if ((state.xkb.ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS)) == NULL)
|
||||||
|
errx(EXIT_FAILURE, "xkb_context_new");
|
||||||
|
|
||||||
|
state.wl_surface = wl_compositor_create_surface(state.compositor);
|
||||||
|
state.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.wl_surface);
|
||||||
|
xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state);
|
||||||
|
state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);
|
||||||
|
state.xdg_toplevel_deco = zxdg_decoration_manager_v1_get_toplevel_decoration(
|
||||||
|
state.xdg_decoration_mgr, state.xdg_toplevel
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Set up the toplevel decoration listener.
|
||||||
|
* Note that this must be done before the setup of the xdg_toplevel attaches any
|
||||||
|
* buffer to it. */
|
||||||
|
zxdg_toplevel_decoration_v1_add_listener(
|
||||||
|
state.xdg_toplevel_deco,
|
||||||
|
&xdg_deco_listener,
|
||||||
|
&state
|
||||||
|
);
|
||||||
|
xdg_toplevel_add_listener(state.xdg_toplevel, &xdg_toplevel_listener, &state);
|
||||||
|
xdg_toplevel_set_title(state.xdg_toplevel, TOPLEVEL_TITLE);
|
||||||
|
xdg_toplevel_set_app_id(state.xdg_toplevel, TOPLEVEL_APP_ID);
|
||||||
|
/* Tell the compositor to add decorations for us */
|
||||||
|
zxdg_toplevel_decoration_v1_set_mode(state.xdg_toplevel_deco, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||||
|
|
||||||
|
struct wl_callback *cb = wl_surface_frame(state.wl_surface);
|
||||||
|
// Bootstrap the frame drawing event sequence
|
||||||
|
wl_callback_add_listener(cb, &wl_surface_frame_listener, &state);
|
||||||
|
wl_surface_commit(state.wl_surface);
|
||||||
|
|
||||||
|
// Event dispatch loop
|
||||||
|
while (!state.closed && wl_display_dispatch(state.display) != -1) {
|
||||||
|
}
|
||||||
|
|
||||||
|
dbgpri("Closing connection and destroying objects");
|
||||||
|
/* Clean up */
|
||||||
|
xkb_keymap_unref(state.xkb.keymap);
|
||||||
|
xkb_state_unref(state.xkb.state);
|
||||||
|
xkb_context_unref(state.xkb.ctx);
|
||||||
|
destroy_memory_pool(&state);
|
||||||
|
zxdg_decoration_manager_v1_destroy(state.xdg_decoration_mgr);
|
||||||
|
zxdg_toplevel_decoration_v1_destroy(state.xdg_toplevel_deco);
|
||||||
|
xdg_toplevel_destroy(state.xdg_toplevel);
|
||||||
|
xdg_surface_destroy(state.xdg_surface);
|
||||||
|
xdg_wm_base_destroy(state.xdg_wm_base);
|
||||||
|
wl_seat_destroy(state.seat);
|
||||||
|
wl_surface_destroy(state.wl_surface);
|
||||||
|
wl_registry_destroy(state.registry);
|
||||||
|
wl_shm_destroy(state.shm);
|
||||||
|
wl_compositor_destroy(state.compositor);
|
||||||
|
wl_display_disconnect(state.display);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
33
audiovisual/wayland/dbgpri.h
Normal file
33
audiovisual/wayland/dbgpri.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef _dbpri_h_
|
||||||
|
#define _dbpri_h_
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#ifdef _DBGPRINT_
|
||||||
|
#define dbgpri(...) dbgpri_impl(__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define dbgpri(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void dbgpri_impl(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
struct timespec now;
|
||||||
|
char msg[2048];
|
||||||
|
|
||||||
|
va_start(args, fmt);
|
||||||
|
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
|
||||||
|
vsnprintf(msg, sizeof(msg), fmt, args);
|
||||||
|
fprintf(stderr, "[%ld.%-9ld] %s\n", now.tv_sec, now.tv_nsec, msg);
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dbgput(const char *str)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s\n", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
42
audiovisual/wayland/meson.build
Normal file
42
audiovisual/wayland/meson.build
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
project('wayland-client-test', 'c', default_options: ['warning_level=2'])
|
||||||
|
|
||||||
|
wl_scanner = dependency('wayland-scanner', native: true)
|
||||||
|
wl_scanner_prog = find_program(wl_scanner.get_variable('wayland_scanner'), native: true)
|
||||||
|
wl_scanner_pcode = generator(
|
||||||
|
wl_scanner_prog,
|
||||||
|
output: '@BASENAME@-protocol.c',
|
||||||
|
arguments: ['private-code', '@INPUT@', '@OUTPUT@']
|
||||||
|
)
|
||||||
|
wl_scanner_client = generator(
|
||||||
|
wl_scanner_prog,
|
||||||
|
output: '@BASENAME@-client-protocol.h',
|
||||||
|
arguments: ['client-header', '@INPUT@', '@OUTPUT@']
|
||||||
|
)
|
||||||
|
|
||||||
|
wl_stable_proto_dir = '/usr/share/wayland-protocols/stable/'
|
||||||
|
wl_unstable_proto_dir = '/usr/share/wayland-protocols/unstable/'
|
||||||
|
wl_protocols = [
|
||||||
|
wl_stable_proto_dir / 'xdg-shell/xdg-shell.xml',
|
||||||
|
wl_unstable_proto_dir / 'xdg-decoration/xdg-decoration-unstable-v1.xml'
|
||||||
|
]
|
||||||
|
|
||||||
|
wl_protocol_srcs = []
|
||||||
|
|
||||||
|
foreach filename : wl_protocols
|
||||||
|
wl_protocol_srcs += wl_scanner_pcode.process(filename)
|
||||||
|
wl_protocol_srcs += wl_scanner_client.process(filename)
|
||||||
|
endforeach
|
||||||
|
|
||||||
|
|
||||||
|
if get_option('buildtype') == 'release'
|
||||||
|
add_project_arguments('-march=native', '-fno-plt', '-flto', language: 'c')
|
||||||
|
endif
|
||||||
|
|
||||||
|
cc = meson.get_compiler('c')
|
||||||
|
deps = [
|
||||||
|
cc.find_library('rt', required: false), # for shm_* functions
|
||||||
|
dependency('wayland-client'),
|
||||||
|
dependency('xkbcommon'),
|
||||||
|
]
|
||||||
|
srcs = ['client.c', 'shm_helper.c', wl_protocol_srcs]
|
||||||
|
executable('client', srcs, dependencies: deps)
|
53
audiovisual/wayland/shm_helper.c
Normal file
53
audiovisual/wayland/shm_helper.c
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// taken from wayland-book.com (public domain)
|
||||||
|
#define _POSIX_C_SOURCE 200112L
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static void
|
||||||
|
randname(char *buf)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
|
long r = ts.tv_nsec;
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
buf[i] = 'A'+(r&15)+(r&16)*2;
|
||||||
|
r >>= 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
create_shm_file(void)
|
||||||
|
{
|
||||||
|
int retries = 100;
|
||||||
|
do {
|
||||||
|
char name[] = "/wl_shm-XXXXXX";
|
||||||
|
randname(name + sizeof(name) - 7);
|
||||||
|
--retries;
|
||||||
|
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||||
|
if (fd >= 0) {
|
||||||
|
shm_unlink(name);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
} while (retries > 0 && errno == EEXIST);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
allocate_shm_file(size_t size)
|
||||||
|
{
|
||||||
|
int fd = create_shm_file();
|
||||||
|
if (fd < 0)
|
||||||
|
return -1;
|
||||||
|
int ret;
|
||||||
|
do {
|
||||||
|
ret = ftruncate(fd, size);
|
||||||
|
} while (ret < 0 && errno == EINTR);
|
||||||
|
if (ret < 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
3
audiovisual/wayland/shm_helper.h
Normal file
3
audiovisual/wayland/shm_helper.h
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
int allocate_shm_file(size_t size);
|
Loading…
Add table
Reference in a new issue