mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-04 04:06:06 -05:00
initial commit: maps an XDG toplevel window
This commit is contained in:
commit
910c540ea9
8 changed files with 874 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/bld
|
||||
146
log.c
Normal file
146
log.c
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#include "log.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <syslog.h>
|
||||
|
||||
static bool colorize = false;
|
||||
|
||||
static void __attribute__((constructor))
|
||||
init(void)
|
||||
{
|
||||
colorize = isatty(STDOUT_FILENO);
|
||||
openlog(NULL, /*LOG_PID*/0, LOG_USER);
|
||||
setlogmask(LOG_UPTO(LOG_WARNING));
|
||||
}
|
||||
|
||||
static void __attribute__((destructor))
|
||||
fini(void)
|
||||
{
|
||||
closelog();
|
||||
}
|
||||
|
||||
static void
|
||||
_log(enum log_class log_class, const char *module, const char *file, int lineno,
|
||||
const char *fmt, int sys_errno, va_list va)
|
||||
{
|
||||
const char *class = "abcd";
|
||||
int class_clr = 0;
|
||||
switch (log_class) {
|
||||
case LOG_CLASS_ERROR: class = " err"; class_clr = 31; break;
|
||||
case LOG_CLASS_WARNING: class = "warn"; class_clr = 33; break;
|
||||
case LOG_CLASS_INFO: class = "info"; class_clr = 97; break;
|
||||
case LOG_CLASS_DEBUG: class = " dbg"; class_clr = 36; break;
|
||||
}
|
||||
|
||||
char clr[16];
|
||||
snprintf(clr, sizeof(clr), "\e[%dm", class_clr);
|
||||
printf("%s%s%s: ", colorize ? clr : "", class, colorize ? "\e[0m" : "");
|
||||
|
||||
if (colorize)
|
||||
printf("\e[2m");
|
||||
#if defined(_DEBUG)
|
||||
printf("%s:%d: ", file, lineno);
|
||||
#else
|
||||
printf("%s: ", module);
|
||||
#endif
|
||||
if (colorize)
|
||||
printf("\e[0m");
|
||||
|
||||
vprintf(fmt, va);
|
||||
|
||||
if (sys_errno != 0)
|
||||
printf(": %s", strerror(sys_errno));
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void
|
||||
_sys_log(enum log_class log_class, const char *module, const char *file,
|
||||
int lineno, const char *fmt, int sys_errno, va_list va)
|
||||
{
|
||||
/* Map our log level to syslog's level */
|
||||
int level = -1;
|
||||
switch (log_class) {
|
||||
case LOG_CLASS_ERROR: level = LOG_ERR; break;
|
||||
case LOG_CLASS_WARNING: level = LOG_WARNING; break;
|
||||
case LOG_CLASS_INFO: level = LOG_INFO; break;
|
||||
case LOG_CLASS_DEBUG: level = LOG_DEBUG; break;
|
||||
}
|
||||
|
||||
assert(level != -1);
|
||||
|
||||
const char *sys_err = sys_errno != 0 ? strerror(sys_errno) : NULL;
|
||||
|
||||
va_list va2;
|
||||
va_copy(va2, va);
|
||||
|
||||
/* Calculate required size of buffer holding the entire log message */
|
||||
int required_len = 0;
|
||||
required_len += strlen(module) + 2; /* "%s: " */
|
||||
required_len += vsnprintf(NULL, 0, fmt, va2); va_end(va2);
|
||||
|
||||
if (sys_errno != 0)
|
||||
required_len += strlen(sys_err) + 2; /* ": %s" */
|
||||
|
||||
/* Format the msg */
|
||||
char *msg = malloc(required_len + 1);
|
||||
int idx = 0;
|
||||
|
||||
idx += snprintf(&msg[idx], required_len + 1 - idx, "%s: ", module);
|
||||
idx += vsnprintf(&msg[idx], required_len + 1 - idx, fmt, va);
|
||||
|
||||
if (sys_errno != 0) {
|
||||
idx += snprintf(
|
||||
&msg[idx], required_len + 1 - idx, ": %s", strerror(sys_errno));
|
||||
}
|
||||
|
||||
syslog(level, "%s", msg);
|
||||
free(msg);
|
||||
}
|
||||
|
||||
void
|
||||
log_msg(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno, const char *fmt, ...)
|
||||
{
|
||||
va_list ap1, ap2;
|
||||
va_start(ap1, fmt);
|
||||
va_copy(ap2, ap1);
|
||||
_log(log_class, module, file, lineno, fmt, 0, ap1);
|
||||
_sys_log(log_class, module, file, lineno, fmt, 0, ap2);
|
||||
va_end(ap1);
|
||||
va_end(ap2);
|
||||
}
|
||||
|
||||
void log_errno(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list ap1, ap2;
|
||||
va_start(ap1, fmt);
|
||||
va_copy(ap2, ap1);
|
||||
_log(log_class, module, file, lineno, fmt, errno, ap1);
|
||||
_sys_log(log_class, module, file, lineno, fmt, errno, ap2);
|
||||
va_end(ap1);
|
||||
va_end(ap2);
|
||||
}
|
||||
|
||||
void log_errno_provided(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno, int _errno,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list ap1, ap2;
|
||||
va_start(ap1, fmt);
|
||||
va_copy(ap2, ap1);
|
||||
_log(log_class, module, file, lineno, fmt, _errno, ap1);
|
||||
_sys_log(log_class, module, file, lineno, fmt, _errno, ap2);
|
||||
va_end(ap1);
|
||||
va_end(ap2);
|
||||
}
|
||||
35
log.h
Normal file
35
log.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
enum log_class { LOG_CLASS_ERROR, LOG_CLASS_WARNING, LOG_CLASS_INFO, LOG_CLASS_DEBUG };
|
||||
|
||||
void log_msg(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno,
|
||||
const char *fmt, ...) __attribute__((format (printf, 5, 6)));
|
||||
|
||||
void log_errno(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno,
|
||||
const char *fmt, ...) __attribute__((format (printf, 5, 6)));
|
||||
|
||||
void log_errno_provided(
|
||||
enum log_class log_class, const char *module,
|
||||
const char *file, int lineno, int _errno,
|
||||
const char *fmt, ...) __attribute__((format (printf, 6, 7)));
|
||||
|
||||
#define LOG_ERR(fmt, ...) \
|
||||
log_msg(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_ERRNO(fmt, ...) \
|
||||
log_errno(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_ERRNO_P(fmt, _errno, ...) \
|
||||
log_errno_provided(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, \
|
||||
_errno, fmt, ## __VA_ARGS__)
|
||||
#define LOG_WARN(fmt, ...) \
|
||||
log_msg(LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_INFO(fmt, ...) \
|
||||
log_msg(LOG_CLASS_INFO, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
|
||||
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
||||
#define LOG_DBG(fmt, ...) \
|
||||
log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#else
|
||||
#define LOG_DBG(fmt, ...)
|
||||
#endif
|
||||
286
main.c
Normal file
286
main.c
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <xdg-shell.h>
|
||||
|
||||
#define LOG_MODULE "main"
|
||||
#define LOG_ENABLE_DBG 1
|
||||
#include "log.h"
|
||||
#include "shm.h"
|
||||
|
||||
struct wayland {
|
||||
struct wl_display *display;
|
||||
struct wl_registry *registry;
|
||||
struct wl_compositor *compositor;
|
||||
struct wl_surface *surface;
|
||||
struct wl_shm *shm;
|
||||
struct xdg_wm_base *shell;
|
||||
struct xdg_surface *xdg_surface;
|
||||
struct xdg_toplevel *xdg_toplevel;
|
||||
};
|
||||
|
||||
struct context {
|
||||
bool quit;
|
||||
struct wayland wl;
|
||||
};
|
||||
|
||||
static void
|
||||
shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct wl_shm_listener shm_listener = {
|
||||
.format = &shm_format,
|
||||
};
|
||||
|
||||
static void
|
||||
xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
|
||||
{
|
||||
LOG_DBG("wm base ping");
|
||||
xdg_wm_base_pong(shell, serial);
|
||||
}
|
||||
|
||||
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
|
||||
.ping = &xdg_wm_base_ping,
|
||||
};
|
||||
|
||||
static void
|
||||
handle_global(void *data, struct wl_registry *registry,
|
||||
uint32_t name, const char *interface, uint32_t version)
|
||||
{
|
||||
LOG_DBG("global: %s", interface);
|
||||
struct context *c = data;
|
||||
|
||||
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
||||
c->wl.compositor = wl_registry_bind(
|
||||
c->wl.registry, name, &wl_compositor_interface, 4);
|
||||
}
|
||||
|
||||
else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||
c->wl.shm = wl_registry_bind(c->wl.registry, name, &wl_shm_interface, 1);
|
||||
wl_shm_add_listener(c->wl.shm, &shm_listener, &c->wl);
|
||||
wl_display_roundtrip(c->wl.display);
|
||||
}
|
||||
|
||||
else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
|
||||
c->wl.shell = wl_registry_bind(
|
||||
c->wl.registry, name, &xdg_wm_base_interface, 1);
|
||||
xdg_wm_base_add_listener(c->wl.shell, &xdg_wm_base_listener, c);
|
||||
}
|
||||
|
||||
#if 0
|
||||
else if (strcmp(interface, wl_output_interface.name) == 0) {
|
||||
struct wl_output *output = wl_registry_bind(
|
||||
c->wl.registry, name, &wl_output_interface, 3);
|
||||
|
||||
tll_push_back(c->wl.monitors, ((struct monitor){.output = output}));
|
||||
|
||||
struct monitor *mon = &tll_back(c->wl.monitors);
|
||||
wl_output_add_listener(output, &output_listener, mon);
|
||||
|
||||
/*
|
||||
* The "output" interface doesn't give us the monitors'
|
||||
* identifiers (e.g. "LVDS-1"). Use the XDG output interface
|
||||
* for that.
|
||||
*/
|
||||
|
||||
assert(c->wl.xdg_output_manager != NULL);
|
||||
mon->xdg = zxdg_output_manager_v1_get_xdg_output(
|
||||
c->wl.xdg_output_manager, mon->output);
|
||||
|
||||
zxdg_output_v1_add_listener(mon->xdg, &xdg_output_listener, mon);
|
||||
wl_display_roundtrip(c->wl.display);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
|
||||
c->wl.layer_shell = wl_registry_bind(
|
||||
c->wl.registry, name, &zwlr_layer_shell_v1_interface, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
||||
c->wl.seat = wl_registry_bind(c->wl.registry, name, &wl_seat_interface, 4);
|
||||
wl_seat_add_listener(c->wl.seat, &seat_listener, c);
|
||||
wl_display_roundtrip(c->wl.display);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
|
||||
c->wl.xdg_output_manager = wl_registry_bind(
|
||||
c->wl.registry, name, &zxdg_output_manager_v1_interface, 2);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
|
||||
int32_t width, int32_t height, struct wl_array *states)
|
||||
{
|
||||
//struct context *c = data;
|
||||
LOG_DBG("xdg-toplevel: configure: %dx%d", width, height);
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
|
||||
{
|
||||
struct context *c = data;
|
||||
LOG_DBG("xdg-toplevel: close");
|
||||
c->quit = true;
|
||||
wl_display_roundtrip(c->wl.display);
|
||||
}
|
||||
|
||||
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
||||
.configure = &xdg_toplevel_configure,
|
||||
.close = &xdg_toplevel_close,
|
||||
};
|
||||
|
||||
static void
|
||||
xdg_surface_configure(void *data, struct xdg_surface *xdg_surface,
|
||||
uint32_t serial)
|
||||
{
|
||||
LOG_DBG("xdg-surface: configure");
|
||||
xdg_surface_ack_configure(xdg_surface, serial);
|
||||
}
|
||||
|
||||
static const struct xdg_surface_listener xdg_surface_listener = {
|
||||
.configure = &xdg_surface_configure,
|
||||
};
|
||||
|
||||
static void
|
||||
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
||||
{
|
||||
LOG_WARN("global removed: %u", name);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
.global = &handle_global,
|
||||
.global_remove = &handle_global_remove,
|
||||
};
|
||||
|
||||
int
|
||||
main(int argc, const char *const *argv)
|
||||
{
|
||||
int ret = EXIT_FAILURE;
|
||||
|
||||
struct context c = {
|
||||
.quit = false,
|
||||
.wl = {0},
|
||||
};
|
||||
|
||||
c.wl.display = wl_display_connect(NULL);
|
||||
if (c.wl.display == NULL) {
|
||||
LOG_ERR("failed to connect to wayland; no compositor running?");
|
||||
goto out;
|
||||
}
|
||||
|
||||
c.wl.registry = wl_display_get_registry(c.wl.display);
|
||||
if (c.wl.registry == NULL) {
|
||||
LOG_ERR("failed to get wayland registry");
|
||||
goto out;
|
||||
}
|
||||
|
||||
wl_registry_add_listener(c.wl.registry, ®istry_listener, &c);
|
||||
wl_display_roundtrip(c.wl.display);
|
||||
|
||||
if (c.wl.compositor == NULL) {
|
||||
LOG_ERR("no compositor");
|
||||
goto out;
|
||||
}
|
||||
if (c.wl.shm == NULL) {
|
||||
LOG_ERR("no shared memory buffers interface");
|
||||
goto out;
|
||||
}
|
||||
if (c.wl.shell == NULL) {
|
||||
LOG_ERR("no XDG shell interface");
|
||||
goto out;
|
||||
}
|
||||
|
||||
c.wl.surface = wl_compositor_create_surface(c.wl.compositor);
|
||||
if (c.wl.surface == NULL) {
|
||||
LOG_ERR("failed to create wayland surface");
|
||||
goto out;
|
||||
}
|
||||
|
||||
c.wl.xdg_surface = xdg_wm_base_get_xdg_surface(c.wl.shell, c.wl.surface);
|
||||
xdg_surface_add_listener(c.wl.xdg_surface, &xdg_surface_listener, &c);
|
||||
|
||||
c.wl.xdg_toplevel = xdg_surface_get_toplevel(c.wl.xdg_surface);
|
||||
xdg_toplevel_add_listener(c.wl.xdg_toplevel, &xdg_toplevel_listener, &c);
|
||||
|
||||
xdg_toplevel_set_app_id(c.wl.xdg_toplevel, "f00ter");
|
||||
xdg_toplevel_set_title(c.wl.xdg_toplevel, "hello world");
|
||||
|
||||
wl_surface_commit(c.wl.surface);
|
||||
wl_display_roundtrip(c.wl.display);
|
||||
|
||||
/* TODO: use font metrics to calculate initial size from ROWS x COLS */
|
||||
const int default_width = 300;
|
||||
const int default_height = 300;
|
||||
struct buffer *buf = shm_get_buffer(c.wl.shm, default_width, default_height);
|
||||
|
||||
cairo_set_operator(buf->cairo, CAIRO_OPERATOR_SOURCE);
|
||||
cairo_set_source_rgba(buf->cairo, 1.0, 0.0, 0.0, 1.0);
|
||||
cairo_rectangle(buf->cairo, 0, 0, buf->width, buf->height);
|
||||
cairo_fill(buf->cairo);
|
||||
|
||||
wl_surface_attach(c.wl.surface, buf->wl_buf, 0, 0);
|
||||
wl_surface_damage(c.wl.surface, 0, 0, buf->width, buf->height);
|
||||
|
||||
|
||||
wl_surface_commit(c.wl.surface);
|
||||
|
||||
wl_display_dispatch_pending(c.wl.display);
|
||||
|
||||
while (!c.quit) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = wl_display_get_fd(c.wl.display), .events = POLLIN},
|
||||
};
|
||||
|
||||
LOG_DBG("polling...");
|
||||
wl_display_flush(c.wl.display);
|
||||
poll(fds, 1, -1);
|
||||
LOG_DBG("lsdjfldsf");
|
||||
|
||||
if (fds[0].revents & POLLHUP) {
|
||||
LOG_WARN("disconnected from wayland");
|
||||
break;
|
||||
}
|
||||
|
||||
wl_display_dispatch(c.wl.display);
|
||||
}
|
||||
|
||||
ret = EXIT_SUCCESS;
|
||||
|
||||
out:
|
||||
shm_fini();
|
||||
if (c.wl.xdg_toplevel != NULL)
|
||||
xdg_toplevel_destroy(c.wl.xdg_toplevel);
|
||||
if (c.wl.xdg_surface != NULL)
|
||||
xdg_surface_destroy(c.wl.xdg_surface);
|
||||
if (c.wl.surface != NULL)
|
||||
wl_surface_destroy(c.wl.surface);
|
||||
if (c.wl.shell != NULL)
|
||||
xdg_wm_base_destroy(c.wl.shell);
|
||||
if (c.wl.shm != NULL)
|
||||
wl_shm_destroy(c.wl.shm);
|
||||
if (c.wl.compositor != NULL)
|
||||
wl_compositor_destroy(c.wl.compositor);
|
||||
if (c.wl.registry != NULL)
|
||||
wl_registry_destroy(c.wl.registry);
|
||||
if (c.wl.display != NULL)
|
||||
wl_display_disconnect(c.wl.display);
|
||||
|
||||
cairo_debug_reset_static_data();
|
||||
return ret;
|
||||
}
|
||||
62
meson.build
Normal file
62
meson.build
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
project('f00ter', 'c',
|
||||
version: '0.9.0',
|
||||
license: 'MIT',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'warning_level=1',
|
||||
'werror=true',
|
||||
'b_ndebug=if-release'])
|
||||
|
||||
is_debug_build = get_option('buildtype').startswith('debug')
|
||||
|
||||
add_project_arguments(
|
||||
['-D_GNU_SOURCE',
|
||||
#'-DF00SEL_VERSION=@0@'.format(version)] +
|
||||
] +
|
||||
(is_debug_build ? ['-D_DEBUG'] : []),
|
||||
language: 'c',
|
||||
)
|
||||
fontconfig = dependency('fontconfig')
|
||||
cairo = dependency('cairo')
|
||||
cairo_ft = dependency('cairo-ft')
|
||||
wayland_protocols = dependency('wayland-protocols')
|
||||
wayland_client = dependency('wayland-client')
|
||||
wayland_cursor = dependency('wayland-cursor')
|
||||
xkb = dependency('xkbcommon')
|
||||
|
||||
wayland_protocols_datadir = wayland_protocols.get_pkgconfig_variable('pkgdatadir')
|
||||
|
||||
wscanner = dependency('wayland-scanner', native: true)
|
||||
wscanner_prog = find_program(
|
||||
wscanner.get_pkgconfig_variable('wayland_scanner'), native: true)
|
||||
|
||||
wl_proto_headers = []
|
||||
wl_proto_src = []
|
||||
foreach prot : [
|
||||
#'external/wlr-layer-shell-unstable-v1.xml',
|
||||
wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml',
|
||||
#wayland_protocols_datadir + '/unstable/xdg-output/xdg-output-unstable-v1.xml']
|
||||
]
|
||||
|
||||
wl_proto_headers += custom_target(
|
||||
prot.underscorify() + '-client-header',
|
||||
output: '@BASENAME@.h',
|
||||
input: prot,
|
||||
command: [wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@'])
|
||||
|
||||
wl_proto_src += custom_target(
|
||||
prot.underscorify() + '-private-code',
|
||||
output: '@BASENAME@.c',
|
||||
input: prot,
|
||||
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
|
||||
endforeach
|
||||
|
||||
executable(
|
||||
'f00ter',
|
||||
'log.c', 'log.h',
|
||||
'main.c',
|
||||
'shm.c', 'shm.h',
|
||||
'tllist.h',
|
||||
wl_proto_src + wl_proto_headers,
|
||||
dependencies: [cairo, cairo_ft, fontconfig, wayland_client, wayland_cursor, xkb],
|
||||
install: true)
|
||||
163
shm.c
Normal file
163
shm.c
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
#include "shm.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/mman.h>
|
||||
#include <linux/memfd.h>
|
||||
|
||||
#define LOG_MODULE "shm"
|
||||
#include "log.h"
|
||||
#include "tllist.h"
|
||||
|
||||
static tll(struct buffer) buffers;
|
||||
|
||||
static void
|
||||
buffer_release(void *data, struct wl_buffer *wl_buffer)
|
||||
{
|
||||
struct buffer *buffer = data;
|
||||
assert(buffer->wl_buf == wl_buffer);
|
||||
assert(buffer->busy);
|
||||
buffer->busy = false;
|
||||
}
|
||||
|
||||
static const struct wl_buffer_listener buffer_listener = {
|
||||
.release = &buffer_release,
|
||||
};
|
||||
|
||||
struct buffer *
|
||||
shm_get_buffer(struct wl_shm *shm, int width, int height)
|
||||
{
|
||||
tll_foreach(buffers, it) {
|
||||
if (!it->item.busy) {
|
||||
it->item.busy = true;
|
||||
return &it->item;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* No existing buffer available. Create a new one by:
|
||||
*
|
||||
* 1. open a memory backed "file" with memfd_create()
|
||||
* 2. mmap() the memory file, to be used by the cairo surface
|
||||
* 3. create a wayland shm buffer for the same memory file
|
||||
*
|
||||
* The cairo surface and the wayland buffer are now sharing
|
||||
* memory.
|
||||
*/
|
||||
|
||||
int pool_fd = -1;
|
||||
void *mmapped = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
struct wl_shm_pool *pool = NULL;
|
||||
struct wl_buffer *buf = NULL;
|
||||
|
||||
cairo_surface_t *cairo_surface = NULL;
|
||||
cairo_t *cairo = NULL;
|
||||
|
||||
/* Backing memory for SHM */
|
||||
pool_fd = memfd_create("f00sel-wayland-shm-buffer-pool", MFD_CLOEXEC);
|
||||
if (pool_fd == -1) {
|
||||
LOG_ERRNO("failed to create SHM backing memory file");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Total size */
|
||||
const uint32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
|
||||
size = stride * height;
|
||||
if (ftruncate(pool_fd, size) == -1) {
|
||||
LOG_ERRNO("failed to truncate SHM pool");
|
||||
goto err;
|
||||
}
|
||||
|
||||
mmapped = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, pool_fd, 0);
|
||||
if (mmapped == MAP_FAILED) {
|
||||
LOG_ERR("failed to mmap SHM backing memory file");
|
||||
goto err;
|
||||
}
|
||||
|
||||
pool = wl_shm_create_pool(shm, pool_fd, size);
|
||||
if (pool == NULL) {
|
||||
LOG_ERR("failed to create SHM pool");
|
||||
goto err;
|
||||
}
|
||||
|
||||
buf = wl_shm_pool_create_buffer(
|
||||
pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888);
|
||||
if (buf == NULL) {
|
||||
LOG_ERR("failed to create SHM buffer");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* We use the entire pool for our single buffer */
|
||||
wl_shm_pool_destroy(pool); pool = NULL;
|
||||
close(pool_fd); pool_fd = -1;
|
||||
|
||||
/* Create a cairo surface around the mmapped memory */
|
||||
cairo_surface = cairo_image_surface_create_for_data(
|
||||
mmapped, CAIRO_FORMAT_ARGB32, width, height, stride);
|
||||
if (cairo_surface_status(cairo_surface) != CAIRO_STATUS_SUCCESS) {
|
||||
LOG_ERR("failed to create cairo surface: %s",
|
||||
cairo_status_to_string(cairo_surface_status(cairo_surface)));
|
||||
goto err;
|
||||
}
|
||||
|
||||
cairo = cairo_create(cairo_surface);
|
||||
if (cairo_status(cairo) != CAIRO_STATUS_SUCCESS) {
|
||||
LOG_ERR("failed to create cairo context: %s",
|
||||
cairo_status_to_string(cairo_status(cairo)));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Push to list of available buffers, but marked as 'busy' */
|
||||
tll_push_back(
|
||||
buffers,
|
||||
((struct buffer){
|
||||
.width = width,
|
||||
.height = height,
|
||||
.busy = true,
|
||||
.size = size,
|
||||
.mmapped = mmapped,
|
||||
.wl_buf = buf,
|
||||
.cairo_surface = cairo_surface,
|
||||
.cairo = cairo}
|
||||
)
|
||||
);
|
||||
|
||||
struct buffer *ret = &tll_back(buffers);
|
||||
wl_buffer_add_listener(ret->wl_buf, &buffer_listener, ret);
|
||||
return ret;
|
||||
|
||||
err:
|
||||
if (cairo != NULL)
|
||||
cairo_destroy(cairo);
|
||||
if (cairo_surface != NULL)
|
||||
cairo_surface_destroy(cairo_surface);
|
||||
if (buf != NULL)
|
||||
wl_buffer_destroy(buf);
|
||||
if (pool != NULL)
|
||||
wl_shm_pool_destroy(pool);
|
||||
if (pool_fd != -1)
|
||||
close(pool_fd);
|
||||
if (mmapped != NULL)
|
||||
munmap(mmapped, size);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
shm_fini(void)
|
||||
{
|
||||
tll_foreach(buffers, it) {
|
||||
struct buffer *buf = &it->item;
|
||||
|
||||
cairo_destroy(buf->cairo);
|
||||
cairo_surface_destroy(buf->cairo_surface);
|
||||
wl_buffer_destroy(buf->wl_buf);
|
||||
munmap(buf->mmapped, buf->size);
|
||||
|
||||
tll_remove(buffers, it);
|
||||
}
|
||||
}
|
||||
24
shm.h
Normal file
24
shm.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <cairo.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
struct buffer {
|
||||
int width;
|
||||
int height;
|
||||
|
||||
bool busy;
|
||||
size_t size;
|
||||
void *mmapped;
|
||||
|
||||
struct wl_buffer *wl_buf;
|
||||
|
||||
cairo_surface_t *cairo_surface;
|
||||
cairo_t *cairo;
|
||||
};
|
||||
|
||||
struct buffer *shm_get_buffer(struct wl_shm *shm, int width, int height);
|
||||
void shm_fini(void);
|
||||
157
tllist.h
Normal file
157
tllist.h
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define TLL_PASTE2( a, b) a##b
|
||||
#define TLL_PASTE( a, b) TLL_PASTE2( a, b)
|
||||
|
||||
/* Utility macro to generate a list element struct with a unique struct tag */
|
||||
#define TLL_UNIQUE_INNER_STRUCT(TYPE, ID) \
|
||||
struct TLL_PASTE(__tllist_ , ID) { \
|
||||
TYPE item; \
|
||||
struct TLL_PASTE(__tllist_, ID) *prev; \
|
||||
struct TLL_PASTE(__tllist_, ID) *next; \
|
||||
} *head, *tail;
|
||||
|
||||
/*
|
||||
* Defines a new typed-list type, or directly instantiate a typed-list variable
|
||||
*
|
||||
* Example a, declare a variable (list of integers):
|
||||
* tll(int) my_list;
|
||||
*
|
||||
* Example b, declare a type, and then use the type:
|
||||
* tll(int, my_list_type);
|
||||
* struct my_list_type my_list;
|
||||
*/
|
||||
#define tll(TYPE, ...) \
|
||||
struct __VA_ARGS__ { \
|
||||
TLL_UNIQUE_INNER_STRUCT(TYPE, __COUNTER__) \
|
||||
size_t length; \
|
||||
}
|
||||
|
||||
/* Initializer: tll(int) my_list = tll_init(); */
|
||||
#define tll_init() {.head = NULL, .tail = NULL, .length = 0}
|
||||
|
||||
/* Length/size of list: printf("size: %zu\n", tll_length(my_list)); */
|
||||
#define tll_length(list) (list).length
|
||||
|
||||
/* Adds a new item to the back of the list */
|
||||
#define tll_push_back(list, new_item) \
|
||||
do { \
|
||||
__typeof__((list).head) __e = malloc(sizeof(*__e)); \
|
||||
__e->item = (new_item); \
|
||||
__e->prev = (list).tail; \
|
||||
__e->next = NULL; \
|
||||
if ((list).head == NULL) \
|
||||
(list).head = (list).tail = __e; \
|
||||
else { \
|
||||
(list).tail->next = __e; \
|
||||
(list).tail = __e; \
|
||||
} \
|
||||
(list).length++; \
|
||||
} while (0)
|
||||
|
||||
/* Adds a new item to the front of the list */
|
||||
#define tll_push_front(list, new_item) \
|
||||
do { \
|
||||
__typeof__((list).head) __e = malloc(sizeof(*__e)); \
|
||||
__e->item = (new_item); \
|
||||
__e->prev = NULL; \
|
||||
__e->next = (list).head; \
|
||||
if ((list).head == NULL) \
|
||||
(list).head = (list).tail = __e; \
|
||||
else { \
|
||||
(list).head->prev = __e; \
|
||||
(list).head = __e; \
|
||||
} \
|
||||
(list).length++; \
|
||||
} while (0)
|
||||
|
||||
|
||||
/*
|
||||
* Iterates the list. <it> is an iterator pointer. You can access the
|
||||
* list item with ->item:
|
||||
*
|
||||
* tll(int) my_list = vinit();
|
||||
* tll_push_back(my_list, 5);
|
||||
*
|
||||
* tll_foreach(my_list i) {
|
||||
* printf("%d\n", i->item);
|
||||
* }
|
||||
*/
|
||||
#define tll_foreach(list, it) \
|
||||
for (__typeof__(*(list).head) *it = (list).head, \
|
||||
*it_next = it != NULL ? it->next : NULL; \
|
||||
it != NULL; \
|
||||
it = it_next, \
|
||||
it_next = it_next != NULL ? it_next->next : NULL)
|
||||
|
||||
/* Same as tll_foreach(), but iterates backwards */
|
||||
#define tll_rforeach(list, it) \
|
||||
for (__typeof__(*(list).tail) *it = (list).tail, \
|
||||
*it_prev = it != NULL ? it->prev : NULL; \
|
||||
it != NULL; \
|
||||
it = it_prev, \
|
||||
it_prev = it_prev != NULL ? it_prev->prev : NULL)
|
||||
|
||||
/*
|
||||
* Removes an entry from the list. <it> is an iterator. I.e. you can
|
||||
* only call this from inside a tll_foreach() or tll_rforeach() loop.
|
||||
*/
|
||||
#define tll_remove(list, it) \
|
||||
do { \
|
||||
assert((list).length > 0); \
|
||||
__typeof__((list).head) __prev = it->prev; \
|
||||
__typeof__((list).head) __next = it->next; \
|
||||
if (__prev != NULL) \
|
||||
__prev->next = __next; \
|
||||
else \
|
||||
(list).head = __next; \
|
||||
if (__next != NULL) \
|
||||
__next->prev = __prev; \
|
||||
else \
|
||||
(list).tail = __prev; \
|
||||
free(it); \
|
||||
(list).length--; \
|
||||
} while (0)
|
||||
|
||||
/* Same as tll_remove(), but calls free_callback(it->item) */
|
||||
#define tll_remove_and_free(list, it, free_callback) \
|
||||
do { \
|
||||
free_callback((it)->item); \
|
||||
tll_remove((list), (it)); \
|
||||
} while (0)
|
||||
|
||||
#define tll_front(list) (list).head->item
|
||||
#define tll_back(list) (list).tail->item
|
||||
|
||||
/*
|
||||
* Removes the first element from the list, and returns it (note:
|
||||
* returns the *actual* item, not an iterator.
|
||||
*/
|
||||
#define tll_pop_front(list) \
|
||||
({__typeof__((list).head) it = (list).head; \
|
||||
__typeof__((list).head->item) __ret = it->item; \
|
||||
tll_remove((list), it); \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
/* Same as tll_pop_front(), but returns/removes the *last* element */
|
||||
#define tll_pop_back(list) \
|
||||
({__typeof__((list).tail) it = (list).tail; \
|
||||
__typeof__((list).tail->item) __ret = it->item; \
|
||||
tll_remove((list), it); \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
/* Frees the list. This call is *not* needed if the list is already empty. */
|
||||
#define tll_free(list) \
|
||||
tll_foreach(list, __it) \
|
||||
tll_remove(list, __it)
|
||||
|
||||
/* Same as tll_free(), but also calls free_callback(item) for every item */
|
||||
#define tll_free_and_free(list, free_callback) \
|
||||
tll_foreach(list, __it) \
|
||||
tll_remove_and_free(list, __it, free_callback)
|
||||
Loading…
Add table
Add a link
Reference in a new issue