diff options
Diffstat (limited to 'reduced_system_layer.c')
-rw-r--r-- | reduced_system_layer.c | 1081 |
1 files changed, 1076 insertions, 5 deletions
diff --git a/reduced_system_layer.c b/reduced_system_layer.c index 07e633e..99c3e72 100644 --- a/reduced_system_layer.c +++ b/reduced_system_layer.c @@ -47,7 +47,6 @@ // - Window // - Wayland // - Windows graphics -// - Screenshot // - Sound // - Windows audio // - Recording @@ -97,6 +96,7 @@ // - Anti-aliasing // - System // - Window - X11, Web +// - Screenshot // - Sound - ALSA, Web // - Networking // - Unix UDP sockets @@ -599,6 +599,9 @@ void p_event_loop(void); // Clipboard void p_clipboard_write(i64 size, c8 *data); +// Take a screenshot +void p_screenshot(i64 max_num_pixels, i64 *width, i64 *height, vec4_f32 *pixels); + // Sound void p_handle_sound(void); void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames); @@ -757,6 +760,14 @@ static u32 rgb_u32_from_f32_(vec3_f32 c) { return (r << 16) | (g << 8) | b; } +static vec3_f32 rgb_f32_from_u32_(u32 c) { + return (vec3_f32) { + .x = ((c >> 16) & 0xff) / 255.f, + .y = ((c >> 8) & 0xff) / 255.f, + .z = ( c & 0xff) / 255.f, + }; +} + static i64 average_frame_duration_(i64 duration) { _frame_duration[_frame_index] = duration; _frame_index = (_frame_index + 1) % NUM_FRAMES_AVERAGED; @@ -1436,11 +1447,988 @@ void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { // ================================================================ // +// Wayland +// +// ================================================================ + +#if defined(__linux__) && !defined(NO_WAYLAND) + +#include <sys/mman.h> +#include <wayland-client.h> +#include <wayland-util.h> + +struct wl_buffer; +struct wl_output; +struct zwlr_screencopy_frame_v1; +struct zwlr_screencopy_manager_v1; + +#ifndef ZWLR_SCREENCOPY_MANAGER_V1_INTERFACE +#define ZWLR_SCREENCOPY_MANAGER_V1_INTERFACE +extern const struct wl_interface zwlr_screencopy_manager_v1_interface; +#endif +#ifndef ZWLR_SCREENCOPY_FRAME_V1_INTERFACE +#define ZWLR_SCREENCOPY_FRAME_V1_INTERFACE +extern const struct wl_interface zwlr_screencopy_frame_v1_interface; +#endif + +#define ZWLR_SCREENCOPY_MANAGER_V1_CAPTURE_OUTPUT 0 +#define ZWLR_SCREENCOPY_MANAGER_V1_CAPTURE_OUTPUT_REGION 1 +#define ZWLR_SCREENCOPY_MANAGER_V1_DESTROY 2 + + +#define ZWLR_SCREENCOPY_MANAGER_V1_CAPTURE_OUTPUT_SINCE_VERSION 1 +#define ZWLR_SCREENCOPY_MANAGER_V1_CAPTURE_OUTPUT_REGION_SINCE_VERSION 1 +#define ZWLR_SCREENCOPY_MANAGER_V1_DESTROY_SINCE_VERSION 1 + +static void +zwlr_screencopy_manager_v1_set_user_data(struct zwlr_screencopy_manager_v1 *zwlr_screencopy_manager_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwlr_screencopy_manager_v1, user_data); +} + +static void * +zwlr_screencopy_manager_v1_get_user_data(struct zwlr_screencopy_manager_v1 *zwlr_screencopy_manager_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwlr_screencopy_manager_v1); +} + +static u32 +zwlr_screencopy_manager_v1_get_version(struct zwlr_screencopy_manager_v1 *zwlr_screencopy_manager_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_manager_v1); +} + +static struct zwlr_screencopy_frame_v1 * +zwlr_screencopy_manager_v1_capture_output(struct zwlr_screencopy_manager_v1 *zwlr_screencopy_manager_v1, i32 overlay_cursor, struct wl_output *output) +{ + struct wl_proxy *frame; + + frame = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_screencopy_manager_v1, + ZWLR_SCREENCOPY_MANAGER_V1_CAPTURE_OUTPUT, &zwlr_screencopy_frame_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_manager_v1), 0, NULL, overlay_cursor, output); + + return (struct zwlr_screencopy_frame_v1 *) frame; +} + +static struct zwlr_screencopy_frame_v1 * +zwlr_screencopy_manager_v1_capture_output_region(struct zwlr_screencopy_manager_v1 *zwlr_screencopy_manager_v1, i32 overlay_cursor, struct wl_output *output, i32 x, i32 y, i32 width, i32 height) +{ + struct wl_proxy *frame; + + frame = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_screencopy_manager_v1, + ZWLR_SCREENCOPY_MANAGER_V1_CAPTURE_OUTPUT_REGION, &zwlr_screencopy_frame_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_manager_v1), 0, NULL, overlay_cursor, output, x, y, width, height); + + return (struct zwlr_screencopy_frame_v1 *) frame; +} + +static void +zwlr_screencopy_manager_v1_destroy(struct zwlr_screencopy_manager_v1 *zwlr_screencopy_manager_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwlr_screencopy_manager_v1, + ZWLR_SCREENCOPY_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_manager_v1), WL_MARSHAL_FLAG_DESTROY); +} + +#ifndef ZWLR_SCREENCOPY_FRAME_V1_ERROR_ENUM +#define ZWLR_SCREENCOPY_FRAME_V1_ERROR_ENUM +enum zwlr_screencopy_frame_v1_error { + ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED = 0, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER = 1, +}; +#endif + +#ifndef ZWLR_SCREENCOPY_FRAME_V1_FLAGS_ENUM +#define ZWLR_SCREENCOPY_FRAME_V1_FLAGS_ENUM +enum zwlr_screencopy_frame_v1_flags { + ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT = 1, +}; +#endif + +struct zwlr_screencopy_frame_v1_listener { + void (*buffer)(void *data, + struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1, + u32 format, + u32 width, + u32 height, + u32 stride); + void (*flags)(void *data, + struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1, + u32 flags); + void (*ready)(void *data, + struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1, + u32 tv_sec_hi, + u32 tv_sec_lo, + u32 tv_nsec); + void (*failed)(void *data, + struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1); +}; + +static int +zwlr_screencopy_frame_v1_add_listener(struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1, + const struct zwlr_screencopy_frame_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zwlr_screencopy_frame_v1, + (void (**)(void)) listener, data); +} + +#define ZWLR_SCREENCOPY_FRAME_V1_COPY 0 +#define ZWLR_SCREENCOPY_FRAME_V1_DESTROY 1 + +#define ZWLR_SCREENCOPY_FRAME_V1_BUFFER_SINCE_VERSION 1 +#define ZWLR_SCREENCOPY_FRAME_V1_FLAGS_SINCE_VERSION 1 +#define ZWLR_SCREENCOPY_FRAME_V1_READY_SINCE_VERSION 1 +#define ZWLR_SCREENCOPY_FRAME_V1_FAILED_SINCE_VERSION 1 + +#define ZWLR_SCREENCOPY_FRAME_V1_COPY_SINCE_VERSION 1 +#define ZWLR_SCREENCOPY_FRAME_V1_DESTROY_SINCE_VERSION 1 + +static void +zwlr_screencopy_frame_v1_set_user_data(struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwlr_screencopy_frame_v1, user_data); +} + +static void * +zwlr_screencopy_frame_v1_get_user_data(struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwlr_screencopy_frame_v1); +} + +static u32 +zwlr_screencopy_frame_v1_get_version(struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_frame_v1); +} + +static void +zwlr_screencopy_frame_v1_copy(struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1, struct wl_buffer *buffer) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwlr_screencopy_frame_v1, + ZWLR_SCREENCOPY_FRAME_V1_COPY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_frame_v1), 0, buffer); +} + +static void +zwlr_screencopy_frame_v1_destroy(struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwlr_screencopy_frame_v1, + ZWLR_SCREENCOPY_FRAME_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_frame_v1), WL_MARSHAL_FLAG_DESTROY); +} + +struct wl_output; +struct zxdg_output_manager_v1; +struct zxdg_output_v1; + +#ifndef ZXDG_OUTPUT_MANAGER_V1_INTERFACE +#define ZXDG_OUTPUT_MANAGER_V1_INTERFACE +extern const struct wl_interface zxdg_output_manager_v1_interface; +#endif +#ifndef ZXDG_OUTPUT_V1_INTERFACE +#define ZXDG_OUTPUT_V1_INTERFACE +extern const struct wl_interface zxdg_output_v1_interface; +#endif + +#define ZXDG_OUTPUT_MANAGER_V1_DESTROY 0 +#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT 1 + +#define ZXDG_OUTPUT_MANAGER_V1_DESTROY_SINCE_VERSION 1 +#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION 1 + +static void +zxdg_output_manager_v1_set_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_manager_v1, user_data); +} + +static void * +zxdg_output_manager_v1_get_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_manager_v1); +} + +static u32 +zxdg_output_manager_v1_get_version(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1); +} + +static void +zxdg_output_manager_v1_destroy(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1, + ZXDG_OUTPUT_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), WL_MARSHAL_FLAG_DESTROY); +} + +static struct zxdg_output_v1 * +zxdg_output_manager_v1_get_xdg_output(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, struct wl_output *output) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1, + ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), 0, NULL, output); + + return (struct zxdg_output_v1 *) id; +} + +struct zxdg_output_v1_listener { + void (*logical_position)(void *data, + struct zxdg_output_v1 *zxdg_output_v1, + i32 x, + i32 y); + void (*logical_size)(void *data, + struct zxdg_output_v1 *zxdg_output_v1, + i32 width, + i32 height); + void (*done)(void *data, + struct zxdg_output_v1 *zxdg_output_v1); + void (*name)(void *data, + struct zxdg_output_v1 *zxdg_output_v1, + c8 const *name); + void (*description)(void *data, + struct zxdg_output_v1 *zxdg_output_v1, + c8 const *description); +}; + +static int +zxdg_output_v1_add_listener(struct zxdg_output_v1 *zxdg_output_v1, + const struct zxdg_output_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zxdg_output_v1, + (void (**)(void)) listener, data); +} + +#define ZXDG_OUTPUT_V1_DESTROY 0 + +#define ZXDG_OUTPUT_V1_LOGICAL_POSITION_SINCE_VERSION 1 +#define ZXDG_OUTPUT_V1_LOGICAL_SIZE_SINCE_VERSION 1 +#define ZXDG_OUTPUT_V1_DONE_SINCE_VERSION 1 +#define ZXDG_OUTPUT_V1_NAME_SINCE_VERSION 2 +#define ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION 2 + +#define ZXDG_OUTPUT_V1_DESTROY_SINCE_VERSION 1 + +static void +zxdg_output_v1_set_user_data(struct zxdg_output_v1 *zxdg_output_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_v1, user_data); +} + +static void * +zxdg_output_v1_get_user_data(struct zxdg_output_v1 *zxdg_output_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_v1); +} + +static u32 +zxdg_output_v1_get_version(struct zxdg_output_v1 *zxdg_output_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1); +} + +static void +zxdg_output_v1_destroy(struct zxdg_output_v1 *zxdg_output_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_v1, + ZXDG_OUTPUT_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1), WL_MARSHAL_FLAG_DESTROY); +} + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_buffer_interface; +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface zwlr_screencopy_frame_v1_interface; + +static const struct wl_interface *wlr_screencopy_unstable_v1_types[] = { + NULL, + NULL, + NULL, + NULL, + &zwlr_screencopy_frame_v1_interface, + NULL, + &wl_output_interface, + &zwlr_screencopy_frame_v1_interface, + NULL, + &wl_output_interface, + NULL, + NULL, + NULL, + NULL, + &wl_buffer_interface, +}; + +static const struct wl_message zwlr_screencopy_manager_v1_requests[] = { + { "capture_output", "nio", wlr_screencopy_unstable_v1_types + 4 }, + { "capture_output_region", "nioiiii", wlr_screencopy_unstable_v1_types + 7 }, + { "destroy", "", wlr_screencopy_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_screencopy_manager_v1_interface = { + "zwlr_screencopy_manager_v1", 1, + 3, zwlr_screencopy_manager_v1_requests, + 0, NULL, +}; + +static const struct wl_message zwlr_screencopy_frame_v1_requests[] = { + { "copy", "o", wlr_screencopy_unstable_v1_types + 14 }, + { "destroy", "", wlr_screencopy_unstable_v1_types + 0 }, +}; + +static const struct wl_message zwlr_screencopy_frame_v1_events[] = { + { "buffer", "uuuu", wlr_screencopy_unstable_v1_types + 0 }, + { "flags", "u", wlr_screencopy_unstable_v1_types + 0 }, + { "ready", "uuu", wlr_screencopy_unstable_v1_types + 0 }, + { "failed", "", wlr_screencopy_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_screencopy_frame_v1_interface = { + "zwlr_screencopy_frame_v1", 1, + 2, zwlr_screencopy_frame_v1_requests, + 4, zwlr_screencopy_frame_v1_events, +}; + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface zxdg_output_v1_interface; + +static const struct wl_interface *xdg_output_unstable_v1_types[] = { + NULL, + NULL, + &zxdg_output_v1_interface, + &wl_output_interface, +}; + +static const struct wl_message zxdg_output_manager_v1_requests[] = { + { "destroy", "", xdg_output_unstable_v1_types + 0 }, + { "get_xdg_output", "no", xdg_output_unstable_v1_types + 2 }, +}; + +WL_PRIVATE const struct wl_interface zxdg_output_manager_v1_interface = { + "zxdg_output_manager_v1", 3, + 2, zxdg_output_manager_v1_requests, + 0, NULL, +}; + +static const struct wl_message zxdg_output_v1_requests[] = { + { "destroy", "", xdg_output_unstable_v1_types + 0 }, +}; + +static const struct wl_message zxdg_output_v1_events[] = { + { "logical_position", "ii", xdg_output_unstable_v1_types + 0 }, + { "logical_size", "ii", xdg_output_unstable_v1_types + 0 }, + { "done", "", xdg_output_unstable_v1_types + 0 }, + { "name", "2s", xdg_output_unstable_v1_types + 0 }, + { "description", "2s", xdg_output_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zxdg_output_v1_interface = { + "zxdg_output_v1", 3, + 1, zxdg_output_v1_requests, + 5, zxdg_output_v1_events, +}; + +// ================================================================ + +typedef struct { + struct wl_display * display; + struct wl_registry * registry; + struct wl_shm * shm; + struct zxdg_output_manager_v1 * xdg_output_manager; + struct zwlr_screencopy_manager_v1 *screencopy_manager; + struct wl_list outputs; + u64 n_done; +} WL_State_; + +typedef struct { + struct wl_buffer * buffer; + void * data; + i32 width; + i32 height; + i32 stride; + u64 size; + enum wl_shm_format format; +} WL_Buffer_; + +typedef struct { + i32 x, y; + i32 width, height; +} WL_Box_; + +typedef struct { + WL_State_ * state; + struct wl_output * output; + struct zxdg_output_v1 * xdg_output; + struct wl_list link; + WL_Box_ geometry; + enum wl_output_transform transform; + i32 scale; + WL_Box_ local_geometry; + WL_Box_ logical_geometry; + f64 logical_scale; + c8 * name; + WL_Buffer_ * buffer; + struct zwlr_screencopy_frame_v1 *screencopy_frame; + u32 screencopy_frame_flags; +} WL_Output_; + +// ================================================================ + +b8 _Wayland_screenshot_status = 0; + +// ================================================================ + +static void get_output_layout_extents(WL_State_ *state, WL_Box_ *box) { + i32 x1 = 0x10000000, y1 = 0x10000000; + i32 x2 = -0x10000000, y2 = -0x10000000; + + WL_Output_ *output; + wl_list_for_each(output, &state->outputs, link) { + if (output->logical_geometry.x < x1) + x1 = output->logical_geometry.x; + if (output->logical_geometry.y < y1) + y1 = output->logical_geometry.y; + if (output->logical_geometry.x + output->logical_geometry.width > x2) + x2 = output->logical_geometry.x + output->logical_geometry.width; + if (output->logical_geometry.y + output->logical_geometry.height > y2) + y2 = output->logical_geometry.y + output->logical_geometry.height; + } + + box->x = x1; + box->y = y1; + box->width = x2 - x1; + box->height = y2 - y1; +} + +static void apply_output_transform(enum wl_output_transform transform, i32 *width, i32 *height) { + if ((transform & WL_OUTPUT_TRANSFORM_90) != 0) { + i32 tmp = *width; + *width = *height; + *height = tmp; + } +} + +static f64 get_output_rotation(enum wl_output_transform transform) { + switch (transform & ~WL_OUTPUT_TRANSFORM_FLIPPED) { + case WL_OUTPUT_TRANSFORM_90: return M_PI / 2; + case WL_OUTPUT_TRANSFORM_180: return M_PI; + case WL_OUTPUT_TRANSFORM_270: return 3 * M_PI / 2; + } + return 0; +} + +static i32 get_output_flipped(enum wl_output_transform transform) { + return (transform & WL_OUTPUT_TRANSFORM_FLIPPED) != 0 ? -1 : 1; +} + +static void guess_output_logical_geometry(WL_Output_ *output) { + output->logical_geometry.x = output->geometry.x; + output->logical_geometry.y = output->geometry.y; + output->logical_geometry.width = output->geometry.width / output->scale; + output->logical_geometry.height = output->geometry.height / output->scale; + apply_output_transform( + output->transform, + &output->logical_geometry.width, + &output->logical_geometry.height + ); + + output->logical_scale = output->scale; +} + +static void randname(c8 *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 i32 anonymous_shm_open(void) { + c8 name[] = "/SCR_XXXXXX"; + i32 retries = 100; + + do { + randname(name + sizeof name - 7); + + --retries; + + i32 fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + LOG_ERROR("shm_open failed.\n"); + return -1; +} + +static i32 create_shm_file(off_t size) { + int fd = anonymous_shm_open(); + if (fd < 0) { + return fd; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + + LOG_ERROR("ftruncate failed.\n"); + return -1; + } + + return fd; +} + +static WL_Buffer_ *create_buffer(struct wl_shm *shm, enum wl_shm_format format, i32 width, i32 height, i32 stride) { + u64 size = stride * height; + + i32 fd = create_shm_file(size); + if (fd == -1) { + return NULL; + } + + void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + close(fd); + + LOG_ERROR("mmap failed.\n"); + return NULL; + } + + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); + struct wl_buffer * wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); + wl_shm_pool_destroy(pool); + + close(fd); + + WL_Buffer_ *buffer = calloc(1, sizeof(WL_Buffer_)); + + if (buffer == NULL) { + LOG_ERROR("calloc failed.\n"); + return NULL; + } + + buffer->buffer = wl_buffer; + buffer->data = data; + buffer->width = width; + buffer->height = height; + buffer->stride = stride; + buffer->size = size; + buffer->format = format; + + return buffer; +} + +static void destroy_buffer(WL_Buffer_ *buffer) { + if (buffer == NULL) + return; + munmap(buffer->data, buffer->size); + wl_buffer_destroy(buffer->buffer); + free(buffer); +} + +static void screencopy_frame_handle_buffer( + void * data, + struct zwlr_screencopy_frame_v1 *frame, + u32 format, + u32 width, + u32 height, + u32 stride +) { + WL_Output_ *output = data; + + output->buffer = create_buffer(output->state->shm, format, width, height, stride); + + if (output->buffer == NULL) + // FAIL + return; + + zwlr_screencopy_frame_v1_copy(frame, output->buffer->buffer); +} + +static void screencopy_frame_handle_flags( + void * data, + struct zwlr_screencopy_frame_v1 *frame, + u32 flags +) { + WL_Output_ *output = data; + output->screencopy_frame_flags = flags; +} + +static void screencopy_frame_handle_ready( + void * data, + struct zwlr_screencopy_frame_v1 *frame, + u32 tv_sec_hi, + u32 tv_sec_lo, + u32 tv_nsec +) { + WL_Output_ *output = data; + ++output->state->n_done; +} + +static void screencopy_frame_handle_failed( + void * data, + struct zwlr_screencopy_frame_v1 *frame +) { + WL_Output_ *output = data; + // FAIL + LOG_ERROR("Screenshot copy failed."); +} + +static struct zwlr_screencopy_frame_v1_listener screencopy_frame_listener = { + .buffer = screencopy_frame_handle_buffer, + .flags = screencopy_frame_handle_flags, + .ready = screencopy_frame_handle_ready, + .failed = screencopy_frame_handle_failed, +}; + +static void xdg_output_handle_logical_position( + void * data, + struct zxdg_output_v1 *xdg_output, + i32 x, + i32 y +) { + WL_Output_ *output = data; + + output->logical_geometry.x = x; + output->logical_geometry.y = y; +} + +static void xdg_output_handle_logical_size( + void * data, + struct zxdg_output_v1 *xdg_output, + i32 width, + i32 height +) { + WL_Output_ *output = data; + + output->logical_geometry.width = width; + output->logical_geometry.height = height; +} + +static void xdg_output_handle_done( + void * data, + struct zxdg_output_v1 *xdg_output +) { + WL_Output_ *output = data; + + i32 width = output->geometry.width; + i32 height = output->geometry.height; + apply_output_transform(output->transform, &width, &height); + output->logical_scale = (f64) width / output->logical_geometry.width; +} + +static void xdg_output_handle_name( + void * data, + struct zxdg_output_v1 *xdg_output, + c8 const * name +) { + WL_Output_ *output = data; + output->name = strdup(name); +} + +static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, c8 const *name) { } + +static void output_handle_geometry( + void * data, + struct wl_output *wl_output, + i32 x, + i32 y, + i32 physical_width, + i32 physical_height, + i32 subpixel, + c8 const * make, + c8 const * model, + i32 transform +) { + WL_Output_ *output = data; + + output->geometry.x = x; + output->geometry.y = y; + output->transform = transform; +} + +static void output_handle_mode( + void * data, + struct wl_output *wl_output, + u32 flags, + i32 width, + i32 height, + i32 refresh +) { + WL_Output_ *output = data; + + if ((flags & WL_OUTPUT_MODE_CURRENT) != 0) { + output->geometry.width = width; + output->geometry.height = height; + } +} + +static void output_handle_done(void *data, struct wl_output *wl_output) { } + +static void output_handle_scale( + void * data, + struct wl_output *wl_output, + i32 factor +) { + WL_Output_ *output = data; + output->scale = factor; +} + +static void handle_global( + void * data, + struct wl_registry *registry, + u32 name, + c8 const * interface, + u32 version +) { + WL_State_ *state = data; + + if (strcmp(interface, wl_shm_interface.name) == 0) + state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { + u32 bind_version = (version > 2) ? 2 : version; + state->xdg_output_manager = wl_registry_bind( + registry, + name, + &zxdg_output_manager_v1_interface, + bind_version + ); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + WL_Output_ *output = calloc(1, sizeof(WL_Output_)); + output->state = state; + output->scale = 1; + output->output = wl_registry_bind(registry, name, &wl_output_interface, 3); + wl_output_add_listener(output->output, &(struct wl_output_listener) { + .geometry = output_handle_geometry, + .mode = output_handle_mode, + .done = output_handle_done, + .scale = output_handle_scale, + }, output); + wl_list_insert(&state->outputs, &output->link); + } else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) + state->screencopy_manager = wl_registry_bind( + registry, + name, + &zwlr_screencopy_manager_v1_interface, + 1 + ); +} + +static void handle_global_remove(void *data, struct wl_registry *registry, u32 name) { } + +static struct wl_registry_listener _wayland_registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + +static b8 is_empty_box(WL_Box_ *box) { + return box->width <= 0 || box->height <= 0; +} + +static b8 intersect_box(WL_Box_ *a, WL_Box_ *b) { + if (is_empty_box(a) || is_empty_box(b)) { + return 0; + } + + i32 x1 = fmax(a->x, b->x); + i32 y1 = fmax(a->y, b->y); + i32 x2 = fmin(a->x + a->width, b->x + b->width); + i32 y2 = fmin(a->y + a->height, b->y + b->height); + + WL_Box_ box = { + .x = x1, + .y = y1, + .width = x2 - x1, + .height = y2 - y1, + }; + + return !is_empty_box(&box); +} + +void wayland_screenshot_(i64 max_num_pixels, i64 *width, i64 *height, vec4_f32 *pixels) { + f32 scale = 1.0; + b8 use_greatest_scale = 1; + + WL_Box_ *geometry = NULL; + c8 * geometry_output = NULL; + + WL_State_ state = {0}; + wl_list_init(&state.outputs); + + state.display = wl_display_connect(NULL); + + if (state.display == NULL) { + LOG_ERROR("wl_display_connect failed."); + return; + } + + state.registry = wl_display_get_registry(state.display); + wl_registry_add_listener( + state.registry, + &_wayland_registry_listener, + &state + ); + wl_display_roundtrip(state.display); + + if (state.shm == NULL) { + LOG_ERROR("Compositor does not support wl_shm."); + return; + } + + if (wl_list_empty(&state.outputs)) { + LOG_ERROR("No wl_output."); + return; + } + + if (state.xdg_output_manager != NULL) { + WL_Output_ *output; + wl_list_for_each(output, &state.outputs, link) { + output->xdg_output = zxdg_output_manager_v1_get_xdg_output( + state.xdg_output_manager, + output->output + ); + + zxdg_output_v1_add_listener( + output->xdg_output, + &(struct zxdg_output_v1_listener) { + .logical_position = xdg_output_handle_logical_position, + .logical_size = xdg_output_handle_logical_size, + .done = xdg_output_handle_done, + .name = xdg_output_handle_name, + .description = xdg_output_handle_description, + }, + output + ); + } + + wl_display_roundtrip(state.display); + } else { + WL_Output_ *output; + wl_list_for_each(output, &state.outputs, link) { + guess_output_logical_geometry(output); + } + } + + if (state.screencopy_manager == NULL) { + LOG_ERROR("Compositor does not support wlr-screencopy-unstable-v1."); + return; + } + + if (geometry_output != NULL) { + WL_Output_ *output; + wl_list_for_each(output, &state.outputs, link) { + if (output->name != NULL && strcmp(output->name, geometry_output) == 0) { + geometry = calloc(1, sizeof(WL_Box_)); + if (geometry == NULL) { + LOG_ERROR("calloc failed.\n"); + } else + memcpy(geometry, &output->logical_geometry, sizeof(WL_Box_)); + } + } + + if (geometry == NULL) { + LOG_ERROR("Unknown output for: %s", geometry_output); + return; + } + } + + u64 n_pending = 0; + WL_Output_ *output; + wl_list_for_each(output, &state.outputs, link) { + if (geometry != NULL && !intersect_box(geometry, &output->logical_geometry)) + continue; + if (use_greatest_scale && output->logical_scale > scale) + scale = output->logical_scale; + + output->screencopy_frame = zwlr_screencopy_manager_v1_capture_output( + state.screencopy_manager, + 1, + output->output + ); + + zwlr_screencopy_frame_v1_add_listener( + output->screencopy_frame, + &screencopy_frame_listener, + output + ); + + ++n_pending; + } + + if (n_pending == 0) { + LOG_ERROR("Supplied geometry did not intersect with any outputs."); + return; + } + + b8 done = 0; + while (!done && wl_display_dispatch(state.display) != -1) { + done = (state.n_done == n_pending); + } + if (!done) { + LOG_ERROR("Failed to screenshoot all outputs."); + return; + } + + if (geometry == NULL) { + geometry = calloc(1, sizeof(WL_Box_)); + get_output_layout_extents(&state, geometry); + } + + // + // TODO: + // SAVE + // + (void) get_output_flipped; + (void) get_output_rotation; + + WL_Output_ *output_tmp; + wl_list_for_each_safe(output, output_tmp, &state.outputs, link) { + wl_list_remove(&output->link); + free(output->name); + if (output->screencopy_frame != NULL) + zwlr_screencopy_frame_v1_destroy(output->screencopy_frame); + destroy_buffer(output->buffer); + if (output->xdg_output != NULL) + zxdg_output_v1_destroy(output->xdg_output); + wl_output_release(output->output); + free(output); + } + zwlr_screencopy_manager_v1_destroy(state.screencopy_manager); + if (state.xdg_output_manager != NULL) + zxdg_output_manager_v1_destroy(state.xdg_output_manager); + + wl_shm_destroy(state.shm); + wl_registry_destroy(state.registry); + wl_display_disconnect(state.display); + + free(geometry); + free(geometry_output); +} + +#endif // defined(__linux__) && !defined(NO_WAYLAND) + +// ================================================================ +// // X11 // // ================================================================ -#if defined(__linux__) +#if defined(__linux__) && !defined(NO_X11) #include <sys/stat.h> #include <X11/Xlib.h> @@ -1453,6 +2441,7 @@ void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { static i64 _frame_time = 0; static XImage _image = {0}; static Display *_display = NULL; +static Window _root_window = 0; static GC _gc = NULL; static XIM _im = NULL; static XIC _ic = NULL; @@ -1474,11 +2463,13 @@ static Atom _dnd_action_copy = 0; static Atom _dnd_selection = 0; static Atom _text_uri_list = 0; static Window _dnd_source = 0; +static b8 _ignore_errors = 0; static i16 _key_table [MAX_NUM_KEYS] = {0}; static b8 _key_repeat [MAX_NUM_KEYS] = {0}; static u32 _pixels_scaled [MAX_NUM_PIXELS] = {0}; static u32 _pixels_internal [MAX_NUM_PIXELS] = {0}; +static c8 _error_buffer [4096] = {0}; static b8 sub_str_eq_(c8 *a, c8 *b, i64 len) { for (i64 i = 0; i < len; ++i) @@ -1487,10 +2478,21 @@ static b8 sub_str_eq_(c8 *a, c8 *b, i64 len) { return 1; } +i32 x11_error_handler_(Display *display, XErrorEvent *event) { + if (!_ignore_errors) { + XGetErrorText(display, event->error_code, _error_buffer, sizeof _error_buffer - 1); + LOG_ERROR("X11: %s", _error_buffer); + } + + return 0; +} + void p_init(void) { for (i64 i = 0; i < NUM_FRAMES_AVERAGED; ++i) _frame_duration[i] = (MIN_FRAME_DURATION + MAX_FRAME_DURATION) / 2; + XSetErrorHandler(x11_error_handler_); + _display = XOpenDisplay(NULL); if (_display == NULL) { @@ -1498,6 +2500,8 @@ void p_init(void) { return; } + _root_window = XDefaultRootWindow(_display); + _key_table[XKeysymToKeycode(_display, XK_Left)] = KEY_LEFT; _key_table[XKeysymToKeycode(_display, XK_Right)] = KEY_RIGHT; _key_table[XKeysymToKeycode(_display, XK_Up)] = KEY_UP; @@ -1635,7 +2639,7 @@ void p_init(void) { i32 x = (display_width - g_platform.frame_width) / 2; i32 y = (display_height - g_platform.frame_height) / 2; - _window = XCreateWindow(_display, XDefaultRootWindow(_display), x, y, g_platform.frame_width, g_platform.frame_height, 0, depth, InputOutput, visual, CWEventMask, &(XSetWindowAttributes) { .event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | SubstructureNotifyMask, }); + _window = XCreateWindow(_display, _root_window, x, y, g_platform.frame_width, g_platform.frame_height, 0, depth, InputOutput, visual, CWEventMask, &(XSetWindowAttributes) { .event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | SubstructureNotifyMask, }); _im = XOpenIM(_display, NULL, NULL, NULL); @@ -1713,7 +2717,6 @@ void p_cleanup(void) { XCloseDisplay (_display); _display = NULL; - _window = 0; sockets_cleanup(); sound_cleanup(); @@ -2229,7 +3232,65 @@ void p_clipboard_write(i64 size, c8 *data) { memcpy(g_platform.clipboard, data, g_platform.clipboard_size); } -#endif // defined(__linux__) +void x11_screenshot_(i64 max_num_pixels, i64 *width, i64 *height, vec4_f32 *pixels) { + if (width == NULL || height == NULL) { + LOG_ERROR("Invalid arguments."); + return; + } + + *width = 0; + *height = 0; + + if (_display == NULL) { + LOG_ERROR("No display."); + return; + } + + i32 screen = DefaultScreen(_display); + i32 display_width = DisplayWidth (_display, screen); + i32 display_height = DisplayHeight(_display, screen); + + _ignore_errors = 1; + + XImage *image = XGetImage(_display, _root_window, 0, 0, display_width, display_height, AllPlanes, ZPixmap); + + _ignore_errors = 0; + + if (image == NULL) { + #if !defined(NO_WAYLAND) + // Fallback for Wayland. + wayland_screenshot_(max_num_pixels, width, height, pixels); + return; + #else + LOG_ERROR("XGetImage failed."); + return; + #endif + } + + *width = image->width; + *height = image->height; + + if (pixels == NULL || image->width * image->height > max_num_pixels) + return; + + for (i64 j = 0; j < image->height; ++j) + for (i64 i = 0; i < image->width; ++i) { + vec3_f32 f = rgb_f32_from_u32_(XGetPixel(image, i, j)); + + pixels[j * image->width + i] = (vec4_f32) { + .x = f.x, + .y = f.y, + .z = f.z, + .w = 1.f, + }; + } +} + +void p_screenshot(i64 max_num_pixels, i64 *width, i64 *height, vec4_f32 *pixels) { + x11_screenshot_(max_num_pixels, width, height, pixels); +} + +#endif // defined(__linux__) && !defined(NO_X11) // ================================================================ // @@ -2500,6 +3561,16 @@ void p_clipboard_write(i64 size, c8 *data) { p_clipboard_write_impl((i32) size, data); } +void p_screenshot(i64 max_num_pixels, i64 *width, i64 *height, vec4_f32 *pixels) { + if (width == NULL || height == NULL) { + LOG_ERROR("Invalid arguments."); + return; + } + + *width = 0; + *height = 0; +} + void p_handle_sound(void) { g_platform.num_sound_samples_elapsed = sound_samples_elapsed_(); _sound_position = (_sound_position + g_platform.num_sound_samples_elapsed * NUM_SOUND_CHANNELS) % MAX_NUM_SOUND_FRAMES; |