summaryrefslogtreecommitdiff
path: root/reduced_system_layer.c
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2025-01-20 00:50:40 +0100
committerMitya Selivanov <automainint@guattari.tech>2025-01-20 00:50:40 +0100
commitb863aec19fa95f39ec69e0e0f66d4851c9e3a358 (patch)
treeb39754f211a023c3323d19c2b2c8428503a97fa8 /reduced_system_layer.c
parentb077de13ec171ab1ebc3b157dd6a2dc94a104f25 (diff)
downloadreduced_system_layer-b863aec19fa95f39ec69e0e0f66d4851c9e3a358.zip
Wayland screenshot
Diffstat (limited to 'reduced_system_layer.c')
-rw-r--r--reduced_system_layer.c1081
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;