diff options
| author | Aleksey Samoilov <[email protected]> | 2026-07-19 23:26:32 +0400 |
|---|---|---|
| committer | Luke from DC <[email protected]> | 2026-08-30 20:01:29 +0000 |
| commit | 60057b83ba1bbb209adc997ddcf0f234d832d426 (patch) | |
| tree | 3105636f026a73d427ed48ff489a90ba7f89c83f | |
| parent | 2374fc3667d926f64a638d46e571a560d1f0a59f (diff) | |
| download | mate-utils-60057b83ba1bbb209adc997ddcf0f234d832d426.tar.bz2 mate-utils-60057b83ba1bbb209adc997ddcf0f234d832d426.tar.xz | |
Add Wayland support for mate-screenshot
| -rw-r--r-- | configure.ac | 47 | ||||
| -rw-r--r-- | mate-screenshot/protocols/wlr-screencopy-unstable-v1.xml | 232 | ||||
| -rw-r--r-- | mate-screenshot/src/Makefile.am | 84 | ||||
| -rw-r--r-- | mate-screenshot/src/mate-screenshot.c | 81 | ||||
| -rw-r--r-- | mate-screenshot/src/screenshot-utils.c | 97 | ||||
| -rw-r--r-- | mate-screenshot/src/screenshot-utils.h | 3 | ||||
| -rw-r--r-- | mate-screenshot/src/wayland-screenshot.c | 755 | ||||
| -rw-r--r-- | mate-screenshot/src/wayland-screenshot.h | 92 | ||||
| -rw-r--r-- | mate-screenshot/src/wayland-select-region.c | 493 | ||||
| -rw-r--r-- | mate-screenshot/src/wayland-select-region.h | 44 |
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 (®ion)) + push_check_file_job (®ion); + 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, ®istry_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 |
