#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "viewporter-protocol.h" #include "wlr-layer-shell-unstable-v1-protocol.h" #include "wlr-screencopy-unstable-v1-protocol.h" #define SWAPCHAIN_LEN 4 struct dp_swapchain_buffer { struct wl_buffer *buffer; void *data; pixman_image_t *image; bool used; }; struct dp_output { uint32_t name; struct wl_output *global; struct wl_surface *surface; struct zwlr_layer_surface_v1 *layer_surface; struct wp_viewport *viewport; struct wl_surface *ui_surface; struct wl_subsurface *ui_subsurface; struct wp_viewport *ui_viewport; int width, height; int px_width, px_height; int frame_width, frame_height; int32_t transform; struct zwlr_screencopy_frame_v1 *frame; bool frame_ready; struct wl_buffer *frame_buffer; int32_t frame_stride; uint32_t frame_format; void *frame_data; bool configured; struct dp_swapchain_buffer swapchain[SWAPCHAIN_LEN]; struct wl_list link; }; enum dp_status { DP_RUNNING, DP_SAVE, DP_QUIT, }; static enum dp_status status = DP_RUNNING; static struct wl_compositor *compositor = NULL; static struct wl_subcompositor *subcompositor = NULL; static struct wp_viewporter *viewporter = NULL; static struct wl_shm *shm = NULL; static struct zwlr_layer_shell_v1 *layer_shell = NULL; static struct zwlr_screencopy_manager_v1 *screencopy_manager = NULL; static struct wl_seat *seat = NULL; static struct wl_keyboard *keyboard = NULL; static struct wl_pointer *pointer = NULL; static struct wl_list outputs; static uint32_t seat_caps = false; static struct xkb_context *xkb_context = NULL; static struct xkb_keymap *xkb_keymap = NULL; static struct xkb_state *xkb_state = NULL; static struct dp_output *selected_output = NULL; static int selected_x = 0; static int selected_y = 0; static int selected_width = 0; static int selected_height = 0; static bool selecting_rect = false; static int select_start_x = 0; static int select_start_y = 0; static struct dp_output *pointer_output = NULL; static int pointer_x = 0; static int pointer_y = 0; static bool quick = false; static pixman_image_t *unselected_fill_image = NULL; static pixman_image_t *selected_fill_image = NULL; static pixman_image_t *border_fill_image = NULL; static void die(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "Fatal: "); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); va_end(args); exit(1); } static void *xzalloc(size_t size) { void *ptr = calloc(1, size); if (ptr == NULL) { die("allocation error"); } return ptr; } static void sc_buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { struct dp_swapchain_buffer *sc_buffer = data; sc_buffer->used = false; } static const struct wl_buffer_listener sc_buffer_listener = { .release = sc_buffer_handle_release, }; static void fnv_1a_cont(uint64_t *h, const void *data, size_t len) { const uint8_t *bytes = data; for (size_t i = 0; i < len; i++) { *h = (*h * 0x00000100000001B3) ^ bytes[i]; } } static void generate_name(char buffer[static 16]) { uint64_t h = 0xcbf29ce484222325; int pid = getpid(); fnv_1a_cont(&h, &pid, sizeof(pid)); struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); fnv_1a_cont(&h, &ts.tv_sec, sizeof(&ts.tv_sec)); fnv_1a_cont(&h, &ts.tv_nsec, sizeof(&ts.tv_nsec)); for (size_t i = 16; i-- > 0;) { buffer[i] = "0123456789abcdef"[h & 0xF]; h >>= 4; } } static struct wl_buffer *create_buffer( int32_t width, int32_t height, int32_t stride, uint32_t format, void **data) { int32_t size = stride * height; static const char template[] = "randfall-"; char name[sizeof(template) + 16] = {0}; memcpy(name, template, sizeof(template) - 1); generate_name(&name[sizeof(template) - 1]); int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd < 0) { die("shm_open() failed"); } shm_unlink(name); if (ftruncate(fd, (off_t)size) < 0) { die("ftruncate() failed"); } struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int32_t)size); *data = mmap(NULL, (size_t)size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { die("mmap() failed"); } struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, (int32_t)stride, format); wl_shm_pool_destroy(pool); close(fd); return buffer; } static void layer_surface_handle_configure(void *data, struct zwlr_layer_surface_v1 *layer_surface, uint32_t serial, uint32_t width, uint32_t height) { struct dp_output *output = data; if (output->configured && (output->width != (int)width || output->height != (int)height)) { die("got unexpected layer surface configure size"); } zwlr_layer_surface_v1_ack_configure(layer_surface, serial); if (output->configured) { wl_surface_commit(output->surface); return; } output->width = (int)width; output->height = (int)height; output->configured = true; wp_viewport_set_destination(output->viewport, output->width, output->height); wp_viewport_set_destination(output->ui_viewport, output->width, output->height); wl_surface_attach(output->surface, output->frame_buffer, 0, 0); wl_surface_commit(output->surface); } static void redraw(struct dp_output *output) { struct dp_swapchain_buffer *sc_buffer = NULL; for (size_t i = 0; i < SWAPCHAIN_LEN; i++) { struct dp_swapchain_buffer *iter = &output->swapchain[i]; if (!iter->used) { sc_buffer = iter; break; } } if (sc_buffer == NULL) { fprintf(stderr, "no free buffers in a swapchain\n"); return; } sc_buffer->used = true; pixman_image_composite32(PIXMAN_OP_SRC, unselected_fill_image, NULL, sc_buffer->image, 0, 0, 0, 0, 0, 0, output->px_width, output->px_height); if (output == selected_output) { int border_size = 2; pixman_image_composite32(PIXMAN_OP_SRC, border_fill_image, NULL, sc_buffer->image, 0, 0, 0, 0, selected_x - border_size, selected_y - border_size, selected_width + border_size * 2, border_size); pixman_image_composite32(PIXMAN_OP_SRC, border_fill_image, NULL, sc_buffer->image, 0, 0, 0, 0, selected_x - border_size, selected_y + selected_height, selected_width + border_size * 2, border_size); pixman_image_composite32(PIXMAN_OP_SRC, border_fill_image, NULL, sc_buffer->image, 0, 0, 0, 0, selected_x - border_size, selected_y, border_size, selected_height); pixman_image_composite32(PIXMAN_OP_SRC, border_fill_image, NULL, sc_buffer->image, 0, 0, 0, 0, selected_x + selected_width, selected_y, border_size, selected_height); pixman_image_composite32(PIXMAN_OP_SRC, selected_fill_image, NULL, sc_buffer->image, 0, 0, 0, 0, selected_x, selected_y, selected_width, selected_height); } wl_surface_attach(output->ui_surface, sc_buffer->buffer, 0, 0); wl_surface_damage(output->ui_surface, 0, 0, output->px_width, output->px_height); wl_surface_commit(output->ui_surface); } static void select_whole(void) { selected_x = 0; selected_y = 0; selected_width = selected_output->px_width; selected_height = selected_output->px_height; } static void set_selected_output(struct dp_output *output) { if (output == selected_output) { return; } struct dp_output *prev_selected_output = selected_output; selected_output = output; redraw(prev_selected_output); } static void layer_surface_handle_closed(void *data, struct zwlr_layer_surface_v1 *layer_surface) { die("a layer surface was closed"); } static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { .configure = layer_surface_handle_configure, .closed = layer_surface_handle_closed, }; static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { struct dp_output *output = data; output->transform = transform; } static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { struct dp_output *output = data; output->frame_width = width; output->frame_height = height; } static const struct wl_output_listener output_listener = { .geometry = output_handle_geometry, .mode = output_handle_mode, }; static void seat_handle_capabilities(void *data, struct wl_seat *seat_, uint32_t caps) { seat_caps = caps; } static void seat_handle_name(void *data, struct wl_seat *seat_, const char *name) { // Ignored } static const struct wl_seat_listener seat_listener = { .capabilities = seat_handle_capabilities, .name = seat_handle_name, }; static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard_, enum wl_keyboard_keymap_format format, int32_t fd, uint32_t size) { void *keymap_buffer; size_t keymap_len; xkb_keymap_unref(xkb_keymap); xkb_state_unref(xkb_state); switch (format) { case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP: xkb_keymap = xkb_keymap_new_from_names(xkb_context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS); break; case WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1: keymap_len = size - 1; keymap_buffer = mmap(NULL, keymap_len, PROT_READ, MAP_PRIVATE, fd, 0); if (keymap_buffer == MAP_FAILED) { die("mmap() for a keymap failed"); } xkb_keymap = xkb_keymap_new_from_buffer(xkb_context, keymap_buffer, keymap_len, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap(keymap_buffer, keymap_len); close(fd); break; } xkb_state = xkb_state_new(xkb_keymap); } static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard_, uint32_t serial, struct wl_surface *surface, struct wl_array *mods) { // Ignored } static void keyboard_handle_leave( void *data, struct wl_keyboard *keyboard_, uint32_t serial, struct wl_surface *surface) { // Ignored } static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard_, uint32_t serial, uint32_t time_msec, uint32_t key, enum wl_keyboard_key_state state) { if (xkb_state == NULL) { return; // Shouldn't happen } if (state != WL_KEYBOARD_KEY_STATE_PRESSED) { return; } xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, key + 8); switch (keysym) { case XKB_KEY_q: status = DP_QUIT; break; case XKB_KEY_s: status = DP_SAVE; break; case XKB_KEY_w: select_whole(); redraw(selected_output); break; case XKB_KEY_Tab: set_selected_output(wl_container_of( (selected_output->link.next != &outputs ? &selected_output->link : &outputs)->next, selected_output, link)); select_whole(); redraw(selected_output); break; } } static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard_, uint32_t serial, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { if (xkb_state == NULL) { return; // Shouldn't happen } xkb_state_update_mask(xkb_state, depressed, latched, locked, 0, 0, group); } static const struct wl_keyboard_listener keyboard_listener = { .keymap = keyboard_handle_keymap, .enter = keyboard_handle_enter, .leave = keyboard_handle_leave, .key = keyboard_handle_key, .modifiers = keyboard_handle_modifiers, }; static void convert_pos(wl_fixed_t x, wl_fixed_t y, int *out_x, int *out_y) { assert(pointer_output != NULL); *out_x = (int)wl_fixed_to_double(x) * pointer_output->px_width / pointer_output->width; *out_y = (int)wl_fixed_to_double(y) * pointer_output->px_height / pointer_output->height; } static void pointer_handle_enter(void *data, struct wl_pointer *pointer_, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) { pointer_output = wl_surface_get_user_data(surface); assert(pointer_output != NULL); convert_pos(sx, sy, &pointer_x, &pointer_y); } static void pointer_handle_leave( void *data, struct wl_pointer *pointer_, uint32_t serial, struct wl_surface *surface) { pointer_output = NULL; selecting_rect = false; } static void pointer_handle_motion( void *data, struct wl_pointer *pointer_, uint32_t serial, wl_fixed_t sx, wl_fixed_t sy) { if (pointer_output == NULL) { return; // Shouldn't happen } convert_pos(sx, sy, &pointer_x, &pointer_y); if (!selecting_rect) { return; } if (pointer_x < select_start_x) { selected_x = pointer_x; selected_width = select_start_x - pointer_x; } else { selected_x = select_start_x; selected_width = pointer_x - select_start_x; } if (pointer_y < select_start_y) { selected_y = pointer_y; selected_height = select_start_y - pointer_y; } else { selected_y = select_start_y; selected_height = pointer_y - select_start_y; } redraw(selected_output); } static void pointer_handle_button(void *data, struct wl_pointer *pointer_, uint32_t serial, uint32_t time_msec, uint32_t button, enum wl_pointer_button_state state) { if (pointer_output == NULL) { return; // Shouldn't happen } if (state != WL_POINTER_BUTTON_STATE_PRESSED) { selecting_rect = false; if (quick) { status = DP_SAVE; } return; } switch (button) { case BTN_LEFT: selecting_rect = true; selected_x = pointer_x; selected_y = pointer_y; selected_width = 0; selected_height = 0; select_start_x = selected_x; select_start_y = selected_y; set_selected_output(pointer_output); redraw(selected_output); break; case BTN_RIGHT: set_selected_output(pointer_output); select_whole(); if (quick) { status = DP_SAVE; return; } redraw(selected_output); break; } } static void pointer_handle_axis(void *data, struct wl_pointer *pointer_, uint32_t time_msec, enum wl_pointer_axis axis, wl_fixed_t value) { } static const struct wl_pointer_listener pointer_listener = { .enter = pointer_handle_enter, .leave = pointer_handle_leave, .motion = pointer_handle_motion, .button = pointer_handle_button, .axis = pointer_handle_axis, }; static void frame_handle_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { struct dp_output *output = data; assert(output->frame_buffer == NULL); output->frame_format = format; output->frame_stride = (int)stride; output->frame_buffer = create_buffer(output->frame_width, output->frame_height, output->frame_stride, output->frame_format, &output->frame_data); zwlr_screencopy_frame_v1_copy(output->frame, output->frame_buffer); } static void frame_handle_flags(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) { if (flags != 0) { // TODO die("who the fuck uses Y_INVERT"); } } static void frame_handle_ready(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { struct dp_output *output = data; output->frame_ready = true; } static void frame_handle_failed(void *data, struct zwlr_screencopy_frame_v1 *frame) { die("frame copying has failed"); } static const struct zwlr_screencopy_frame_v1_listener frame_listener = { .buffer = frame_handle_buffer, .flags = frame_handle_flags, .ready = frame_handle_ready, .failed = frame_handle_failed, }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (strcmp(interface, wl_compositor_interface.name) == 0) { if (version >= 2) { compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 2); } } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1); } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1); } else if (strcmp(interface, wl_shm_interface.name) == 0) { shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); } else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { screencopy_manager = wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, 1); } else if (strcmp(interface, wl_output_interface.name) == 0) { struct dp_output *output = xzalloc(sizeof(*output)); output->name = name; output->global = wl_registry_bind(registry, name, &wl_output_interface, 1); wl_output_add_listener(output->global, &output_listener, output); wl_list_insert(&outputs, &output->link); } else if (strcmp(interface, wl_seat_interface.name) == 0) { // No multiseat if (seat == NULL) { seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); wl_seat_add_listener(seat, &seat_listener, NULL); } } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct dp_output *output; wl_list_for_each (output, &outputs, link) { if (output->name == name) { die("an output was removed"); } } } static const struct wl_registry_listener registry_listener = { .global = registry_handle_global, .global_remove = registry_handle_global_remove, }; static pixman_format_code_t shm_to_pixman(enum wl_shm_format shm_format) { struct { enum wl_shm_format shm; pixman_format_code_t pixman; } formats[] = { #if DP_BIG_ENDIAN {WL_SHM_FORMAT_ARGB8888, PIXMAN_b8g8r8a8}, {WL_SHM_FORMAT_XRGB8888, PIXMAN_b8g8r8x8}, {WL_SHM_FORMAT_ABGR8888, PIXMAN_r8g8b8a8}, {WL_SHM_FORMAT_XBGR8888, PIXMAN_r8g8b8x8}, {WL_SHM_FORMAT_BGRA8888, PIXMAN_a8r8g8b8}, {WL_SHM_FORMAT_BGRX8888, PIXMAN_x8r8g8b8}, {WL_SHM_FORMAT_RGBA8888, PIXMAN_a8b8g8r8}, {WL_SHM_FORMAT_RGBX8888, PIXMAN_x8b8g8r8}, #else {WL_SHM_FORMAT_RGB332, PIXMAN_r3g3b2}, {WL_SHM_FORMAT_BGR233, PIXMAN_b2g3r3}, {WL_SHM_FORMAT_ARGB4444, PIXMAN_a4r4g4b4}, {WL_SHM_FORMAT_XRGB4444, PIXMAN_x4r4g4b4}, {WL_SHM_FORMAT_ABGR4444, PIXMAN_a4b4g4r4}, {WL_SHM_FORMAT_XBGR4444, PIXMAN_x4b4g4r4}, {WL_SHM_FORMAT_ARGB1555, PIXMAN_a1r5g5b5}, {WL_SHM_FORMAT_XRGB1555, PIXMAN_x1r5g5b5}, {WL_SHM_FORMAT_ABGR1555, PIXMAN_a1b5g5r5}, {WL_SHM_FORMAT_XBGR1555, PIXMAN_x1b5g5r5}, {WL_SHM_FORMAT_RGB565, PIXMAN_r5g6b5}, {WL_SHM_FORMAT_BGR565, PIXMAN_b5g6r5}, {WL_SHM_FORMAT_RGB888, PIXMAN_r8g8b8}, {WL_SHM_FORMAT_BGR888, PIXMAN_b8g8r8}, {WL_SHM_FORMAT_ARGB8888, PIXMAN_a8r8g8b8}, {WL_SHM_FORMAT_XRGB8888, PIXMAN_x8r8g8b8}, {WL_SHM_FORMAT_ABGR8888, PIXMAN_a8b8g8r8}, {WL_SHM_FORMAT_XBGR8888, PIXMAN_x8b8g8r8}, {WL_SHM_FORMAT_BGRA8888, PIXMAN_b8g8r8a8}, {WL_SHM_FORMAT_BGRX8888, PIXMAN_b8g8r8x8}, {WL_SHM_FORMAT_RGBA8888, PIXMAN_r8g8b8a8}, {WL_SHM_FORMAT_RGBX8888, PIXMAN_r8g8b8x8}, {WL_SHM_FORMAT_ARGB2101010, PIXMAN_a2r10g10b10}, {WL_SHM_FORMAT_ABGR2101010, PIXMAN_a2b10g10r10}, {WL_SHM_FORMAT_XRGB2101010, PIXMAN_x2r10g10b10}, {WL_SHM_FORMAT_XBGR2101010, PIXMAN_x2b10g10r10}, #endif }; for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); i++) { if (formats[i].shm == shm_format) { return formats[i].pixman; } } return 0; } int main(void) { struct wl_display *display = wl_display_connect(NULL); if (display == NULL) { die("failed to connect to a Wayland compositor"); } xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); wl_list_init(&outputs); struct wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, NULL); // Collect globals wl_display_roundtrip(display); if (compositor == NULL) { die("the compositor has no wl_compositor"); } else if (subcompositor == NULL) { die("the compositor has no wl_subcompositor"); } else if (viewporter == NULL) { die("the compositor has no wp_viewporter"); } else if (shm == NULL) { die("the compositor has no wl_shm"); } else if (layer_shell == NULL) { die("the compositor has no zwlr_layer_shell_v1"); } else if (screencopy_manager == NULL) { die("the compositor has no zwlr_screencopy_manager_v1"); } else if (seat == NULL) { die("the compositor has no wl_seat"); } if (wl_list_empty(&outputs)) { die("no outputs found"); } struct dp_output *output; // Get output/seat info wl_display_roundtrip(display); if ((seat_caps & WL_SEAT_CAPABILITY_KEYBOARD) == 0) { die("the seat has no keyboard capabilities"); } if ((seat_caps & WL_SEAT_CAPABILITY_POINTER) == 0) { die("the seat has no pointer capabilities"); } keyboard = wl_seat_get_keyboard(seat); wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL); pointer = wl_seat_get_pointer(seat); wl_pointer_add_listener(pointer, &pointer_listener, NULL); wl_list_for_each (output, &outputs, link) { if ((output->transform & WL_OUTPUT_TRANSFORM_90) != 0) { output->px_width = output->frame_height; output->px_height = output->frame_width; } else { output->px_width = output->frame_width; output->px_height = output->frame_height; } output->frame = zwlr_screencopy_manager_v1_capture_output( screencopy_manager, false, output->global); zwlr_screencopy_frame_v1_add_listener(output->frame, &frame_listener, output); } // Get frames wl_display_roundtrip(display); wl_list_for_each (output, &outputs, link) { if (output->frame_buffer == NULL) { die("failed to create a buffer for an output"); } while (!output->frame_ready && wl_display_dispatch(display) != -1) { // Wait } } // Init surfaces wl_list_for_each (output, &outputs, link) { output->surface = wl_compositor_create_surface(compositor); output->layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell, output->surface, output->global, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "dulcepan"); output->viewport = wp_viewporter_get_viewport(viewporter, output->surface); wl_surface_set_buffer_transform(output->surface, output->transform); struct wl_region *empty_region = wl_compositor_create_region(compositor); wl_surface_set_input_region(output->surface, empty_region); wl_region_destroy(empty_region); zwlr_layer_surface_v1_add_listener(output->layer_surface, &layer_surface_listener, output); zwlr_layer_surface_v1_set_anchor(output->layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); zwlr_layer_surface_v1_set_keyboard_interactivity( output->layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE); zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1); output->ui_surface = wl_compositor_create_surface(compositor); output->ui_subsurface = wl_subcompositor_get_subsurface(subcompositor, output->ui_surface, output->surface); output->ui_viewport = wp_viewporter_get_viewport(viewporter, output->ui_surface); wl_subsurface_set_desync(output->ui_subsurface); wl_surface_set_user_data(output->surface, output); wl_surface_set_user_data(output->ui_surface, output); for (size_t i = 0; i < SWAPCHAIN_LEN; i++) { struct dp_swapchain_buffer *sc_buffer = &output->swapchain[i]; int stride = output->frame_width * 4; sc_buffer->buffer = create_buffer(output->px_width, output->px_height, stride, WL_SHM_FORMAT_ARGB8888, &sc_buffer->data); sc_buffer->image = pixman_image_create_bits( PIXMAN_a8r8g8b8, output->px_width, output->px_height, sc_buffer->data, stride); wl_buffer_add_listener(sc_buffer->buffer, &sc_buffer_listener, sc_buffer); } wl_surface_commit(output->surface); } wl_list_for_each (output, &outputs, link) { while (!output->configured && wl_display_dispatch(display) != -1) { // Wait } } unselected_fill_image = pixman_image_create_solid_fill(&(pixman_color_t){ .red = UINT16_MAX / 2, .green = UINT16_MAX / 2, .blue = UINT16_MAX / 2, .alpha = UINT16_MAX / 2, }); selected_fill_image = pixman_image_create_solid_fill(&(pixman_color_t){ .red = 0, .green = 0, .blue = 0, .alpha = 0, }); border_fill_image = pixman_image_create_solid_fill(&(pixman_color_t){ .red = UINT16_MAX, .green = UINT16_MAX, .blue = UINT16_MAX, .alpha = UINT16_MAX, }); // Select the "first" output, whatever that is selected_output = wl_container_of(outputs.next, selected_output, link); select_whole(); wl_list_for_each (output, &outputs, link) { redraw(output); } while (wl_display_dispatch(display) != -1 && status == DP_RUNNING) { // Wait } if (status == DP_SAVE) { if (selected_width == 0 || selected_height == 0) { die("selected region is empty"); } pixman_format_code_t pixman_format = shm_to_pixman(selected_output->frame_format); if (pixman_format == 0) { die("failed to get a matching Pixman format for wl_shm format"); } pixman_image_t *frame = pixman_image_create_bits(pixman_format, selected_output->frame_width, selected_output->frame_height, selected_output->frame_data, selected_output->frame_stride); static const int sines[] = {0, -1, 0, 1, 0, 1, 0, -1}; static const int cosines[] = {1, 0, -1, 0, 1, 0, -1, 0}; static const int flips[] = {1, 1, 1, 1, -1, -1, -1, -1}; struct pixman_transform frame_transform; pixman_transform_init_identity(&frame_transform); pixman_transform_translate(&frame_transform, NULL, pixman_int_to_fixed(-selected_output->px_width / 2), pixman_int_to_fixed(-selected_output->px_height / 2)); pixman_transform_rotate(&frame_transform, NULL, pixman_int_to_fixed(cosines[selected_output->transform]), pixman_int_to_fixed(sines[selected_output->transform])); pixman_transform_scale(&frame_transform, NULL, pixman_int_to_fixed(flips[selected_output->transform]), pixman_fixed_1); pixman_transform_translate(&frame_transform, NULL, pixman_int_to_fixed(selected_output->frame_width / 2), pixman_int_to_fixed(selected_output->frame_height / 2)); pixman_image_set_transform(frame, &frame_transform); pixman_image_t *result = pixman_image_create_bits(PIXMAN_a8r8g8b8, selected_width, selected_height, NULL, 0); pixman_image_composite32(PIXMAN_OP_SRC, frame, NULL, result, selected_x, selected_y, 0, 0, 0, 0, selected_width, selected_height); pixman_image_unref(frame); // TODO: a better format uint32_t *data = pixman_image_get_data(result); fprintf(stdout, "P6\n%d %d\n255\n", selected_width, selected_height); for (int y = 0; y < selected_height; y++) { for (int x = 0; x < selected_width; x++) { uint32_t pixel = *(data++); char bytes[3] = { (char)((pixel >> 16) & 0xff), (char)((pixel >> 8) & 0xff), (char)((pixel >> 0) & 0xff), }; fwrite(bytes, 1, sizeof(bytes), stdout); } } fflush(stdout); pixman_image_unref(result); } wl_registry_destroy(registry); wl_display_disconnect(display); return 0; }