pax_global_header00006660000000000000000000000064134747177270014535gustar00rootroot0000000000000052 comment=3cd56b4d10d66767618cd5117eb608bdc8dc72af slurp-1.2.0/000077500000000000000000000000001347471772700127025ustar00rootroot00000000000000slurp-1.2.0/.build.yml000066400000000000000000000003461347471772700146050ustar00rootroot00000000000000image: archlinux packages: - meson - wayland - wayland-protocols - cairo sources: - https://github.com/emerison/slurp tasks: - setup: | cd slurp meson build - build: | cd slurp ninja -C build slurp-1.2.0/.editorconfig000066400000000000000000000002011347471772700153500ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf indent_style = tab insert_final_newline = true trim_trailing_whitespace = true slurp-1.2.0/.gitignore000066400000000000000000000006661347471772700147020ustar00rootroot00000000000000# Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf /build slurp-1.2.0/LICENSE000066400000000000000000000020511347471772700137050ustar00rootroot00000000000000MIT License Copyright (c) 2018 emersion Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. slurp-1.2.0/README.md000066400000000000000000000020401347471772700141550ustar00rootroot00000000000000# slurp Select a region in a Wayland compositor and print it to the standard output. Works well with [grim](https://github.com/emersion/grim). It currently works on Sway 1.0. ## Building Install dependencies: * meson * wayland * cairo * scdoc (optional: man pages) Then run: ```shell meson build ninja -C build build/slurp ``` ## Example usage Select a region and print it to stdout: ```sh slurp ``` Select a single point instead of a region: ```sh slurp -p ``` Select an output under Sway, using `swaymsg` and `jq`: ```sh swaymsg -t get_outputs | jq -r '.[] | select(.active) | .rect | "\(.x),\(.y) \(.width)x\(.height)"' | slurp ``` Select a window under Sway, using `swaymsg` and `jq`: ```sh swaymsg -t get_tree | jq -r '.. | (.nodes? // empty)[] | select(.pid and .visible) | .rect | "\(.x),\(.y) \(.width)x\(.height)"' | slurp ``` ## Contributing Either [send GitHub pull requests][1] or [send patches on the mailing list][2]. ## License MIT [1]: https://github.com/emersion/slurp [2]: https://lists.sr.ht/%7Eemersion/public-inbox slurp-1.2.0/include/000077500000000000000000000000001347471772700143255ustar00rootroot00000000000000slurp-1.2.0/include/pool-buffer.h000066400000000000000000000007441347471772700167230ustar00rootroot00000000000000#ifndef _POOL_BUFFER_H #define _POOL_BUFFER_H #include #include #include #include struct pool_buffer { struct wl_buffer *buffer; cairo_surface_t *surface; cairo_t *cairo; uint32_t width, height; void *data; size_t size; bool busy; }; struct pool_buffer *get_next_buffer(struct wl_shm *shm, struct pool_buffer pool[static 2], uint32_t width, uint32_t height); void finish_buffer(struct pool_buffer *buffer); #endif slurp-1.2.0/include/render.h000066400000000000000000000001551347471772700157560ustar00rootroot00000000000000#ifndef _RENDER_H #define _RENDER_H struct slurp_output; void render(struct slurp_output *output); #endif slurp-1.2.0/include/slurp.h000066400000000000000000000036611347471772700156510ustar00rootroot00000000000000#ifndef _SLURP_H #define _SLURP_H #include #include #include #include "pool-buffer.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" struct slurp_box { int32_t x, y; int32_t width, height; struct wl_list link; }; struct slurp_state { bool running; struct wl_display *display; struct wl_registry *registry; struct wl_shm *shm; struct wl_compositor *compositor; struct zwlr_layer_shell_v1 *layer_shell; struct zxdg_output_manager_v1 *xdg_output_manager; struct wl_list outputs; // slurp_output::link struct wl_list seats; // slurp_seat::link struct { uint32_t background; uint32_t border; uint32_t selection; } colors; uint32_t border_weight; bool display_dimensions; bool single_point; struct wl_list boxes; // slurp_box::link struct slurp_box result; }; struct slurp_output { struct wl_output *wl_output; struct slurp_state *state; struct wl_list link; // slurp_state::outputs struct slurp_box geometry; struct slurp_box logical_geometry; int32_t scale; struct wl_surface *surface; struct zwlr_layer_surface_v1 *layer_surface; struct zxdg_output_v1 *xdg_output; struct wl_callback *frame_callback; bool configured; bool dirty; int32_t width, height; struct pool_buffer buffers[2]; struct pool_buffer *current_buffer; struct wl_cursor_theme *cursor_theme; struct wl_cursor_image *cursor_image; }; struct slurp_seat { struct wl_surface *cursor_surface; struct slurp_state *state; struct wl_seat *wl_seat; struct wl_list link; // slurp_state::seats // keyboard: struct wl_keyboard *wl_keyboard; // pointer: struct wl_pointer *wl_pointer; enum wl_pointer_button_state button_state; struct slurp_output *current_output; int32_t x, y; int32_t pressed_x, pressed_y; struct slurp_box selection; bool has_selection; }; bool box_intersect(const struct slurp_box *a, const struct slurp_box *b); #endif slurp-1.2.0/main.c000066400000000000000000000504351347471772700140010ustar00rootroot00000000000000#define _POSIX_C_SOURCE 2 #include #include #include #include #include #include #include #include "slurp.h" #include "render.h" static void noop() { // This space intentionally left blank } static void set_output_dirty(struct slurp_output *output); bool box_intersect(const struct slurp_box *a, const struct slurp_box *b) { return a->x < b->x + b->width && a->x + a->width > b->x && a->y < b->y + b->height && a->height + a->y > b->y; } static struct slurp_output *output_from_surface(struct slurp_state *state, struct wl_surface *surface); static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct slurp_seat *seat = data; struct slurp_output *output = output_from_surface(seat->state, surface); if (output == NULL) { return; } // TODO: handle multiple overlapping outputs seat->current_output = output; seat->x = wl_fixed_to_int(surface_x) + seat->current_output->logical_geometry.x; seat->y = wl_fixed_to_int(surface_y) + seat->current_output->logical_geometry.y; wl_surface_set_buffer_scale(seat->cursor_surface, output->scale); wl_surface_attach(seat->cursor_surface, wl_cursor_image_get_buffer(output->cursor_image), 0, 0); wl_pointer_set_cursor(wl_pointer, serial, seat->cursor_surface, output->cursor_image->hotspot_x / output->scale, output->cursor_image->hotspot_y / output->scale); wl_surface_commit(seat->cursor_surface); } static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { struct slurp_seat *seat = data; // TODO: handle multiple overlapping outputs seat->current_output = NULL; } static void seat_set_outputs_dirty(struct slurp_seat *seat) { struct slurp_output *output; wl_list_for_each(output, &seat->state->outputs, link) { if (box_intersect(&output->logical_geometry, &seat->selection)) { set_output_dirty(output); } } } static bool in_box(const struct slurp_box *box, int32_t x, int32_t y) { return box->x <= x && box->x + box->width >= x && box->y <= y && box->y + box->height >= y; } static int32_t box_size(const struct slurp_box *box) { return box->width * box->height; } static int min(int a, int b) { return (a < b) ? a : b; } static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct slurp_seat *seat = data; // the places the cursor moved away from are also dirty if (seat->has_selection) { seat_set_outputs_dirty(seat); } seat->x = wl_fixed_to_int(surface_x) + seat->current_output->logical_geometry.x; seat->y = wl_fixed_to_int(surface_y) + seat->current_output->logical_geometry.y; switch (seat->button_state) { case WL_POINTER_BUTTON_STATE_RELEASED: seat->has_selection = false; // find smallest box intersecting the cursor struct slurp_box *box; wl_list_for_each(box, &seat->state->boxes, link) { if (in_box(box, seat->x, seat->y)) { if (seat->has_selection && box_size(&seat->selection) < box_size(box)) { continue; } seat->selection = *box; seat->has_selection = true; } } break; case WL_POINTER_BUTTON_STATE_PRESSED: seat->has_selection = true; seat->selection.x = min(seat->pressed_x, seat->x); seat->selection.y = min(seat->pressed_y, seat->y); seat->selection.width = abs(seat->x - seat->pressed_x); seat->selection.height = abs(seat->y - seat->pressed_y); break; } if (seat->has_selection) { seat_set_outputs_dirty(seat); } } static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state) { struct slurp_seat *seat = data; struct slurp_state *state = seat->state; seat->button_state = button_state; switch (button_state) { case WL_POINTER_BUTTON_STATE_PRESSED: if (state->single_point) { state->result.x = seat->x; state->result.y = seat->y; state->result.width = state->result.height = 1; state->running = false; } else { seat->pressed_x = seat->x; seat->pressed_y = seat->y; } break; case WL_POINTER_BUTTON_STATE_RELEASED: if (state->single_point) { break; } if (seat->has_selection) { state->result = seat->selection; } state->running = false; break; } } 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 = noop, }; static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t key_state) { struct slurp_seat *seat = data; struct slurp_state *state = seat->state; if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED) { if (key == KEY_ESC) { seat->has_selection = false; state->running = false; } } } static const struct wl_keyboard_listener keyboard_listener = { .keymap = noop, .enter = noop, .leave = noop, .key = keyboard_handle_key, .modifiers = noop, }; static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) { struct slurp_seat *seat = data; if (capabilities & WL_SEAT_CAPABILITY_POINTER) { seat->wl_pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { seat->wl_keyboard = wl_seat_get_keyboard(wl_seat); wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, seat); } } static const struct wl_seat_listener seat_listener = { .capabilities = seat_handle_capabilities, }; static void create_seat(struct slurp_state *state, struct wl_seat *wl_seat) { struct slurp_seat *seat = calloc(1, sizeof(struct slurp_seat)); if (seat == NULL) { fprintf(stderr, "allocation failed\n"); return; } seat->state = state; seat->wl_seat = wl_seat; wl_list_insert(&state->seats, &seat->link); wl_seat_add_listener(wl_seat, &seat_listener, seat); } static void destroy_seat(struct slurp_seat *seat) { wl_list_remove(&seat->link); wl_surface_destroy(seat->cursor_surface); if (seat->wl_pointer) { wl_pointer_destroy(seat->wl_pointer); } if (seat->wl_keyboard) { wl_keyboard_destroy(seat->wl_keyboard); } wl_seat_destroy(seat->wl_seat); free(seat); } static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { struct slurp_output *output = data; output->geometry.x = x; output->geometry.y = y; } 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 slurp_output *output = data; if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) { return; } output->geometry.width = width; output->geometry.height = height; } static void output_handle_scale(void *data, struct wl_output *wl_output, int32_t scale) { struct slurp_output *output = data; output->scale = scale; } static const struct wl_output_listener output_listener = { .geometry = output_handle_geometry, .mode = output_handle_mode, .done = noop, .scale = output_handle_scale, }; static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { struct slurp_output *output = data; output->logical_geometry.x = x; output->logical_geometry.y = y; } static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { struct slurp_output *output = data; output->logical_geometry.width = width; output->logical_geometry.height = height; } static const struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = xdg_output_handle_logical_position, .logical_size = xdg_output_handle_logical_size, .done = noop, .name = noop, .description = noop, }; static void create_output(struct slurp_state *state, struct wl_output *wl_output) { struct slurp_output *output = calloc(1, sizeof(struct slurp_output)); if (output == NULL) { fprintf(stderr, "allocation failed\n"); return; } output->wl_output = wl_output; output->state = state; output->scale = 1; wl_list_insert(&state->outputs, &output->link); wl_output_add_listener(wl_output, &output_listener, output); } static void destroy_output(struct slurp_output *output) { if (output == NULL) { return; } wl_list_remove(&output->link); finish_buffer(&output->buffers[0]); finish_buffer(&output->buffers[1]); wl_cursor_theme_destroy(output->cursor_theme); zwlr_layer_surface_v1_destroy(output->layer_surface); if (output->xdg_output) { zxdg_output_v1_destroy(output->xdg_output); } wl_surface_destroy(output->surface); if (output->frame_callback) { wl_callback_destroy(output->frame_callback); } wl_output_destroy(output->wl_output); free(output); } static const struct wl_callback_listener output_frame_listener; static void send_frame(struct slurp_output *output) { struct slurp_state *state = output->state; if (!output->configured) { return; } int32_t buffer_width = output->width * output->scale; int32_t buffer_height = output->height * output->scale; output->current_buffer = get_next_buffer(state->shm, output->buffers, buffer_width, buffer_height); if (output->current_buffer == NULL) { return; } render(output); // Schedule a frame in case the output becomes dirty again output->frame_callback = wl_surface_frame(output->surface); wl_callback_add_listener(output->frame_callback, &output_frame_listener, output); wl_surface_attach(output->surface, output->current_buffer->buffer, 0, 0); wl_surface_damage(output->surface, 0, 0, output->width, output->height); wl_surface_set_buffer_scale(output->surface, output->scale); wl_surface_commit(output->surface); output->dirty = false; } static void output_frame_handle_done(void *data, struct wl_callback *callback, uint32_t time) { struct slurp_output *output = data; wl_callback_destroy(callback); output->frame_callback = NULL; if (output->dirty) { send_frame(output); } } static const struct wl_callback_listener output_frame_listener = { .done = output_frame_handle_done, }; static void set_output_dirty(struct slurp_output *output) { output->dirty = true; if (output->frame_callback) { return; } output->frame_callback = wl_surface_frame(output->surface); wl_callback_add_listener(output->frame_callback, &output_frame_listener, output); wl_surface_commit(output->surface); } static struct slurp_output *output_from_surface(struct slurp_state *state, struct wl_surface *surface) { struct slurp_output *output; wl_list_for_each(output, &state->outputs, link) { if (output->surface == surface) { return output; } } return NULL; } static void layer_surface_handle_configure(void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) { struct slurp_output *output = data; output->configured = true; output->width = width; output->height = height; zwlr_layer_surface_v1_ack_configure(surface, serial); send_frame(output); } static void layer_surface_handle_closed(void *data, struct zwlr_layer_surface_v1 *surface) { struct slurp_output *output = data; destroy_output(output); } static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { .configure = layer_surface_handle_configure, .closed = layer_surface_handle_closed, }; static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct slurp_state *state = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { state->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); } 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, wl_seat_interface.name) == 0) { struct wl_seat *wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); create_seat(state, wl_seat); } else if (strcmp(interface, wl_output_interface.name) == 0) { struct wl_output *wl_output = wl_registry_bind(registry, name, &wl_output_interface, 3); create_output(state, wl_output); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { state->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 1); } } static const struct wl_registry_listener registry_listener = { .global = handle_global, .global_remove = noop, }; static const char usage[] = "Usage: slurp [options...]\n" "\n" " -h Show help message and quit.\n" " -d Display dimensions of selection.\n" " -b #rrggbbaa Set background color.\n" " -c #rrggbbaa Set border color.\n" " -s #rrggbbaa Set selection color.\n" " -w n Set border weight.\n" " -f s Set output format.\n" " -p Select a single point.\n"; uint32_t parse_color(const char *color) { if (color[0] == '#') { ++color; } int len = strlen(color); if (len != 6 && len != 8) { fprintf(stderr, "Invalid color %s, " "defaulting to color 0xFFFFFFFF\n", color); return 0xFFFFFFFF; } uint32_t res = (uint32_t)strtoul(color, NULL, 16); if (strlen(color) == 6) { res = (res << 8) | 0xFF; } return res; } static void print_formatted_result(const struct slurp_box *result, const char *format) { for (size_t i = 0; format[i] != '\0'; i++) { char c = format[i]; if (c == '%') { char next = format[i + 1]; i++; // Skip the next character (x, y, w or h) switch (next) { case 'x': printf("%u", result->x); continue; case 'y': printf("%u", result->y); continue; case 'w': printf("%u", result->width); continue; case 'h': printf("%u", result->height); continue; default: // If no case was executed, revert i back - we don't need to // skip the next character. i--; } } printf("%c", c); } printf("\n"); } static void add_choice_box(struct slurp_state *state, const struct slurp_box *box) { struct slurp_box *b = calloc(1, sizeof(struct slurp_box)); if (b == NULL) { fprintf(stderr, "allocation failed\n"); return; } *b = *box; wl_list_insert(state->boxes.prev, &b->link); } int main(int argc, char *argv[]) { struct slurp_state state = { .colors = { .background = 0xFFFFFF40, .border = 0x000000FF, .selection = 0x00000000, }, .border_weight = 2, .display_dimensions = false, }; int opt; char *format = "%x,%y %wx%h"; while ((opt = getopt(argc, argv, "hdb:c:s:w:pf:")) != -1) { switch (opt) { case 'h': printf("%s", usage); return EXIT_SUCCESS; case 'd': state.display_dimensions = true; break; case 'b': state.colors.background = parse_color(optarg); break; case 'c': state.colors.border = parse_color(optarg); break; case 's': state.colors.selection = parse_color(optarg); break; case 'f': format = optarg; break; case 'w': { errno = 0; char *endptr; state.border_weight = strtol(optarg, &endptr, 10); if (*endptr || errno) { fprintf(stderr, "Error: expected numeric argument for -w\n"); exit(EXIT_FAILURE); } break; case 'p': state.single_point = true; break; } default: printf("%s", usage); return EXIT_FAILURE; } } wl_list_init(&state.boxes); if (!isatty(STDIN_FILENO) && !state.single_point) { struct slurp_box in_box = {0}; while (fscanf(stdin, "%d,%d %dx%d\n", &in_box.x, &in_box.y, &in_box.width, &in_box.height) == 4) { add_choice_box(&state, &in_box); } } wl_list_init(&state.outputs); wl_list_init(&state.seats); state.display = wl_display_connect(NULL); if (state.display == NULL) { fprintf(stderr, "failed to create display\n"); return EXIT_FAILURE; } state.registry = wl_display_get_registry(state.display); wl_registry_add_listener(state.registry, ®istry_listener, &state); wl_display_roundtrip(state.display); if (state.compositor == NULL) { fprintf(stderr, "compositor doesn't support wl_compositor\n"); return EXIT_FAILURE; } if (state.shm == NULL) { fprintf(stderr, "compositor doesn't support wl_shm\n"); return EXIT_FAILURE; } if (state.layer_shell == NULL) { fprintf(stderr, "compositor doesn't support zwlr_layer_shell_v1\n"); return EXIT_FAILURE; } if (state.xdg_output_manager == NULL) { fprintf(stderr, "compositor doesn't support xdg-output. " "Guessing geometry from physical output size.\n"); } if (wl_list_empty(&state.outputs)) { fprintf(stderr, "no wl_output\n"); return EXIT_FAILURE; } const char *cursor_theme = getenv("XCURSOR_THEME"); const char *cursor_size_str = getenv("XCURSOR_SIZE"); int cursor_size = 24; if (cursor_size_str != NULL) { char *end; errno = 0; cursor_size = strtol(cursor_size_str, &end, 10); if (errno != 0 || cursor_size_str[0] == '\0' || end[0] != '\0') { fprintf(stderr, "invalid XCURSOR_SIZE value\n"); return EXIT_FAILURE; } } struct slurp_output *output; wl_list_for_each(output, &state.outputs, link) { output->surface = wl_compositor_create_surface(state.compositor); // TODO: wl_surface_add_listener(output->surface, &surface_listener, output); output->layer_surface = zwlr_layer_shell_v1_get_layer_surface( state.layer_shell, output->surface, output->wl_output, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "selection"); zwlr_layer_surface_v1_add_listener(output->layer_surface, &layer_surface_listener, output); if (state.xdg_output_manager) { output->xdg_output = zxdg_output_manager_v1_get_xdg_output( state.xdg_output_manager, output->wl_output); zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); } else { // guess output->logical_geometry = output->geometry; output->logical_geometry.width /= output->scale; output->logical_geometry.height /= output->scale; } zwlr_layer_surface_v1_set_anchor(output->layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM); zwlr_layer_surface_v1_set_keyboard_interactivity(output->layer_surface, true); zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1); wl_surface_commit(output->surface); output->cursor_theme = wl_cursor_theme_load(cursor_theme, cursor_size * output->scale, state.shm); if (output->cursor_theme == NULL) { fprintf(stderr, "failed to load cursor theme\n"); return EXIT_FAILURE; } struct wl_cursor *cursor = wl_cursor_theme_get_cursor(output->cursor_theme, "crosshair"); if (cursor == NULL) { // Fallback cursor = wl_cursor_theme_get_cursor(output->cursor_theme, "left_ptr"); } if (cursor == NULL) { fprintf(stderr, "failed to load cursor\n"); return EXIT_FAILURE; } output->cursor_image = cursor->images[0]; } // second roundtrip for xdg-output wl_display_roundtrip(state.display); struct slurp_seat *seat; wl_list_for_each(seat, &state.seats, link) { seat->cursor_surface = wl_compositor_create_surface(state.compositor); } state.running = true; while (state.running && wl_display_dispatch(state.display) != -1) { // This space intentionally left blank } struct slurp_output *output_tmp; wl_list_for_each_safe(output, output_tmp, &state.outputs, link) { destroy_output(output); } struct slurp_seat *seat_tmp; wl_list_for_each_safe(seat, seat_tmp, &state.seats, link) { destroy_seat(seat); } // Make sure the compositor has unmapped our surfaces by the time we exit wl_display_roundtrip(state.display); zwlr_layer_shell_v1_destroy(state.layer_shell); if (state.xdg_output_manager != NULL) { zxdg_output_manager_v1_destroy(state.xdg_output_manager); } wl_compositor_destroy(state.compositor); wl_shm_destroy(state.shm); wl_registry_destroy(state.registry); wl_display_disconnect(state.display); struct slurp_box *box, *box_tmp; wl_list_for_each_safe(box, box_tmp, &state.boxes, link) { wl_list_remove(&box->link); free(box); } if (state.result.width == 0 && state.result.height == 0) { fprintf(stderr, "selection cancelled\n"); return EXIT_FAILURE; } print_formatted_result(&state.result, format); return EXIT_SUCCESS; } slurp-1.2.0/meson.build000066400000000000000000000024451347471772700150510ustar00rootroot00000000000000project( 'slurp', 'c', version: '1.1.0', license: 'MIT', meson_version: '>=0.48.0', default_options: [ 'c_std=c11', 'warning_level=2', 'werror=true', ], ) add_project_arguments('-Wno-unused-parameter', language: 'c') slurp_inc = include_directories('include') cc = meson.get_compiler('c') cairo = dependency('cairo') realtime = cc.find_library('rt') wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols', version: '>=1.14') subdir('protocol') executable( 'slurp', files([ 'main.c', 'pool-buffer.c', 'render.c', ]), dependencies: [ cairo, client_protos, realtime, wayland_client, wayland_cursor, ], include_directories: [slurp_inc], install: true, ) scdoc = find_program('scdoc', required: get_option('man-pages')) if scdoc.found() sh = find_program('sh') man_pages = ['slurp.1.scd'] mandir = get_option('mandir') foreach src : man_pages topic = src.split('.')[0] section = src.split('.')[1] output = '@0@.@1@'.format(topic, section) custom_target( output, input: src, output: output, command: [ sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc.path(), output) ], install: true, install_dir: '@0@/man@1@'.format(mandir, section) ) endforeach endif slurp-1.2.0/meson_options.txt000066400000000000000000000001431347471772700163350ustar00rootroot00000000000000option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') slurp-1.2.0/pool-buffer.c000066400000000000000000000061151347471772700152710ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include #include #include #include "pool-buffer.h" static void randname(char *buf) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); long r = ts.tv_nsec; for (int i = 0; i < 6; ++i) { buf[i] = 'A'+(r&15)+(r&16)*2; r >>= 5; } } static int anonymous_shm_open(void) { char name[] = "/grim-XXXXXX"; int retries = 100; do { randname(name + strlen(name) - 6); --retries; // shm_open guarantees that O_CLOEXEC is set int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) { shm_unlink(name); return fd; } } while (retries > 0 && errno == EEXIST); return -1; } static int create_shm_file(off_t size) { int fd = anonymous_shm_open(); if (fd < 0) { return fd; } if (ftruncate(fd, size) < 0) { close(fd); return -1; } return fd; } static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { struct pool_buffer *buffer = data; buffer->busy = false; } static const struct wl_buffer_listener buffer_listener = { .release = buffer_handle_release, }; static struct pool_buffer *create_buffer(struct wl_shm *shm, struct pool_buffer *buf, int32_t width, int32_t height) { const enum wl_shm_format wl_fmt = WL_SHM_FORMAT_ARGB8888; const cairo_format_t cairo_fmt = CAIRO_FORMAT_ARGB32; uint32_t stride = cairo_format_stride_for_width(cairo_fmt, width); size_t size = stride * height; void *data = NULL; if (size > 0) { int fd = create_shm_file(size); if (fd == -1) { return NULL; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { close(fd); return NULL; } struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); buf->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, wl_fmt); wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); wl_shm_pool_destroy(pool); close(fd); } buf->data = data; buf->size = size; buf->width = width; buf->height = height; buf->surface = cairo_image_surface_create_for_data(data, cairo_fmt, width, height, stride); buf->cairo = cairo_create(buf->surface); return buf; } void finish_buffer(struct pool_buffer *buffer) { if (buffer->buffer) { wl_buffer_destroy(buffer->buffer); } if (buffer->cairo) { cairo_destroy(buffer->cairo); } if (buffer->surface) { cairo_surface_destroy(buffer->surface); } if (buffer->data) { munmap(buffer->data, buffer->size); } memset(buffer, 0, sizeof(struct pool_buffer)); } struct pool_buffer *get_next_buffer(struct wl_shm *shm, struct pool_buffer pool[static 2], uint32_t width, uint32_t height) { struct pool_buffer *buffer = NULL; for (size_t i = 0; i < 2; ++i) { if (pool[i].busy) { continue; } buffer = &pool[i]; } if (!buffer) { return NULL; } if (buffer->width != width || buffer->height != height) { finish_buffer(buffer); } if (!buffer->buffer) { if (!create_buffer(shm, buffer, width, height)) { return NULL; } } return buffer; } slurp-1.2.0/protocol/000077500000000000000000000000001347471772700145435ustar00rootroot00000000000000slurp-1.2.0/protocol/meson.build000066400000000000000000000023701347471772700167070ustar00rootroot00000000000000wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') # should check wayland_scanner's version, but it is hard to get if wayland_client.version().version_compare('>=1.14.91') code_type = 'private-code' else code_type = 'code' endif wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', arguments: [code_type, '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) client_protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'], ] client_protos_src = [] client_protos_headers = [] foreach p : client_protocols xml = join_paths(p) client_protos_src += wayland_scanner_code.process(xml) client_protos_headers += wayland_scanner_client.process(xml) endforeach lib_client_protos = static_library( 'client_protos', client_protos_src + client_protos_headers, dependencies: [wayland_client] ) # for the include directory client_protos = declare_dependency( link_with: lib_client_protos, sources: client_protos_headers, ) slurp-1.2.0/protocol/wlr-layer-shell-unstable-v1.xml000066400000000000000000000320701347471772700224510ustar00rootroot00000000000000 Copyright © 2017 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. Layer surface state (size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. If two orthoginal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. Anchor is double-buffered, see wl_surface.commit. Requests that the compositor avoids occluding an area of the surface with other surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. A positive value is only meaningful if the surface is anchored to an edge, rather than a corner. The zone is the number of surface-local coordinates from the edge that are considered exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding surfaces with a positive excluzive zone. If set to -1, the surface indicates that it would not like to be moved to accomodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. For example, a panel might set its exclusive zone to 10, so that maximized shell surfaces are not shown on top of it. A notification might set its exclusive zone to 0, so that it is moved to avoid occluding the panel, but shell surfaces are shown underneath it. A wallpaper or lock screen might set their exclusive zone to -1, so that they stretch below or over the panel. The default value is 0. Exclusive zone is double-buffered, see wl_surface.commit. Requests that the surface be placed some distance away from the anchor point on the output, in surface-local coordinates. Setting this value for edges you are not anchored to has no effect. The exclusive zone includes the margin. Margin is double-buffered, see wl_surface.commit. Set to 1 to request that the seat send keyboard events to this layer surface. For layers below the shell surface layer, the seat will use normal focus semantics. For layers above the shell surface layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to true. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. Events is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. This request destroys the layer surface. The configure event asks the client to resize its surface. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. slurp-1.2.0/render.c000066400000000000000000000036051347471772700143310ustar00rootroot00000000000000#include #include #include #include "pool-buffer.h" #include "render.h" #include "slurp.h" static void set_source_u32(cairo_t *cairo, uint32_t color) { cairo_set_source_rgba(cairo, (color >> (3*8) & 0xFF) / 255.0, (color >> (2*8) & 0xFF) / 255.0, (color >> (1*8) & 0xFF) / 255.0, (color >> (0*8) & 0xFF) / 255.0); } void render(struct slurp_output *output) { struct slurp_state *state = output->state; struct pool_buffer *buffer = output->current_buffer; cairo_t *cairo = buffer->cairo; int32_t scale = output->scale; // Clear cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); set_source_u32(cairo, state->colors.background); cairo_paint(cairo); struct slurp_seat *seat; wl_list_for_each(seat, &state->seats, link) { if (!seat->wl_pointer) continue; if (!seat->has_selection) { continue; } if(!box_intersect(&output->logical_geometry, &seat->selection)) { continue; } struct slurp_box b = seat->selection; b.x -= output->logical_geometry.x; b.y -= output->logical_geometry.y; // Draw border set_source_u32(cairo, state->colors.selection); cairo_rectangle(cairo, b.x * scale, b.y * scale, b.width * scale, b.height * scale); cairo_fill(cairo); set_source_u32(cairo, state->colors.border); cairo_set_line_width(cairo, state->border_weight * scale); cairo_rectangle(cairo, b.x * scale, b.y * scale, b.width * scale, b.height * scale); cairo_stroke(cairo); if (state->display_dimensions) { cairo_select_font_face(cairo, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cairo, 14 * scale); // buffer of 12 can hold selections up to 99999x99999 char dimensions[12]; snprintf(dimensions, sizeof(dimensions), "%ix%i", b.width, b.height); cairo_move_to(cairo, (b.x + b.width + 10) * scale, (b.y + b.height + 20) * scale); cairo_show_text(cairo, dimensions); } } } slurp-1.2.0/slurp.1.scd000066400000000000000000000027021347471772700147020ustar00rootroot00000000000000slurp(1) # NAME slurp - select a region in a Wayland compositor # DESCRIPTION *slurp* [options...] # SYNOPSIS slurp is a command-line utility to select a region from Wayland compositors which support the layer-shell protocol. It lets the user hold the pointer to select, or click to cancel the selection. If the standard input is not a TTY, slurp will read a list of predefined rectangles for quick selection. Each line must be in the form ", x". # OPTIONS *-h* Show help message and quit. *-d* Display dimensions of selection. *-b* _color_ Set background color. See *COLORS* for more detail. *-c* _color_ Set border color. See *COLORS* for more detail. *-s* _color_ Set selection color. See *COLORS* for more detail. *-w* _weight_ Set border weight. *-f* _format_ Set format. See *FORMAT* for more detail. *-p* Select a single pixel instead of a rectangle. This mode ignores any predefined rectangles read from the standard input. # COLORS Colors may be specified in #RRGGBB or #RRGGBBAA format. The # is optional. # FORMAT Interpreted sequences are: %x The x-coordinate of the selection %y The y-coordinate of the selection %w The width of the selection %h The height of the selection The default format is "%x,%y %wx%h". # AUTHORS Maintained by Simon Ser , who is assisted by other open-source contributors. For more information about slurp development, see https://github.com/emersion/slurp.