diff --git a/README.md b/README.md index d7cb244..637a88d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # swappy -A Wayland native snapshot and editor tool, inspired by [Snappy] on macOS. Works great with [grim], [slurp] and [sway]. Also works with other screenshot tools if you use the `-f` option. See [below](#example-usage). - -Wayland code was largely taken from [grim] and requires a compositor that implements the [wlr-screencopy-unstable-v1] protocol. - -You can use this tool in two ways, either use it as the output of `grim` (**recommended**) or grab the geometry yourself (`wayland` code is still WIP). +A Wayland native snapshot and editor tool, inspired by [Snappy] on macOS. Works great with [grim], [slurp] and [sway]. But can easily work with other screen copy tools that can output a final PNG image to `stdout`. See [below](#example-usage). ## Screenshot @@ -18,24 +14,12 @@ Output of `grim` (or any tool outputing a PNG file): grim -g "$(slurp)" - | swappy -f - ``` -Swappshot a PNG file (good for compositors not supporting screencopy protocol): +Swappshot a PNG file: ```sh swappy -f "~/Desktop/my-gnome-saved-file.png" ``` -Swappshot a region: - -```sh -swappy -g "100,100 200x200" -``` - -Select a region and swappshot it: - -```sh -swappy -g "$(slurp)" -``` - Print final surface to stdout (useful to pipe with other tools): ```sh @@ -118,7 +102,6 @@ Install dependencies (on Arch, name can vary for other distros): - meson - ninja -- wayland - cairo - pango - gtk @@ -127,7 +110,7 @@ Install dependencies (on Arch, name can vary for other distros): Optional dependencies: -- wayland-protocols (for the `-g` option to work with [wlr-screencopy-unstable-v1] protocol) +- `wl-clipboard` (to make sure the copy is saved if you close swappy) - wl-clipboard (to make sure the copy is saved if you close swappy) - libnotify (not get notified when swappshot is copied or saved) @@ -151,4 +134,3 @@ MIT [grim]: https://github.com/emersion/grim [sway]: https://github.com/swaywm/sway [wl-clipboard]: https://github.com/bugaevc/wl-clipboard -[wlr-screencopy-unstable-v1]: https://github.com/swaywm/wlr-protocols/blob/master/unstable/wlr-screencopy-unstable-v1.xml diff --git a/include/buffer.h b/include/buffer.h index 47d9767..5e95fdc 100644 --- a/include/buffer.h +++ b/include/buffer.h @@ -1,17 +1,7 @@ #pragma once -#include -#include - #include "swappy.h" -void buffer_wayland_destroy(struct swappy_buffer *buffer); - -bool buffer_init_from_screencopy(struct swappy_state *state); bool buffer_init_from_file(struct swappy_state *state); - -bool buffer_parse_geometry(struct swappy_state *state); - void buffer_resize_patterns(struct swappy_state *state); - void buffer_free_all(struct swappy_state *state); diff --git a/include/swappy.h b/include/swappy.h index 4ee6f9a..c060ae4 100644 --- a/include/swappy.h +++ b/include/swappy.h @@ -5,17 +5,9 @@ #include #include #include -#include -#ifdef HAVE_WAYLAND_PROTOCOLS -#include -#endif - -#include "wlr-screencopy-unstable-v1-client-protocol.h" #define MAX_PATH 4096 -#define GEOMETRY_PATTERN "xx,yy wwxhh" - #define SWAPPY_LINE_SIZE_MIN 1 #define SWAPPY_LINE_SIZE_MAX 50 @@ -140,47 +132,6 @@ struct swappy_state_ui { GtkButton *text_size; }; -struct swappy_buffer { - struct wl_buffer *wl_buffer; - void *data; - int32_t width, height, stride; - size_t size; - enum wl_shm_format format; -}; - -struct swappy_output { - struct swappy_state *state; - struct swappy_box geometry; - struct swappy_box logical_geometry; - struct wl_output *wl_output; - struct wl_list link; - int32_t scale; - struct swappy_buffer *buffer; - - char *name; - - enum wl_output_transform transform; - struct zwlr_screencopy_frame_v1 *screencopy_frame; - uint32_t screencopy_frame_flags; // enum zwlr_screencopy_frame_v1_flags - -#ifdef HAVE_WAYLAND_PROTOCOLS - struct zxdg_output_v1 *xdg_output; -#endif -}; - -struct swappy_wayland { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct wl_shm *shm; - struct wl_list outputs; - struct zwlr_screencopy_manager_v1 *zwlr_screencopy_manager; - size_t n_done; -#ifdef HAVE_WAYLAND_PROTOCOLS - struct zxdg_output_manager_v1 *xdg_output_manager; -#endif -}; - struct swappy_config { char *config_file; char *save_dir; @@ -195,7 +146,6 @@ struct swappy_state { struct swappy_state_ui *ui; struct swappy_config *config; - struct swappy_wayland *wl; cairo_surface_t *original_image_surface; cairo_surface_t *scaled_image_surface; @@ -206,7 +156,6 @@ struct swappy_state { enum swappy_paint_type mode; /* Options */ - char *geometry_str; char *file_str; char *output_file; diff --git a/include/wayland.h b/include/wayland.h deleted file mode 100644 index 1244f46..0000000 --- a/include/wayland.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "swappy.h" - -bool wayland_init(struct swappy_state *state); -void wayland_finish(struct swappy_state *state); diff --git a/meson.build b/meson.build index 1c6c14a..ef4d8f9 100644 --- a/meson.build +++ b/meson.build @@ -28,9 +28,6 @@ math = cc.find_library('m') realtime = cc.find_library('rt') gtk = dependency('gtk+-3.0', version: '>=3.20.0') gio = cc.find_library('gio-2.0') -wayland_client = dependency('wayland-client') -wayland_cursor = dependency('wayland-cursor') -wayland_protos = dependency('wayland-protocols', version: '>=1.14', required: false) libnotify = dependency('libnotify', required: false) @@ -38,13 +35,7 @@ if libnotify.found() add_project_arguments('-DHAVE_LIBNOTIFY', language: 'c') endif -if wayland_protos.found() - add_project_arguments('-DHAVE_WAYLAND_PROTOCOLS', language: 'c') -endif - - subdir('res') -subdir('protocol') executable( 'swappy', @@ -63,19 +54,15 @@ executable( 'src/render.c', 'src/notification.c', 'src/util.c', - 'src/wayland.c', ]), dependencies: [ cairo, pango, - client_protos, gio, gtk, libnotify, math, realtime, - wayland_client, - wayland_cursor, ], link_args: '-rdynamic', include_directories: [swappy_inc], diff --git a/protocol/meson.build b/protocol/meson.build deleted file mode 100644 index a3bd96a..0000000 --- a/protocol/meson.build +++ /dev/null @@ -1,54 +0,0 @@ -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@'], -) - -if wayland_protos.found() - wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') - client_protocols = [ - [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], - [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], - ['wlr-screencopy-unstable-v1.xml'], - ] -else - client_protocols = [ - ['wlr-screencopy-unstable-v1.xml'], - ] -endif - - -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, -) diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml deleted file mode 100644 index e4c21f8..0000000 --- a/protocol/wlr-screencopy-unstable-v1.xml +++ /dev/null @@ -1,207 +0,0 @@ - - - - Copyright © 2018 Simon Ser - Copyright © 2019 Andri Yngvason - - 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 (including the next - paragraph) 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. - - - - This protocol allows clients to ask the compositor to copy part of the - screen content to a client buffer. - - Warning! The protocol described in this file is experimental and - backward incompatible changes may be made. Backward compatible changes - may be added together with the corresponding interface version bump. - Backward incompatible changes are done by bumping the version number in - the protocol and interface names and resetting the interface version. - Once the protocol is to be declared stable, the 'z' prefix and the - version number in the protocol and interface names are removed and the - interface version number is reset. - - - - - This object is a manager which offers requests to start capturing from a - source. - - - - - Capture the next frame of an entire output. - - - - - - - - - Capture the next frame of an output's region. - - The region is given in output logical coordinates, see - xdg_output.logical_size. The region will be clipped to the output's - extents. - - - - - - - - - - - - - All objects created by the manager will still remain valid, until their - appropriate destroy request has been called. - - - - - - - This object represents a single frame. - - When created, a "buffer" event will be sent. The client will then be able - to send a "copy" request. If the capture is successful, the compositor - will send a "flags" followed by a "ready" event. - - If the capture failed, the "failed" event is sent. This can happen anytime - before the "ready" event. - - Once either a "ready" or a "failed" event is received, the client should - destroy the frame. - - - - - Provides information about the frame's buffer. This event is sent once - as soon as the frame is created. - - The client should then create a buffer with the provided attributes, and - send a "copy" request. - - - - - - - - - - Copy the frame to the supplied buffer. The buffer must have a the - correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to - have a supported format. - - If the frame is successfully copied, a "flags" and a "ready" events are - sent. Otherwise, a "failed" event is sent. - - - - - - - - - - - - - - - - Provides flags about the frame. This event is sent once before the - "ready" event. - - - - - - - Called as soon as the frame is copied, indicating it is available - for reading. This event includes the time at which presentation happened - at. - - The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, - each component being an unsigned 32-bit value. Whole seconds are in - tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, - and the additional fractional part in tv_nsec as nanoseconds. Hence, - for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part - may have an arbitrary offset at start. - - After receiving this event, the client should destroy the object. - - - - - - - - - This event indicates that the attempted frame copy has failed. - - After receiving this event, the client should destroy the object. - - - - - - Destroys the frame. This request can be sent at any time by the client. - - - - - - - Same as copy, except it waits until there is damage to copy. - - - - - - - This event is sent right before the ready event when copy_with_damage is - requested. It may be generated multiple times for each copy_with_damage - request. - - The arguments describe a box around an area that has changed since the - last copy request that was derived from the current screencopy manager - instance. - - The union of all regions received between the call to copy_with_damage - and a ready event is the total damage since the prior ready event. - - - - - - - - diff --git a/src/application.c b/src/application.c index 695cce2..9cb312b 100644 --- a/src/application.c +++ b/src/application.c @@ -11,7 +11,6 @@ #include "pixbuf.h" #include "render.h" #include "swappy.h" -#include "wayland.h" static void update_ui_undo_redo(struct swappy_state *state) { GtkWidget *undo = GTK_WIDGET(state->ui->undo); @@ -235,13 +234,11 @@ void application_finish(struct swappy_state *state) { cairo_surface_destroy(state->original_image_surface); cairo_surface_destroy(state->scaled_image_surface); g_free(state->file_str); - g_free(state->geometry_str); g_free(state->geometry); g_free(state->window); g_free(state->ui); g_object_unref(state->app); - wayland_finish(state); config_free(state); } @@ -689,10 +686,6 @@ static bool init_gtk_window(struct swappy_state *state) { return true; } -static gboolean has_option_geometry(struct swappy_state *state) { - return (state->geometry_str != NULL); -} - static gboolean has_option_file(struct swappy_state *state) { return (state->file_str != NULL); } @@ -716,22 +709,6 @@ static gint command_line_handler(GtkApplication *app, config_load(state); init_settings(state); - if (!wayland_init(state)) { - g_warning( - "error while initializing wayland objects, can only be used in file " - "mode"); - } - - if (has_option_geometry(state)) { - if (!buffer_parse_geometry(state)) { - return EXIT_FAILURE; - } - - if (!buffer_init_from_screencopy(state)) { - return EXIT_FAILURE; - } - } - if (has_option_file(state)) { if (is_file_from_stdin(state->file_str)) { char *new_file_str = file_dump_stdin_into_a_temp_file(); @@ -753,14 +730,6 @@ static gint command_line_handler(GtkApplication *app, bool application_init(struct swappy_state *state) { const GOptionEntry cli_options[] = { - { - .long_name = "geometry", - .short_name = 'g', - .arg = G_OPTION_ARG_STRING, - .arg_data = &state->geometry_str, - .description = - "Set the region to capture. (Can be an output of slurp)", - }, { .long_name = "file", .short_name = 'f', diff --git a/src/buffer.c b/src/buffer.c index 5e7106d..a22a127 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -1,251 +1,9 @@ #include "buffer.h" -#include -#include -#include -#include -#include +#include #include "box.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[] = "/swappy-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 cairo_format_t get_cairo_format(enum wl_shm_format wl_fmt) { - switch (wl_fmt) { - case WL_SHM_FORMAT_ARGB8888: - return CAIRO_FORMAT_ARGB32; - case WL_SHM_FORMAT_XRGB8888: - return CAIRO_FORMAT_RGB24; - default: - return CAIRO_FORMAT_INVALID; - } -} - -static int get_output_flipped(enum wl_output_transform transform) { - return transform & WL_OUTPUT_TRANSFORM_FLIPPED ? -1 : 1; -} - -static void apply_output_transform(enum wl_output_transform transform, - int32_t *width, int32_t *height) { - if (transform & WL_OUTPUT_TRANSFORM_90) { - int32_t tmp = *width; - *width = *height; - *height = tmp; - } -} - -static struct swappy_buffer *create_buffer(struct wl_shm *shm, - enum wl_shm_format format, - int32_t width, int32_t height, - int32_t stride) { - size_t size = stride * height; - - int fd = create_shm_file(size); - if (fd == -1) { - return NULL; - } - - void *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); - struct wl_buffer *wl_buffer = - wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); - wl_shm_pool_destroy(pool); - - close(fd); - - struct swappy_buffer *buffer = calloc(1, sizeof(struct swappy_buffer)); - buffer->wl_buffer = wl_buffer; - buffer->data = data; - buffer->width = width; - buffer->height = height; - buffer->stride = stride; - buffer->size = size; - buffer->format = format; - - return buffer; -} - -static void screencopy_frame_handle_buffer( - void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, - uint32_t width, uint32_t height, uint32_t stride) { - struct swappy_output *output = data; - - output->buffer = - create_buffer(output->state->wl->shm, format, width, height, stride); - if (output->buffer == NULL) { - g_warning("failed to create buffer"); - exit(EXIT_FAILURE); - } - - zwlr_screencopy_frame_v1_copy(frame, output->buffer->wl_buffer); -} - -static void screencopy_frame_handle_flags( - void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) { - struct swappy_output *output = data; - output->screencopy_frame_flags = flags; -} - -static void screencopy_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 swappy_output *output = data; - ++output->state->wl->n_done; -} - -static void screencopy_frame_handle_failed( - void *data, struct zwlr_screencopy_frame_v1 *frame) { - struct swappy_output *output = data; - g_warning("screencopy: failed to copy output %s", output->name); - exit(EXIT_FAILURE); -} - -bool buffer_init_from_screencopy(struct swappy_state *state) { - struct swappy_box *geometry = state->geometry; - int32_t with_cursor = 0; - size_t n_pending = 0; - struct swappy_output *output; - - g_assert(geometry != NULL); - - const struct zwlr_screencopy_frame_v1_listener screencopy_frame_listener = { - .buffer = screencopy_frame_handle_buffer, - .flags = screencopy_frame_handle_flags, - .ready = screencopy_frame_handle_ready, - .failed = screencopy_frame_handle_failed, - }; - - wl_list_for_each(output, &state->wl->outputs, link) { - if (state->geometry != NULL && - !intersect_box(state->geometry, &output->logical_geometry)) { - continue; - } - - output->screencopy_frame = zwlr_screencopy_manager_v1_capture_output( - state->wl->zwlr_screencopy_manager, with_cursor, output->wl_output); - zwlr_screencopy_frame_v1_add_listener(output->screencopy_frame, - &screencopy_frame_listener, output); - - ++n_pending; - } - if (n_pending == 0) { - g_warning("screencopy: region is empty"); - return EXIT_FAILURE; - } - - bool done = false; - while (!done && wl_display_dispatch(state->wl->display) != -1) { - done = (state->wl->n_done == n_pending); - } - if (!done) { - g_warning("failed to screenshot all outputs"); - return EXIT_FAILURE; - } - - wl_list_for_each(output, &state->wl->outputs, link) { - struct swappy_buffer *buffer = output->buffer; - - if (output->buffer == NULL) { - // screencopy buffer is empty, cannot draw it onto the paint area" - continue; - } - - cairo_format_t format = get_cairo_format(buffer->format); - - g_assert(format != CAIRO_FORMAT_INVALID); - - int32_t output_x = output->logical_geometry.x - geometry->x; - int32_t output_y = output->logical_geometry.y - geometry->y; - int32_t output_width = output->logical_geometry.width; - int32_t output_height = output->logical_geometry.height; - int32_t scale = output->scale; - - int32_t raw_output_width = output->geometry.width; - int32_t raw_output_height = output->geometry.height; - apply_output_transform(output->transform, &raw_output_width, - &raw_output_height); - - int output_flipped_x = get_output_flipped(output->transform); - int output_flipped_y = - output->screencopy_frame_flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT - ? -1 - : 1; - - cairo_surface_t *output_surface = cairo_image_surface_create_for_data( - buffer->data, format, buffer->width, buffer->height, buffer->stride); - cairo_pattern_t *output_pattern = - cairo_pattern_create_for_surface(output_surface); - - // All transformations are in pattern-local coordinates - cairo_matrix_t matrix; - cairo_matrix_init_identity(&matrix); - cairo_matrix_translate(&matrix, (double)output->geometry.width / 2, - (double)output->geometry.height / 2); - // cairo_matrix_rotate(&matrix, -get_output_rotation(output->transform)); - cairo_matrix_scale( - &matrix, (double)raw_output_width / output_width * output_flipped_x, - (double)raw_output_height / output_height * output_flipped_y); - cairo_matrix_translate(&matrix, -(double)output_width / 2, - -(double)output_height / 2); - cairo_matrix_translate(&matrix, -output_x, -output_y); - cairo_matrix_scale(&matrix, 1 / scale, 1 / scale); - cairo_pattern_set_matrix(output_pattern, &matrix); - - cairo_pattern_set_filter(output_pattern, CAIRO_FILTER_BEST); - - state->patterns = g_list_append(state->patterns, output_pattern); - - cairo_surface_destroy(output_surface); - } - - return true; -} +#include "swappy.h" bool buffer_init_from_file(struct swappy_state *state) { char *file = state->file_str; @@ -280,19 +38,6 @@ bool buffer_init_from_file(struct swappy_state *state) { return true; } -bool buffer_parse_geometry(struct swappy_state *state) { - struct swappy_box *geometry = g_new(struct swappy_box, 1); - char *geometry_str = state->geometry_str; - state->geometry = geometry; - - if (!box_parse(geometry, geometry_str)) { - g_critical("%s is not a valid geometry, must follow the pattern \"%s", - geometry_str, GEOMETRY_PATTERN); - return false; - } - return true; -} - static void scale_pattern(gpointer data, gpointer user_data) { struct swappy_state *state = (struct swappy_state *)user_data; cairo_pattern_t *pattern = (cairo_pattern_t *)data; @@ -333,15 +78,6 @@ void buffer_resize_patterns(struct swappy_state *state) { g_list_foreach(state->patterns, scale_pattern, state); } -void buffer_wayland_destroy(struct swappy_buffer *buffer) { - if (buffer == NULL) { - return; - } - munmap(buffer->data, buffer->size); - wl_buffer_destroy(buffer->wl_buffer); - free(buffer); -} - static void free_pattern(gpointer data) { cairo_pattern_t *pattern = data; cairo_pattern_destroy(pattern); diff --git a/src/paint.c b/src/paint.c index 86caee7..a7a0898 100644 --- a/src/paint.c +++ b/src/paint.c @@ -1,5 +1,7 @@ #include "paint.h" +#include + #include "util.h" static void cursor_move_backward(struct swappy_paint_text *text) { @@ -157,8 +159,8 @@ void paint_update_temporary_shape(struct swappy_state *state, double x, // Bounding x and y to the window dimensions to avoid side effects in // rendering. - x = fmin(fmax(x, 0), width); - y = fmin(fmax(y, 0), height); + x = MIN(MAX(x, 0), width); + y = MIN(MAX(y, 0), height); switch (paint->type) { case SWAPPY_PAINT_MODE_BLUR: diff --git a/src/wayland.c b/src/wayland.c deleted file mode 100644 index d349aac..0000000 --- a/src/wayland.c +++ /dev/null @@ -1,292 +0,0 @@ -#define _POSIX_C_SOURCE 2000809L - -#include -#include -#include - -#include "buffer.h" -#include "swappy.h" -#include "wlr-screencopy-unstable-v1-client-protocol.h" - -#ifdef HAVE_WAYLAND_PROTOCOLS -#include "xdg-output-unstable-v1-client-protocol.h" -#endif - -static bool guess_output_logical_geometry(struct swappy_output *output) { - // TODO Implement - g_warning("guessing output is not yet implemented"); - return false; -} - -void apply_output_transform(enum wl_output_transform transform, int32_t *width, - int32_t *height) { - if (transform & WL_OUTPUT_TRANSFORM_90) { - int32_t tmp = *width; - *width = *height; - *height = tmp; - } -} - -#ifdef HAVE_WAYLAND_PROTOCOLS -static void xdg_output_handle_logical_position( - void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { - struct swappy_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 swappy_output *output = data; - - output->logical_geometry.width = width; - output->logical_geometry.height = height; -} - -static void xdg_output_handle_done(void *data, - struct zxdg_output_v1 *xdg_output) { - struct swappy_output *output = data; - - // Guess the output scale from the logical size - int32_t width = output->geometry.width; - int32_t height = output->geometry.height; - apply_output_transform(output->transform, &width, &height); -} - -static void xdg_output_handle_name(void *data, - struct zxdg_output_v1 *xdg_output, - const char *name) { - struct swappy_output *output = data; - output->name = strdup(name); -} - -static void xdg_output_handle_description(void *data, - struct zxdg_output_v1 *xdg_output, - const char *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 = xdg_output_handle_done, - .name = xdg_output_handle_name, - .description = xdg_output_handle_description, -}; -#endif - -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 swappy_output *output = data; - - output->geometry.x = x; - output->geometry.y = y; - 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 swappy_output *output = data; - - if ((flags & WL_OUTPUT_MODE_CURRENT) != 0) { - output->geometry.width = width; - output->geometry.height = height; - } -} - -static void output_handle_done(void *data, struct wl_output *wl_output) { - // No-op -} - -static void output_handle_scale(void *data, struct wl_output *wl_output, - int32_t factor) { - struct swappy_output *output = data; - output->scale = factor; -} - -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 global_registry_handler(void *data, struct wl_registry *registry, - uint32_t name, const char *interface, - uint32_t version) { - g_debug("got a registry event for interface: %s, name: %d", interface, name); - - struct swappy_state *state = data; - bool bound = false; - - if (strcmp(interface, wl_compositor_interface.name) == 0) { - state->wl->compositor = - wl_registry_bind(registry, name, &wl_compositor_interface, version); - bound = true; - } else if (strcmp(interface, wl_shm_interface.name) == 0) { - state->wl->shm = - wl_registry_bind(registry, name, &wl_shm_interface, version); - bound = true; - } else if (strcmp(interface, wl_output_interface.name) == 0) { - struct swappy_output *output = calloc(1, sizeof(struct swappy_output)); - output->state = state; - output->scale = 1; - output->wl_output = - wl_registry_bind(registry, name, &wl_output_interface, 3); - wl_output_add_listener(output->wl_output, &output_listener, output); - wl_list_insert(&state->wl->outputs, &output->link); - bound = true; - } else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == - 0) { - state->wl->zwlr_screencopy_manager = wl_registry_bind( - registry, name, &zwlr_screencopy_manager_v1_interface, version); - bound = true; - } else { -#ifdef HAVE_WAYLAND_PROTOCOLS - if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { - state->wl->xdg_output_manager = wl_registry_bind( - registry, name, &zxdg_output_manager_v1_interface, version); - bound = true; - } -#endif - } - - if (bound) { - g_debug("bound registry: %s", interface); - } -} - -static void global_registry_remove_handler(void *data, - struct wl_registry *wl_registry, - uint32_t name) {} - -static struct wl_registry_listener registry_listener = { - .global = global_registry_handler, - .global_remove = global_registry_remove_handler, -}; - -bool wayland_init(struct swappy_state *state) { - state->wl = g_new(struct swappy_wayland, 1); - - state->wl->display = wl_display_connect(NULL); - state->wl->n_done = 0; - if (state->wl->display == NULL) { - g_warning("cannot connect to wayland display"); - return false; - } - - wl_list_init(&state->wl->outputs); - state->wl->registry = wl_display_get_registry(state->wl->display); - wl_registry_add_listener(state->wl->registry, ®istry_listener, state); - wl_display_roundtrip(state->wl->display); - - if (state->wl->compositor == NULL) { - g_warning("compositor doesn't support wl_compositor"); - return false; - } - if (state->wl->shm == NULL) { - g_warning("compositor doesn't support wl_shm"); - return false; - } - - if (wl_list_empty(&state->wl->outputs)) { - g_warning("no wl_output found"); - return false; - } - - bool found_output_layout = false; -#ifdef HAVE_WAYLAND_PROTOCOLS - if (state->wl->xdg_output_manager != NULL) { - struct swappy_output *output; - wl_list_for_each(output, &state->wl->outputs, link) { - output->xdg_output = zxdg_output_manager_v1_get_xdg_output( - state->wl->xdg_output_manager, output->wl_output); - zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, - output); - } - - wl_display_dispatch(state->wl->display); - wl_display_roundtrip(state->wl->display); - found_output_layout = true; - } -#endif - if (!found_output_layout) { - g_warning( - "zxdg_output_manager_v1 isn't available, guessing the output layout"); - - struct swappy_output *output; - bool output_guessed = false; - wl_list_for_each(output, &state->wl->outputs, link) { - output_guessed = guess_output_logical_geometry(output); - } - if (!output_guessed) { - g_warning("could not guess output logical geometry"); - return false; - } - } - - if (state->wl->zwlr_screencopy_manager == NULL) { - g_warning("compositor does not support zwlr_screencopy_v1"); - return false; - } - - return true; -} - -void wayland_finish(struct swappy_state *state) { - struct swappy_output *output; - struct swappy_output *output_tmp; - - if (!state->wl) { - return; - } - - wl_list_for_each_safe(output, output_tmp, &state->wl->outputs, link) { - wl_list_remove(&output->link); - free(output->name); - if (output->screencopy_frame != NULL) { - zwlr_screencopy_frame_v1_destroy(output->screencopy_frame); - } -#ifdef HAVE_WAYLAND_PROTOCOLS - if (output->xdg_output != NULL) { - zxdg_output_v1_destroy(output->xdg_output); - } -#endif - wl_output_release(output->wl_output); - buffer_wayland_destroy(output->buffer); - free(output); - } - - if (state->wl->compositor != NULL) { - wl_compositor_destroy(state->wl->compositor); - } - - if (state->wl->zwlr_screencopy_manager != NULL) { - zwlr_screencopy_manager_v1_destroy(state->wl->zwlr_screencopy_manager); - } - -#ifdef HAVE_WAYLAND_PROTOCOLS - if (state->wl->xdg_output_manager != NULL) { - zxdg_output_manager_v1_destroy(state->wl->xdg_output_manager); - } -#endif - - if (state->wl->shm != NULL) { - wl_shm_destroy(state->wl->shm); - } - - if (state->wl->registry != NULL) { - wl_registry_destroy(state->wl->registry); - } - - if (state->wl->display) { - wl_display_disconnect(state->wl->display); - } - - g_free(state->wl); - state->wl = NULL; -} \ No newline at end of file diff --git a/swappy.1.scd b/swappy.1.scd index bbf5986..68c85a0 100644 --- a/swappy.1.scd +++ b/swappy.1.scd @@ -11,12 +11,10 @@ swappy - grab and edit on the fly snapshots of a Wayland compositor # SYNOPSIS swappy is a command-line utility to take and edit screenshots of Wayland -desktops. It can also work on regular X11 desktops if using the *-f* option. +desktops. Works great with grim, slurp and sway. But can easily work with +other screen copy tools that can output a final PNG image to *stdout*. -Can be used in two ways, either as the output of grim (recommended) or by -grabbing the geometry (if the compositor supports the screencopy protocol). - -swappy will save the swappshot images to the config *save_dir*, see below. +swappy will save the annotated images to the config *save_dir*, see below. If absent, then if it will try to default to a *Desktop* folder following this pattern: *$XDG\_DESKTOP\_DIR*. If this variable is not set, it will revert to: @@ -28,11 +26,6 @@ to: *$HOME/Desktop*. *-h* Show help message and quit. -*-g* ", x" - Set the region to capture, in layout coordinates. This is slurp friendly. - - Requires a Wayland compositor that supports screencopy protocol. - *-f* A PNG file to load for editing.