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, &registry_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) {

Reply via email to