Hello community, here is the log from the commit of package slurp for openSUSE:Factory checked in at 2020-10-18 16:31:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/slurp (Old) and /work/SRC/openSUSE:Factory/.slurp.new.3486 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "slurp" Sun Oct 18 16:31:53 2020 rev:5 rq:842187 version:1.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/slurp/slurp.changes 2019-06-04 12:14:29.743779422 +0200 +++ /work/SRC/openSUSE:Factory/.slurp.new.3486/slurp.changes 2020-10-18 16:33:31.812814190 +0200 @@ -1,0 +2,11 @@ +Fri Oct 16 19:26:29 UTC 2020 - Michael Vetter <mvet...@suse.com> + +- Update to 1.3.0: + * Touch input is now supported + * The current selection can now be moved by holding the Space key + * Choice boxes specified on stdin can now be labelled + * A new format option prints the name of the output + * The new -o option allows to add all outputs as choice boxes + * The new -r option forces the user to select one of the choice boxes + +------------------------------------------------------------------- Old: ---- v1.2.0.tar.gz New: ---- v1.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ slurp.spec ++++++ --- /var/tmp/diff_new_pack.QskGsR/_old 2020-10-18 16:33:36.980816490 +0200 +++ /var/tmp/diff_new_pack.QskGsR/_new 2020-10-18 16:33:36.984816492 +0200 @@ -1,7 +1,7 @@ # # spec file for package slurp # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2020 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -12,12 +12,12 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ # Name: slurp -Version: 1.2.0 +Version: 1.3.0 Release: 0 Summary: Wayland region selector License: MIT @@ -30,6 +30,7 @@ BuildRequires: pkgconfig(cairo) BuildRequires: pkgconfig(wayland-client) BuildRequires: pkgconfig(wayland-protocols) >= 1.14 +BuildRequires: pkgconfig(xkbcommon) %description Tool to select a region in a Wayland compositor. ++++++ v1.2.0.tar.gz -> v1.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slurp-1.2.0/.build.yml new/slurp-1.3.0/.build.yml --- old/slurp-1.2.0/.build.yml 2019-06-02 12:07:19.000000000 +0200 +++ new/slurp-1.3.0/.build.yml 2020-10-16 16:55:45.000000000 +0200 @@ -4,6 +4,7 @@ - wayland - wayland-protocols - cairo + - libxkbcommon sources: - https://github.com/emerison/slurp tasks: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slurp-1.2.0/README.md new/slurp-1.3.0/README.md --- old/slurp-1.2.0/README.md 2019-06-02 12:07:19.000000000 +0200 +++ new/slurp-1.3.0/README.md 2020-10-16 16:55:45.000000000 +0200 @@ -5,17 +5,20 @@ It currently works on Sway 1.0. +Join the IRC channel: ##emersion on Freenode. + ## Building Install dependencies: * meson * wayland * cairo +* libxkbcommon * scdoc (optional: man pages) Then run: -```shell +```sh meson build ninja -C build build/slurp @@ -44,7 +47,7 @@ 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 +swaymsg -t get_tree | jq -r '.. | select(.pid? and .visible?) | .rect | "\(.x),\(.y) \(.width)x\(.height)"' | slurp ``` ## Contributing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slurp-1.2.0/include/slurp.h new/slurp-1.3.0/include/slurp.h --- old/slurp-1.2.0/include/slurp.h 2019-06-02 12:07:19.000000000 +0200 +++ new/slurp-1.3.0/include/slurp.h 2020-10-16 16:55:45.000000000 +0200 @@ -9,14 +9,26 @@ #include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" +#define TOUCH_ID_EMPTY -1 + struct slurp_box { int32_t x, y; int32_t width, height; + char *label; struct wl_list link; }; +struct slurp_selection { + struct slurp_output *current_output; + int32_t x, y; + int32_t anchor_x, anchor_y; + struct slurp_box selection; + bool has_selection; +}; + struct slurp_state { bool running; + bool edit_anchor; struct wl_display *display; struct wl_registry *registry; @@ -27,15 +39,19 @@ struct wl_list outputs; // slurp_output::link struct wl_list seats; // slurp_seat::link + struct xkb_context *xkb_context; + struct { uint32_t background; uint32_t border; uint32_t selection; + uint32_t choice; } colors; uint32_t border_weight; bool display_dimensions; bool single_point; + bool restrict_selection; struct wl_list boxes; // slurp_box::link struct slurp_box result; @@ -75,14 +91,22 @@ // keyboard: struct wl_keyboard *wl_keyboard; + //selection (pointer/touch): + + struct slurp_selection pointer_selection; + struct slurp_selection touch_selection; + // 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; + + // keymap: + struct xkb_keymap *xkb_keymap; + struct xkb_state *xkb_state; + + // touch: + struct wl_touch *wl_touch; + int32_t touch_id; }; bool box_intersect(const struct slurp_box *a, const struct slurp_box *b); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slurp-1.2.0/main.c new/slurp-1.3.0/main.c --- old/slurp-1.2.0/main.c 2019-06-02 12:07:19.000000000 +0200 +++ new/slurp-1.3.0/main.c 2020-10-16 16:55:45.000000000 +0200 @@ -1,20 +1,26 @@ -#define _POSIX_C_SOURCE 2 +#define _POSIX_C_SOURCE 200809L + #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/mman.h> #include <unistd.h> #include <wayland-cursor.h> +#include <xkbcommon/xkbcommon.h> #include <linux/input-event-codes.h> #include "slurp.h" #include "render.h" +#define BG_COLOR 0xFFFFFF40 +#define BORDER_COLOR 0x000000FF +#define SELECTION_COLOR 0x00000000 + 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) { @@ -24,9 +30,76 @@ a->height + a->y > b->y; } +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 int max(int a, int b) { + return (a > b) ? a : b; +} + static struct slurp_output *output_from_surface(struct slurp_state *state, struct wl_surface *surface); +static void move_seat(struct slurp_seat *seat, wl_fixed_t surface_x, + wl_fixed_t surface_y, + struct slurp_selection *current_selection) { + int x = wl_fixed_to_int(surface_x) + + current_selection->current_output->logical_geometry.x; + int y = wl_fixed_to_int(surface_y) + current_selection->current_output->logical_geometry.y; + + if (seat->state->edit_anchor) { + current_selection->anchor_x += x - current_selection->x; + current_selection->anchor_y += y - current_selection->y; + } + + current_selection->x = x; + current_selection->y = y; +} + +static void seat_update_selection(struct slurp_seat *seat) { + seat->pointer_selection.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->pointer_selection.x, + seat->pointer_selection.y)) { + if (seat->pointer_selection.has_selection && + box_size( + &seat->pointer_selection.selection) < + box_size(box)) { + continue; + } + seat->pointer_selection.selection = *box; + seat->pointer_selection.has_selection = true; + } + } +} + +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->pointer_selection.selection) || + box_intersect(&output->logical_geometry, + &seat->touch_selection.selection)) { + set_output_dirty(output); + } + } +} + 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) { @@ -36,10 +109,11 @@ return; } // TODO: handle multiple overlapping outputs - seat->current_output = output; + seat->pointer_selection.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; + move_seat(seat, surface_x, surface_y, &seat->pointer_selection); + seat_update_selection(seat); + seat_set_outputs_dirty(seat); wl_surface_set_buffer_scale(seat->cursor_surface, output->scale); wl_surface_attach(seat->cursor_surface, @@ -55,103 +129,94 @@ struct slurp_seat *seat = data; // TODO: handle multiple overlapping outputs - seat->current_output = NULL; + seat->pointer_selection.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 handle_active_selection_motion(struct slurp_seat *seat, struct slurp_selection *current_selection) { + int32_t anchor_x = max(0, current_selection->anchor_x); + int32_t anchor_y = max(0, current_selection->anchor_y); + current_selection->selection.x = min(anchor_x, current_selection->x); + current_selection->selection.y = min(anchor_y, current_selection->y); + // selection includes the seat and anchor positions + current_selection->selection.width = + abs(current_selection->x - anchor_x) + 1; + current_selection->selection.height = + abs(current_selection->y - anchor_y) + 1; } 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) { + if (seat->pointer_selection.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; + move_seat(seat, surface_x, surface_y, &seat->pointer_selection); 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; - } - } + seat_update_selection(seat); 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); + case WL_POINTER_BUTTON_STATE_PRESSED:; + handle_active_selection_motion(seat, &seat->pointer_selection); break; } - if (seat->has_selection) { + if (seat->pointer_selection.has_selection) { seat_set_outputs_dirty(seat); } } +static void handle_selection_start(struct slurp_seat *seat, + struct slurp_selection *current_selection) { + struct slurp_state *state = seat->state; + + if (state->single_point) { + state->result.x = current_selection->x; + state->result.y = current_selection->y; + state->result.width = state->result.height = 1; + state->running = false; + } else if (state->restrict_selection) { + if (current_selection->has_selection) { + state->result = current_selection->selection; + state->running = false; + } + } else { + current_selection->has_selection = true; + current_selection->anchor_x = current_selection->x; + current_selection->anchor_y = current_selection->y; + } +} + +static void handle_selection_end(struct slurp_seat *seat, + struct slurp_selection *current_selection) { + struct slurp_state *state = seat->state; + if (state->single_point || state->restrict_selection) { + return; + } + if (current_selection->has_selection) { + state->result = current_selection->selection; + } + state->running = false; +} + 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; + if (seat->touch_selection.has_selection) { + return; + } 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; - } + handle_selection_start(seat, &seat->pointer_selection); break; case WL_POINTER_BUTTON_STATE_RELEASED: - if (state->single_point) { - break; - } - if (seat->has_selection) { - state->result = seat->selection; - } - state->running = false; + handle_selection_end(seat, &seat->pointer_selection); break; } } @@ -164,26 +229,138 @@ .axis = noop, }; +static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + const uint32_t format, const int32_t fd, const uint32_t size) { + struct slurp_seat *seat = data; + 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:; + void *buffer; + if ((buffer = mmap(NULL, size - 1, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { + fprintf(stderr, "mmap failed\n"); + exit(EXIT_FAILURE); + } + seat->xkb_keymap = + xkb_keymap_new_from_buffer(seat->state->xkb_context, + buffer, size - 1, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(buffer, size - 1); + close(fd); + break; + } + seat->xkb_state = xkb_state_new(seat->xkb_keymap); +} + 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) { + const uint32_t serial, const uint32_t time, const uint32_t key, + const 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; + const xkb_keysym_t keysym = xkb_state_key_get_one_sym(seat->xkb_state, key + 8); + + switch (key_state) { + case WL_KEYBOARD_KEY_STATE_PRESSED: + switch (keysym) { + case XKB_KEY_Escape: + seat->pointer_selection.has_selection = false; + seat->touch_selection.has_selection = false; + state->edit_anchor = false; state->running = false; + break; + + case XKB_KEY_space: + if (!seat->pointer_selection.has_selection && + !seat->touch_selection.has_selection) { + break; + } + state->edit_anchor = true; + break; } + break; + + case WL_KEYBOARD_KEY_STATE_RELEASED: + if (keysym == XKB_KEY_space) { + state->edit_anchor = false; + } + break; } + +} + +static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, + const uint32_t serial, const uint32_t mods_depressed, + const uint32_t mods_latched, const uint32_t mods_locked, + const uint32_t group) { + struct slurp_seat *seat = data; + xkb_state_update_mask(seat->xkb_state, mods_depressed, mods_latched, + mods_locked, 0, 0, group); } static const struct wl_keyboard_listener keyboard_listener = { - .keymap = noop, + .keymap = keyboard_handle_keymap, .enter = noop, .leave = noop, .key = keyboard_handle_key, - .modifiers = noop, + .modifiers = keyboard_handle_modifiers, }; +static void touch_handle_down(void *data, struct wl_touch *touch, + uint32_t serial, uint32_t time, + struct wl_surface *surface, int32_t id, + wl_fixed_t x, wl_fixed_t y) { + struct slurp_seat *seat = data; + if (seat->pointer_selection.has_selection) { + return; + } + if (seat->touch_id == TOUCH_ID_EMPTY) { + seat->touch_id = id; + seat->touch_selection.current_output = + output_from_surface(seat->state, surface); + move_seat(seat, x, y, &seat->touch_selection); + handle_selection_start(seat, &seat->touch_selection); + } +} + +static void touch_clear_state(struct slurp_seat *seat) { + seat->touch_id = TOUCH_ID_EMPTY; + seat->touch_selection.current_output = NULL; +} + +static void touch_handle_up(void *data, struct wl_touch *touch, uint32_t serial, + uint32_t time, int32_t id) { + struct slurp_seat *seat = data; + handle_selection_end(seat, &seat->touch_selection); + touch_clear_state(seat); +} + +static void touch_handle_motion(void *data, struct wl_touch *touch, + uint32_t time, int32_t id, wl_fixed_t x, + wl_fixed_t y) { + struct slurp_seat *seat = data; + if (seat->touch_id == id) { + move_seat(seat, x, y, &seat->touch_selection); + handle_active_selection_motion(seat, &seat->touch_selection); + seat_set_outputs_dirty(seat); + } +} + +static void touch_handle_cancel(void *data, struct wl_touch *touch) { + struct slurp_seat *seat = data; + touch_clear_state(seat); +} + +static const struct wl_touch_listener touch_listener = { + .down = touch_handle_down, + .up = touch_handle_up, + .frame = noop, + .motion = touch_handle_motion, + .orientation = noop, + .shape = noop, + .cancel = touch_handle_cancel, +}; static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) { @@ -197,6 +374,10 @@ seat->wl_keyboard = wl_seat_get_keyboard(wl_seat); wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, seat); } + if (capabilities & WL_SEAT_CAPABILITY_TOUCH) { + seat->wl_touch = wl_seat_get_touch(wl_seat); + wl_touch_add_listener(seat->wl_touch, &touch_listener, seat); + } } static const struct wl_seat_listener seat_listener = { @@ -211,6 +392,7 @@ } seat->state = state; seat->wl_seat = wl_seat; + seat->touch_id = TOUCH_ID_EMPTY; wl_list_insert(&state->seats, &seat->link); wl_seat_add_listener(wl_seat, &seat_listener, seat); } @@ -224,6 +406,8 @@ if (seat->wl_keyboard) { wl_keyboard_destroy(seat->wl_keyboard); } + xkb_state_unref(seat->xkb_state); + xkb_keymap_unref(seat->xkb_keymap); wl_seat_destroy(seat->wl_seat); free(seat); } @@ -268,6 +452,7 @@ 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; @@ -275,11 +460,16 @@ output->logical_geometry.height = height; } +static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { + struct slurp_output *output = data; + output->logical_geometry.label = strdup(name); +} + 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, + .name = xdg_output_handle_name, .description = noop, }; @@ -315,6 +505,7 @@ wl_callback_destroy(output->frame_callback); } wl_output_destroy(output->wl_output); + free(output->logical_geometry.label); free(output); } @@ -438,7 +629,7 @@ 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); + &zxdg_output_manager_v1_interface, 2); } } @@ -455,9 +646,12 @@ " -b #rrggbbaa Set background color.\n" " -c #rrggbbaa Set border color.\n" " -s #rrggbbaa Set selection color.\n" + " -B #rrggbbaa Set option box color.\n" " -w n Set border weight.\n" " -f s Set output format.\n" - " -p Select a single point.\n"; + " -o Select a display output.\n" + " -p Select a single point.\n" + " -r Restrict selection to predefined boxes.\n"; uint32_t parse_color(const char *color) { if (color[0] == '#') { @@ -477,7 +671,23 @@ return res; } -static void print_formatted_result(const struct slurp_box *result, +static void print_output_name(const struct slurp_box *result, struct wl_list *outputs) { + struct slurp_output *output; + wl_list_for_each(output, outputs, link) { + // For now just use the top-left corner + struct slurp_box *geometry = &output->logical_geometry; + if (in_box(geometry, result->x, result->y)) { + if (geometry->label) { + printf("%s", geometry->label); + return; + } + break; + } + } + printf("<unknown>"); +} + +static void print_formatted_result(const struct slurp_box *result, struct wl_list *outputs, const char *format) { for (size_t i = 0; format[i] != '\0'; i++) { char c = format[i]; @@ -487,16 +697,24 @@ i++; // Skip the next character (x, y, w or h) switch (next) { case 'x': - printf("%u", result->x); + printf("%d", result->x); continue; case 'y': - printf("%u", result->y); + printf("%d", result->y); continue; case 'w': - printf("%u", result->width); + printf("%d", result->width); continue; case 'h': - printf("%u", result->height); + printf("%d", result->height); + continue; + case 'l': + if (result->label) { + printf("%s", result->label); + } + continue; + case 'o': + print_output_name(result, outputs); continue; default: // If no case was executed, revert i back - we don't need to @@ -506,7 +724,7 @@ } printf("%c", c); } - printf("\n"); + fflush(stdout); } static void add_choice_box(struct slurp_state *state, @@ -517,23 +735,32 @@ return; } *b = *box; + // copy label, so that this has ownership of its label + if (box->label) { + b->label = strdup(box->label); + } wl_list_insert(state->boxes.prev, &b->link); } int main(int argc, char *argv[]) { + int status = EXIT_SUCCESS; + struct slurp_state state = { .colors = { - .background = 0xFFFFFF40, - .border = 0x000000FF, - .selection = 0x00000000, + .background = BG_COLOR, + .border = BORDER_COLOR, + .selection = SELECTION_COLOR, + .choice = BG_COLOR, }, .border_weight = 2, .display_dimensions = false, + .restrict_selection = false, }; int opt; - char *format = "%x,%y %wx%h"; - while ((opt = getopt(argc, argv, "hdb:c:s:w:pf:")) != -1) { + char *format = "%x,%y %wx%h\n"; + bool output_boxes = false; + while ((opt = getopt(argc, argv, "hdb:c:s:B:w:prof:")) != -1) { switch (opt) { case 'h': printf("%s", usage); @@ -550,6 +777,9 @@ case 's': state.colors.selection = parse_color(optarg); break; + case 'B': + state.colors.choice = parse_color(optarg); + break; case 'f': format = optarg; break; @@ -562,23 +792,42 @@ exit(EXIT_FAILURE); } break; + } case 'p': state.single_point = true; break; - } + case 'o': + output_boxes = true; + break; + case 'r': + state.restrict_selection = true; + break; default: printf("%s", usage); return EXIT_FAILURE; } } + if (state.single_point && state.restrict_selection) { + fprintf(stderr, "-p and -r cannot be used together\n"); + 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) { + char *line = NULL; + size_t line_size = 0; + while (getline(&line, &line_size, stdin) >= 0) { + struct slurp_box in_box = {0}; + if (sscanf(line, "%d,%d %dx%d %m[^\n]", &in_box.x, &in_box.y, + &in_box.width, &in_box.height, &in_box.label) < 4) { + fprintf(stderr, "invalid box format: %s\n", line); + return EXIT_FAILURE; + } add_choice_box(&state, &in_box); + free(in_box.label); } + free(line); } wl_list_init(&state.outputs); wl_list_init(&state.seats); @@ -589,6 +838,11 @@ return EXIT_FAILURE; } + if ((state.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS)) == NULL) { + fprintf(stderr, "xkb_context_new failed\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); @@ -681,6 +935,13 @@ // second roundtrip for xdg-output wl_display_roundtrip(state.display); + if (output_boxes) { + struct slurp_output *box_output; + wl_list_for_each(box_output, &state.outputs, link) { + add_choice_box(&state, &box_output->logical_geometry); + } + } + struct slurp_seat *seat; wl_list_for_each(seat, &state.seats, link) { seat->cursor_surface = @@ -692,6 +953,14 @@ // This space intentionally left blank } + + if (state.result.width == 0 && state.result.height == 0) { + fprintf(stderr, "selection cancelled\n"); + status = EXIT_FAILURE; + } else { + print_formatted_result(&state.result, &state.outputs, format); + } + struct slurp_output *output_tmp; wl_list_for_each_safe(output, output_tmp, &state.outputs, link) { destroy_output(output); @@ -711,19 +980,15 @@ wl_compositor_destroy(state.compositor); wl_shm_destroy(state.shm); wl_registry_destroy(state.registry); + xkb_context_unref(state.xkb_context); 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->label); 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; + return status; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slurp-1.2.0/meson.build new/slurp-1.3.0/meson.build --- old/slurp-1.2.0/meson.build 2019-06-02 12:07:19.000000000 +0200 +++ new/slurp-1.3.0/meson.build 2020-10-16 16:55:45.000000000 +0200 @@ -1,7 +1,7 @@ project( 'slurp', 'c', - version: '1.1.0', + version: '1.3.0', license: 'MIT', meson_version: '>=0.48.0', default_options: [ @@ -22,6 +22,7 @@ wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols', version: '>=1.14') +xkbcommon = dependency('xkbcommon') subdir('protocol') @@ -38,6 +39,7 @@ realtime, wayland_client, wayland_cursor, + xkbcommon, ], include_directories: [slurp_inc], install: true, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slurp-1.2.0/render.c new/slurp-1.3.0/render.c --- old/slurp-1.2.0/render.c 2019-06-02 12:07:19.000000000 +0200 +++ new/slurp-1.3.0/render.c 2020-10-16 16:55:45.000000000 +0200 @@ -7,11 +7,21 @@ #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); + 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); +} + +static void draw_rect(cairo_t *cairo, struct slurp_box *box, uint32_t color, int32_t scale) { + set_source_u32(cairo, color); + cairo_rectangle(cairo, box->x * scale, box->y * scale, + box->width * scale, box->height * scale); +} + +static void box_layout_to_output(struct slurp_box *box, struct slurp_output *output) { + box->x -= output->logical_geometry.x; + box->y -= output->logical_geometry.y; } void render(struct slurp_output *output) { @@ -25,40 +35,55 @@ set_source_u32(cairo, state->colors.background); cairo_paint(cairo); + // Draw option boxes from input + struct slurp_box *choice_box; + wl_list_for_each(choice_box, &state->boxes, link) { + if (box_intersect(&output->logical_geometry, + choice_box)) { + struct slurp_box b = *choice_box; + box_layout_to_output(&b, output); + draw_rect(cairo, &b, state->colors.choice, scale); + cairo_fill(cairo); + } + } + struct slurp_seat *seat; wl_list_for_each(seat, &state->seats, link) { - if (!seat->wl_pointer) continue; - if (!seat->has_selection) { + struct slurp_selection *current_selection = + seat->touch_selection.has_selection ? + &seat->touch_selection : + &seat->pointer_selection; + + if (!current_selection->has_selection) { continue; } - if(!box_intersect(&output->logical_geometry, &seat->selection)) { + if (!box_intersect(&output->logical_geometry, + ¤t_selection->selection)) { continue; } - struct slurp_box b = seat->selection; - b.x -= output->logical_geometry.x; - b.y -= output->logical_geometry.y; + struct slurp_box b = current_selection->selection; + box_layout_to_output(&b, output); - // Draw border - set_source_u32(cairo, state->colors.selection); - cairo_rectangle(cairo, b.x * scale, b.y * scale, - b.width * scale, b.height * scale); + draw_rect(cairo, &b, state->colors.selection, scale); cairo_fill(cairo); - set_source_u32(cairo, state->colors.border); + // Draw 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); + draw_rect(cairo, &b, state->colors.border, scale); cairo_stroke(cairo); if (state->display_dimensions) { - cairo_select_font_face(cairo, "Sans", CAIRO_FONT_SLANT_NORMAL, - CAIRO_FONT_WEIGHT_NORMAL); + 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); + 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); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slurp-1.2.0/slurp.1.scd new/slurp-1.3.0/slurp.1.scd --- old/slurp-1.2.0/slurp.1.scd 2019-06-02 12:07:19.000000000 +0200 +++ new/slurp-1.3.0/slurp.1.scd 2020-10-16 16:55:45.000000000 +0200 @@ -14,9 +14,14 @@ 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>,<y> <width>x<height>". +If the standard input is not a TTY or the -r option is used, slurp will read a +list of predefined rectangles for quick selection. Each line must be in the form +"<x>,<y> <width>x<height> [label]". The label is optional and can be any string +that doesn't contain newlines. It can be accessed using the "%l" sequence in a +format string. + +If the _Esc_ key is pressed, selection is cancelled. If the _Space_ key is +held, the selection is moved instead of being resized. # OPTIONS @@ -35,6 +40,10 @@ *-s* _color_ Set selection color. See *COLORS* for more detail. +*-B* _color_ + Set color for highlighting predefined rectangles from standard input when not + selected. + *-w* _weight_ Set border weight. @@ -45,6 +54,15 @@ Select a single pixel instead of a rectangle. This mode ignores any predefined rectangles read from the standard input. +*-o* + Add predefined rectangles for all outputs, as if provided on standard input. + The label will be the name of the output. + +*-r* + Require the user to select one of the predefined rectangles. These can come + from standard input, if *-o* is used, the rectangles of all display outputs. + This option conflicts with *-p*. + # COLORS Colors may be specified in #RRGGBB or #RRGGBBAA format. The # is optional. @@ -61,7 +79,12 @@ %h The height of the selection -The default format is "%x,%y %wx%h". +%l Label included with region from stdin + +%o The name of the output containing the top left corner, or "<unknown>" if + not known + +The default format is "%x,%y %wx%h\\n". # AUTHORS