summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Samoilov <[email protected]>2026-07-19 23:26:32 +0400
committerLuke from DC <[email protected]>2026-08-30 20:01:29 +0000
commit60057b83ba1bbb209adc997ddcf0f234d832d426 (patch)
tree3105636f026a73d427ed48ff489a90ba7f89c83f
parent2374fc3667d926f64a638d46e571a560d1f0a59f (diff)
downloadmate-utils-60057b83ba1bbb209adc997ddcf0f234d832d426.tar.bz2
mate-utils-60057b83ba1bbb209adc997ddcf0f234d832d426.tar.xz
Add Wayland support for mate-screenshot
-rw-r--r--configure.ac47
-rw-r--r--mate-screenshot/protocols/wlr-screencopy-unstable-v1.xml232
-rw-r--r--mate-screenshot/src/Makefile.am84
-rw-r--r--mate-screenshot/src/mate-screenshot.c81
-rw-r--r--mate-screenshot/src/screenshot-utils.c97
-rw-r--r--mate-screenshot/src/screenshot-utils.h3
-rw-r--r--mate-screenshot/src/wayland-screenshot.c755
-rw-r--r--mate-screenshot/src/wayland-screenshot.h92
-rw-r--r--mate-screenshot/src/wayland-select-region.c493
-rw-r--r--mate-screenshot/src/wayland-select-region.h44
10 files changed, 1922 insertions, 6 deletions
diff --git a/configure.ac b/configure.ac
index f8993f80..43797901 100644
--- a/configure.ac
+++ b/configure.ac
@@ -187,9 +187,54 @@ AC_SUBST(GDICT_VERSION)
# xext for mate-screenshot; in theory checking for xext should be
# enough but there are a lot of broken distros out there
PKG_CHECK_MODULES(XSHAPE, xext x11,
- [AC_CHECK_HEADERS(X11/extensions/shape.h, XSHAPE_LIBS="-lXext -lX11")])
+ [
+ AC_CHECK_HEADERS(X11/extensions/shape.h, XSHAPE_LIBS="-lXext -lX11")
+ ])
AC_SUBST(XSHAPE_LIBS)
+# Wayland support for mate-screenshot
+AC_ARG_ENABLE([wayland],
+ [AS_HELP_STRING([--enable-wayland=@<:@yes/no/auto@:>@],
+ [Enable Wayland support (default: auto)])],
+ [enable_wayland=$enableval],
+ [enable_wayland=auto])
+
+AS_CASE([$enable_wayland],
+ [auto],
+ [
+ PKG_CHECK_MODULES([WAYLAND_CLIENT], [wayland-client >= 1.18.0],
+ [enable_wayland=yes],
+ [enable_wayland=no])
+ ],
+ [yes],
+ [
+ PKG_CHECK_MODULES([WAYLAND_CLIENT], [wayland-client >= 1.18.0])
+ ],
+ [no], [],
+ [*], [AC_MSG_ERROR([Invalid value for --enable-wayland])]
+)
+
+AS_IF([test "x$enable_wayland" = "xyes"],
+ [
+ PKG_CHECK_MODULES([WAYLAND_PROTOCOLS], [wayland-protocols >= 1.24])
+ PKG_CHECK_MODULES([GDK_WAYLAND], [gdk-wayland-3.0 >= $GTK_REQUIRED])
+ PKG_CHECK_MODULES([GTK_LAYER_SHELL], [gtk-layer-shell-0 >= 0.6])
+
+ # Check for wayland-scanner
+ AC_PATH_PROG([WAYLAND_SCANNER], [wayland-scanner])
+ AS_IF([test -z "$WAYLAND_SCANNER"],
+ [AC_MSG_ERROR([wayland-scanner not found. Install wayland-tools or wayland-scanner.])])
+
+ # Get wayland-protocols directory
+ WAYLAND_PROTOCOLS_DIR=$($PKG_CONFIG --variable=pkgdatadir wayland-protocols)
+ AC_SUBST([WAYLAND_PROTOCOLS_DIR])
+
+ AC_DEFINE([ENABLE_WAYLAND], [1], [Enable Wayland support])
+ ]
+)
+
+AM_CONDITIONAL([ENABLE_WAYLAND], [test "x$enable_wayland" = "xyes"])
+
AC_ARG_ENABLE([gdict-applet],
[AS_HELP_STRING([--enable-gdict-applet=@<:@yes/no@:>@],
[Whether to build the Dictionary mate-panel applet])],
diff --git a/mate-screenshot/protocols/wlr-screencopy-unstable-v1.xml b/mate-screenshot/protocols/wlr-screencopy-unstable-v1.xml
new file mode 100644
index 00000000..acc82120
--- /dev/null
+++ b/mate-screenshot/protocols/wlr-screencopy-unstable-v1.xml
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_screencopy_unstable_v1">
+ <copyright>
+ Copyright © 2018 Simon Ser
+ Copyright © 2019 Andri Yngvason
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom
+ the Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="screen content capturing on client buffers">
+ This protocol allows clients to ask the compositor to copy part of the
+ screen content to a client buffer.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+ </description>
+
+ <interface name="zwlr_screencopy_manager_v1" version="3">
+ <description summary="manager to inform clients and begin capturing">
+ This object is a manager which offers requests to start capturing from a
+ source.
+ </description>
+
+ <request name="capture_output">
+ <description summary="capture an output">
+ Capture the next frame of an entire output.
+ </description>
+ <arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
+ <arg name="overlay_cursor" type="int"
+ summary="composite cursor onto the frame"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+
+ <request name="capture_output_region">
+ <description summary="capture an output's region">
+ Capture the next frame of an output's region.
+
+ The region is given in output logical coordinates, see
+ xdg_output.logical_size. The region will be clipped to the output's
+ extents.
+ </description>
+ <arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
+ <arg name="overlay_cursor" type="int"
+ summary="composite cursor onto the frame"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the manager">
+ All objects created by the manager will still remain valid, until their
+ appropriate destroy request has been called.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_screencopy_frame_v1" version="3">
+ <description summary="a frame ready for copy">
+ This object represents a single frame.
+
+ When created, a series of buffer events will be sent, each representing a
+ supported buffer type. The "buffer_done" event is sent afterwards to
+ indicate that all supported buffer types have been enumerated. The client
+ will then be able to send a "copy" request. If the capture is successful,
+ the compositor will send a "flags" followed by a "ready" event.
+
+ For objects version 2 or lower, wl_shm buffers are always supported, ie.
+ the "buffer" event is guaranteed to be sent.
+
+ If the capture failed, the "failed" event is sent. This can happen anytime
+ before the "ready" event.
+
+ Once either a "ready" or a "failed" event is received, the client should
+ destroy the frame.
+ </description>
+
+ <event name="buffer">
+ <description summary="wl_shm buffer information">
+ Provides information about wl_shm buffer parameters that need to be
+ used for this frame. This event is sent once after the frame is created
+ if wl_shm buffers are supported.
+ </description>
+ <arg name="format" type="uint" enum="wl_shm.format" summary="buffer format"/>
+ <arg name="width" type="uint" summary="buffer width"/>
+ <arg name="height" type="uint" summary="buffer height"/>
+ <arg name="stride" type="uint" summary="buffer stride"/>
+ </event>
+
+ <request name="copy">
+ <description summary="copy the frame">
+ Copy the frame to the supplied buffer. The buffer must have a the
+ correct size, see zwlr_screencopy_frame_v1.buffer and
+ zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a
+ supported format.
+
+ If the frame is successfully copied, a "flags" and a "ready" events are
+ sent. Otherwise, a "failed" event is sent.
+ </description>
+ <arg name="buffer" type="object" interface="wl_buffer"/>
+ </request>
+
+ <enum name="error">
+ <entry name="already_used" value="0"
+ summary="the object has already been used to copy a wl_buffer"/>
+ <entry name="invalid_buffer" value="1"
+ summary="buffer attributes are invalid"/>
+ </enum>
+
+ <enum name="flags" bitfield="true">
+ <entry name="y_invert" value="1" summary="contents are y-inverted"/>
+ </enum>
+
+ <event name="flags">
+ <description summary="frame flags">
+ Provides flags about the frame. This event is sent once before the
+ "ready" event.
+ </description>
+ <arg name="flags" type="uint" enum="flags" summary="frame flags"/>
+ </event>
+
+ <event name="ready">
+ <description summary="indicates frame is available for reading">
+ Called as soon as the frame is copied, indicating it is available
+ for reading. This event includes the time at which presentation happened
+ at.
+
+ The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
+ each component being an unsigned 32-bit value. Whole seconds are in
+ tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
+ and the additional fractional part in tv_nsec as nanoseconds. Hence,
+ for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
+ may have an arbitrary offset at start.
+
+ After receiving this event, the client should destroy the object.
+ </description>
+ <arg name="tv_sec_hi" type="uint"
+ summary="high 32 bits of the seconds part of the timestamp"/>
+ <arg name="tv_sec_lo" type="uint"
+ summary="low 32 bits of the seconds part of the timestamp"/>
+ <arg name="tv_nsec" type="uint"
+ summary="nanoseconds part of the timestamp"/>
+ </event>
+
+ <event name="failed">
+ <description summary="frame copy failed">
+ This event indicates that the attempted frame copy has failed.
+
+ After receiving this event, the client should destroy the object.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="delete this object, used or not">
+ Destroys the frame. This request can be sent at any time by the client.
+ </description>
+ </request>
+
+ <!-- Version 2 additions -->
+ <request name="copy_with_damage" since="2">
+ <description summary="copy the frame when it's damaged">
+ Same as copy, except it waits until there is damage to copy.
+ </description>
+ <arg name="buffer" type="object" interface="wl_buffer"/>
+ </request>
+
+ <event name="damage" since="2">
+ <description summary="carries the coordinates of the damaged region">
+ This event is sent right before the ready event when copy_with_damage is
+ requested. It may be generated multiple times for each copy_with_damage
+ request.
+
+ The arguments describe a box around an area that has changed since the
+ last copy request that was derived from the current screencopy manager
+ instance.
+
+ The union of all regions received between the call to copy_with_damage
+ and a ready event is the total damage since the prior ready event.
+ </description>
+ <arg name="x" type="uint" summary="damaged x coordinates"/>
+ <arg name="y" type="uint" summary="damaged y coordinates"/>
+ <arg name="width" type="uint" summary="current width"/>
+ <arg name="height" type="uint" summary="current height"/>
+ </event>
+
+ <!-- Version 3 additions -->
+ <event name="linux_dmabuf" since="3">
+ <description summary="linux-dmabuf buffer information">
+ Provides information about linux-dmabuf buffer parameters that need to
+ be used for this frame. This event is sent once after the frame is
+ created if linux-dmabuf buffers are supported.
+ </description>
+ <arg name="format" type="uint" summary="fourcc pixel format"/>
+ <arg name="width" type="uint" summary="buffer width"/>
+ <arg name="height" type="uint" summary="buffer height"/>
+ </event>
+
+ <event name="buffer_done" since="3">
+ <description summary="all buffer types reported">
+ This event is sent once after all buffer events have been sent.
+
+ The client should proceed to create a buffer of one of the supported
+ types, and send a "copy" request.
+ </description>
+ </event>
+ </interface>
+</protocol>
diff --git a/mate-screenshot/src/Makefile.am b/mate-screenshot/src/Makefile.am
index dd7b46ef..ded207c8 100644
--- a/mate-screenshot/src/Makefile.am
+++ b/mate-screenshot/src/Makefile.am
@@ -68,8 +68,92 @@ mate_screenshot_LDADD = \
screenshot-resources.h screenshot-resources.c: $(srcdir)/../data/org.mate.screenshot.gresource.xml Makefile $(shell $(GLIB_COMPILE_RESOURCES) --generate-dependencies --sourcedir $(srcdir)/../data $(srcdir)/../data/org.mate.screenshot.gresource.xml)
$(AM_V_GEN) XMLLINT=$(XMLLINT) $(GLIB_COMPILE_RESOURCES) --target $@ --sourcedir $(srcdir)/../data --generate --c-name screenshot $<
+# Wayland protocol support
+if ENABLE_WAYLAND
+
+# Protocol XML sources
+EXT_IMAGE_CAPTURE_SOURCE_XML = $(WAYLAND_PROTOCOLS_DIR)/staging/ext-image-capture-source/ext-image-capture-source-v1.xml
+EXT_IMAGE_COPY_CAPTURE_XML = $(WAYLAND_PROTOCOLS_DIR)/staging/ext-image-copy-capture/ext-image-copy-capture-v1.xml
+EXT_FOREIGN_TOPLEVEL_XML = $(WAYLAND_PROTOCOLS_DIR)/staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml
+WLR_SCREENCOPY_XML = $(srcdir)/../protocols/wlr-screencopy-unstable-v1.xml
+
+# Generate per-protocol client headers and code
+ext-image-capture-source-client.h: $(EXT_IMAGE_CAPTURE_SOURCE_XML)
+ $(AM_V_GEN) $(WAYLAND_SCANNER) client-header $< $@
+
+ext-image-capture-source-client.c: $(EXT_IMAGE_CAPTURE_SOURCE_XML)
+ $(AM_V_GEN) $(WAYLAND_SCANNER) private-code $< $@
+
+ext-image-copy-capture-client.h: $(EXT_IMAGE_COPY_CAPTURE_XML)
+ $(AM_V_GEN) $(WAYLAND_SCANNER) client-header $< $@
+
+ext-image-copy-capture-client.c: $(EXT_IMAGE_COPY_CAPTURE_XML)
+ $(AM_V_GEN) $(WAYLAND_SCANNER) private-code $< $@
+
+wlr-screencopy-client.h: $(WLR_SCREENCOPY_XML)
+ $(AM_V_GEN) $(WAYLAND_SCANNER) client-header $< $@
+
+wlr-screencopy-client.c: $(WLR_SCREENCOPY_XML)
+ $(AM_V_GEN) $(WAYLAND_SCANNER) private-code $< $@
+
+ext-foreign-toplevel-list-client.h: $(EXT_FOREIGN_TOPLEVEL_XML)
+ $(AM_V_GEN) $(WAYLAND_SCANNER) client-header $< $@
+
+ext-foreign-toplevel-list-client.c: $(EXT_FOREIGN_TOPLEVEL_XML)
+ $(AM_V_GEN) $(WAYLAND_SCANNER) private-code $< $@
+
+WAYLAND_PROTOCOL_SRCS = \
+ ext-image-capture-source-client.c \
+ ext-image-capture-source-client.h \
+ ext-image-copy-capture-client.c \
+ ext-image-copy-capture-client.h \
+ wlr-screencopy-client.c \
+ wlr-screencopy-client.h \
+ ext-foreign-toplevel-list-client.c \
+ ext-foreign-toplevel-list-client.h \
+ $(NULL)
+
+# Add Wayland-specific sources and flags
+mate_screenshot_SOURCES += \
+ wayland-screenshot.c \
+ wayland-screenshot.h \
+ wayland-select-region.c \
+ wayland-select-region.h \
+ $(WAYLAND_PROTOCOL_SRCS) \
+ $(NULL)
+
+BUILT_SOURCES += \
+ ext-image-capture-source-client.h \
+ ext-image-copy-capture-client.h \
+ wlr-screencopy-client.h \
+ ext-foreign-toplevel-list-client.h \
+ $(NULL)
+
+mate_screenshot_CFLAGS += \
+ $(WAYLAND_CLIENT_CFLAGS) \
+ $(WAYLAND_PROTOCOLS_CFLAGS) \
+ $(GDK_WAYLAND_CFLAGS) \
+ $(GTK_LAYER_SHELL_CFLAGS) \
+ $(NULL)
+
+mate_screenshot_LDADD += \
+ $(WAYLAND_CLIENT_LIBS) \
+ $(WAYLAND_PROTOCOLS_LIBS) \
+ $(GDK_WAYLAND_LIBS) \
+ $(GTK_LAYER_SHELL_LIBS) \
+ $(NULL)
+endif
+
CLEANFILES = \
$(BUILT_SOURCES) \
+ ext-image-capture-source-client.h \
+ ext-image-capture-source-client.c \
+ ext-image-copy-capture-client.h \
+ ext-image-copy-capture-client.c \
+ wlr-screencopy-client.h \
+ wlr-screencopy-client.c \
+ ext-foreign-toplevel-list-client.h \
+ ext-foreign-toplevel-list-client.c \
$(NULL)
install-exec-local:
diff --git a/mate-screenshot/src/mate-screenshot.c b/mate-screenshot/src/mate-screenshot.c
index df203a88..a4d37141 100644
--- a/mate-screenshot/src/mate-screenshot.c
+++ b/mate-screenshot/src/mate-screenshot.c
@@ -23,7 +23,9 @@
#include <config.h>
#endif
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif
#include <gdk/gdkkeysyms.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -40,9 +42,16 @@
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <pwd.h>
+#ifdef GDK_WINDOWING_X11
#include <X11/Xutil.h>
+#endif
#include <canberra-gtk.h>
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+#include "wayland-screenshot.h"
+#include "wayland-select-region.h"
+#endif
+
#include "screenshot-shadow.h"
#include "screenshot-utils.h"
#include "screenshot-save.h"
@@ -122,6 +131,8 @@ static GtkWidget *effect_combo = NULL;
static GtkWidget *effect_label = NULL;
static GtkWidget *effects_vbox = NULL;
static GtkWidget *delay_hbox = NULL;
+static GtkWidget *window_radio = NULL;
+static GtkWidget *area_radio = NULL;
void loop_dialog_screenshot (void);
@@ -391,6 +402,15 @@ create_effects_frame (GtkWidget *outer_vbox,
gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
gtk_widget_show (combo);
effect_combo = combo;
+
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+ if (wayland_screenshot_is_available ())
+ {
+ gtk_widget_hide (border_check);
+ gtk_widget_hide (effect_label);
+ gtk_widget_hide (effect_combo);
+ }
+#endif
}
static void
@@ -458,6 +478,13 @@ create_screenshot_frame (GtkWidget *outer_vbox,
gtk_box_pack_start (GTK_BOX (vbox), radio, FALSE, FALSE, 0);
group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio));
gtk_widget_show (radio);
+ window_radio = radio;
+
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+ /** Capturing individual windows currently not available in Wayland **/
+ if (wayland_screenshot_is_available ())
+ gtk_widget_hide (window_radio);
+#endif
/** Grab area of the desktop **/
radio = gtk_radio_button_new_with_mnemonic (group,
@@ -469,6 +496,7 @@ create_screenshot_frame (GtkWidget *outer_vbox,
GINT_TO_POINTER (TARGET_TOGGLE_AREA));
gtk_box_pack_start (GTK_BOX (vbox), radio, FALSE, FALSE, 0);
gtk_widget_show (radio);
+ area_radio = radio;
/** Grab after delay **/
delay_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
@@ -714,6 +742,7 @@ save_screenshot_in_clipboard (GdkDisplay *display, GdkPixbuf *screenshot)
GtkClipboard *clipboard =
gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD);
gtk_clipboard_set_image (clipboard, screenshot);
+ gtk_clipboard_store (clipboard);
}
static void
@@ -799,7 +828,9 @@ play_sound_effect (GdkWindow *window)
if (res < 0)
goto done;
- if (window != NULL)
+#ifdef GDK_WINDOWING_X11
+ /* On Wayland, we can't use X11 window IDs for sound routing */
+ if (!wayland_screenshot_is_available () && window != NULL)
{
res = ca_proplist_setf (p,
CA_PROP_WINDOW_X11_XID,
@@ -808,6 +839,7 @@ play_sound_effect (GdkWindow *window)
if (res < 0)
goto done;
}
+#endif
ca_context_play_full (c, 0, p, NULL, NULL);
@@ -1101,6 +1133,18 @@ rectangle_found_cb (GdkRectangle *rectangle)
static void
prepare_screenshot (void)
{
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+ if (take_area_shot && wayland_screenshot_is_available ())
+ {
+ GdkRectangle region;
+ if (wayland_select_region (&region))
+ push_check_file_job (&region);
+ else
+ gtk_main_quit ();
+ return;
+ }
+#endif
+
if (take_area_shot)
screenshot_select_area_async (rectangle_found_cb);
else
@@ -1347,6 +1391,17 @@ main (int argc, char *argv[])
exit (1);
}
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+ if (wayland_screenshot_init ())
+ {
+ g_info ("Wayland screenshot support initialized");
+ }
+ else
+ {
+ g_info ("Wayland screenshot not available, using X11");
+ }
+#endif
+
gtk_window_set_default_icon_name (SCREENSHOOTER_ICON);
settings = g_settings_new (MATE_SCREENSHOT_SCHEMA);
@@ -1373,10 +1428,34 @@ main (int argc, char *argv[])
if (delay_arg > 0)
delay = delay_arg;
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+ if (wayland_screenshot_is_available ())
+ {
+ if (window_arg)
+ {
+ g_printerr (_("Error: window capture is not supported on Wayland.\n"));
+ exit (1);
+ }
+
+ if (include_border_arg)
+ g_printerr (_("Warning: --include-border is not supported on Wayland.\n"));
+
+ if (disable_border_arg)
+ g_printerr (_("Warning: --remove-border is not supported on Wayland.\n"));
+
+ if (border_effect_arg)
+ g_printerr (_("Warning: --border-effect is not supported on Wayland.\n"));
+ }
+#endif
+
g_unix_signal_add (SIGINT, signal_handler, NULL);
g_unix_signal_add (SIGTERM, signal_handler, NULL);
loop_dialog_screenshot();
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+ wayland_screenshot_cleanup ();
+#endif
+
return EXIT_SUCCESS;
}
diff --git a/mate-screenshot/src/screenshot-utils.c b/mate-screenshot/src/screenshot-utils.c
index 527c68c0..f610440c 100644
--- a/mate-screenshot/src/screenshot-utils.c
+++ b/mate-screenshot/src/screenshot-utils.c
@@ -24,16 +24,22 @@
#include "screenshot-utils.h"
-#include <X11/Xatom.h>
-#include <gdk/gdkx.h>
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+#include "wayland-screenshot.h"
+#endif
+
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <glib/gi18n.h>
+#ifdef GDK_WINDOWING_X11
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
#ifdef HAVE_X11_EXTENSIONS_SHAPE_H
#include <X11/extensions/shape.h>
#endif
+#endif /* GDK_WINDOWING_X11 */
static GtkWidget *selection_window;
@@ -46,10 +52,15 @@ static GtkWidget *selection_window;
gboolean
screenshot_grab_lock (void)
{
+#ifdef GDK_WINDOWING_X11
GdkAtom selection_atom;
gboolean result = FALSE;
GdkDisplay *display;
+ /* On Wayland, we don't need to grab the server */
+ if (wayland_screenshot_is_available ())
+ return TRUE;
+
selection_atom = gdk_atom_intern (SELECTION_NAME, FALSE);
gdk_x11_grab_server ();
@@ -77,6 +88,9 @@ screenshot_grab_lock (void)
gdk_display_flush (display);
return result;
+#else
+ return TRUE;
+#endif
}
void
@@ -84,11 +98,17 @@ screenshot_release_lock (void)
{
GdkDisplay *display;
+#ifdef GDK_WINDOWING_X11
+ /* On Wayland, we don't need to release the server grab */
+ if (wayland_screenshot_is_available ())
+ return;
+
if (selection_window)
{
gtk_widget_destroy (selection_window);
selection_window = NULL;
}
+#endif
display = gdk_display_get_default ();
gdk_display_flush (display);
@@ -97,6 +117,7 @@ screenshot_release_lock (void)
static GdkWindow *
screen_get_active_window (GdkScreen *screen)
{
+#ifdef GDK_WINDOWING_X11
GdkWindow *ret = NULL;
Atom type_return;
gint format_return;
@@ -104,6 +125,10 @@ screen_get_active_window (GdkScreen *screen)
gulong bytes_after_return;
guchar *data = NULL;
+ /* On Wayland, we can't get the active window this way */
+ if (wayland_screenshot_is_available ())
+ return NULL;
+
if (!gdk_x11_screen_supports_net_wm_hint (screen,
gdk_atom_intern_static_string ("_NET_ACTIVE_WINDOW")))
return NULL;
@@ -134,6 +159,9 @@ screen_get_active_window (GdkScreen *screen)
XFree (data);
return ret;
+#else
+ return NULL;
+#endif
}
static GdkWindow *
@@ -350,6 +378,8 @@ create_select_window (void)
{
GdkScreen *screen = gdk_screen_get_default ();
GtkWidget *window = gtk_window_new (GTK_WINDOW_POPUP);
+ GdkMonitor *monitor;
+ GdkRectangle monitor_geom;
GdkVisual *visual = gdk_screen_get_rgba_visual (screen);
if (gdk_screen_is_composited (screen) && visual)
@@ -360,8 +390,15 @@ create_select_window (void)
g_signal_connect (window, "draw", G_CALLBACK (draw), NULL);
- gtk_window_move (GTK_WINDOW (window), -100, -100);
- gtk_window_resize (GTK_WINDOW (window), 10, 10);
+ /* Make the window cover the entire screen so the seat grab
+ * delivers pointer events everywhere. */
+ monitor = gdk_display_get_primary_monitor (gdk_display_get_default ());
+ if (!monitor)
+ monitor = gdk_display_get_monitor (gdk_display_get_default (), 0);
+
+ gdk_monitor_get_geometry (monitor, &monitor_geom);
+ gtk_window_move (GTK_WINDOW (window), monitor_geom.x, monitor_geom.y);
+ gtk_window_resize (GTK_WINDOW (window), monitor_geom.width, monitor_geom.height);
gtk_widget_show (window);
return window;
}
@@ -446,12 +483,16 @@ out:
g_timeout_add (200, emit_select_callback_in_idle, cb_data);
}
+#ifdef GDK_WINDOWING_X11
static Window
find_wm_window (Window xid)
{
Window root, parent, *children;
unsigned int nchildren;
+ if (wayland_screenshot_is_available ())
+ return None;
+
do
{
if (XQueryTree (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xid, &root,
@@ -468,6 +509,7 @@ find_wm_window (Window xid)
}
while (TRUE);
}
+#endif /* GDK_WINDOWING_X11 */
static cairo_region_t *
make_region_with_monitors (GdkScreen *screen)
@@ -568,6 +610,7 @@ blank_region_in_pixbuf (GdkPixbuf *pixbuf, cairo_region_t *region)
* not be visible in the monitors, so that screenshot do not end up with content
* that the user won't ever see.
*/
+#ifdef GDK_WINDOWING_X11
static void
mask_monitors (GdkPixbuf *pixbuf, GdkWindow *root_window)
{
@@ -577,6 +620,10 @@ mask_monitors (GdkPixbuf *pixbuf, GdkWindow *root_window)
cairo_rectangle_int_t rect;
gint scale;
+ /* On Wayland, we don't need to mask monitors */
+ if (wayland_screenshot_is_available ())
+ return;
+
screen = gdk_window_get_screen (root_window);
scale = gdk_window_get_scale_factor (root_window);
@@ -595,6 +642,7 @@ mask_monitors (GdkPixbuf *pixbuf, GdkWindow *root_window)
cairo_region_destroy (region_with_monitors);
cairo_region_destroy (invisible_region);
}
+#endif /* GDK_WINDOWING_X11 */
GdkPixbuf *
screenshot_get_pixbuf (GdkWindow *window,
@@ -603,6 +651,43 @@ screenshot_get_pixbuf (GdkWindow *window,
gboolean include_border,
gboolean include_mask)
{
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+ if (wayland_screenshot_is_available ())
+ {
+ GdkPixbuf *screenshot = NULL;
+
+ if (rectangle)
+ {
+ /* On Wayland, capture the full output containing the region,
+ * then crop to the requested rectangle. */
+ screenshot = wayland_screenshot_capture_screen (include_pointer);
+ if (screenshot != NULL)
+ {
+ GdkPixbuf *cropped;
+ gint src_x = MAX (0, rectangle->x);
+ gint src_y = MAX (0, rectangle->y);
+ gint src_w = MIN (rectangle->width, gdk_pixbuf_get_width (screenshot) - src_x);
+ gint src_h = MIN (rectangle->height, gdk_pixbuf_get_height (screenshot) - src_y);
+
+ if (src_w > 0 && src_h > 0)
+ {
+ cropped = gdk_pixbuf_new_subpixbuf (screenshot, src_x, src_y, src_w, src_h);
+ g_object_unref (screenshot);
+ screenshot = cropped;
+ }
+ }
+ }
+ else
+ {
+ /* Capture entire screen */
+ screenshot = wayland_screenshot_capture_screen (include_pointer);
+ }
+
+ return screenshot;
+ }
+#endif
+
+#ifdef GDK_WINDOWING_X11
GdkWindow *root;
GdkPixbuf *screenshot;
gint x_real_orig, y_real_orig, x_orig, y_orig;
@@ -898,6 +983,10 @@ screenshot_get_pixbuf (GdkWindow *window,
}
return screenshot;
+#else
+ /* Fallback for unsupported platforms */
+ return NULL;
+#endif
}
void
diff --git a/mate-screenshot/src/screenshot-utils.h b/mate-screenshot/src/screenshot-utils.h
index 0b7e5700..eb613245 100644
--- a/mate-screenshot/src/screenshot-utils.h
+++ b/mate-screenshot/src/screenshot-utils.h
@@ -21,7 +21,10 @@
#define __SCREENSHOT_UTILS_H__
#include <gtk/gtk.h>
+
+#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
+#endif
G_BEGIN_DECLS
diff --git a/mate-screenshot/src/wayland-screenshot.c b/mate-screenshot/src/wayland-screenshot.c
new file mode 100644
index 00000000..d0b6d1b6
--- /dev/null
+++ b/mate-screenshot/src/wayland-screenshot.c
@@ -0,0 +1,755 @@
+/* Copyright (C) 2001-2006 Jonathan Blandford <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ * Copyright (C) 2012-2021 MATE Developers
+ *
+ * This file is part of MATE Utils.
+ *
+ * MATE Utils is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MATE Utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MATE Utils. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gdk/gdkwayland.h>
+
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+
+#include "wayland-screenshot.h"
+#include "screenshot-utils.h"
+#include <glib/gi18n.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "ext-image-capture-source-client.h"
+#include "ext-image-copy-capture-client.h"
+#include "wlr-screencopy-client.h"
+
+#include <wayland-client.h>
+
+typedef struct {
+ struct wl_display *display;
+ struct wl_registry *registry;
+ struct wl_shm *shm;
+ struct zwlr_screencopy_manager_v1 *screencopy_manager;
+ struct ext_output_image_capture_source_manager_v1 *output_image_capture_source_manager;
+ struct ext_image_copy_capture_manager_v1 *image_copy_capture_manager;
+} ClientData;
+
+typedef struct {
+ ClientData *client_data;
+ GdkMonitor *monitor;
+ struct zwlr_screencopy_frame_v1 *frame;
+ struct wl_buffer *buffer;
+ struct wl_shm_pool *pool;
+ unsigned char *shm_data;
+ int width;
+ int height;
+ int stride;
+ int size;
+ enum wl_shm_format format;
+ gboolean has_format;
+ gboolean capture_done;
+ gboolean capture_failed;
+ gboolean y_inverted;
+ struct ext_image_copy_capture_session_v1 *image_copy_capture_session;
+ struct ext_image_copy_capture_frame_v1 *image_copy_capture_frame;
+} OutputData;
+
+static gboolean wayland_initialized = FALSE;
+
+/* --- Helpers --- */
+
+static gboolean
+is_shm_format_supported (enum wl_shm_format format)
+{
+ return format == WL_SHM_FORMAT_ARGB8888 ||
+ format == WL_SHM_FORMAT_XRGB8888 ||
+ format == WL_SHM_FORMAT_ABGR8888 ||
+ format == WL_SHM_FORMAT_XBGR8888 ||
+ format == WL_SHM_FORMAT_BGR888;
+}
+
+static gint
+get_bpp_from_format (enum wl_shm_format format)
+{
+ switch (format)
+ {
+ case WL_SHM_FORMAT_ARGB8888:
+ case WL_SHM_FORMAT_XRGB8888:
+ case WL_SHM_FORMAT_ABGR8888:
+ case WL_SHM_FORMAT_XBGR8888:
+ return 4;
+ case WL_SHM_FORMAT_BGR888:
+ return 3;
+ default:
+ return 0;
+ }
+}
+
+static GdkPixbuf *
+convert_buffer_to_pixbuf (OutputData *output)
+{
+ guint8 *data = output->shm_data;
+ enum wl_shm_format format = output->format;
+ gboolean has_alpha = TRUE;
+ GdkPixbuf *pixbuf;
+
+ if (format == WL_SHM_FORMAT_ARGB8888 || format == WL_SHM_FORMAT_XRGB8888)
+ {
+ for (int y = 0; y < output->height; y++)
+ {
+ for (int x = 0; x < output->width; x++)
+ {
+ gint offset = y * output->stride + x * 4;
+ guint32 *px = (guint32 *)(gpointer)(data + offset);
+ guint8 blue = *px & 0xFF;
+ guint8 green = (*px >> 8) & 0xFF;
+ guint8 red = (*px >> 16) & 0xFF;
+ guint8 alpha = (*px >> 24) & 0xFF;
+ data[offset + 0] = red;
+ data[offset + 1] = green;
+ data[offset + 2] = blue;
+ data[offset + 3] = alpha;
+ }
+ }
+ }
+ else if (format == WL_SHM_FORMAT_ABGR8888 || format == WL_SHM_FORMAT_XBGR8888)
+ {
+ for (int y = 0; y < output->height; y++)
+ {
+ for (int x = 0; x < output->width; x++)
+ {
+ gint offset = y * output->stride + x * 4;
+ guint32 *px = (guint32 *)(gpointer)(data + offset);
+ guint8 red = *px & 0xFF;
+ guint8 green = (*px >> 8) & 0xFF;
+ guint8 blue = (*px >> 16) & 0xFF;
+ guint8 alpha = (*px >> 24) & 0xFF;
+ data[offset + 0] = red;
+ data[offset + 1] = green;
+ data[offset + 2] = blue;
+ data[offset + 3] = alpha;
+ }
+ }
+ }
+ else if (format == WL_SHM_FORMAT_BGR888)
+ {
+ has_alpha = FALSE;
+ for (int y = 0; y < output->height; y++)
+ {
+ for (int x = 0; x < output->width; x++)
+ {
+ gint offset = y * output->stride + x * 3;
+ guint8 *px = (guint8 *)(gpointer)(data + offset);
+ guint8 blue = px[2];
+ guint8 green = px[1];
+ guint8 red = px[0];
+ data[offset + 0] = red;
+ data[offset + 1] = green;
+ data[offset + 2] = blue;
+ }
+ }
+ }
+ else
+ {
+ g_warning ("Wayland screenshot: unsupported pixel format: 0x%x", format);
+ return NULL;
+ }
+
+ pixbuf = gdk_pixbuf_new_from_data (data, GDK_COLORSPACE_RGB, has_alpha, 8,
+ output->width, output->height,
+ output->stride, NULL, NULL);
+ return pixbuf;
+}
+
+/* --- Registry --- */
+
+static void
+handle_global (void *data, struct wl_registry *registry, uint32_t name,
+ const char *interface, uint32_t version)
+{
+ ClientData *cd = data;
+
+ if (g_strcmp0 (interface, wl_shm_interface.name) == 0)
+ cd->shm = wl_registry_bind (registry, name, &wl_shm_interface, 1);
+ else if (g_strcmp0 (interface, zwlr_screencopy_manager_v1_interface.name) == 0)
+ cd->screencopy_manager = wl_registry_bind (registry, name,
+ &zwlr_screencopy_manager_v1_interface, 1);
+ else if (g_strcmp0 (interface, ext_output_image_capture_source_manager_v1_interface.name) == 0)
+ cd->output_image_capture_source_manager = wl_registry_bind (registry, name,
+ &ext_output_image_capture_source_manager_v1_interface, 1);
+ else if (g_strcmp0 (interface, ext_image_copy_capture_manager_v1_interface.name) == 0)
+ cd->image_copy_capture_manager = wl_registry_bind (registry, name,
+ &ext_image_copy_capture_manager_v1_interface, 1);
+}
+
+static void
+handle_global_remove (void *data, struct wl_registry *reg, uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = handle_global,
+ .global_remove = handle_global_remove,
+};
+
+/* --- wlr-screencopy frame listener --- */
+
+static void
+handle_frame_buffer (void *data, struct zwlr_screencopy_frame_v1 *frame,
+ uint32_t format, uint32_t width, uint32_t height, uint32_t stride)
+{
+ OutputData *output = data;
+ char template[] = "/tmp/mate-screenshot-buffer-XXXXXX";
+ int fd;
+
+ output->format = format;
+ output->width = width;
+ output->height = height;
+ output->stride = stride;
+ output->size = stride * height;
+
+ g_info ("Wayland screenshot: wlr frame buffer %dx%d stride=%d format=0x%x size=%d",
+ width, height, stride, format, output->size);
+
+ fd = mkstemp (template);
+ if (fd == -1)
+ {
+ g_warning ("Wayland screenshot: failed to create temp file");
+ output->capture_failed = TRUE;
+ return;
+ }
+ ftruncate (fd, output->size);
+ unlink (template);
+
+ output->shm_data = mmap (NULL, output->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (output->shm_data == MAP_FAILED)
+ {
+ g_warning ("Wayland screenshot: failed to mmap buffer");
+ output->shm_data = NULL;
+ close (fd);
+ output->capture_failed = TRUE;
+ return;
+ }
+
+ output->pool = wl_shm_create_pool (output->client_data->shm, fd, output->size);
+ output->buffer = wl_shm_pool_create_buffer (output->pool, 0, width, height, stride, format);
+ close (fd);
+ wl_shm_pool_destroy (output->pool);
+ output->pool = NULL;
+
+ zwlr_screencopy_frame_v1_copy (frame, output->buffer);
+}
+
+static void
+handle_frame_flags (void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags)
+{
+ OutputData *output = data;
+ output->y_inverted = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT ? TRUE : FALSE;
+}
+
+static void
+handle_frame_ready (void *data, struct zwlr_screencopy_frame_v1 *frame,
+ uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec)
+{
+ OutputData *output = data;
+ output->capture_done = TRUE;
+}
+
+static void
+handle_frame_failed (void *data, struct zwlr_screencopy_frame_v1 *frame)
+{
+ OutputData *output = data;
+ g_warning ("Wayland screenshot: wlr frame failed");
+ output->capture_failed = TRUE;
+}
+
+static const struct zwlr_screencopy_frame_v1_listener frame_listener = {
+ .buffer = handle_frame_buffer,
+ .flags = handle_frame_flags,
+ .ready = handle_frame_ready,
+ .failed = handle_frame_failed,
+};
+
+/* --- ext-image-copy-capture frame listener --- */
+
+static void
+handle_ext_frame_transform (void *data, struct ext_image_copy_capture_frame_v1 *frame,
+ uint32_t transform) { }
+
+static void
+handle_ext_frame_damage (void *data, struct ext_image_copy_capture_frame_v1 *frame,
+ int32_t x, int32_t y, int32_t width, int32_t height) { }
+
+static void
+handle_ext_frame_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) { }
+
+static void
+handle_ext_frame_ready (void *data, struct ext_image_copy_capture_frame_v1 *frame)
+{
+ OutputData *output = data;
+ output->capture_done = TRUE;
+}
+
+static void
+handle_ext_frame_failed (void *data, struct ext_image_copy_capture_frame_v1 *frame,
+ uint32_t reason)
+{
+ OutputData *output = data;
+ g_warning ("Wayland screenshot: ext frame failed (reason %u)", reason);
+ output->capture_failed = TRUE;
+}
+
+static const struct ext_image_copy_capture_frame_v1_listener ext_frame_listener = {
+ .transform = handle_ext_frame_transform,
+ .damage = handle_ext_frame_damage,
+ .presentation_time = handle_ext_frame_presentation_time,
+ .ready = handle_ext_frame_ready,
+ .failed = handle_ext_frame_failed,
+};
+
+/* --- ext-image-copy-capture session listener --- */
+
+static void
+handle_ext_session_buffer_size (void *data, struct ext_image_copy_capture_session_v1 *session,
+ uint32_t width, uint32_t height)
+{
+ OutputData *output = data;
+ output->width = width;
+ output->height = height;
+}
+
+static void
+handle_ext_session_shm_format (void *data, struct ext_image_copy_capture_session_v1 *session,
+ uint32_t format)
+{
+ OutputData *output = data;
+ if (output->has_format || !is_shm_format_supported (format))
+ return;
+ output->format = format;
+ output->has_format = TRUE;
+}
+
+static void
+handle_ext_session_dmabuf_device (void *data, struct ext_image_copy_capture_session_v1 *session,
+ struct wl_array *device)
+{
+ /* We use SHM, ignore dmabuf */
+}
+
+static void
+handle_ext_session_dmabuf_format (void *data, struct ext_image_copy_capture_session_v1 *session,
+ uint32_t format, struct wl_array *modifiers)
+{
+ /* We use SHM, ignore dmabuf */
+}
+
+static void
+handle_ext_session_done (void *data, struct ext_image_copy_capture_session_v1 *session)
+{
+ OutputData *output = data;
+ char template[] = "/tmp/mate-screenshot-buffer-XXXXXX";
+ int fd;
+
+ if (output->image_copy_capture_frame != NULL)
+ return;
+
+ output->stride = output->width * get_bpp_from_format (output->format);
+ output->size = output->stride * output->height;
+
+ if (output->size <= 0 || !output->has_format)
+ {
+ g_warning ("Wayland screenshot: ext session done but invalid size=%d or no format", output->size);
+ output->capture_failed = TRUE;
+ return;
+ }
+
+ fd = mkstemp (template);
+ if (fd == -1)
+ {
+ g_warning ("Wayland screenshot: failed to create temp file");
+ output->capture_failed = TRUE;
+ return;
+ }
+ ftruncate (fd, output->size);
+ unlink (template);
+
+ output->shm_data = mmap (NULL, output->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (output->shm_data == MAP_FAILED)
+ {
+ g_warning ("Wayland screenshot: failed to mmap buffer");
+ output->shm_data = NULL;
+ close (fd);
+ output->capture_failed = TRUE;
+ return;
+ }
+
+ output->pool = wl_shm_create_pool (output->client_data->shm, fd, output->size);
+ output->buffer = wl_shm_pool_create_buffer (output->pool, 0,
+ output->width, output->height,
+ output->stride, output->format);
+ close (fd);
+ wl_shm_pool_destroy (output->pool);
+ output->pool = NULL;
+
+ if (output->buffer == NULL)
+ {
+ g_warning ("Wayland screenshot: failed to create wl_buffer");
+ output->capture_failed = TRUE;
+ return;
+ }
+
+ output->image_copy_capture_frame =
+ ext_image_copy_capture_session_v1_create_frame (session);
+ ext_image_copy_capture_frame_v1_add_listener (output->image_copy_capture_frame,
+ &ext_frame_listener, output);
+ ext_image_copy_capture_frame_v1_attach_buffer (output->image_copy_capture_frame,
+ output->buffer);
+ ext_image_copy_capture_frame_v1_damage_buffer (output->image_copy_capture_frame,
+ 0, 0, INT32_MAX, INT32_MAX);
+ ext_image_copy_capture_frame_v1_capture (output->image_copy_capture_frame);
+}
+
+static void
+handle_ext_session_stopped (void *data, struct ext_image_copy_capture_session_v1 *session)
+{
+ g_warning ("Wayland screenshot: ext session stopped");
+}
+
+static const struct ext_image_copy_capture_session_v1_listener ext_session_listener = {
+ .buffer_size = handle_ext_session_buffer_size,
+ .shm_format = handle_ext_session_shm_format,
+ .dmabuf_device = handle_ext_session_dmabuf_device,
+ .dmabuf_format = handle_ext_session_dmabuf_format,
+ .done = handle_ext_session_done,
+ .stopped = handle_ext_session_stopped,
+};
+
+/* --- Output data cleanup --- */
+
+static void
+free_output_data (gpointer data)
+{
+ OutputData *output = data;
+ if (output->shm_data != NULL)
+ munmap (output->shm_data, output->size);
+ if (output->buffer != NULL)
+ wl_buffer_destroy (output->buffer);
+ if (output->frame != NULL)
+ zwlr_screencopy_frame_v1_destroy (output->frame);
+ if (output->image_copy_capture_session != NULL)
+ ext_image_copy_capture_session_v1_destroy (output->image_copy_capture_session);
+ if (output->image_copy_capture_frame != NULL)
+ ext_image_copy_capture_frame_v1_destroy (output->image_copy_capture_frame);
+ g_free (output);
+}
+
+static void
+free_client_data (ClientData *cd)
+{
+ if (cd->shm != NULL)
+ wl_shm_destroy (cd->shm);
+ if (cd->screencopy_manager != NULL)
+ zwlr_screencopy_manager_v1_destroy (cd->screencopy_manager);
+ if (cd->output_image_capture_source_manager != NULL)
+ ext_output_image_capture_source_manager_v1_destroy (cd->output_image_capture_source_manager);
+ if (cd->image_copy_capture_manager != NULL)
+ ext_image_copy_capture_manager_v1_destroy (cd->image_copy_capture_manager);
+ wl_registry_destroy (cd->registry);
+}
+
+/* --- Capture --- */
+
+static GdkPixbuf *
+compose_screenshot (GList *outputs)
+{
+ gint max_scale = 1;
+ gint total_width = 0;
+ gint total_height = 0;
+
+ /* Calculate the virtual framebuffer size and find max scale factor */
+ for (GList *elem = outputs; elem; elem = elem->next)
+ {
+ OutputData *od = elem->data;
+ GdkRectangle geometry;
+ gint scale;
+
+ gdk_monitor_get_geometry (od->monitor, &geometry);
+ scale = (gdouble) od->width / (gdouble) geometry.width;
+ if (scale < 1)
+ scale = 1;
+
+ gint sx = geometry.x + od->width;
+ gint sy = geometry.y + od->height;
+ if (sx > total_width)
+ total_width = sx;
+ if (sy > total_height)
+ total_height = sy;
+ if (scale > max_scale)
+ max_scale = scale;
+ }
+
+ if (total_width == 0 || total_height == 0)
+ return NULL;
+
+ /* Create the destination pixbuf for the virtual framebuffer */
+ GdkPixbuf *dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
+ total_width, total_height);
+
+ /* Composite each output into the destination */
+ for (GList *elem = outputs; elem; elem = elem->next)
+ {
+ OutputData *od = elem->data;
+ GdkRectangle geometry;
+ GdkPixbuf *raw;
+
+ if (od->capture_failed)
+ continue;
+
+ raw = convert_buffer_to_pixbuf (od);
+ if (raw == NULL)
+ continue;
+
+ /* Handle Y-inverted buffer from wlr-screencopy */
+ if (od->y_inverted)
+ {
+ GdkPixbuf *flipped = gdk_pixbuf_rotate_simple (raw, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
+ g_object_unref (raw);
+ raw = flipped;
+ }
+
+ gdk_monitor_get_geometry (od->monitor, &geometry);
+ gint scale = (gdouble) od->width / (gdouble) geometry.width;
+ if (scale < 1)
+ scale = 1;
+
+ gdouble composite_scale = (gdouble) max_scale / (gdouble) scale;
+ gint dest_x = geometry.x * max_scale;
+ gint dest_y = geometry.y * max_scale;
+ gint dest_w = (gdouble) od->width * composite_scale;
+ gint dest_h = (gdouble) od->height * composite_scale;
+
+ g_info ("Wayland screenshot: compositing output %dx%d at (%d,%d) scale %d->%.1f",
+ od->width, od->height, dest_x, dest_y, scale, composite_scale);
+
+ gdk_pixbuf_composite (raw, dest,
+ dest_x, dest_y, dest_w, dest_h,
+ dest_x, dest_y,
+ composite_scale, composite_scale,
+ GDK_INTERP_BILINEAR, 255);
+ g_object_unref (raw);
+ }
+
+ return dest;
+}
+
+static GdkPixbuf *
+capture_screenshot (gboolean capture_all_monitors, gboolean show_mouse)
+{
+ ClientData client_data = { 0 };
+ GList *outputs = NULL;
+ GdkPixbuf *screenshot = NULL;
+ gboolean failure = FALSE;
+ int n_monitors;
+
+ client_data.display = gdk_wayland_display_get_wl_display (gdk_display_get_default ());
+ client_data.registry = wl_display_get_registry (client_data.display);
+ wl_registry_add_listener (client_data.registry, &registry_listener, &client_data);
+ wl_display_roundtrip (client_data.display);
+
+ if (client_data.shm == NULL)
+ {
+ g_warning ("Wayland screenshot: wl_shm not available");
+ free_client_data (&client_data);
+ return NULL;
+ }
+
+ if (client_data.output_image_capture_source_manager == NULL &&
+ client_data.image_copy_capture_manager == NULL &&
+ client_data.screencopy_manager == NULL)
+ {
+ g_warning ("Wayland screenshot: no capture protocol available");
+ free_client_data (&client_data);
+ return NULL;
+ }
+
+ n_monitors = gdk_display_get_n_monitors (gdk_display_get_default ());
+ for (int i = 0; i < n_monitors; i++)
+ {
+ OutputData *output;
+ GdkMonitor *monitor = gdk_display_get_monitor (gdk_display_get_default (), i);
+ struct wl_output *wl_output = gdk_wayland_monitor_get_wl_output (monitor);
+
+ if (wl_output == NULL)
+ {
+ g_warning ("Wayland screenshot: no wl_output for monitor %d", i);
+ continue;
+ }
+
+ output = g_new0 (OutputData, 1);
+ output->monitor = monitor;
+ output->client_data = &client_data;
+ outputs = g_list_append (outputs, output);
+
+ if (client_data.image_copy_capture_manager != NULL &&
+ client_data.output_image_capture_source_manager != NULL)
+ {
+ struct ext_image_capture_source_v1 *source =
+ ext_output_image_capture_source_manager_v1_create_source (
+ client_data.output_image_capture_source_manager, wl_output);
+
+ guint32 options = show_mouse ? EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS : 0;
+ output->image_copy_capture_session =
+ ext_image_copy_capture_manager_v1_create_session (
+ client_data.image_copy_capture_manager, source, options);
+
+ ext_image_copy_capture_session_v1_add_listener (
+ output->image_copy_capture_session, &ext_session_listener, output);
+
+ ext_image_capture_source_v1_destroy (source);
+ }
+ else if (client_data.screencopy_manager != NULL)
+ {
+ output->frame = zwlr_screencopy_manager_v1_capture_output (
+ client_data.screencopy_manager, show_mouse ? 1 : 0, wl_output);
+ zwlr_screencopy_frame_v1_add_listener (output->frame, &frame_listener, output);
+ }
+ }
+
+ /* Dispatch events until all outputs are captured or failed */
+ while (TRUE)
+ {
+ gboolean done = TRUE;
+ for (GList *elem = outputs; elem; elem = elem->next)
+ {
+ OutputData *od = elem->data;
+ if (!od->capture_done && !od->capture_failed)
+ done = FALSE;
+ if (od->capture_failed)
+ failure = TRUE;
+ }
+ if (done)
+ break;
+ wl_display_dispatch (client_data.display);
+ }
+
+ if (!failure && outputs != NULL)
+ {
+ if (capture_all_monitors && g_list_length (outputs) > 1)
+ {
+ screenshot = compose_screenshot (outputs);
+ }
+ else
+ {
+ /* Single monitor: just convert directly */
+ OutputData *od = outputs->data;
+ if (!od->capture_failed)
+ {
+ GdkPixbuf *raw = convert_buffer_to_pixbuf (od);
+ if (raw != NULL)
+ {
+ if (od->y_inverted)
+ {
+ screenshot = gdk_pixbuf_rotate_simple (raw, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
+ g_object_unref (raw);
+ }
+ else
+ {
+ screenshot = gdk_pixbuf_copy (raw);
+ g_object_unref (raw);
+ }
+ }
+ }
+ }
+ }
+
+ free_client_data (&client_data);
+ g_list_free_full (outputs, free_output_data);
+
+ return screenshot;
+}
+
+/* --- Public API --- */
+
+gboolean
+wayland_screenshot_init (void)
+{
+ GdkDisplay *gdk_display;
+
+ gdk_display = gdk_display_get_default ();
+ if (!GDK_IS_WAYLAND_DISPLAY (gdk_display))
+ return FALSE;
+
+ wayland_initialized = TRUE;
+
+ g_info ("Wayland screenshot: support initialized");
+
+ return TRUE;
+}
+
+void
+wayland_screenshot_cleanup (void)
+{
+ wayland_initialized = FALSE;
+}
+
+gboolean
+wayland_screenshot_is_available (void)
+{
+ return wayland_initialized;
+}
+
+GdkPixbuf *
+wayland_screenshot_capture_screen (gboolean include_pointer)
+{
+ if (!wayland_initialized)
+ return NULL;
+
+ return capture_screenshot (TRUE, include_pointer);
+}
+
+GdkPixbuf *
+wayland_screenshot_capture_output (GdkMonitor *monitor, gboolean include_pointer)
+{
+ if (!wayland_initialized || !monitor)
+ return NULL;
+
+ return capture_screenshot (FALSE, include_pointer);
+}
+
+GdkPixbuf *
+wayland_screenshot_capture_region (GdkRectangle *region, gboolean include_pointer)
+{
+ GdkDisplay *display;
+ GdkMonitor *monitor;
+
+ if (!wayland_initialized || !region)
+ return NULL;
+
+ display = gdk_display_get_default ();
+ monitor = gdk_display_get_monitor_at_point (display, region->x, region->y);
+
+ if (!monitor)
+ monitor = gdk_display_get_primary_monitor (display);
+ if (!monitor)
+ monitor = gdk_display_get_monitor (display, 0);
+
+ return wayland_screenshot_capture_output (monitor, include_pointer);
+}
+
+#endif /* ENABLE_WAYLAND && GDK_WINDOWING_WAYLAND */
diff --git a/mate-screenshot/src/wayland-screenshot.h b/mate-screenshot/src/wayland-screenshot.h
new file mode 100644
index 00000000..2404429d
--- /dev/null
+++ b/mate-screenshot/src/wayland-screenshot.h
@@ -0,0 +1,92 @@
+/* Copyright (C) 2001-2006 Jonathan Blandford <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ * Copyright (C) 2012-2021 MATE Developers
+ *
+ * This file is part of MATE Utils.
+ *
+ * MATE Utils is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MATE Utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MATE Utils. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __WAYLAND_SCREENSHOT_H__
+#define __WAYLAND_SCREENSHOT_H__
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+
+typedef struct _WaylandScreenshot WaylandScreenshot;
+
+/* Initialize Wayland screenshot support */
+gboolean wayland_screenshot_init (void);
+
+/* Cleanup Wayland screenshot support */
+void wayland_screenshot_cleanup (void);
+
+/* Check if we're running on Wayland */
+gboolean wayland_screenshot_is_available (void);
+
+/* Capture the entire screen */
+GdkPixbuf *wayland_screenshot_capture_screen (gboolean include_pointer);
+
+/* Capture a specific output */
+GdkPixbuf *wayland_screenshot_capture_output (GdkMonitor *monitor, gboolean include_pointer);
+
+/* Capture a specific region */
+GdkPixbuf *wayland_screenshot_capture_region (GdkRectangle *region, gboolean include_pointer);
+
+#else /* !(ENABLE_WAYLAND && GDK_WINDOWING_WAYLAND) */
+
+/* Stub implementations when Wayland support is disabled */
+static inline gboolean
+wayland_screenshot_init (void)
+{
+ return FALSE;
+}
+
+static inline void
+wayland_screenshot_cleanup (void)
+{
+}
+
+static inline gboolean
+wayland_screenshot_is_available (void)
+{
+ return FALSE;
+}
+
+static inline GdkPixbuf *
+wayland_screenshot_capture_screen (gboolean include_pointer)
+{
+ return NULL;
+}
+
+static inline GdkPixbuf *
+wayland_screenshot_capture_output (GdkMonitor *monitor, gboolean include_pointer)
+{
+ return NULL;
+}
+
+static inline GdkPixbuf *
+wayland_screenshot_capture_region (GdkRectangle *region, gboolean include_pointer)
+{
+ return NULL;
+}
+
+#endif /* ENABLE_WAYLAND && GDK_WINDOWING_WAYLAND */
+
+G_END_DECLS
+
+#endif /* __WAYLAND_SCREENSHOT_H__ */ \ No newline at end of file
diff --git a/mate-screenshot/src/wayland-select-region.c b/mate-screenshot/src/wayland-select-region.c
new file mode 100644
index 00000000..a8096ed8
--- /dev/null
+++ b/mate-screenshot/src/wayland-select-region.c
@@ -0,0 +1,493 @@
+/* Copyright (C) 2001-2006 Jonathan Blandford <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ * Copyright (C) 2012-2026 MATE Developers
+ *
+ * This file is part of MATE Utils.
+ *
+ * MATE Utils is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MATE Utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MATE Utils. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gdk/gdkwayland.h>
+
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+
+#include "wayland-select-region.h"
+
+#include <gtk-layer-shell.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <string.h>
+
+#define BACKGROUND_TRANSPARENCY 0.4
+#define DROP_SHADOW_SIZE 2
+#define SIZE_DISPLAY_OFFSET 4
+
+enum {
+ ANCHOR_UNSET = 0,
+ ANCHOR_NONE = 1,
+ ANCHOR_TOP = 2,
+ ANCHOR_LEFT = 4
+};
+
+typedef struct {
+ gboolean left_pressed;
+ gboolean rubber_banding;
+ gboolean cancelled;
+ gboolean move_rectangle;
+ gboolean finished;
+ gint anchor;
+ gint x;
+ gint y;
+ gint x_root;
+ gint y_root;
+ gint cursor_x;
+ gint cursor_y;
+ cairo_rectangle_int_t old_text_rect;
+ cairo_rectangle_int_t rectangle;
+ PangoLayout *text_layout;
+ GSList *overlays;
+} RubberBandData;
+
+typedef struct {
+ GtkWidget *window;
+ GdkMonitor *monitor;
+ GdkRectangle monitor_geometry;
+} OverlayData;
+
+static void
+destroy_overlay (OverlayData *overlay)
+{
+ gtk_widget_destroy (overlay->window);
+ g_free (overlay);
+}
+
+static void
+close_overlays (RubberBandData *rbdata)
+{
+ GSList *l;
+ OverlayData *first_overlay;
+ GdkFrameClock *frame_clock;
+
+ rbdata->finished = TRUE;
+
+ for (l = rbdata->overlays; l != NULL; l = l->next)
+ {
+ OverlayData *data = l->data;
+ gtk_widget_queue_draw (data->window);
+ }
+
+ if (rbdata->overlays != NULL)
+ {
+ first_overlay = rbdata->overlays->data;
+ frame_clock = gtk_widget_get_frame_clock (first_overlay->window);
+ g_signal_connect (frame_clock, "after-paint", G_CALLBACK (gtk_main_quit), NULL);
+ }
+ else
+ {
+ gtk_main_quit ();
+ }
+}
+
+static gboolean
+cb_key_pressed (GtkWidget *widget,
+ GdkEventKey *event,
+ RubberBandData *rbdata)
+{
+ guint key = event->keyval;
+
+ if (key == GDK_KEY_Escape)
+ {
+ rbdata->cancelled = TRUE;
+ close_overlays (rbdata);
+ return TRUE;
+ }
+
+ if (rbdata->left_pressed && (key == GDK_KEY_Control_L || key == GDK_KEY_Control_R))
+ {
+ rbdata->move_rectangle = TRUE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+cb_key_released (GtkWidget *widget,
+ GdkEventKey *event,
+ RubberBandData *rbdata)
+{
+ guint key = event->keyval;
+
+ if (rbdata->left_pressed && (key == GDK_KEY_Control_L || key == GDK_KEY_Control_R))
+ {
+ rbdata->move_rectangle = FALSE;
+ rbdata->anchor = ANCHOR_UNSET;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+cb_draw (GtkWidget *widget,
+ cairo_t *cr,
+ RubberBandData *rbdata)
+{
+ cairo_set_source_rgba (cr, 0, 0, 0, rbdata->finished ? 0 : BACKGROUND_TRANSPARENCY);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint (cr);
+
+ if (rbdata->rubber_banding)
+ {
+ OverlayData *overlay;
+ cairo_rectangle_int_t intersection;
+ cairo_rectangle_int_t selection_rect_local = rbdata->rectangle;
+
+ overlay = g_object_get_data (G_OBJECT (widget), "overlay");
+
+ selection_rect_local.x -= overlay->monitor_geometry.x;
+ selection_rect_local.y -= overlay->monitor_geometry.y;
+
+ if (gdk_rectangle_intersect ((GdkRectangle *) &selection_rect_local,
+ &(GdkRectangle) {0, 0, overlay->monitor_geometry.width, overlay->monitor_geometry.height},
+ &intersection))
+ {
+ gchar *coords;
+ gint rect_width, rect_height, x_offset, y_offset, text_width, text_height, x_text, y_text;
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ gdk_cairo_rectangle (cr, &intersection);
+ cairo_fill (cr);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_set_source_rgba (cr, 1.0f, 1.0f, 1.0f, 0.1f);
+ gdk_cairo_rectangle (cr, &intersection);
+ cairo_fill (cr);
+
+ if (rbdata->cursor_x < overlay->monitor_geometry.x ||
+ rbdata->cursor_x > overlay->monitor_geometry.x + overlay->monitor_geometry.width ||
+ rbdata->cursor_y < overlay->monitor_geometry.y ||
+ rbdata->cursor_y > overlay->monitor_geometry.y + overlay->monitor_geometry.height)
+ return FALSE;
+
+ rect_width = rbdata->rectangle.width;
+ rect_height = rbdata->rectangle.height;
+
+ if (rbdata->rectangle.x < 0) rect_width += rbdata->rectangle.x;
+ if (rbdata->rectangle.y < 0) rect_height += rbdata->rectangle.y;
+
+ if (rbdata->text_layout == NULL)
+ {
+ rbdata->text_layout = pango_cairo_create_layout (cr);
+ pango_layout_set_font_description (rbdata->text_layout,
+ pango_font_description_from_string ("monospace 10"));
+ }
+
+ coords = g_strdup_printf ("%d x %d", rect_width, rect_height);
+ pango_layout_set_text (rbdata->text_layout, coords, -1);
+ pango_layout_get_pixel_size (rbdata->text_layout, &text_width, &text_height);
+ g_free (coords);
+
+ x_offset = SIZE_DISPLAY_OFFSET;
+ y_offset = SIZE_DISPLAY_OFFSET;
+
+ if (rbdata->cursor_x - overlay->monitor_geometry.x > overlay->monitor_geometry.width - text_width - x_offset)
+ x_offset -= text_width + SIZE_DISPLAY_OFFSET;
+
+ if (rbdata->cursor_y - overlay->monitor_geometry.y > overlay->monitor_geometry.height - text_height - y_offset)
+ y_offset -= text_height + SIZE_DISPLAY_OFFSET;
+
+ rbdata->old_text_rect.x = rbdata->cursor_x + x_offset;
+ rbdata->old_text_rect.y = rbdata->cursor_y + y_offset;
+ rbdata->old_text_rect.width = text_width + DROP_SHADOW_SIZE;
+ rbdata->old_text_rect.height = text_height + DROP_SHADOW_SIZE;
+
+ x_text = rbdata->old_text_rect.x - overlay->monitor_geometry.x;
+ y_text = rbdata->old_text_rect.y - overlay->monitor_geometry.y;
+
+ cairo_move_to (cr, x_text + DROP_SHADOW_SIZE, y_text + DROP_SHADOW_SIZE);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ pango_cairo_show_layout (cr, rbdata->text_layout);
+
+ cairo_move_to (cr, x_text, y_text);
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ pango_cairo_show_layout (cr, rbdata->text_layout);
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+cb_button_pressed (GtkWidget *widget,
+ GdkEventButton *event,
+ RubberBandData *rbdata)
+{
+ if (event->button == 1)
+ {
+ OverlayData *overlay;
+
+ overlay = g_object_get_data (G_OBJECT (widget), "overlay");
+
+ rbdata->left_pressed = TRUE;
+ rbdata->x_root = overlay->monitor_geometry.x + event->x;
+ rbdata->y_root = overlay->monitor_geometry.y + event->y;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+cb_button_released (GtkWidget *widget,
+ GdkEventButton *event,
+ RubberBandData *rbdata)
+{
+ if (event->button == 1)
+ {
+ if (rbdata->rubber_banding && rbdata->rectangle.width > 0 && rbdata->rectangle.height > 0)
+ {
+ rbdata->left_pressed = rbdata->rubber_banding = FALSE;
+ close_overlays (rbdata);
+ return TRUE;
+ }
+ rbdata->left_pressed = rbdata->rubber_banding = FALSE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+cb_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event,
+ RubberBandData *rbdata)
+{
+ if (rbdata->left_pressed)
+ {
+ cairo_rectangle_int_t *new_rect;
+ cairo_rectangle_int_t old_rect, intersect;
+ cairo_region_t *region;
+ OverlayData *overlay;
+ gint event_x_root, event_y_root;
+ GSList *l;
+
+ overlay = g_object_get_data (G_OBJECT (widget), "overlay");
+ rbdata->cursor_x = event_x_root = overlay->monitor_geometry.x + event->x - 1;
+ rbdata->cursor_y = event_y_root = overlay->monitor_geometry.y + event->y - 1;
+ new_rect = &rbdata->rectangle;
+
+ if (!rbdata->rubber_banding)
+ {
+ rbdata->rubber_banding = TRUE;
+ rbdata->x = event_x_root;
+ rbdata->y = event_y_root;
+ old_rect.x = rbdata->x;
+ old_rect.y = rbdata->y;
+ old_rect.height = old_rect.width = 1;
+ }
+ else
+ {
+ old_rect.x = new_rect->x;
+ old_rect.y = new_rect->y;
+ old_rect.width = new_rect->width;
+ old_rect.height = new_rect->height;
+ }
+
+ if (rbdata->move_rectangle)
+ {
+ if (rbdata->anchor == ANCHOR_UNSET)
+ {
+ rbdata->anchor = ANCHOR_NONE;
+ rbdata->anchor |= (event_x_root < rbdata->x) ? ANCHOR_LEFT : 0;
+ rbdata->anchor |= (event_y_root < rbdata->y) ? ANCHOR_TOP : 0;
+ }
+
+ if (rbdata->anchor & ANCHOR_LEFT)
+ rbdata->x = (new_rect->x = event_x_root) + new_rect->width;
+ else
+ rbdata->x = new_rect->x = event_x_root - new_rect->width;
+
+ if (rbdata->anchor & ANCHOR_TOP)
+ rbdata->y = (new_rect->y = event_y_root) + new_rect->height;
+ else
+ rbdata->y = new_rect->y = event_y_root - new_rect->height;
+ }
+ else
+ {
+ new_rect->x = MIN (rbdata->x, event_x_root);
+ new_rect->y = MIN (rbdata->y, event_y_root);
+ new_rect->width = ABS (rbdata->x - event_x_root) + 1;
+ new_rect->height = ABS (rbdata->y - event_y_root) + 1;
+ }
+
+ region = cairo_region_create_rectangle (&old_rect);
+ cairo_region_union_rectangle (region, new_rect);
+
+ if (gdk_rectangle_intersect (&old_rect, new_rect, &intersect) && intersect.width > 2 && intersect.height > 2)
+ {
+ intersect.x += 1;
+ intersect.width -= 2;
+ intersect.y += 1;
+ intersect.height -= 2;
+ cairo_region_subtract_rectangle (region, &intersect);
+ }
+
+ rbdata->old_text_rect.x -= rbdata->old_text_rect.width;
+ rbdata->old_text_rect.y -= rbdata->old_text_rect.height;
+ rbdata->old_text_rect.width += 2 * rbdata->old_text_rect.width;
+ rbdata->old_text_rect.height += 2 * rbdata->old_text_rect.height;
+ cairo_region_union_rectangle (region, &(rbdata->old_text_rect));
+
+ for (l = rbdata->overlays; l != NULL; l = l->next)
+ {
+ OverlayData *data = l->data;
+ cairo_region_translate (region, -(data->monitor_geometry.x), -(data->monitor_geometry.y));
+ gdk_window_invalidate_region (gtk_widget_get_window (data->window), region, TRUE);
+ cairo_region_translate (region, data->monitor_geometry.x, data->monitor_geometry.y);
+ }
+
+ cairo_region_destroy (region);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+wayland_select_region (GdkRectangle *region)
+{
+ RubberBandData rbdata = {0};
+ GdkCursor *xhair_cursor;
+ GdkDisplay *display;
+ int n_monitors;
+ GdkRectangle root_geometry = {0};
+
+ if (!gtk_layer_is_supported ())
+ {
+ g_warning ("Compositor does not support zwlr_layer_shell_v1");
+ return FALSE;
+ }
+
+ display = gdk_display_get_default ();
+ xhair_cursor = gdk_cursor_new_for_display (display, GDK_CROSSHAIR);
+ n_monitors = gdk_display_get_n_monitors (display);
+
+ for (int n_monitor = 0; n_monitor < n_monitors; n_monitor++)
+ {
+ GdkMonitor *monitor;
+ GtkWidget *window;
+ OverlayData *overlay;
+
+ monitor = gdk_display_get_monitor (display, n_monitor);
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ overlay = g_new0 (OverlayData, 1);
+ overlay->window = window;
+ overlay->monitor = monitor;
+ gdk_monitor_get_geometry (monitor, &(overlay->monitor_geometry));
+ gdk_rectangle_union (&(overlay->monitor_geometry), &root_geometry, &root_geometry);
+ rbdata.overlays = g_slist_append (rbdata.overlays, overlay);
+ g_object_set_data (G_OBJECT (window), "overlay", overlay);
+
+ gtk_window_set_title (GTK_WINDOW (window), _("MATE Screenshot overlay"));
+ gtk_window_set_decorated (GTK_WINDOW (window), FALSE);
+ gtk_window_set_deletable (GTK_WINDOW (window), FALSE);
+ gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
+ gtk_widget_set_can_focus (window, FALSE);
+ gtk_widget_set_app_paintable (window, TRUE);
+ gtk_widget_add_events (window,
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_KEY_PRESS_MASK |
+ GDK_ENTER_NOTIFY_MASK);
+ gtk_widget_set_visual (window, gdk_screen_get_rgba_visual (gdk_screen_get_default ()));
+
+ gtk_layer_init_for_window (GTK_WINDOW (window));
+ gtk_layer_set_monitor (GTK_WINDOW (window), monitor);
+ gtk_layer_set_layer (GTK_WINDOW (window), GTK_LAYER_SHELL_LAYER_OVERLAY);
+ gtk_layer_set_keyboard_mode (GTK_WINDOW (window), GTK_LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE);
+ gtk_layer_set_anchor (GTK_WINDOW (window), GTK_LAYER_SHELL_EDGE_TOP, TRUE);
+ gtk_layer_set_anchor (GTK_WINDOW (window), GTK_LAYER_SHELL_EDGE_LEFT, TRUE);
+ gtk_layer_set_anchor (GTK_WINDOW (window), GTK_LAYER_SHELL_EDGE_BOTTOM, TRUE);
+ gtk_layer_set_anchor (GTK_WINDOW (window), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE);
+
+ /* Allow dragging from screen edges */
+ gtk_layer_set_margin (GTK_WINDOW (window), GTK_LAYER_SHELL_EDGE_TOP, -1);
+ gtk_layer_set_margin (GTK_WINDOW (window), GTK_LAYER_SHELL_EDGE_LEFT, -1);
+ gtk_layer_set_margin (GTK_WINDOW (window), GTK_LAYER_SHELL_EDGE_BOTTOM, -1);
+ gtk_layer_set_margin (GTK_WINDOW (window), GTK_LAYER_SHELL_EDGE_RIGHT, -1);
+ gtk_layer_set_exclusive_zone (GTK_WINDOW (window), -1);
+
+ g_signal_connect (window, "key-press-event", G_CALLBACK (cb_key_pressed), &rbdata);
+ g_signal_connect (window, "key-release-event", G_CALLBACK (cb_key_released), &rbdata);
+ g_signal_connect (window, "draw", G_CALLBACK (cb_draw), &rbdata);
+ g_signal_connect (window, "button-press-event", G_CALLBACK (cb_button_pressed), &rbdata);
+ g_signal_connect (window, "button-release-event", G_CALLBACK (cb_button_released), &rbdata);
+ g_signal_connect (window, "motion-notify-event", G_CALLBACK (cb_motion_notify), &rbdata);
+
+ gtk_widget_show_all (window);
+ gdk_window_set_cursor (gtk_widget_get_window (window), xhair_cursor);
+ }
+
+ gtk_main ();
+
+ g_slist_foreach (rbdata.overlays, (GFunc) destroy_overlay, NULL);
+ g_slist_free (rbdata.overlays);
+ g_object_unref (xhair_cursor);
+ if (rbdata.text_layout != NULL)
+ g_object_unref (rbdata.text_layout);
+
+ if (rbdata.cancelled)
+ return FALSE;
+
+ region->x = rbdata.rectangle.x;
+ region->y = rbdata.rectangle.y;
+ region->width = rbdata.rectangle.width;
+ region->height = rbdata.rectangle.height;
+
+ /* Clamp to screen bounds */
+ if (region->x < 0)
+ region->width += region->x;
+ if (region->y < 0)
+ region->height += region->y;
+
+ region->x = MAX (0, MIN (region->x, root_geometry.width));
+ region->y = MAX (0, MIN (region->y, root_geometry.height));
+
+ if (region->x + region->width > root_geometry.width)
+ region->width = root_geometry.width - region->x;
+ if (region->y + region->height > root_geometry.height)
+ region->height = root_geometry.height - region->y;
+
+ if (region->width == 0 && region->x == root_geometry.width)
+ {
+ region->width = 1;
+ region->x--;
+ }
+ if (region->height == 0 && region->y == root_geometry.height)
+ {
+ region->height = 1;
+ region->y--;
+ }
+
+ return TRUE;
+}
+
+#endif /* ENABLE_WAYLAND && GDK_WINDOWING_WAYLAND */
diff --git a/mate-screenshot/src/wayland-select-region.h b/mate-screenshot/src/wayland-select-region.h
new file mode 100644
index 00000000..371183f3
--- /dev/null
+++ b/mate-screenshot/src/wayland-select-region.h
@@ -0,0 +1,44 @@
+/* Copyright (C) 2001-2006 Jonathan Blandford <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ * Copyright (C) 2012-2021 MATE Developers
+ *
+ * This file is part of MATE Utils.
+ *
+ * MATE Utils is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MATE Utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MATE Utils. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __WAYLAND_SELECT_REGION_H__
+#define __WAYLAND_SELECT_REGION_H__
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#if defined(ENABLE_WAYLAND) && defined(GDK_WINDOWING_WAYLAND)
+
+gboolean wayland_select_region (GdkRectangle *region);
+
+#else
+
+static inline gboolean
+wayland_select_region (GdkRectangle *region)
+{
+ return FALSE;
+}
+
+#endif
+
+G_END_DECLS
+
+#endif