Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package grim for openSUSE:Factory checked in at 2026-04-20 16:12:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/grim (Old) and /work/SRC/openSUSE:Factory/.grim.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "grim" Mon Apr 20 16:12:28 2026 rev:9 rq:1348117 version:1.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/grim/grim.changes 2023-06-23 21:53:34.558948917 +0200 +++ /work/SRC/openSUSE:Factory/.grim.new.11940/grim.changes 2026-04-20 16:12:41.672606833 +0200 @@ -1,0 +2,35 @@ +Sun Apr 19 15:37:27 UTC 2026 - Khang LĂȘ <[email protected]> + +- Update to 1.5.0 + * write_jpg: use no/444 subsampling + * build: print feature summary + * Move man pages into doc/ directory + * build: always set HAVE_JPEG + * build: drop grim_inc + * build: use cc.get_supported_arguments() to check for -W flags + * build: turn on -Wundef + * build: find wayland-scanner via pkg-config + * build: require wayland-scanner >=1.14.91 + * build: drop unnecessary join_paths() call + * build: drop unnecessary intermediate protocols static library + * Define _POSIX_C_SOURCE globally + * build: move wayland-protocols dep to protocol/ + * Move generated protocol header includes to source files + * Check for wl_display_roundtrip() errors + * Drop "client" suffix in generated protocol header filenames + * Move screencopy manager check up + * gitignore: only ignore Meson subprojects/ directory + * Add support for ext-image-copy-capture-v1 + * Prefer wl_output.name over xdg-output-unstable-v1 when available + * Migrate to gitlab.freedesktop.org + * Remove unnecessary strdup() for -o + * Bail out when both -o and -g are provided + * readme: quote -o argument in output capture example + * Add comment to describe what grim_output.geometry describes + * Stop using output->geometry in render() + * Introduce grim_capture + * Add support for toplevel capture + * Split grim_output.geometry into fallback_{x,y} and mode_{width,height + * build: bump version to 1.5.0 + +------------------------------------------------------------------- Old: ---- grim-1.4.1.tar.gz New: ---- grim-1.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ grim.spec ++++++ --- /var/tmp/diff_new_pack.TNbM0P/_old 2026-04-20 16:12:42.188628381 +0200 +++ /var/tmp/diff_new_pack.TNbM0P/_new 2026-04-20 16:12:42.192628547 +0200 @@ -1,7 +1,7 @@ # # spec file for package grim # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,13 +17,13 @@ Name: grim -Version: 1.4.1 +Version: 1.5.0 Release: 0 Summary: Wayland compositor image grabber License: MIT Group: Productivity/Graphics/Other -URL: https://git.sr.ht/~emersion/grim -Source: https://git.sr.ht/~emersion/grim/refs/download/v%{version}/grim-%{version}.tar.gz +URL: https://gitlab.freedesktop.org/emersion/grim +Source: https://gitlab.freedesktop.org/emersion/grim/-/releases/v%{version}/downloads/%{name}-%{version}.tar.gz BuildRequires: meson >= 0.59.0 BuildRequires: pkgconfig BuildRequires: scdoc @@ -31,7 +31,8 @@ BuildRequires: pkgconfig(libpng) BuildRequires: pkgconfig(pixman-1) BuildRequires: pkgconfig(wayland-client) -BuildRequires: pkgconfig(wayland-protocols) >= 1.14 +BuildRequires: pkgconfig(wayland-protocols) >= 1.14.91 +BuildRequires: pkgconfig(wayland-scanner) %description This tool can grab images from a Wayland compositor. @@ -47,7 +48,7 @@ %meson_install %files -%license LICENSE +%license LICENSE* %doc README.md %{_bindir}/%{name} %{_mandir}/man?/%{name}* ++++++ grim-1.4.1.tar.gz -> grim-1.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/.build.yml new/grim-1.5.0/.build.yml --- old/grim-1.4.1/.build.yml 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/.build.yml 2025-07-06 20:24:01.000000000 +0200 @@ -7,7 +7,7 @@ - libpng - libjpeg-turbo sources: - - https://git.sr.ht/~emersion/grim + - https://gitlab.freedesktop.org/emersion/grim.git tasks: - setup: | cd grim diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/.gitignore new/grim-1.5.0/.gitignore --- old/grim-1.4.1/.gitignore 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/.gitignore 2025-07-06 20:24:01.000000000 +0200 @@ -1,54 +1 @@ -# 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 \ No newline at end of file +/subprojects/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/.gitlab-ci.yml new/grim-1.5.0/.gitlab-ci.yml --- old/grim-1.4.1/.gitlab-ci.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/grim-1.5.0/.gitlab-ci.yml 2025-07-06 20:24:01.000000000 +0200 @@ -0,0 +1 @@ +include: https://gitlab.freedesktop.org/emersion/dalligi/-/raw/master/templates/single.yml diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/README.md new/grim-1.5.0/README.md --- old/grim-1.4.1/README.md 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/README.md 2025-07-06 20:24:01.000000000 +0200 @@ -44,14 +44,14 @@ `jq`: ```sh -grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') +grim -o "$(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name')" ``` Grab a screenshot from the focused window under Sway, using `swaymsg` and `jq`: ```sh -grim -g "$(swaymsg -t get_tree | jq -j '.. | select(.type?) | select(.focused).rect | "\(.x),\(.y) \(.width)x\(.height)"')" +grim -T "$(swaymsg -t get_tree | jq -j '.. | select(.type?) | select(.focused).foreign_toplevel_identifier')" ``` Pick a color, using ImageMagick: @@ -82,7 +82,7 @@ ## Contributing -Report bugs on the [issue tracker], send patches on the [mailing list]. +Report bugs and send patches on [GitLab]. Join the IRC channel: [#emersion on Libera Chat]. @@ -91,6 +91,5 @@ MIT [slurp]: https://github.com/emersion/slurp -[issue tracker]: https://todo.sr.ht/~emersion/grim -[mailing list]: https://lists.sr.ht/~emersion/grim-dev +[GitLab]: https://gitlab.freedesktop.org/emersion/grim [#emersion on Libera Chat]: ircs://irc.libera.chat/#emersion diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/buffer.c new/grim-1.5.0/buffer.c --- old/grim-1.4.1/buffer.c 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/buffer.c 2025-07-06 20:24:01.000000000 +0200 @@ -1,4 +1,3 @@ -#define _POSIX_C_SOURCE 200112L #include <errno.h> #include <fcntl.h> #include <stdio.h> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/doc/grim.1.scd new/grim-1.5.0/doc/grim.1.scd --- old/grim-1.4.1/doc/grim.1.scd 1970-01-01 01:00:00.000000000 +0100 +++ new/grim-1.5.0/doc/grim.1.scd 2025-07-06 20:24:01.000000000 +0200 @@ -0,0 +1,66 @@ +grim(1) + +# NAME + +grim - grab images from a Wayland compositor + +# SYNOPSIS + +*grim* [options...] [output-file] + +# DESCRIPTION + +grim is a command-line utility to take screenshots of Wayland desktops. For now +it requires support for the screencopy protocol to work. Support for the +xdg-output protocol is optional, but improves fractional scaling support. + +grim will write an image to _output-file_, or to a timestamped file name in +*$GRIM_DEFAULT_DIR* if not specified. If *$GRIM_DEFAULT_DIR* is not set, it +falls back first to *$XDG_PICTURES_DIR* and then to the current working +directory. If _output-file_ is *-*, grim will write the image to the standard +output instead. + +# OPTIONS + +*-h* + Show help message and quit. + +*-s* <factor> + Set the output image's scale factor to _factor_. By default, the scale + factor is set to the highest of all outputs. + +*-g* "<x>,<y> <width>x<height>" + Set the region to capture, in layout coordinates. + + If set to *-*, read the region from the standard input instead. + +*-t* <type> + Set the output image's file format to _type_. By default, the filetype + is set to *png*, valid values are *png*, *jpeg* or *ppm*. + +*-q* <quality> + Set the output jpeg's filetype compression rate to _quality_. By default, + the jpeg quality is *80*, valid values are between 0-100. + +*-l* <level> + Set the output PNG's filetype compression level to _level_. By default, + the PNG compression level is 6 on a scale from 0 to 9. Level 9 gives + the highest compression ratio, but may be slow; level 1 gives a lower + compression ratio, but is faster. Level 0 does no compression at all, + and produces very large files; it can be useful when grim is used + in a pipeline with other commands. + +*-o* <output> + Set the output name to capture. + +*-c* + Include cursors in the screenshot. + +*-T* <identifier> + Set the identifier of a foreign toplevel handle to capture. + +# AUTHORS + +Maintained by Simon Ser <[email protected]>, who is assisted by other +open-source contributors. For more information about grim development, see +<https://gitlab.freedesktop.org/emersion/grim>. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/doc/meson.build new/grim-1.5.0/doc/meson.build --- old/grim-1.4.1/doc/meson.build 1970-01-01 01:00:00.000000000 +0100 +++ new/grim-1.5.0/doc/meson.build 2025-07-06 20:24:01.000000000 +0200 @@ -0,0 +1,23 @@ +scdoc = find_program('scdoc', required: get_option('man-pages')) +if not scdoc.found() + subdir_done() +endif + +man_pages = ['grim.1.scd'] + +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: scdoc, + feed: true, + capture: true, + install: true, + install_dir: '@0@/man@1@'.format(get_option('mandir'), section), + ) +endforeach diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/grim.1.scd new/grim-1.5.0/grim.1.scd --- old/grim-1.4.1/grim.1.scd 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/grim.1.scd 1970-01-01 01:00:00.000000000 +0100 @@ -1,63 +0,0 @@ -grim(1) - -# NAME - -grim - grab images from a Wayland compositor - -# SYNOPSIS - -*grim* [options...] [output-file] - -# DESCRIPTION - -grim is a command-line utility to take screenshots of Wayland desktops. For now -it requires support for the screencopy protocol to work. Support for the -xdg-output protocol is optional, but improves fractional scaling support. - -grim will write an image to _output-file_, or to a timestamped file name in -*$GRIM_DEFAULT_DIR* if not specified. If *$GRIM_DEFAULT_DIR* is not set, it -falls back first to *$XDG_PICTURES_DIR* and then to the current working -directory. If _output-file_ is *-*, grim will write the image to the standard -output instead. - -# OPTIONS - -*-h* - Show help message and quit. - -*-s* <factor> - Set the output image's scale factor to _factor_. By default, the scale - factor is set to the highest of all outputs. - -*-g* "<x>,<y> <width>x<height>" - Set the region to capture, in layout coordinates. - - If set to *-*, read the region from the standard input instead. - -*-t* <type> - Set the output image's file format to _type_. By default, the filetype - is set to *png*, valid values are *png*, *jpeg* or *ppm*. - -*-q* <quality> - Set the output jpeg's filetype compression rate to _quality_. By default, - the jpeg quality is *80*, valid values are between 0-100. - -*-l* <level> - Set the output PNG's filetype compression level to _level_. By default, - the PNG compression level is 6 on a scale from 0 to 9. Level 9 gives - the highest compression ratio, but may be slow; level 1 gives a lower - compression ratio, but is faster. Level 0 does no compression at all, - and produces very large files; it can be useful when grim is used - in a pipeline with other commands. - -*-o* <output> - Set the output name to capture. - -*-c* - Include cursors in the screenshot. - -# AUTHORS - -Maintained by Simon Ser <[email protected]>, who is assisted by other -open-source contributors. For more information about grim development, see -<https://sr.ht/~emersion/grim>. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/include/grim.h new/grim-1.5.0/include/grim.h --- old/grim-1.4.1/include/grim.h 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/include/grim.h 2025-07-06 20:24:01.000000000 +0200 @@ -4,8 +4,6 @@ #include <wayland-client.h> #include "box.h" -#include "wlr-screencopy-unstable-v1-client-protocol.h" -#include "xdg-output-unstable-v1-client-protocol.h" enum grim_filetype { GRIM_FILETYPE_PNG, @@ -18,9 +16,16 @@ struct wl_registry *registry; struct wl_shm *shm; struct zxdg_output_manager_v1 *xdg_output_manager; + struct ext_output_image_capture_source_manager_v1 *ext_output_image_capture_source_manager; + struct ext_foreign_toplevel_image_capture_source_manager_v1 *ext_foreign_toplevel_image_capture_source_manager; + struct ext_image_copy_capture_manager_v1 *ext_image_copy_capture_manager; struct zwlr_screencopy_manager_v1 *screencopy_manager; + struct ext_foreign_toplevel_list_v1 *foreign_toplevel_list; + struct wl_list outputs; + struct wl_list toplevels; + struct wl_list captures; size_t n_done; }; @@ -32,17 +37,41 @@ struct zxdg_output_v1 *xdg_output; struct wl_list link; - struct grim_box geometry; + int32_t fallback_x, fallback_y; // legacy position from wl_output.geometry + uint32_t mode_width, mode_height; // current mode size enum wl_output_transform transform; int32_t scale; struct grim_box logical_geometry; double logical_scale; // guessed from the logical size char *name; +}; + +struct grim_capture { + struct grim_state *state; + struct grim_output *output; + struct wl_list link; + + enum wl_output_transform transform; + struct grim_box logical_geometry; struct grim_buffer *buffer; + + struct ext_image_copy_capture_session_v1 *ext_image_copy_capture_session; + struct ext_image_copy_capture_frame_v1 *ext_image_copy_capture_frame; + uint32_t buffer_width, buffer_height; + enum wl_shm_format shm_format; + bool has_shm_format; + struct zwlr_screencopy_frame_v1 *screencopy_frame; uint32_t screencopy_frame_flags; // enum zwlr_screencopy_frame_v1_flags }; +struct grim_toplevel { + struct ext_foreign_toplevel_handle_v1 *handle; + struct wl_list link; + + char *identifier; +}; + #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/include/output-layout.h new/grim-1.5.0/include/output-layout.h --- old/grim-1.4.1/include/output-layout.h 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/include/output-layout.h 2025-07-06 20:24:01.000000000 +0200 @@ -5,7 +5,7 @@ #include "grim.h" -void get_output_layout_extents(struct grim_state *state, struct grim_box *box); +void get_capture_layout_extents(struct grim_state *state, struct grim_box *box); void apply_output_transform(enum wl_output_transform transform, int32_t *width, int32_t *height); double get_output_rotation(enum wl_output_transform transform); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/include/render.h new/grim-1.5.0/include/render.h --- old/grim-1.4.1/include/render.h 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/include/render.h 2025-07-06 20:24:01.000000000 +0200 @@ -2,9 +2,13 @@ #define _RENDER_H #include <pixman.h> +#include <stdbool.h> #include "grim.h" +bool is_format_supported(enum wl_shm_format fmt); +uint32_t get_format_min_stride(enum wl_shm_format fmt, uint32_t width); + pixman_image_t *render(struct grim_state *state, struct grim_box *geometry, double scale); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/main.c new/grim-1.5.0/main.c --- old/grim-1.4.1/main.c 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/main.c 2025-07-06 20:24:01.000000000 +0200 @@ -1,4 +1,3 @@ -#define _POSIX_C_SOURCE 200809L #include <assert.h> #include <errno.h> #include <limits.h> @@ -16,43 +15,49 @@ #include "output-layout.h" #include "render.h" #include "write_ppm.h" -#ifdef HAVE_JPEG +#if HAVE_JPEG #include "write_jpg.h" #endif #include "write_png.h" +#include "ext-foreign-toplevel-list-v1-protocol.h" +#include "ext-image-capture-source-v1-protocol.h" +#include "ext-image-copy-capture-v1-protocol.h" +#include "wlr-screencopy-unstable-v1-protocol.h" +#include "xdg-output-unstable-v1-protocol.h" + 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 grim_output *output = data; + struct grim_capture *capture = data; - output->buffer = - create_buffer(output->state->shm, format, width, height, stride); - if (output->buffer == NULL) { + capture->buffer = + create_buffer(capture->state->shm, format, width, height, stride); + if (capture->buffer == NULL) { fprintf(stderr, "failed to create buffer\n"); exit(EXIT_FAILURE); } - zwlr_screencopy_frame_v1_copy(frame, output->buffer->wl_buffer); + zwlr_screencopy_frame_v1_copy(frame, capture->buffer->wl_buffer); } static void screencopy_frame_handle_flags(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) { - struct grim_output *output = data; - output->screencopy_frame_flags = flags; + struct grim_capture *capture = data; + capture->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 grim_output *output = data; - ++output->state->n_done; + struct grim_capture *capture = data; + ++capture->state->n_done; } static void screencopy_frame_handle_failed(void *data, struct zwlr_screencopy_frame_v1 *frame) { - struct grim_output *output = data; - fprintf(stderr, "failed to copy output %s\n", output->name); + struct grim_capture *capture = data; + fprintf(stderr, "failed to copy output %s\n", capture->output->name); exit(EXIT_FAILURE); } @@ -64,6 +69,186 @@ }; +static void ext_image_copy_capture_frame_handle_transform(void *data, + struct ext_image_copy_capture_frame_v1 *frame, uint32_t transform) { + struct grim_capture *capture = data; + capture->transform = transform; +} + +static void ext_image_copy_capture_frame_handle_damage(void *data, + struct ext_image_copy_capture_frame_v1 *frame, int32_t x, int32_t y, + int32_t wdth, int32_t height) { + // No-op +} + +static void ext_image_copy_capture_frame_handle_presentation_time(void *data, + struct ext_image_copy_capture_frame_v1 *frame, uint32_t tv_sec_hi, + uint32_t tv_sec_lo, uint32_t tv_nsec) { + // No-op +} + +static void ext_image_copy_capture_frame_handle_ready(void *data, + struct ext_image_copy_capture_frame_v1 *frame) { + struct grim_capture *capture = data; + ++capture->state->n_done; +} + +static void ext_image_copy_capture_frame_handle_failed(void *data, + struct ext_image_copy_capture_frame_v1 *frame, uint32_t reason) { + // TODO: retry depending on reason + struct grim_capture *capture = data; + fprintf(stderr, "failed to copy output %s\n", capture->output->name); + exit(EXIT_FAILURE); +} + +static const struct ext_image_copy_capture_frame_v1_listener ext_image_copy_capture_frame_listener = { + .transform = ext_image_copy_capture_frame_handle_transform, + .damage = ext_image_copy_capture_frame_handle_damage, + .presentation_time = ext_image_copy_capture_frame_handle_presentation_time, + .ready = ext_image_copy_capture_frame_handle_ready, + .failed = ext_image_copy_capture_frame_handle_failed, +}; + +static void ext_image_copy_capture_session_handle_buffer_size(void *data, + struct ext_image_copy_capture_session_v1 *session, uint32_t width, uint32_t height) { + struct grim_capture *capture = data; + capture->buffer_width = width; + capture->buffer_height = height; + + if (capture->output == NULL) { + // TODO: improve this + capture->logical_geometry.width = width; + capture->logical_geometry.height = height; + } +} + +static void ext_image_copy_capture_session_handle_shm_format(void *data, + struct ext_image_copy_capture_session_v1 *session, uint32_t format) { + struct grim_capture *capture = data; + + if (capture->has_shm_format || !is_format_supported(format)) { + return; + } + + capture->shm_format = format; + capture->has_shm_format = true; +} + +static void ext_image_copy_capture_session_handle_dmabuf_device(void *data, + struct ext_image_copy_capture_session_v1 *session, struct wl_array *dev_id_array) { + // No-op +} + +static void ext_image_copy_capture_session_handle_dmabuf_format(void *data, + struct ext_image_copy_capture_session_v1 *session, uint32_t format, + struct wl_array *modifiers_array) { + // No-op +} + +static void ext_image_copy_capture_session_handle_done(void *data, + struct ext_image_copy_capture_session_v1 *session) { + struct grim_capture *capture = data; + + if (capture->ext_image_copy_capture_frame != NULL) { + return; + } + + if (!capture->has_shm_format) { + fprintf(stderr, "no supported format found\n"); + exit(EXIT_FAILURE); + } + + int32_t stride = get_format_min_stride(capture->shm_format, capture->buffer_width); + capture->buffer = + create_buffer(capture->state->shm, capture->shm_format, capture->buffer_width, capture->buffer_height, stride); + if (capture->buffer == NULL) { + fprintf(stderr, "failed to create buffer\n"); + exit(EXIT_FAILURE); + } + + capture->ext_image_copy_capture_frame = ext_image_copy_capture_session_v1_create_frame(session); + ext_image_copy_capture_frame_v1_add_listener(capture->ext_image_copy_capture_frame, + &ext_image_copy_capture_frame_listener, capture); + + ext_image_copy_capture_frame_v1_attach_buffer(capture->ext_image_copy_capture_frame, capture->buffer->wl_buffer); + ext_image_copy_capture_frame_v1_damage_buffer(capture->ext_image_copy_capture_frame, + 0, 0, INT32_MAX, INT32_MAX); + ext_image_copy_capture_frame_v1_capture(capture->ext_image_copy_capture_frame); +} + +static void ext_image_copy_capture_session_handle_stopped(void *data, + struct ext_image_copy_capture_session_v1 *session) { + // No-op +} + +static const struct ext_image_copy_capture_session_v1_listener ext_image_copy_capture_session_listener = { + .buffer_size = ext_image_copy_capture_session_handle_buffer_size, + .shm_format = ext_image_copy_capture_session_handle_shm_format, + .dmabuf_device = ext_image_copy_capture_session_handle_dmabuf_device, + .dmabuf_format = ext_image_copy_capture_session_handle_dmabuf_format, + .done = ext_image_copy_capture_session_handle_done, + .stopped = ext_image_copy_capture_session_handle_stopped, +}; + + +static void foreign_toplevel_handle_closed(void *data, + struct ext_foreign_toplevel_handle_v1 *toplevel_handle) { + // No-op +} + +static void foreign_toplevel_handle_done(void *data, + struct ext_foreign_toplevel_handle_v1 *toplevel_handle) { + // TODO: wait for the done event +} + +static void foreign_toplevel_handle_title(void *data, + struct ext_foreign_toplevel_handle_v1 *toplevel_handle, const char *title) { + // No-op +} + +static void foreign_toplevel_handle_app_id(void *data, + struct ext_foreign_toplevel_handle_v1 *toplevel_handle, const char *app_id) { + // No-op +} + +static void foreign_toplevel_handle_identifier(void *data, + struct ext_foreign_toplevel_handle_v1 *toplevel_handle, const char *identifier) { + struct grim_toplevel *toplevel = data; + toplevel->identifier = strdup(identifier); +} + +static const struct ext_foreign_toplevel_handle_v1_listener foreign_toplevel_listener = { + .closed = foreign_toplevel_handle_closed, + .done = foreign_toplevel_handle_done, + .title = foreign_toplevel_handle_title, + .app_id = foreign_toplevel_handle_app_id, + .identifier = foreign_toplevel_handle_identifier, +}; + +static void foreign_toplevel_list_handle_toplevel(void *data, + struct ext_foreign_toplevel_list_v1 *list, + struct ext_foreign_toplevel_handle_v1 *toplevel_handle) { + struct grim_state *state = data; + + struct grim_toplevel *toplevel = calloc(1, sizeof(*toplevel)); + wl_list_insert(&state->toplevels, &toplevel->link); + + toplevel->handle = toplevel_handle; + + ext_foreign_toplevel_handle_v1_add_listener(toplevel_handle, &foreign_toplevel_listener, toplevel); +} + +static void foreign_toplevel_list_handle_finished(void *data, + struct ext_foreign_toplevel_list_v1 *list) { + // No-op +} + +static const struct ext_foreign_toplevel_list_v1_listener foreign_toplevel_list_listener = { + .toplevel = foreign_toplevel_list_handle_toplevel, + .finished = foreign_toplevel_list_handle_finished, +}; + + static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { struct grim_output *output = data; @@ -85,8 +270,8 @@ struct grim_output *output = data; // Guess the output scale from the logical size - int32_t width = output->geometry.width; - int32_t height = output->geometry.height; + int32_t width = output->mode_width; + int32_t height = output->mode_height; apply_output_transform(output->transform, &width, &height); output->logical_scale = (double)width / output->logical_geometry.width; } @@ -94,6 +279,9 @@ static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { struct grim_output *output = data; + if (output->name) { + return; // prefer wl_output.name if available + } output->name = strdup(name); } @@ -117,8 +305,8 @@ int32_t transform) { struct grim_output *output = data; - output->geometry.x = x; - output->geometry.y = y; + output->fallback_x = x; + output->fallback_y = y; output->transform = transform; } @@ -127,8 +315,8 @@ struct grim_output *output = data; if ((flags & WL_OUTPUT_MODE_CURRENT) != 0) { - output->geometry.width = width; - output->geometry.height = height; + output->mode_width = width; + output->mode_height = height; } } @@ -142,11 +330,24 @@ output->scale = factor; } +static void output_handle_name(void *data, struct wl_output *wl_output, + const char *name) { + struct grim_output *output = data; + output->name = strdup(name); +} + +static void output_handle_description(void *data, struct wl_output *wl_output, + const char *description) { + // No-op +} + static const struct wl_output_listener output_listener = { .geometry = output_handle_geometry, .mode = output_handle_mode, .done = output_handle_done, .scale = output_handle_scale, + .name = output_handle_name, + .description = output_handle_description, }; @@ -161,16 +362,31 @@ state->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, bind_version); } else if (strcmp(interface, wl_output_interface.name) == 0) { + uint32_t bind_version = (version >= 4) ? 4 : 3; struct grim_output *output = calloc(1, sizeof(struct grim_output)); output->state = state; output->scale = 1; output->wl_output = wl_registry_bind(registry, name, - &wl_output_interface, 3); + &wl_output_interface, bind_version); wl_output_add_listener(output->wl_output, &output_listener, output); wl_list_insert(&state->outputs, &output->link); + } else if (strcmp(interface, ext_output_image_capture_source_manager_v1_interface.name) == 0) { + state->ext_output_image_capture_source_manager = wl_registry_bind(registry, name, + &ext_output_image_capture_source_manager_v1_interface, 1); + } else if (strcmp(interface, ext_foreign_toplevel_image_capture_source_manager_v1_interface.name) == 0) { + state->ext_foreign_toplevel_image_capture_source_manager = wl_registry_bind(registry, name, + &ext_foreign_toplevel_image_capture_source_manager_v1_interface, 1); + } else if (strcmp(interface, ext_image_copy_capture_manager_v1_interface.name) == 0) { + state->ext_image_copy_capture_manager = wl_registry_bind(registry, name, + &ext_image_copy_capture_manager_v1_interface, 1); } else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { state->screencopy_manager = wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, 1); + } else if (strcmp(interface, ext_foreign_toplevel_list_v1_interface.name) == 0) { + state->foreign_toplevel_list = wl_registry_bind(registry, name, + &ext_foreign_toplevel_list_v1_interface, 1); + ext_foreign_toplevel_list_v1_add_listener(state->foreign_toplevel_list, + &foreign_toplevel_list_listener, state); } } @@ -306,6 +522,52 @@ return strdup("."); } +static void create_output_capture(struct grim_state *state, struct grim_output *output, bool with_cursor) { + struct grim_capture *capture = calloc(1, sizeof(*capture)); + capture->state = state; + capture->output = output; + capture->transform = output->transform; + capture->logical_geometry = output->logical_geometry; + wl_list_insert(&state->captures, &capture->link); + + if (state->ext_output_image_capture_source_manager != NULL) { + uint32_t options = 0; + if (with_cursor) { + options |= EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS; + } + struct ext_image_capture_source_v1 *source = ext_output_image_capture_source_manager_v1_create_source( + state->ext_output_image_capture_source_manager, output->wl_output); + capture->ext_image_copy_capture_session = ext_image_copy_capture_manager_v1_create_session( + state->ext_image_copy_capture_manager, source, options); + ext_image_copy_capture_session_v1_add_listener(capture->ext_image_copy_capture_session, + &ext_image_copy_capture_session_listener, capture); + ext_image_capture_source_v1_destroy(source); + } else { + capture->screencopy_frame = zwlr_screencopy_manager_v1_capture_output( + state->screencopy_manager, with_cursor, output->wl_output); + zwlr_screencopy_frame_v1_add_listener(capture->screencopy_frame, + &screencopy_frame_listener, capture); + } +} + +static void create_toplevel_capture(struct grim_state *state, struct grim_toplevel *toplevel, bool with_cursor) { + struct grim_capture *capture = calloc(1, sizeof(*capture)); + capture->state = state; + wl_list_insert(&state->captures, &capture->link); + + uint32_t options = 0; + if (with_cursor) { + options |= EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS; + } + struct ext_image_capture_source_v1 *source = ext_foreign_toplevel_image_capture_source_manager_v1_create_source( + state->ext_foreign_toplevel_image_capture_source_manager, toplevel->handle); + struct ext_image_copy_capture_session_v1 *session = ext_image_copy_capture_manager_v1_create_session( + state->ext_image_copy_capture_manager, source, options); + ext_image_copy_capture_session_v1_add_listener(session, + &ext_image_copy_capture_session_listener, capture); + ext_image_capture_source_v1_destroy(source); +} + static const char usage[] = "Usage: grim [options...] [output-file]\n" "\n" @@ -317,19 +579,21 @@ " -q <quality> Set the JPEG filetype quality 0-100. Defaults to 80.\n" " -l <level> Set the PNG filetype compression level 0-9. Defaults to 6.\n" " -o <output> Set the output name to capture.\n" + " -T <identifier> Set the identifier of a foreign toplevel handle to capture.\n" " -c Include cursors in the screenshot.\n"; int main(int argc, char *argv[]) { double scale = 1.0; bool use_greatest_scale = true; struct grim_box *geometry = NULL; - char *geometry_output = NULL; + const char *geometry_output = NULL; enum grim_filetype output_filetype = GRIM_FILETYPE_PNG; int jpeg_quality = 80; int png_level = 6; // current default png/zlib compression level bool with_cursor = false; + const char *toplevel_identifier = NULL; int opt; - while ((opt = getopt(argc, argv, "hs:g:t:q:l:o:c")) != -1) { + while ((opt = getopt(argc, argv, "hs:g:t:q:l:o:cT:")) != -1) { switch (opt) { case 'h': printf("%s", usage); @@ -371,7 +635,7 @@ } else if (strcmp(optarg, "ppm") == 0) { output_filetype = GRIM_FILETYPE_PPM; } else if (strcmp(optarg, "jpeg") == 0) { -#ifdef HAVE_JPEG +#if HAVE_JPEG output_filetype = GRIM_FILETYPE_JPEG; #else fprintf(stderr, "jpeg support disabled\n"); @@ -419,17 +683,28 @@ } break; case 'o': - free(geometry_output); - geometry_output = strdup(optarg); + geometry_output = optarg; break; case 'c': with_cursor = true; break; + case 'T': + toplevel_identifier = optarg; + break; default: return EXIT_FAILURE; } } + if (geometry_output != NULL && geometry != NULL) { + fprintf(stderr, "-o and -g are mutually exclusive\n"); + return EXIT_FAILURE; + } + if (geometry_output != NULL && toplevel_identifier != NULL) { + fprintf(stderr, "-o and -T are mutually exclusive\n"); + return EXIT_FAILURE; + } + const char *output_filename; char *output_filepath; char tmp[64]; @@ -459,6 +734,8 @@ struct grim_state state = {0}; wl_list_init(&state.outputs); + wl_list_init(&state.toplevels); + wl_list_init(&state.captures); state.display = wl_display_connect(NULL); if (state.display == NULL) { @@ -468,13 +745,27 @@ state.registry = wl_display_get_registry(state.display); wl_registry_add_listener(state.registry, ®istry_listener, &state); - wl_display_roundtrip(state.display); + if (wl_display_roundtrip(state.display) < 0) { + fprintf(stderr, "wl_display_roundtrip() failed\n"); + return EXIT_FAILURE; + } if (state.shm == NULL) { fprintf(stderr, "compositor doesn't support wl_shm\n"); return EXIT_FAILURE; } - if (wl_list_empty(&state.outputs)) { + bool can_capture; + if (toplevel_identifier != NULL) { + can_capture = state.ext_foreign_toplevel_image_capture_source_manager != NULL && state.ext_image_copy_capture_manager; + } else { + can_capture = state.screencopy_manager != NULL || + (state.ext_output_image_capture_source_manager != NULL && state.ext_image_copy_capture_manager != NULL);; + } + if (!can_capture) { + fprintf(stderr, "compositor doesn't support the screen capture protocol\n"); + return EXIT_FAILURE; + } + if (toplevel_identifier == NULL && wl_list_empty(&state.outputs)) { fprintf(stderr, "no wl_output\n"); return EXIT_FAILURE; } @@ -487,8 +778,6 @@ zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); } - - wl_display_roundtrip(state.display); } else { fprintf(stderr, "warning: zxdg_output_manager_v1 isn't available, " "guessing the output layout\n"); @@ -499,16 +788,17 @@ } } - if (state.screencopy_manager == NULL) { - fprintf(stderr, "compositor doesn't support wlr-screencopy-unstable-v1\n"); - return EXIT_FAILURE; + if (state.xdg_output_manager != NULL || toplevel_identifier != NULL) { + if (wl_display_roundtrip(state.display) < 0) { + fprintf(stderr, "wl_display_roundtrip() failed\n"); + return EXIT_FAILURE; + } } if (geometry_output != NULL) { struct grim_output *output; wl_list_for_each(output, &state.outputs, link) { - if (output->name != NULL && - strcmp(output->name, geometry_output) == 0) { + if (output->name != NULL && strcmp(output->name, geometry_output) == 0) { geometry = calloc(1, sizeof(struct grim_box)); memcpy(geometry, &output->logical_geometry, sizeof(struct grim_box)); @@ -521,42 +811,54 @@ } } - size_t n_pending = 0; - struct grim_output *output; - wl_list_for_each(output, &state.outputs, link) { - if (geometry != NULL && - !intersect_box(geometry, &output->logical_geometry)) { - continue; + if (toplevel_identifier != NULL) { + bool found = false; + struct grim_toplevel *toplevel; + wl_list_for_each(toplevel, &state.toplevels, link) { + if (strcmp(toplevel->identifier, toplevel_identifier) == 0) { + found = true; + break; + } } - if (use_greatest_scale && output->logical_scale > scale) { - scale = output->logical_scale; + if (!found) { + fprintf(stderr, "cannot find toplevel\n"); + return EXIT_FAILURE; } - output->screencopy_frame = zwlr_screencopy_manager_v1_capture_output( - state.screencopy_manager, with_cursor, output->wl_output); - zwlr_screencopy_frame_v1_add_listener(output->screencopy_frame, - &screencopy_frame_listener, output); + create_toplevel_capture(&state, toplevel, with_cursor); + } else { + struct grim_output *output; + wl_list_for_each(output, &state.outputs, link) { + if (geometry != NULL && + !intersect_box(geometry, &output->logical_geometry)) { + continue; + } + if (use_greatest_scale && output->logical_scale > scale) { + scale = output->logical_scale; + } - ++n_pending; - } + create_output_capture(&state, output, with_cursor); + } - if (n_pending == 0) { - fprintf(stderr, "supplied geometry did not intersect with any outputs\n"); - return EXIT_FAILURE; + if (wl_list_empty(&state.captures)) { + fprintf(stderr, "supplied geometry did not intersect with any outputs\n"); + return EXIT_FAILURE; + } } + size_t n_pending = wl_list_length(&state.captures); bool done = false; while (!done && wl_display_dispatch(state.display) != -1) { done = (state.n_done == n_pending); } if (!done) { - fprintf(stderr, "failed to screenshoot all outputs\n"); + fprintf(stderr, "failed to screenshoot all sources\n"); return EXIT_FAILURE; } if (geometry == NULL) { geometry = calloc(1, sizeof(struct grim_box)); - get_output_layout_extents(&state, geometry); + get_capture_layout_extents(&state, geometry); } pixman_image_t *image = render(&state, geometry, scale); @@ -604,21 +906,53 @@ free(output_filepath); pixman_image_unref(image); - struct grim_output *output_tmp; + struct grim_capture *capture, *capture_tmp; + wl_list_for_each_safe(capture, capture_tmp, &state.captures, link) { + wl_list_remove(&capture->link); + if (capture->ext_image_copy_capture_frame != NULL) { + ext_image_copy_capture_frame_v1_destroy(capture->ext_image_copy_capture_frame); + } + if (capture->ext_image_copy_capture_session != NULL) { + ext_image_copy_capture_session_v1_destroy(capture->ext_image_copy_capture_session); + } + if (capture->screencopy_frame != NULL) { + zwlr_screencopy_frame_v1_destroy(capture->screencopy_frame); + } + destroy_buffer(capture->buffer); + free(capture); + } + struct grim_output *output, *output_tmp; wl_list_for_each_safe(output, output_tmp, &state.outputs, link) { wl_list_remove(&output->link); free(output->name); - if (output->screencopy_frame != NULL) { - zwlr_screencopy_frame_v1_destroy(output->screencopy_frame); - } - destroy_buffer(output->buffer); if (output->xdg_output != NULL) { zxdg_output_v1_destroy(output->xdg_output); } wl_output_release(output->wl_output); free(output); } - zwlr_screencopy_manager_v1_destroy(state.screencopy_manager); + struct grim_toplevel *toplevel, *toplevel_tmp; + wl_list_for_each_safe(toplevel, toplevel_tmp, &state.toplevels, link) { + wl_list_remove(&toplevel->link); + free(toplevel->identifier); + ext_foreign_toplevel_handle_v1_destroy(toplevel->handle); + free(toplevel); + } + if (state.foreign_toplevel_list != NULL) { + ext_foreign_toplevel_list_v1_destroy(state.foreign_toplevel_list); + } + if (state.ext_output_image_capture_source_manager != NULL) { + ext_output_image_capture_source_manager_v1_destroy(state.ext_output_image_capture_source_manager); + } + if (state.ext_foreign_toplevel_image_capture_source_manager != NULL) { + ext_foreign_toplevel_image_capture_source_manager_v1_destroy(state.ext_foreign_toplevel_image_capture_source_manager); + } + if (state.ext_image_copy_capture_manager != NULL) { + ext_image_copy_capture_manager_v1_destroy(state.ext_image_copy_capture_manager); + } + if (state.screencopy_manager != NULL) { + zwlr_screencopy_manager_v1_destroy(state.screencopy_manager); + } if (state.xdg_output_manager != NULL) { zxdg_output_manager_v1_destroy(state.xdg_output_manager); } @@ -626,6 +960,5 @@ wl_registry_destroy(state.registry); wl_display_disconnect(state.display); free(geometry); - free(geometry_output); return EXIT_SUCCESS; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/meson.build new/grim-1.5.0/meson.build --- old/grim-1.4.1/meson.build 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/meson.build 2025-07-06 20:24:01.000000000 +0200 @@ -1,32 +1,32 @@ project( 'grim', 'c', - version: '1.4.1', + version: '1.5.0', license: 'MIT', meson_version: '>=0.59.0', default_options: ['c_std=c11', 'warning_level=3', 'werror=true'], ) -add_project_arguments('-Wno-unused-parameter', language: 'c') - -grim_inc = include_directories('include') - cc = meson.get_compiler('c') +add_project_arguments(cc.get_supported_arguments([ + '-Wno-unused-parameter', + '-Wundef', +]), language: 'c') + png = dependency('libpng') jpeg = dependency('libjpeg', required: get_option('jpeg')) math = cc.find_library('m') pixman = dependency('pixman-1') realtime = cc.find_library('rt') wayland_client = dependency('wayland-client') -wayland_protos = dependency('wayland-protocols', version: '>=1.14') - -if jpeg.found() - add_project_arguments('-DHAVE_JPEG', language: 'c') -endif is_le = host_machine.endian() == 'little' -add_project_arguments('-DGRIM_LITTLE_ENDIAN=@0@'.format(is_le.to_int()), language: 'c') +add_project_arguments([ + '-D_POSIX_C_SOURCE=200809L', + '-DGRIM_LITTLE_ENDIAN=@0@'.format(is_le.to_int()), + '-DHAVE_JPEG=@0@'.format(jpeg.found().to_int()), +], language: 'c') subdir('contrib/completions') subdir('protocol') @@ -42,7 +42,6 @@ ] grim_deps = [ - client_protos, math, pixman, png, @@ -57,31 +56,15 @@ executable( 'grim', - files(grim_files), + [files(grim_files), protocols_src], dependencies: grim_deps, - include_directories: [grim_inc], + include_directories: 'include', install: true, ) -scdoc = find_program('scdoc', required: get_option('man-pages')) +subdir('doc') -if scdoc.found() - man_pages = ['grim.1.scd'] - - 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: scdoc, - feed: true, - capture: true, - install: true, - install_dir: '@0@/man@1@'.format(get_option('mandir'), section), - ) - endforeach -endif +summary({ + 'JPEG': jpeg.found(), + 'Manual pages': scdoc.found(), +}, bool_yn: true) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/output-layout.c new/grim-1.5.0/output-layout.c --- old/grim-1.4.1/output-layout.c 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/output-layout.c 2025-07-06 20:24:01.000000000 +0200 @@ -1,27 +1,30 @@ -#define _XOPEN_SOURCE 500 #include <limits.h> #include <math.h> #include "output-layout.h" #include "grim.h" -void get_output_layout_extents(struct grim_state *state, struct grim_box *box) { +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +void get_capture_layout_extents(struct grim_state *state, struct grim_box *box) { int32_t x1 = INT_MAX, y1 = INT_MAX; int32_t x2 = INT_MIN, y2 = INT_MIN; - struct grim_output *output; - wl_list_for_each(output, &state->outputs, link) { - if (output->logical_geometry.x < x1) { - x1 = output->logical_geometry.x; + struct grim_capture *capture; + wl_list_for_each(capture, &state->captures, link) { + if (capture->logical_geometry.x < x1) { + x1 = capture->logical_geometry.x; } - if (output->logical_geometry.y < y1) { - y1 = output->logical_geometry.y; + if (capture->logical_geometry.y < y1) { + y1 = capture->logical_geometry.y; } - if (output->logical_geometry.x + output->logical_geometry.width > x2) { - x2 = output->logical_geometry.x + output->logical_geometry.width; + if (capture->logical_geometry.x + capture->logical_geometry.width > x2) { + x2 = capture->logical_geometry.x + capture->logical_geometry.width; } - if (output->logical_geometry.y + output->logical_geometry.height > y2) { - y2 = output->logical_geometry.y + output->logical_geometry.height; + if (capture->logical_geometry.y + capture->logical_geometry.height > y2) { + y2 = capture->logical_geometry.y + capture->logical_geometry.height; } } @@ -57,10 +60,10 @@ } void guess_output_logical_geometry(struct grim_output *output) { - output->logical_geometry.x = output->geometry.x; - output->logical_geometry.y = output->geometry.y; - output->logical_geometry.width = output->geometry.width / output->scale; - output->logical_geometry.height = output->geometry.height / output->scale; + output->logical_geometry.x = output->fallback_x; + output->logical_geometry.y = output->fallback_y; + output->logical_geometry.width = output->mode_width / output->scale; + output->logical_geometry.height = output->mode_height / output->scale; apply_output_transform(output->transform, &output->logical_geometry.width, &output->logical_geometry.height); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/protocol/meson.build new/grim-1.5.0/protocol/meson.build --- old/grim-1.4.1/protocol/meson.build 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/protocol/meson.build 2025-07-06 20:24:01.000000000 +0200 @@ -1,47 +1,32 @@ +wayland_protos = dependency('wayland-protocols', version: '>=1.37') wl_protocol_dir = wayland_protos.get_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 = dependency('wayland-scanner', version: '>=1.14.91', native: true) +wayland_scanner_path = wayland_scanner.get_variable(pkgconfig: 'wayland_scanner') +wayland_scanner_prog = find_program(wayland_scanner_path, native: true) wayland_scanner_code = generator( - wayland_scanner, + wayland_scanner_prog, output: '@[email protected]', - arguments: [code_type, '@INPUT@', '@OUTPUT@'], + arguments: ['private-code', '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( - wayland_scanner, - output: '@[email protected]', + wayland_scanner_prog, + output: '@[email protected]', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) -client_protocols = [ - [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], - ['wlr-screencopy-unstable-v1.xml'], +protocols = [ + wl_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml', + wl_protocol_dir / 'staging/ext-image-capture-source/ext-image-capture-source-v1.xml', + wl_protocol_dir / 'staging/ext-image-copy-capture/ext-image-copy-capture-v1.xml', + wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', + 'wlr-screencopy-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) +protocols_src = [] +foreach xml : protocols + protocols_src += wayland_scanner_code.process(xml) + protocols_src += 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/render.c new/grim-1.5.0/render.c --- old/grim-1.4.1/render.c 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/render.c 2025-07-06 20:24:01.000000000 +0200 @@ -9,6 +9,8 @@ #include "output-layout.h" #include "render.h" +#include "wlr-screencopy-unstable-v1-protocol.h" + static pixman_format_code_t get_pixman_format(enum wl_shm_format wl_fmt) { switch (wl_fmt) { #if GRIM_LITTLE_ENDIAN @@ -87,6 +89,15 @@ } } +bool is_format_supported(enum wl_shm_format fmt) { + return get_pixman_format(fmt) != 0; +} + +uint32_t get_format_min_stride(enum wl_shm_format fmt, uint32_t width) { + uint32_t bits_per_pixel = PIXMAN_FORMAT_BPP(get_pixman_format(fmt)); + return ((width * bits_per_pixel + 0x1f) >> 5) * sizeof(uint32_t); +} + static void compute_composite_region(const struct pixman_f_transform *out2com, int output_width, int output_height, struct grim_box *dest, bool *grid_aligned) { @@ -141,9 +152,9 @@ return NULL; } - struct grim_output *output; - wl_list_for_each(output, &state->outputs, link) { - struct grim_buffer *buffer = output->buffer; + struct grim_capture *capture; + wl_list_for_each(capture, &state->captures, link) { + struct grim_buffer *buffer = capture->buffer; if (buffer == NULL) { continue; } @@ -155,18 +166,17 @@ return NULL; } - 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 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); + int32_t output_x = capture->logical_geometry.x - geometry->x; + int32_t output_y = capture->logical_geometry.y - geometry->y; + int32_t output_width = capture->logical_geometry.width; + int32_t output_height = capture->logical_geometry.height; + + int32_t raw_output_width = buffer->width; + int32_t raw_output_height = buffer->height; + apply_output_transform(capture->transform, &raw_output_width, &raw_output_height); - int output_flipped_x = get_output_flipped(output->transform); - int output_flipped_y = output->screencopy_frame_flags & + int output_flipped_x = get_output_flipped(capture->transform); + int output_flipped_y = capture->screencopy_frame_flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT ? -1 : 1; pixman_image_t *output_image = pixman_image_create_bits( @@ -182,14 +192,14 @@ struct pixman_f_transform out2com; pixman_f_transform_init_identity(&out2com); pixman_f_transform_translate(&out2com, NULL, - -(double)output->geometry.width / 2, - -(double)output->geometry.height / 2); + -(double)buffer->width / 2, + -(double)buffer->height / 2); pixman_f_transform_scale(&out2com, NULL, (double)output_width / raw_output_width, (double)output_height * output_flipped_y / raw_output_height); pixman_f_transform_rotate(&out2com, NULL, - round(cos(get_output_rotation(output->transform))), - round(sin(get_output_rotation(output->transform)))); + round(cos(get_output_rotation(capture->transform))), + round(sin(get_output_rotation(capture->transform)))); pixman_f_transform_scale(&out2com, NULL, output_flipped_x, 1); pixman_f_transform_translate(&out2com, NULL, (double)output_width / 2, @@ -236,10 +246,10 @@ } bool overlapping = false; - struct grim_output *other_output; - wl_list_for_each(other_output, &state->outputs, link) { - if (output != other_output && intersect_box(&output->logical_geometry, - &other_output->logical_geometry)) { + struct grim_capture *other_capture; + wl_list_for_each(other_capture, &state->captures, link) { + if (capture != other_capture && intersect_box(&capture->logical_geometry, + &other_capture->logical_geometry)) { overlapping = true; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.4.1/write_jpg.c new/grim-1.5.0/write_jpg.c --- old/grim-1.4.1/write_jpg.c 2023-06-14 14:25:09.000000000 +0200 +++ new/grim-1.5.0/write_jpg.c 2025-07-06 20:24:01.000000000 +0200 @@ -40,6 +40,17 @@ jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); + // Ensure 444 subsampling instead of 420; this significantly improves + // the accuracy with which colored text and single pixel features are + // rendered. Probably due to sRGB-incorrect blending, chroma subsampling + // can introduce significant visible changes in brightness, even at 100% + // quality. Note that anyone editing and resaving the image as 420 may + // encounter these issues again. + for (int i = 0; i < cinfo.num_components; i++) { + cinfo.comp_info[i].h_samp_factor = 1; + cinfo.comp_info[i].v_samp_factor = 1; + } + jpeg_start_compress(&cinfo, TRUE); while (cinfo.next_scanline < cinfo.image_height) {
