You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
393 lines
13 KiB
393 lines
13 KiB
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <wayland-client.h>
|
|
|
|
#include "ext-image-capture-source-v1.prot.h"
|
|
#include "ext-image-copy-capture-v1.prot.h"
|
|
#include "ext-foreign-toplevel-list-v1.prot.h"
|
|
|
|
#include "rif.h"
|
|
|
|
#define ASSERT(cond, msg)\
|
|
if(!(cond)) {\
|
|
fprintf(stderr, "Runtime assertion %s at %s:%d failed: %s\n", #cond, __FILE__, __LINE__, msg);\
|
|
exit(-1);\
|
|
}
|
|
|
|
// Not included in any standard header
|
|
// Thus, declaring manually.
|
|
// See memfd_create(2).
|
|
int memfd_create(const char* name, unsigned int flags);
|
|
|
|
struct image_data {
|
|
uint32_t width;
|
|
uint32_t height;
|
|
uint32_t shm_format;
|
|
};
|
|
|
|
struct wl_output* stream_output = NULL;
|
|
struct wl_shm* wl_shm = NULL;
|
|
struct wl_buffer* frame_buffer = NULL;
|
|
struct ext_output_image_capture_source_manager_v1* output_source_manager = NULL;
|
|
struct ext_image_copy_capture_manager_v1* capture_manager = NULL;
|
|
struct ext_foreign_toplevel_list_v1* toplevel_list = NULL;
|
|
struct ext_foreign_toplevel_image_capture_source_manager_v1* toplevel_source_manager = NULL;
|
|
|
|
void* shm_data = NULL;
|
|
|
|
struct image_data img_data;
|
|
|
|
uint8_t is_frame_ready = 0;
|
|
uint8_t is_capture_ready = 0;
|
|
uint8_t full_screenshot = 0;
|
|
|
|
uint32_t buf_size;
|
|
|
|
void handle_global(
|
|
void* data,
|
|
struct wl_registry* registry,
|
|
uint32_t name,
|
|
const char* interface,
|
|
uint32_t version) {
|
|
if(strcmp(interface, wl_output_interface.name) == 0) {
|
|
stream_output = wl_registry_bind(registry, name, &wl_output_interface, version);
|
|
}
|
|
if(strcmp(interface, wl_shm_interface.name) == 0) {
|
|
wl_shm = wl_registry_bind(registry, name, &wl_shm_interface, version);
|
|
}
|
|
if(strcmp(interface, ext_output_image_capture_source_manager_v1_interface.name) == 0) {
|
|
output_source_manager = wl_registry_bind(registry, name, &ext_output_image_capture_source_manager_v1_interface, version);
|
|
}
|
|
if(strcmp(interface, ext_image_copy_capture_manager_v1_interface.name) == 0) {
|
|
capture_manager = wl_registry_bind(registry, name, &ext_image_copy_capture_manager_v1_interface, version);
|
|
}
|
|
if(strcmp(interface, ext_foreign_toplevel_list_v1_interface.name) == 0) {
|
|
toplevel_list = wl_registry_bind(registry, name, &ext_foreign_toplevel_list_v1_interface, version);
|
|
}
|
|
if(strcmp(interface, ext_foreign_toplevel_image_capture_source_manager_v1_interface.name) == 0) {
|
|
toplevel_source_manager = wl_registry_bind(registry, name, &ext_foreign_toplevel_image_capture_source_manager_v1_interface, version);
|
|
}
|
|
}
|
|
|
|
void handle_global_remove(
|
|
void* data,
|
|
struct wl_registry* registry,
|
|
uint32_t name) {
|
|
// Who cares
|
|
}
|
|
|
|
const struct wl_registry_listener reg_callbacks = {
|
|
.global = handle_global,
|
|
.global_remove = handle_global_remove,
|
|
};
|
|
|
|
void buffer_size(void* data,
|
|
struct ext_image_copy_capture_session_v1* ext_image_copy_capture_session_v1,
|
|
uint32_t width,
|
|
uint32_t height) {
|
|
printf("x: %d, y: %d\n", width, height);
|
|
img_data.width = width;
|
|
img_data.height = height;
|
|
}
|
|
|
|
void shm_format(void* data,
|
|
struct ext_image_copy_capture_session_v1* ext_image_copy_capture_session_v1,
|
|
uint32_t format) {
|
|
img_data.shm_format = format;
|
|
ASSERT(format == WL_SHM_FORMAT_XBGR8888 || format == WL_SHM_FORMAT_ABGR8888, "Unsupported buffer format.");
|
|
}
|
|
|
|
void dmabuf_device(void* data,
|
|
struct ext_image_copy_capture_session_v1* ext_image_copy_capture_session_v1,
|
|
struct wl_array* device) {
|
|
|
|
}
|
|
|
|
|
|
void dmabuf_format(void* data,
|
|
struct ext_image_copy_capture_session_v1* ext_image_copy_capture_session_v1,
|
|
uint32_t format,
|
|
struct wl_array* modifiers) {
|
|
|
|
}
|
|
|
|
void capture_done(void* data,
|
|
struct ext_image_copy_capture_session_v1* ext_image_copy_capture_session_v1) {
|
|
const int shm_fd = memfd_create("wl_shm data", 0);
|
|
ASSERT(shm_fd != -1, "Failed to create memfd.");
|
|
|
|
// Format is hardcoded to chan width 4
|
|
const uint32_t chan_width = 4;
|
|
const uint32_t stride = img_data.width* chan_width;
|
|
buf_size = img_data.height* stride;
|
|
|
|
ASSERT(ftruncate(shm_fd, buf_size) != -1, "Failed to ftruncate the memfd.");
|
|
|
|
shm_data = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
|
|
ASSERT(shm_data != MAP_FAILED, "Failed to mmap the memfd.");
|
|
|
|
struct wl_shm_pool* pool = wl_shm_create_pool(wl_shm, shm_fd, buf_size);
|
|
ASSERT(pool != NULL, "Failed to create the wl_shm_pool.");
|
|
|
|
frame_buffer = wl_shm_pool_create_buffer(pool, 0, img_data.width, img_data.height, stride, img_data.shm_format);
|
|
ASSERT(frame_buffer != NULL, "Failed to create the wl_buffer.");
|
|
|
|
is_frame_ready = 1;
|
|
}
|
|
|
|
|
|
void stopped(void* data,
|
|
struct ext_image_copy_capture_session_v1* ext_image_copy_capture_session_v1) {
|
|
|
|
}
|
|
|
|
const struct ext_image_copy_capture_session_v1_listener capture_listener = {
|
|
.buffer_size = buffer_size,
|
|
.shm_format = shm_format,
|
|
.dmabuf_device = dmabuf_device,
|
|
.dmabuf_format = dmabuf_format,
|
|
.done = capture_done,
|
|
.stopped = stopped
|
|
};
|
|
|
|
|
|
void transform(void* data,
|
|
struct ext_image_copy_capture_frame_v1* ext_image_copy_capture_frame_v1,
|
|
uint32_t transform) {
|
|
|
|
}
|
|
void damage(void* data,
|
|
struct ext_image_copy_capture_frame_v1* ext_image_copy_capture_frame_v1,
|
|
int32_t x,
|
|
int32_t y,
|
|
int32_t width,
|
|
int32_t height) {
|
|
|
|
}
|
|
void presentation_time(void* data,
|
|
struct ext_image_copy_capture_frame_v1* ext_image_copy_capture_frame_v1,
|
|
uint32_t tv_sec_hi,
|
|
uint32_t tv_sec_lo,
|
|
uint32_t tv_nsec) {
|
|
|
|
}
|
|
void ready(void* data,
|
|
struct ext_image_copy_capture_frame_v1* ext_image_copy_capture_frame_v1) {
|
|
is_capture_ready = 1;
|
|
}
|
|
void failed(void* data,
|
|
struct ext_image_copy_capture_frame_v1* ext_image_copy_capture_frame_v1,
|
|
uint32_t reason) {
|
|
switch(reason) {
|
|
case 0:
|
|
printf("Capture failed. Reason: Unknown Runtime Error.");
|
|
break;
|
|
case 1:
|
|
printf("Capture failed. Reason: Buffer Constraints Mismatch.");
|
|
break;
|
|
case 2:
|
|
printf("Capture failed. Reason: Session Is No Longer Available.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
const struct ext_image_copy_capture_frame_v1_listener frame_listener = {
|
|
.transform = transform,
|
|
.damage = damage,
|
|
.presentation_time = presentation_time,
|
|
.ready = ready,
|
|
.failed = failed
|
|
};
|
|
|
|
struct handle_data {
|
|
struct ext_foreign_toplevel_handle_v1* handle;
|
|
const char* title;
|
|
const char* app_id;
|
|
const char* identifier;
|
|
};
|
|
|
|
struct handle_data*** handles;
|
|
size_t handles_len = 0;
|
|
|
|
void closed(void *data,
|
|
struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1) {
|
|
// TODO handle properly
|
|
}
|
|
void title(void *data,
|
|
struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1,
|
|
const char *title) {
|
|
struct handle_data* handle_data = (struct handle_data*)data;
|
|
size_t size = strlen(title)+1;
|
|
char* title_data = malloc(size);
|
|
memcpy(title_data, title, size);
|
|
handle_data->title = title_data;
|
|
}
|
|
void app_id(void *data,
|
|
struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1,
|
|
const char *app_id) {
|
|
struct handle_data* handle_data = (struct handle_data*)data;
|
|
size_t size = strlen(app_id)+1;
|
|
char* app_id_data = malloc(size);
|
|
memcpy(app_id_data, app_id, size);
|
|
handle_data->app_id = app_id_data;
|
|
}
|
|
void identifier(void *data,
|
|
struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1,
|
|
const char *identifier) {
|
|
struct handle_data* handle_data = (struct handle_data*)data;
|
|
size_t size = strlen(identifier)+1;
|
|
char* identifier_data = malloc(size);
|
|
memcpy(identifier_data, identifier, size);
|
|
handle_data->identifier = identifier_data;
|
|
}
|
|
void toplevel_done(void *data,
|
|
struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1) {
|
|
struct handle_data* handle_data = (struct handle_data*)data;
|
|
}
|
|
|
|
struct ext_foreign_toplevel_handle_v1_listener handle_listener = {
|
|
.closed = closed,
|
|
.done = toplevel_done,
|
|
.title = title,
|
|
.app_id = app_id,
|
|
.identifier = identifier,
|
|
};
|
|
|
|
void toplevel(void* data,
|
|
struct ext_foreign_toplevel_list_v1* ext_foreign_toplevel_list_v1,
|
|
struct ext_foreign_toplevel_handle_v1* toplevel) {
|
|
handles_len++;
|
|
*handles = realloc(*handles, handles_len*sizeof(void*));
|
|
|
|
struct handle_data* handle_data = malloc(sizeof(struct handle_data));
|
|
|
|
handle_data->handle = toplevel;
|
|
handle_data->title = NULL;
|
|
handle_data->app_id = NULL;
|
|
handle_data->identifier = NULL;
|
|
|
|
(*handles)[handles_len-1] = handle_data;
|
|
|
|
ext_foreign_toplevel_handle_v1_add_listener(toplevel, &handle_listener, handle_data);
|
|
}
|
|
void finished(void* data,
|
|
struct ext_foreign_toplevel_list_v1* ext_foreign_toplevel_list_v1) {
|
|
|
|
// TODO handle exploding properly
|
|
}
|
|
|
|
const struct ext_foreign_toplevel_list_v1_listener toplevel_listener = {
|
|
.toplevel = toplevel,
|
|
.finished = finished
|
|
};
|
|
|
|
|
|
int main(int argc, char* argv[]) {
|
|
struct wl_display* dpy = wl_display_connect(NULL);
|
|
ASSERT(dpy != NULL, "Unable to connect to Wayland");
|
|
|
|
struct wl_registry* registry = wl_display_get_registry(dpy);
|
|
wl_registry_add_listener(registry, ®_callbacks, NULL);
|
|
wl_display_roundtrip(dpy);
|
|
|
|
ASSERT(wl_shm != NULL, "core.wl_shm not supported.");
|
|
|
|
ASSERT(stream_output != NULL, "No outputs found!");
|
|
|
|
ASSERT(output_source_manager != NULL, "ext-image-capture-source-v1.ext_output_image_capture_source_manager_v1 not supported.");
|
|
|
|
struct handle_data* toplevel_handle = NULL;
|
|
if(argc == 2) {
|
|
ASSERT(toplevel_list != NULL, "ext-foreign-toplevel-list-v1.ext_foreign_toplevel_list_v1 not supported.");
|
|
|
|
handles = malloc(sizeof(void*));
|
|
*handles = malloc(sizeof(void*));
|
|
|
|
handles_len = 0;
|
|
|
|
ext_foreign_toplevel_list_v1_add_listener(toplevel_list, &toplevel_listener, NULL);
|
|
|
|
if(strcmp(argv[1], "?") == 0) {
|
|
wl_display_roundtrip(dpy);
|
|
for(int i = 0; i < handles_len; i++) {
|
|
struct handle_data* handle_data = (*handles)[i];
|
|
printf("title: %s, app_id: %s, identifier: %s\n", handle_data->title, handle_data->app_id, handle_data->identifier);
|
|
}
|
|
return 0;
|
|
} else {
|
|
wl_display_roundtrip(dpy);
|
|
for(int i = 0; i < handles_len; i++) {
|
|
if(strcmp((*handles)[i]->identifier, argv[1]) == 0) {
|
|
toplevel_handle = (*handles)[i];
|
|
printf("Found handle: ");
|
|
printf("title: %s, app_id: %s, identifier: %s\n", toplevel_handle->title, toplevel_handle->app_id, toplevel_handle->identifier);
|
|
}
|
|
}
|
|
}
|
|
} else if(argc == 1) {
|
|
full_screenshot = 1;
|
|
wl_display_roundtrip(dpy);
|
|
} else {
|
|
ASSERT(argc >= 1, "Invalid exec!");
|
|
printf("Usage: %s ?|<identifier>\n");
|
|
exit(-1);
|
|
}
|
|
|
|
ASSERT(capture_manager != NULL, "ext-image-copy-capture-v1.ext_image_copy_capture_manager_v1 not supported.");
|
|
|
|
struct ext_image_capture_source_v1* capture_source;
|
|
if(full_screenshot) {
|
|
ASSERT(output_source_manager != NULL, "ext-image-capture-source-v1.ext_output_image_capture_source_manager_v1 not supported.");
|
|
capture_source =
|
|
ext_output_image_capture_source_manager_v1_create_source(output_source_manager, stream_output);
|
|
ASSERT(capture_source != NULL, "Failed creating a source for the default output!")
|
|
} else {
|
|
ASSERT(toplevel_handle != NULL, "Invalid toplevel handle!");
|
|
ASSERT(toplevel_source_manager != NULL, "ext-image-capture-source-v1.ext_foreign_toplevel_image_capture_source_manager_v1 not supported.");
|
|
capture_source =
|
|
ext_foreign_toplevel_image_capture_source_manager_v1_create_source(toplevel_source_manager, toplevel_handle->handle);
|
|
ASSERT(capture_source != NULL, "Failed creating a source for the toplevel!")
|
|
}
|
|
|
|
struct ext_image_copy_capture_session_v1* capture_session =
|
|
ext_image_copy_capture_manager_v1_create_session(capture_manager, capture_source, EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS);
|
|
ASSERT(capture_session != NULL, "Failed creating a session for the capture!");
|
|
|
|
ext_image_copy_capture_session_v1_add_listener(capture_session, &capture_listener, NULL);
|
|
wl_display_roundtrip(dpy);
|
|
|
|
while(is_frame_ready == 0 && wl_display_dispatch(dpy) != -1) {
|
|
// Empty
|
|
}
|
|
|
|
struct ext_image_copy_capture_frame_v1* frame = ext_image_copy_capture_session_v1_create_frame(capture_session);
|
|
ASSERT(frame != NULL, "Failed to create frame!");
|
|
|
|
ext_image_copy_capture_frame_v1_add_listener(frame, &frame_listener, NULL);
|
|
|
|
ext_image_copy_capture_frame_v1_attach_buffer(frame, frame_buffer);
|
|
ext_image_copy_capture_frame_v1_damage_buffer(frame, 0, 0, img_data.width, img_data.height);
|
|
ext_image_copy_capture_frame_v1_capture(frame);
|
|
|
|
wl_display_roundtrip(dpy);
|
|
|
|
while(is_capture_ready == 0 && wl_display_dispatch(dpy) != -1) {
|
|
// Empty
|
|
}
|
|
|
|
printf("Saving screenshot.\n");
|
|
|
|
// void write_rif_little(char* path, uint32_t width, uint32_t size, uint8_t format, void* data) {
|
|
// Thanks to endianess shenanigans, ABGR/XBGR is RGBA.
|
|
write_rif_little("out.rif", img_data.width, buf_size, RIF_FORMAT_R8G8B8A8, shm_data);
|
|
|
|
ext_image_copy_capture_frame_v1_destroy(frame);
|
|
|
|
return 0;
|
|
}
|