From 143616266388d9ed9e7cc5d0046cc5f862a626f3 Mon Sep 17 00:00:00 2001 From: Kirill Primak Date: Wed, 19 Jun 2024 18:40:22 +0300 Subject: [PATCH] =?UTF-8?q?Make=20it=20good=E2=84=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 20 + README.md | 13 +- dulcepan.cfg | 13 + main.c | 855 ------------------------------------------ meson.build | 29 +- protocols/meson.build | 1 - src/buffer.c | 63 ++++ src/config.c | 180 +++++++++ src/dulcepan.h | 173 +++++++++ src/main.c | 230 ++++++++++++ src/meson.build | 10 + src/output.c | 345 +++++++++++++++++ src/save.c | 170 +++++++++ src/seat.c | 216 +++++++++++ src/select.c | 63 ++++ src/util.c | 57 +++ 16 files changed, 1576 insertions(+), 862 deletions(-) create mode 100644 .editorconfig create mode 100644 dulcepan.cfg delete mode 100644 main.c create mode 100644 src/buffer.c create mode 100644 src/config.c create mode 100644 src/dulcepan.h create mode 100644 src/main.c create mode 100644 src/meson.build create mode 100644 src/output.c create mode 100644 src/save.c create mode 100644 src/seat.c create mode 100644 src/select.c create mode 100644 src/util.c diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5466909 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +max_line_length = 100 + +[*.[ch]] +indent_style = tab +indent_size = 4 + +[*.xml] +indent_style = space +indent_size = 2 + +[meson.build] +indent_style = space +indent_size = 2 diff --git a/README.md b/README.md index 707de33..dcd9e33 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # dulcepan -A (WIP) screenshot tool. +A screenshot tool for Wayland compositors. Requires wlr-screencopy-unstable-v1 +support. Discuss in [#eclairs on Libera.Chat]. @@ -13,6 +14,16 @@ meson setup build/ ninja -C build/ ``` +## Configuration + +dulcepan will try to load a configuration file from +`$XDG_CONFIG_DIRS/dulcepan.cfg`, which is a set of `key=value` pairs. See the +example configuration file `dulcepan.cfg` for more information. + +## Usage + +See `dulcepan -h`. + ## License GPL-3.0-only diff --git a/dulcepan.cfg b/dulcepan.cfg new file mode 100644 index 0000000..682c5b0 --- /dev/null +++ b/dulcepan.cfg @@ -0,0 +1,13 @@ +# An example configuration. + +# RRGGBB or RRGGBBAA +unselected-color = ffffff40 +selected-color = 00000000 +border-color = ffffff + +# 0 to disable borders +border-size = 2 + +# If true, dulcepan will save immediately when interactive selection is stopped +# or when a whole output is selected with a mouse button. +quick-select = false diff --git a/main.c b/main.c deleted file mode 100644 index 6353630..0000000 --- a/main.c +++ /dev/null @@ -1,855 +0,0 @@ -#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; -} diff --git a/meson.build b/meson.build index 92be204..81cb82c 100644 --- a/meson.build +++ b/meson.build @@ -44,13 +44,30 @@ wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols') pixman = dependency('pixman-1') -xkbcommon = dependency('xkbcommon') +spng = dependency('spng') +xkbcommon = dependency( + 'xkbcommon', + fallback: 'libxkbcommon', + default_options: [ + 'enable-tools=false', + 'enable-x11=false', + 'enable-docs=false', + 'enable-xkbregistry=false', + ], +) + +sfdo_basedir = dependency( + 'libsfdo-basedir', + version: '>=0.1.0', + fallback: 'libsfdo', + default_options: [ + 'default_library=static', + 'examples=false', + ], +) subdir('protocols') - -src = files( - 'main.c', -) +subdir('src') executable( meson.project_name(), @@ -59,7 +76,9 @@ executable( client_protos, wayland_client, pixman, + spng, xkbcommon, + sfdo_basedir, ], install: true, ) diff --git a/protocols/meson.build b/protocols/meson.build index a0c4ef2..0a7cc8f 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -16,7 +16,6 @@ wayland_scanner_client = generator( client_protocols = [ wl_protocol_dir / 'stable/viewporter/viewporter.xml', wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', # layer-shell dependency - wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', 'wlr-layer-shell-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml', ] diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..22ab806 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include + +#include "dulcepan.h" + +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; + } +} + +struct wl_buffer *dp_buffer_create(struct dp_state *state, int32_t width, int32_t height, + int32_t stride, uint32_t format, void **data, size_t *size) { + *size = (size_t)(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) { + dp_log_fatal("shm_open() failed"); + } + shm_unlink(name); + + if (ftruncate(fd, (off_t)*size) < 0) { + dp_log_fatal("ftruncate() failed"); + } + + struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, fd, (int32_t)*size); + *data = mmap(NULL, (size_t)*size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + dp_log_fatal("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; +} diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..3007c7c --- /dev/null +++ b/src/config.c @@ -0,0 +1,180 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dulcepan.h" + +#define CONFIG_SUBPATH "dulcepan.cfg" + +#define COLOR_MUL (UINT16_MAX / UINT8_MAX) + +static void bytes_to_color(uint32_t bytes[static 4], pixman_color_t *out) { + out->red = (uint16_t)(bytes[0] * bytes[3] * COLOR_MUL / UINT8_MAX); + out->green = (uint16_t)(bytes[1] * bytes[3] * COLOR_MUL / UINT8_MAX); + out->blue = (uint16_t)(bytes[2] * bytes[3] * COLOR_MUL / UINT8_MAX); + out->alpha = (uint16_t)(bytes[3] * COLOR_MUL); +} + +static void load_color(const char *value, int line_idx, pixman_color_t *out) { + size_t len = strlen(value); + + uint32_t bytes[4] = {0, 0, 0, 0}; + if (len == 6 && len != 8) { + bytes[3] = UINT8_MAX; + } else if (len != 8) { + goto bad; + } + + for (size_t i = 0; i < len; i++) { + uint32_t digit; + if (value[i] >= '0' && value[i] <= '9') { + digit = (uint32_t)value[i] - '0'; + } else if (value[i] >= 'A' && value[i] <= 'F') { + digit = (uint32_t)value[i] - 'A' + 10; + } else if (value[i] >= 'a' && value[i] <= 'f') { + digit = (uint32_t)value[i] - 'a' + 10; + } else { + goto bad; + } + bytes[i / 2] = bytes[i / 2] * 16 + digit; + } + + bytes_to_color(bytes, out); + return; + +bad: + dp_log_fatal("Config: invalid color %s on line %d", value, line_idx); +} + +static void load_int(const char *value, int line_idx, int *out) { + // Only nonnegative numbers + *out = 0; + for (size_t i = 0; value[i] != '\0'; i++) { + if ((value[i] < '0' && value[i] > 9) || *out > INT_MAX / 10) { + dp_log_fatal("Config: invalid number %s on line %d", value, line_idx); + } + *out = *out * 10 - '0' + value[i]; + } +} + +static void load_bool(const char *value, int line_idx, bool *out) { + if (strcmp(value, "true") == 0) { + *out = true; + } else if (strcmp(value, "false") == 0) { + *out = false; + } else { + dp_log_fatal("Config: invalid boolean value %s on line %d", value, line_idx); + } +} + +void dp_config_load(struct dp_config *config, const char *user_path) { + FILE *fp = NULL; + + *config = (struct dp_config){ + .border_size = 2, + .quick_select = false, + }; + bytes_to_color((uint32_t[]){0xff, 0xff, 0xff, 0x40}, &config->unselected_color); + bytes_to_color((uint32_t[]){0x00, 0x00, 0x00, 0xff}, &config->selected_color); + bytes_to_color((uint32_t[]){0xff, 0xff, 0xff, 0xff}, &config->border_color); + + if (user_path != NULL) { + fp = fopen(user_path, "r"); + if (fp == NULL) { + dp_log_fatal("Failed to open %s: %s", user_path, strerror(errno)); + } + } else { + struct sfdo_basedir_ctx *basedir_ctx = sfdo_basedir_ctx_create(); + + size_t n_dirs; + const struct sfdo_string *dirs = sfdo_basedir_get_config_dirs(basedir_ctx, &n_dirs); + for (size_t i = 0; i < n_dirs && fp == NULL; i++) { + const struct sfdo_string *dir = &dirs[i]; + size_t size = dir->len + sizeof(CONFIG_SUBPATH); + char *path = dp_zalloc(size); + memcpy(path, dir->data, dir->len); + memcpy(path + dir->len, CONFIG_SUBPATH, sizeof(CONFIG_SUBPATH)); + fp = fopen(path, "r"); + if (fp == NULL) { + if (errno != ENOENT) { + dp_log_fatal("Failed to open %s: %s", path, strerror(errno)); + } + } + free(path); + } + + sfdo_basedir_ctx_destroy(basedir_ctx); + } + + if (fp == NULL) { + return; + } + + int line_idx = 0; + char *line = NULL; + size_t line_cap = 0; + ssize_t line_len; + while ((line_len = getline(&line, &line_cap, fp)) != -1) { + ++line_idx; + + if (line[line_len - 1] == '\n') { + line[--line_len] = '\0'; + } + + // One of: + // [whitespace] [# comment] + // [whitespace] [whitespace] = [whitespace] [whitespace] [# comment] + + ssize_t i = 0; + while (isspace(line[line_len]) && i++ < line_len) { + // Skip whitespace + } + if (line[i] == '\0' || line[i] == '#') { + continue; + } + + char *key = &line[i]; + while (!isspace(line[i]) && line[i] != '=' && line[i] != '#' && i++ < line_len) { + // Read key + } + char *key_end = &line[i]; + + while (isspace(line[i]) && i++ < line_len) { + // Skip whitespace + } + if (line[i++] != '=') { + dp_log_fatal("Config: invalid syntax on line %d", line_idx); + } + *key_end = '\0'; + while (isspace(line[i]) && i++ < line_len) { + // Skip whitespace + } + + char *value = &line[i]; + while (!isspace(line[i]) && line[i] != '#' && i++ < line_len) { + // Read value + } + line[i] = '\0'; + + if (strcmp(key, "unselected-color") == 0) { + load_color(value, line_idx, &config->unselected_color); + } else if (strcmp(key, "selected-color") == 0) { + load_color(value, line_idx, &config->selected_color); + } else if (strcmp(key, "border-color") == 0) { + load_color(value, line_idx, &config->border_color); + } else if (strcmp(key, "border-size") == 0) { + load_int(value, line_idx, &config->border_size); + } else if (strcmp(key, "quick-select") == 0) { + load_bool(value, line_idx, &config->quick_select); + } else { + dp_log_fatal("Config: unknown key %s on line %d", key, line_idx); + } + } + free(line); + + fclose(fp); +} diff --git a/src/dulcepan.h b/src/dulcepan.h new file mode 100644 index 0000000..9ee63e9 --- /dev/null +++ b/src/dulcepan.h @@ -0,0 +1,173 @@ +#ifndef DULCEPAN_H +#define DULCEPAN_H + +#include +#include +#include +#include + +// Per-output +#define DP_SWAPCHAIN_LEN 2 + +enum dp_status { + DP_STATUS_RUNNING, + DP_STATUS_SAVED, + DP_STATUS_QUIT, +}; + +struct dp_buffer { + struct wl_buffer *wl_buffer; + pixman_image_t *image; + void *data; + size_t size; + bool used; +}; + +struct dp_output { + struct dp_state *state; + + uint32_t name; + struct wl_output *wl_output; + + struct wl_surface *main_surface; + struct zwlr_layer_surface_v1 *main_layer_surface; + struct wp_viewport *main_viewport; + + struct wl_surface *select_surface; + struct wl_subsurface *select_subsurface; + struct wp_viewport *select_viewport; + + int width, height; + int32_t transform; + bool has_geom; + int transformed_width, transformed_height; + + int effective_width, effective_height; + bool initialized; + + struct wl_callback *redraw_callback; + bool needs_redraw; + + struct zwlr_screencopy_frame_v1 *frame; + // XXX: this is completely untested + bool y_invert; + + struct wl_buffer *frame_buffer; + int32_t frame_stride; + uint32_t frame_format; + void *frame_data; + size_t frame_size; + + struct dp_buffer swapchain[DP_SWAPCHAIN_LEN]; + + struct wl_list link; +}; + +struct dp_seat { + struct dp_state *state; + + uint32_t name; + struct wl_seat *wl_seat; + + struct wl_keyboard *keyboard; + struct wl_pointer *pointer; + + struct xkb_keymap *xkb_keymap; + struct xkb_state *xkb_state; + + // The output the pointer is on + struct dp_output *ptr_output; + // In buffer space + int ptr_x, ptr_y; + + struct wl_list link; +}; + +struct dp_selection { + struct dp_output *output; // May be NULL + int x, y, width, height; + + struct dp_output *interactive_output; // NULL if not interactively selecting + int interactive_x, interactive_y; + bool interactive_moved; +}; + +enum dp_file_format { + DP_FILE_UNKNOWN = 0, + + DP_FILE_PNG, + DP_FILE_PPM, +}; + +struct dp_config { + pixman_color_t unselected_color; + pixman_color_t selected_color; + pixman_color_t border_color; + int border_size; // 0 if disabled + bool quick_select; +}; + +struct dp_state { + enum dp_status status; + + struct wl_display *display; + struct wl_registry *registry; + + struct wl_compositor *compositor; + struct wl_subcompositor *subcompositor; + struct wp_viewporter *viewporter; + struct wl_shm *shm; + struct zwlr_layer_shell_v1 *layer_shell; + struct zwlr_screencopy_manager_v1 *screencopy_manager; + + bool initialized; + + struct xkb_context *xkb_context; + + struct wl_list outputs; + struct wl_list seats; + + struct dp_selection selection; + + struct dp_config config; + const char *output_path; // May be NULL + enum dp_file_format output_format; + + pixman_image_t *unselected_fill_image; + pixman_image_t *selected_fill_image; + pixman_image_t *border_fill_image; +}; + +void dp_config_load(struct dp_config *config, const char *user_path); + +// When done, data must be unmapped +struct wl_buffer *dp_buffer_create(struct dp_state *state, int32_t width, int32_t height, + int32_t stride, uint32_t format, void **data, size_t *size); + +void dp_output_create(struct dp_state *state, uint32_t name, struct wl_output *wl_output); +void dp_output_destroy(struct dp_output *output); + +void dp_output_redraw(struct dp_output *output); + +void dp_output_hide_surface(struct dp_output *output); + +void dp_seat_create(struct dp_state *state, uint32_t name, struct wl_seat *wl_seat); +void dp_seat_destroy(struct dp_seat *seat); + +void dp_select_interactive_start( + struct dp_selection *selection, struct dp_output *output, int x, int y); +void dp_select_interactive_move(struct dp_selection *selection, int x, int y); +void dp_select_interactive_stop(struct dp_selection *selection); +void dp_select_whole(struct dp_selection *selection, struct dp_output *output); + +void dp_save(struct dp_state *state); + +void dp_log_error(const char *fmt, ...); +void dp_log_fatal(const char *fmt, ...); + +void *dp_zalloc(size_t size); + +const char *dp_ext_from_path(const char *path); +enum dp_file_format dp_ext_to_format(const char *ext); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..ba71136 --- /dev/null +++ b/src/main.c @@ -0,0 +1,230 @@ +#include +#include +#include +#include +#include +#include + +#include "dulcepan.h" +#include "viewporter-protocol.h" +#include "wlr-layer-shell-unstable-v1-protocol.h" +#include "wlr-screencopy-unstable-v1-protocol.h" + +static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, + const char *interface, uint32_t version) { + struct dp_state *state = data; + if (strcmp(interface, wl_compositor_interface.name) == 0) { + if (version >= 2) { + state->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 2); + } + } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { + state->subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1); + } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { + state->viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + state->layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); + } 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); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + if (state->initialized) { + dp_log_fatal("An output was added after initialization"); + } else if (version < 2) { + dp_log_fatal("An output has too low version (%d)", version); + } + struct wl_output *global = wl_registry_bind(registry, name, &wl_output_interface, 2); + dp_output_create(state, name, global); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + struct wl_seat *global = wl_registry_bind(registry, name, &wl_seat_interface, 1); + dp_seat_create(state, name, global); + } +} + +static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { + struct dp_state *state = data; + + struct dp_output *output; + wl_list_for_each (output, &state->outputs, link) { + if (output->name == name) { + dp_log_fatal("An output was removed"); + } + } + + struct dp_seat *seat; + wl_list_for_each (seat, &state->seats, link) { + if (seat->name == name) { + dp_seat_destroy(seat); + return; + } + } +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_handle_global, + .global_remove = registry_handle_global_remove, +}; + +static void run(struct dp_state *state) { + struct wl_registry *registry = wl_display_get_registry(state->display); + wl_registry_add_listener(registry, ®istry_listener, state); + + // Collect globals + wl_display_roundtrip(state->display); + + if (state->compositor == NULL) { + dp_log_fatal("The compositor has no wl_compositor"); + } else if (state->subcompositor == NULL) { + dp_log_fatal("The compositor has no wl_subcompositor"); + } else if (state->viewporter == NULL) { + dp_log_fatal("The compositor has no wp_viewporter"); + } else if (state->shm == NULL) { + dp_log_fatal("The compositor has no wl_shm"); + } else if (state->layer_shell == NULL) { + dp_log_fatal("The compositor has no zwlr_layer_shell_v1"); + } else if (state->screencopy_manager == NULL) { + dp_log_fatal("The compositor has no zwlr_screencopy_manager_v1"); + } + + if (wl_list_empty(&state->outputs)) { + dp_log_fatal("No outputs found"); + } else if (wl_list_empty(&state->seats)) { + dp_log_fatal("No seats found"); + } + + state->initialized = true; + + struct dp_output *output; + wl_list_for_each (output, &state->outputs, link) { + while (!output->initialized && wl_display_dispatch(state->display) != -1) { + // Wait for output to fully initialize + } + } + + wl_list_for_each (output, &state->outputs, link) { + dp_output_redraw(output); + } + + while (wl_display_dispatch(state->display) != -1 && state->status == DP_STATUS_RUNNING) { + // Wait + } + + if (state->status == DP_STATUS_SAVED) { + // Hide everything before saving + wl_list_for_each (output, &state->outputs, link) { + dp_output_hide_surface(output); + } + wl_display_flush(state->display); + + dp_save(state); + } + + struct dp_seat *seat, *seat_tmp; + wl_list_for_each_safe (seat, seat_tmp, &state->seats, link) { + dp_seat_destroy(seat); + } + + struct dp_output *output_tmp; + wl_list_for_each_safe (output, output_tmp, &state->outputs, link) { + dp_output_destroy(output); + } + + wl_compositor_destroy(state->compositor); + wl_subcompositor_destroy(state->subcompositor); + wp_viewporter_destroy(state->viewporter); + wl_shm_destroy(state->shm); + zwlr_layer_shell_v1_destroy(state->layer_shell); + zwlr_screencopy_manager_v1_destroy(state->screencopy_manager); + + wl_registry_destroy(registry); +} + +static void help(const char *prog) { + fprintf(stderr, + "Usage: %s [options]\n" + "\n" + " -h Show this help message and quit.\n" + " -c Specify the configuration file path.\n" + " -f Specify the output file format.\n" + " -o Specify the output file path.\n" + "\n" + "If the output file path is not specified, the resuling image will be printed\n" + "to the standard output. If the output file format is not specified, it is\n" + "guessed from the output file path if it's specified, and assumed to be PNG\n" + "otherwise.\n" + "\n" + "Supported formats: png, ppm.\n", + prog); +} + +int main(int argc, char **argv) { + struct dp_state state = {0}; + + const char *config_path = NULL; + + int opt; + while ((opt = getopt(argc, argv, "hc:f:o:")) != -1) { + switch (opt) { + case 'h': + help(argv[0]); + return 0; + case 'c': + config_path = optarg; + break; + case 'f': + state.output_format = dp_ext_to_format(optarg); + if (state.output_format == DP_FILE_UNKNOWN) { + dp_log_fatal("Unknown format %s", optarg); + } + break; + case 'o': + state.output_path = optarg; + break; + default: // '?' + help(argv[0]); + return 1; + } + } + + dp_config_load(&state.config, config_path); + + if (state.output_format == DP_FILE_UNKNOWN) { + if (state.output_path != NULL) { + const char *ext = dp_ext_from_path(state.output_path); + state.output_format = dp_ext_to_format(ext); + if (state.output_format == DP_FILE_UNKNOWN) { + dp_log_fatal("Unknown format of %s", state.output_path); + } + } else { + // Default format + state.output_format = DP_FILE_PNG; + } + } + + wl_list_init(&state.outputs); + wl_list_init(&state.seats); + + state.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + state.display = wl_display_connect(NULL); + if (state.display == NULL) { + dp_log_fatal("failed to connect to a Wayland compositor"); + } + + state.unselected_fill_image = pixman_image_create_solid_fill(&state.config.unselected_color); + state.selected_fill_image = pixman_image_create_solid_fill(&state.config.selected_color); + state.border_fill_image = pixman_image_create_solid_fill(&state.config.border_color); + + run(&state); + + pixman_image_unref(state.unselected_fill_image); + pixman_image_unref(state.selected_fill_image); + pixman_image_unref(state.border_fill_image); + + xkb_context_unref(state.xkb_context); + + wl_display_disconnect(state.display); + + return 0; +} diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..fc57b5c --- /dev/null +++ b/src/meson.build @@ -0,0 +1,10 @@ +src = files( + 'buffer.c', + 'config.c', + 'main.c', + 'output.c', + 'save.c', + 'seat.c', + 'select.c', + 'util.c', +) diff --git a/src/output.c b/src/output.c new file mode 100644 index 0000000..6a4121a --- /dev/null +++ b/src/output.c @@ -0,0 +1,345 @@ +#include +#include +#include +#include + +#include "dulcepan.h" +#include "viewporter-protocol.h" +#include "wlr-layer-shell-unstable-v1-protocol.h" +#include "wlr-screencopy-unstable-v1-protocol.h" + +// Initialiation flow: +// - Wait for mode+transform info +// - Wait for done, compute transformed size +// - Prepare surfaces, get a frame, wait for it to be ready +// - Commit the main surface, wait for a configure event +// - Save effective size, put the frame buffer on the main surface +// - The output is initialized + +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; + + // Thanks layer-shell + int i_width = (int)width, i_height = (int)height; + + zwlr_layer_surface_v1_ack_configure(layer_surface, serial); + + if (output->initialized) { + if (i_width != output->effective_width || i_height != output->effective_height) { + dp_log_fatal("Layer surface size has changed: %dx%d => %dx%d", output->effective_width, + output->effective_height, i_width, i_height); + } + + wl_surface_commit(output->main_surface); + return; + } + + output->effective_width = (int)width; + output->effective_height = (int)height; + + wp_viewport_set_destination( + output->main_viewport, output->effective_width, output->effective_height); + wp_viewport_set_destination( + output->select_viewport, output->effective_width, output->effective_height); + + if (output->y_invert) { + wl_surface_set_buffer_transform(output->main_surface, WL_OUTPUT_TRANSFORM_FLIPPED_180); + } + + wl_surface_attach(output->main_surface, output->frame_buffer, 0, 0); + wl_surface_commit(output->main_surface); + + output->initialized = true; +} + +static void layer_surface_handle_closed(void *data, struct zwlr_layer_surface_v1 *layer_surface) { + dp_log_fatal("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 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; + + if (output->frame_buffer != NULL) { + wl_buffer_destroy(output->frame_buffer); + } + + output->frame_format = format; + output->frame_stride = (int)stride; + output->frame_buffer = dp_buffer_create(output->state, output->width, output->height, + output->frame_stride, output->frame_format, &output->frame_data, &output->frame_size); + 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) { + struct dp_output *output = data; + if ((flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT) != 0) { + output->y_invert = true; + } +} + +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; + + zwlr_screencopy_frame_v1_destroy(output->frame); + output->frame = NULL; + + wl_surface_commit(output->main_surface); +} + +static void frame_handle_failed(void *data, struct zwlr_screencopy_frame_v1 *frame) { + dp_log_fatal("Failed to copy a frame"); +} + +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 buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { + struct dp_buffer *buffer = data; + buffer->used = false; +} + +static const struct wl_buffer_listener buffer_listener = { + .release = buffer_handle_release, +}; + +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; + + if (output->has_geom) { + if (transform != output->transform) { + dp_log_fatal("Output transform has changed: %d (%#x) => %d (%#x)", output->transform, + output->transform, transform, transform); + } + } + + 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; + + if (output->has_geom) { + if (width != output->width || height != output->height) { + dp_log_fatal("Output mode has changed: %dx%d => %dx%d", output->width, output->height, + width, height); + } + } + + output->width = width; + output->height = height; +} + +static void output_handle_done(void *data, struct wl_output *wl_output) { + struct dp_output *output = data; + + if (output->has_geom) { + return; + } + + if ((output->transform & WL_OUTPUT_TRANSFORM_90) != 0) { + output->transformed_width = output->height; + output->transformed_height = output->width; + } else { + output->transformed_width = output->width; + output->transformed_height = output->height; + } + + output->has_geom = true; + + output->frame = zwlr_screencopy_manager_v1_capture_output( + output->state->screencopy_manager, false, output->wl_output); + zwlr_screencopy_frame_v1_add_listener(output->frame, &frame_listener, output); + + struct dp_state *state = output->state; + + output->main_surface = wl_compositor_create_surface(state->compositor); + output->main_layer_surface = zwlr_layer_shell_v1_get_layer_surface(state->layer_shell, + output->main_surface, output->wl_output, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "dulcepan"); + output->main_viewport = wp_viewporter_get_viewport(state->viewporter, output->main_surface); + + wl_surface_set_buffer_transform(output->main_surface, output->transform); + + struct wl_region *empty_region = wl_compositor_create_region(state->compositor); + wl_surface_set_input_region(output->main_surface, empty_region); + wl_region_destroy(empty_region); + + zwlr_layer_surface_v1_add_listener(output->main_layer_surface, &layer_surface_listener, output); + + zwlr_layer_surface_v1_set_anchor(output->main_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->main_layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE); + zwlr_layer_surface_v1_set_exclusive_zone(output->main_layer_surface, -1); + + output->select_surface = wl_compositor_create_surface(state->compositor); + output->select_subsurface = wl_subcompositor_get_subsurface( + state->subcompositor, output->select_surface, output->main_surface); + output->select_viewport = wp_viewporter_get_viewport(state->viewporter, output->select_surface); + wl_subsurface_set_desync(output->select_subsurface); + + wl_surface_set_user_data(output->select_surface, output); + + for (size_t i = 0; i < DP_SWAPCHAIN_LEN; i++) { + struct dp_buffer *buffer = &output->swapchain[i]; + int stride = output->transformed_width * 4; + buffer->wl_buffer = + dp_buffer_create(state, output->transformed_width, output->transformed_height, + stride, WL_SHM_FORMAT_ARGB8888, &buffer->data, &buffer->size); + buffer->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, output->transformed_width, + output->transformed_height, buffer->data, stride); + + wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer); + } +} + +static void output_handle_scale(void *data, struct wl_output *wl_output, int32_t scale) { + // Ignored +} + +static const struct wl_output_listener output_listener = { + .geometry = output_handle_geometry, + .mode = output_handle_mode, + .done = output_handle_done, + .scale = output_handle_scale, +}; + +static void redraw(struct dp_output *output); + +static void redraw_callback_handle_done( + void *data, struct wl_callback *callback, uint32_t callback_data) { + struct dp_output *output = data; + wl_callback_destroy(output->redraw_callback); + output->redraw_callback = NULL; + if (output->needs_redraw) { + redraw(output); + } +} + +static const struct wl_callback_listener redraw_callback_listener = { + .done = redraw_callback_handle_done, +}; + +static void redraw(struct dp_output *output) { + assert(output->redraw_callback == NULL); + + struct dp_buffer *buffer = NULL; + for (size_t i = 0; i < DP_SWAPCHAIN_LEN; i++) { + struct dp_buffer *iter = &output->swapchain[i]; + if (!iter->used) { + buffer = iter; + break; + } + } + if (buffer == NULL) { + dp_log_error("No free buffers in a swapchain\n"); + return; + } + buffer->used = true; + + struct dp_state *state = output->state; + + pixman_image_composite32(PIXMAN_OP_SRC, state->unselected_fill_image, NULL, buffer->image, 0, 0, + 0, 0, 0, 0, output->transformed_width, output->transformed_height); + + struct dp_selection *selection = &state->selection; + if (output == selection->output) { + int border_size = state->config.border_size; + if (border_size != 0) { + pixman_image_composite32(PIXMAN_OP_SRC, state->border_fill_image, NULL, buffer->image, + 0, 0, 0, 0, selection->x - border_size, selection->y - border_size, + selection->width + border_size * 2, border_size); + pixman_image_composite32(PIXMAN_OP_SRC, state->border_fill_image, NULL, buffer->image, + 0, 0, 0, 0, selection->x - border_size, selection->y + selection->height, + selection->width + border_size * 2, border_size); + pixman_image_composite32(PIXMAN_OP_SRC, state->border_fill_image, NULL, buffer->image, + 0, 0, 0, 0, selection->x - border_size, selection->y, border_size, + selection->height); + pixman_image_composite32(PIXMAN_OP_SRC, state->border_fill_image, NULL, buffer->image, + 0, 0, 0, 0, selection->x + selection->width, selection->y, border_size, + selection->height); + } + + pixman_image_composite32(PIXMAN_OP_SRC, state->selected_fill_image, NULL, buffer->image, 0, + 0, 0, 0, selection->x, selection->y, selection->width, selection->height); + } + + wl_surface_attach(output->select_surface, buffer->wl_buffer, 0, 0); + wl_surface_damage( + output->select_surface, 0, 0, output->transformed_width, output->transformed_height); + + output->redraw_callback = wl_surface_frame(output->select_surface); + wl_callback_add_listener(output->redraw_callback, &redraw_callback_listener, output); + output->needs_redraw = false; + + wl_surface_commit(output->select_surface); +} + +void dp_output_redraw(struct dp_output *output) { + if (output->redraw_callback != NULL) { + output->needs_redraw = true; + return; + } + redraw(output); +} + +void dp_output_hide_surface(struct dp_output *output) { + wl_surface_attach(output->main_surface, NULL, 0, 0); + wl_surface_commit(output->main_surface); +} + +void dp_output_create(struct dp_state *state, uint32_t name, struct wl_output *wl_output) { + struct dp_output *output = dp_zalloc(sizeof(*output)); + output->state = state; + output->name = name; + output->wl_output = wl_output; + + wl_output_add_listener(output->wl_output, &output_listener, output); + + wl_list_insert(&state->outputs, &output->link); +} + +void dp_output_destroy(struct dp_output *output) { + wl_output_release(output->wl_output); + + assert(output->frame == NULL); + wl_buffer_destroy(output->frame_buffer); + munmap(output->frame_data, output->frame_size); + + if (output->redraw_callback != NULL) { + wl_callback_destroy(output->redraw_callback); + } + + for (size_t i = 0; i < DP_SWAPCHAIN_LEN; i++) { + struct dp_buffer *buffer = &output->swapchain[i]; + wl_buffer_destroy(buffer->wl_buffer); + pixman_image_unref(buffer->image); + munmap(buffer->data, buffer->size); + } + + wl_subsurface_destroy(output->select_subsurface); + wp_viewport_destroy(output->select_viewport); + wl_surface_destroy(output->select_surface); + + zwlr_layer_surface_v1_destroy(output->main_layer_surface); + wp_viewport_destroy(output->main_viewport); + wl_surface_destroy(output->main_surface); + + wl_list_remove(&output->link); + free(output); +} diff --git a/src/save.c b/src/save.c new file mode 100644 index 0000000..64f9480 --- /dev/null +++ b/src/save.c @@ -0,0 +1,170 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dulcepan.h" + +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; + } + } + dp_log_fatal("failed to get a matching Pixman format for wl_shm format"); + return 0; // Unreachable, actually +} + +static void write_png(FILE *fp, pixman_image_t *image, int width, int height) { + void *data = pixman_image_get_data(image); + size_t size = (size_t)(width * height * 4); + + struct spng_ctx *spng_ctx = spng_ctx_new(SPNG_CTX_ENCODER); + struct spng_ihdr ihdr = { + .width = (uint32_t)width, + .height = (uint32_t)height, + .bit_depth = 8, + .color_type = SPNG_COLOR_TYPE_TRUECOLOR_ALPHA, + }; + + spng_set_ihdr(spng_ctx, &ihdr); + spng_set_png_file(spng_ctx, fp); + + int err = spng_encode_image(spng_ctx, data, size, SPNG_FMT_PNG, SPNG_ENCODE_FINALIZE); + if (err != 0) { + dp_log_fatal("spng_encode_image() failed: %s", spng_strerror(err)); + } + + spng_ctx_free(spng_ctx); +} + +static void write_ppm(FILE *fp, pixman_image_t *image, int width, int height) { + uint32_t *data = pixman_image_get_data(image); + size_t size = (size_t)(width * height); + + fprintf(fp, "P6\n%d %d\n255\n", width, height); + for (size_t i = 0; i < size; i++) { + uint32_t pixel = data[i]; + char bytes[3] = { + (char)((pixel >> 0) & 0xff), + (char)((pixel >> 8) & 0xff), + (char)((pixel >> 16) & 0xff), + }; + fwrite(bytes, 1, sizeof(bytes), fp); + } + fflush(fp); + + if (ferror(fp) != 0) { + dp_log_fatal("Failed to write the output file\n"); + } +} + +void dp_save(struct dp_state *state) { + struct dp_selection *selection = &state->selection; + struct dp_output *output = selection->output; + + if (output == NULL || selection->width == 0 || selection->height == 0) { + dp_log_fatal("The selection is empty"); + } + + pixman_format_code_t pixman_format = shm_to_pixman(output->frame_format); + + pixman_image_t *frame_image = pixman_image_create_bits( + pixman_format, output->width, output->height, output->frame_data, output->frame_stride); + + static const pixman_fixed_t cosines[] = {pixman_fixed_1, 0, -pixman_fixed_1, 0}; + + pixman_fixed_t scale_x = (output->transform & WL_OUTPUT_TRANSFORM_FLIPPED) != 0 + ? -pixman_fixed_1 + : pixman_fixed_1; + pixman_fixed_t scale_y = output->y_invert ? -pixman_fixed_1 : pixman_fixed_1; + + struct pixman_transform frame_transform; + pixman_transform_init_identity(&frame_transform); + pixman_transform_translate(&frame_transform, NULL, + -pixman_int_to_fixed(output->transformed_width) / 2, + -pixman_int_to_fixed(output->transformed_height) / 2); + pixman_transform_scale(&frame_transform, NULL, scale_x, scale_y); + pixman_transform_rotate(&frame_transform, NULL, cosines[output->transform % 4], + cosines[(output->transform + 1) % 4]); + pixman_transform_translate(&frame_transform, NULL, pixman_int_to_fixed(output->width) / 2, + pixman_int_to_fixed(output->height) / 2); + + pixman_image_set_transform(frame_image, &frame_transform); + + pixman_image_t *out_image = + pixman_image_create_bits(PIXMAN_x8b8g8r8, selection->width, selection->height, NULL, 0); + pixman_image_composite32(PIXMAN_OP_SRC, frame_image, NULL, out_image, selection->x, + selection->y, 0, 0, 0, 0, selection->width, selection->height); + pixman_image_unref(frame_image); + + FILE *fp = NULL; + if (state->output_path == NULL) { + fp = stdout; + } else { + fp = fopen(state->output_path, "w"); + if (fp == NULL) { + dp_log_fatal("Failed to open %s: %s", state->output_path, strerror(errno)); + } + } + + switch (state->output_format) { + case DP_FILE_UNKNOWN: + abort(); // Unreachable + case DP_FILE_PNG: + write_png(fp, out_image, selection->width, selection->height); + break; + case DP_FILE_PPM: + write_ppm(fp, out_image, selection->width, selection->height); + break; + } + + pixman_image_unref(out_image); + fclose(fp); +} diff --git a/src/seat.c b/src/seat.c new file mode 100644 index 0000000..bccf4fe --- /dev/null +++ b/src/seat.c @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dulcepan.h" + +static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + enum wl_keyboard_keymap_format format, int32_t fd, uint32_t size) { + struct dp_seat *seat = data; + + void *keymap_buffer; + size_t keymap_len; + + xkb_keymap_unref(seat->xkb_keymap); + xkb_state_unref(seat->xkb_state); + + switch (format) { + case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP: + seat->xkb_keymap = xkb_keymap_new_from_names( + seat->state->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) { + dp_log_fatal("mmap() for a keymap failed"); + } + seat->xkb_keymap = xkb_keymap_new_from_buffer(seat->state->xkb_context, keymap_buffer, + keymap_len, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(keymap_buffer, keymap_len); + close(fd); + break; + } + + seat->xkb_state = xkb_state_new(seat->xkb_keymap); +} + +static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + struct wl_surface *surface, struct wl_array *keys) { + // Ignored +} + +static void keyboard_handle_leave( + void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) { + // Ignored +} + +static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + uint32_t time_msec, uint32_t keycode, enum wl_keyboard_key_state key_state) { + struct dp_seat *seat = data; + if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) { + return; + } + + xkb_keysym_t keysym = xkb_state_key_get_one_sym(seat->xkb_state, keycode + 8); + + // TODO: configurable + // TODO: more actions + switch (keysym) { + case XKB_KEY_q: + seat->state->status = DP_STATUS_QUIT; + break; + case XKB_KEY_s: + seat->state->status = DP_STATUS_SAVED; + break; + } +} + +static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { + struct dp_seat *seat = data; + xkb_state_update_mask(seat->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 save_position(struct dp_seat *seat, wl_fixed_t x, wl_fixed_t y) { + struct dp_output *output = seat->ptr_output; + seat->ptr_x = + (int)(wl_fixed_to_double(x) * output->transformed_width / output->effective_width); + seat->ptr_y = + (int)(wl_fixed_to_double(y) * output->transformed_height / output->effective_height); +} + +static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, + struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) { + struct dp_seat *seat = data; + seat->ptr_output = wl_surface_get_user_data(surface); + assert(seat->ptr_output != NULL); + save_position(seat, sx, sy); +} + +static void pointer_handle_leave( + void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { + struct dp_seat *seat = data; + seat->ptr_output = NULL; +} + +static void pointer_handle_motion( + void *data, struct wl_pointer *wl_pointer, uint32_t serial, wl_fixed_t sx, wl_fixed_t sy) { + struct dp_seat *seat = data; + if (seat->ptr_output == NULL) { + return; // Shouldn't happen + } + save_position(seat, sx, sy); + dp_select_interactive_move(&seat->state->selection, seat->ptr_x, seat->ptr_y); +} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, + uint32_t time_msec, uint32_t button, enum wl_pointer_button_state button_state) { + struct dp_seat *seat = data; + struct dp_state *state = seat->state; + + struct dp_selection *selection = &state->selection; + if (button_state != WL_POINTER_BUTTON_STATE_PRESSED) { + if (selection->interactive_moved && state->config.quick_select) { + state->status = DP_STATUS_SAVED; + } + dp_select_interactive_stop(selection); + return; + } + + if (seat->ptr_output == NULL) { + return; // Shouldn't happen + } + + switch (button) { + case BTN_LEFT: + dp_select_interactive_start(selection, seat->ptr_output, seat->ptr_x, seat->ptr_y); + break; + case BTN_RIGHT: + dp_select_whole(selection, seat->ptr_output); + if (state->config.quick_select) { + state->status = DP_STATUS_SAVED; + } + break; + case BTN_MIDDLE: + state->status = DP_STATUS_SAVED; + return; + } +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time_msec, + enum wl_pointer_axis axis, wl_fixed_t value) { + // Ignored +} + +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 seat_handle_capabilities(void *data, struct wl_seat *wl_seat, uint32_t caps) { + struct dp_seat *seat = data; + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) != 0 && seat->keyboard == NULL) { + seat->keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, seat); + } + if ((caps & WL_SEAT_CAPABILITY_POINTER) != 0 && seat->pointer == NULL) { + seat->pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(seat->pointer, &pointer_listener, seat); + } +} + +static void seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name) { + // Ignored +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = seat_handle_name, +}; + +void dp_seat_create(struct dp_state *state, uint32_t name, struct wl_seat *wl_seat) { + struct dp_seat *seat = dp_zalloc(sizeof(*seat)); + seat->state = state; + seat->name = name; + seat->wl_seat = wl_seat; + + seat->xkb_keymap = + xkb_keymap_new_from_names(seat->state->xkb_context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS); + seat->xkb_state = xkb_state_new(seat->xkb_keymap); + + wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); + + wl_list_insert(&state->seats, &seat->link); +} + +void dp_seat_destroy(struct dp_seat *seat) { + wl_seat_release(seat->wl_seat); + if (seat->keyboard != NULL) { + wl_keyboard_release(seat->keyboard); + } + if (seat->pointer != NULL) { + wl_pointer_release(seat->pointer); + } + + xkb_keymap_unref(seat->xkb_keymap); + xkb_state_unref(seat->xkb_state); + + wl_list_remove(&seat->link); + free(seat); +} diff --git a/src/select.c b/src/select.c new file mode 100644 index 0000000..8a91ac7 --- /dev/null +++ b/src/select.c @@ -0,0 +1,63 @@ +#include "dulcepan.h" + +static void set_selected_output(struct dp_selection *selection, struct dp_output *output) { + if (selection->output == output) { + return; + } + struct dp_output *prev = selection->output; + selection->output = output; + if (prev != NULL) { + dp_output_redraw(prev); + } +} + +void dp_select_interactive_start( + struct dp_selection *selection, struct dp_output *output, int x, int y) { + selection->interactive_output = output; + selection->interactive_x = x; + selection->interactive_y = y; + selection->interactive_moved = false; + + dp_output_redraw(output); +} + +void dp_select_interactive_move(struct dp_selection *selection, int x, int y) { + if (selection->interactive_output == NULL) { + return; + } + set_selected_output(selection, selection->interactive_output); + selection->interactive_moved = true; + + if (x < selection->interactive_x) { + selection->x = x; + selection->width = selection->interactive_x - x; + } else { + selection->x = selection->interactive_x; + selection->width = x - selection->interactive_x; + } + if (y < selection->interactive_y) { + selection->y = y; + selection->height = selection->interactive_y - y; + } else { + selection->y = selection->interactive_y; + selection->height = y - selection->interactive_y; + } + + dp_output_redraw(selection->output); +} + +void dp_select_interactive_stop(struct dp_selection *selection) { + selection->interactive_output = NULL; + selection->interactive_moved = false; +} + +void dp_select_whole(struct dp_selection *selection, struct dp_output *output) { + set_selected_output(selection, output); + + selection->x = 0; + selection->y = 0; + selection->width = output->transformed_width; + selection->height = output->transformed_height; + + dp_output_redraw(output); +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..a99efe7 --- /dev/null +++ b/src/util.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include + +#include "dulcepan.h" + +static void vlog_prefixed(const char *prefix, const char *fmt, va_list args) { + fprintf(stderr, "[%s] ", prefix); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +void dp_log_error(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vlog_prefixed("error", fmt, args); + va_end(args); +} + +void dp_log_fatal(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vlog_prefixed("FATAL", fmt, args); + va_end(args); + exit(1); +} + +void *dp_zalloc(size_t size) { + void *ptr = calloc(1, size); + if (ptr == NULL) { + dp_log_fatal("Failed to allocate %zu bytes", size); + } + return ptr; +} + +const char *dp_ext_from_path(const char *path) { + size_t len = strlen(path); + for (size_t i = len; i-- > 0;) { + if (path[i] == '.') { + return path + i + 1; + } else if (path[i] == '/') { + break; + } + } + return path + len; +} + +enum dp_file_format dp_ext_to_format(const char *ext) { + if (strcasecmp(ext, "ppm") == 0) { + return DP_FILE_PPM; + } else if (strcasecmp(ext, "png") == 0) { + return DP_FILE_PNG; + } + return DP_FILE_UNKNOWN; +}