From b65644ec0a4b42769c8dbfbad02769adf7309983 Mon Sep 17 00:00:00 2001 From: importbot <releng@rockylinux.org> Date: Fri, 14 Feb 2025 14:28:59 +0000 Subject: [PATCH] import gnome-kiosk-47.0-7.el10 --- ...-compositor-Add-screenshot-utilities.patch | 1046 +++++++++++++++++ ...positor-Add-Shell-Screenshot-support.patch | 892 ++++++++++++++ SPECS/gnome-kiosk.spec | 9 +- 3 files changed, 1946 insertions(+), 1 deletion(-) create mode 100644 SOURCES/0001-compositor-Add-screenshot-utilities.patch create mode 100644 SOURCES/0002-compositor-Add-Shell-Screenshot-support.patch diff --git a/SOURCES/0001-compositor-Add-screenshot-utilities.patch b/SOURCES/0001-compositor-Add-screenshot-utilities.patch new file mode 100644 index 0000000..025b5c4 --- /dev/null +++ b/SOURCES/0001-compositor-Add-screenshot-utilities.patch @@ -0,0 +1,1046 @@ +From 52f9b3792196e405d7ce9917e735c707d369b8f9 Mon Sep 17 00:00:00 2001 +From: Olivier Fourdan <ofourdan@redhat.com> +Date: Wed, 22 Jan 2025 11:20:38 +0100 +Subject: [PATCH 1/2] compositor: Add screenshot utilities + +This code is a simplified and expunged version based on GNOME Shell +implementation of ShellScreenshot. + +This is preparation work for implementing support for the Shell +screenshot DBus API org.gnome.Shell.Screenshot. + +Part-of: <https://gitlab.gnome.org/GNOME/gnome-kiosk/-/merge_requests/61> +(cherry picked from commit 20edc0cc81a4ecc29d74c32446717ee969b7b33e) +--- + compositor/kiosk-screenshot.c | 930 ++++++++++++++++++++++++++++++++++ + compositor/kiosk-screenshot.h | 59 +++ + meson.build | 3 + + 3 files changed, 992 insertions(+) + create mode 100644 compositor/kiosk-screenshot.c + create mode 100644 compositor/kiosk-screenshot.h + +diff --git a/compositor/kiosk-screenshot.c b/compositor/kiosk-screenshot.c +new file mode 100644 +index 0000000..04b7b99 +--- /dev/null ++++ b/compositor/kiosk-screenshot.c +@@ -0,0 +1,930 @@ ++#include "config.h" ++#include "kiosk-compositor.h" ++#include "kiosk-screenshot.h" ++#include "kiosk-gobject-utils.h" ++ ++#include <stdlib.h> ++#include <string.h> ++ ++#include <cogl/cogl.h> ++ ++#include <meta/display.h> ++#include <meta/util.h> ++#include <meta/compositor-mutter.h> ++ ++#include <meta/meta-backend.h> ++#include <meta/meta-context.h> ++#include <meta/meta-cursor-tracker.h> ++#include <meta/meta-plugin.h> ++#include <meta/meta-monitor-manager.h> ++#include <meta/meta-background-actor.h> ++#include <meta/meta-background-content.h> ++#include <meta/meta-background-group.h> ++#include <meta/meta-background-image.h> ++#include <meta/meta-background.h> ++ ++/* This code is a largely based on GNOME Shell implementation of ShellScreenshot */ ++ ++typedef enum _KioskScreenshotFlag ++{ ++ KIOSK_SCREENSHOT_FLAG_NONE, ++ KIOSK_SCREENSHOT_FLAG_INCLUDE_CURSOR, ++} KioskScreenshotFlag; ++ ++typedef enum _KioskScreenshotMode ++{ ++ KIOSK_SCREENSHOT_SCREEN, ++ KIOSK_SCREENSHOT_WINDOW, ++ KIOSK_SCREENSHOT_AREA, ++} KioskScreenshotMode; ++ ++enum ++{ ++ SCREENSHOT_TAKEN, ++ ++ LAST_SIGNAL ++}; ++ ++static guint signals[LAST_SIGNAL] = { 0, }; ++ ++typedef struct _KioskScreenshot KioskScreenshot; ++ ++struct _KioskScreenshot ++{ ++ GObject parent_instance; ++ ++ /* weak references */ ++ KioskCompositor *compositor; ++ MetaDisplay *display; ++ MetaContext *context; ++ MetaBackend *backend; ++ ClutterActor *stage; ++ ++ /* strong references */ ++ GOutputStream *stream; ++ KioskScreenshotFlag flags; ++ KioskScreenshotMode mode; ++ ++ GDateTime *datetime; ++ ++ cairo_surface_t *image; ++ MtkRectangle screenshot_area; ++ ++ gboolean include_frame; ++ ++ float scale; ++ ClutterContent *cursor_content; ++ graphene_point_t cursor_point; ++ float cursor_scale; ++}; ++ ++enum ++{ ++ PROP_COMPOSITOR = 1, ++ NUMBER_OF_PROPERTIES ++}; ++static GParamSpec *kiosk_screenshot_properties[NUMBER_OF_PROPERTIES] = { NULL, }; ++ ++G_DEFINE_TYPE (KioskScreenshot, kiosk_screenshot, G_TYPE_OBJECT); ++ ++static void ++kiosk_screenshot_dispose (GObject *object) ++{ ++ KioskScreenshot *self = KIOSK_SCREENSHOT (object); ++ ++ g_clear_weak_pointer (&self->context); ++ g_clear_weak_pointer (&self->backend); ++ g_clear_weak_pointer (&self->stage); ++ g_clear_weak_pointer (&self->display); ++ g_clear_weak_pointer (&self->compositor); ++ ++ G_OBJECT_CLASS (kiosk_screenshot_parent_class)->dispose (object); ++} ++ ++static void ++kiosk_screenshot_constructed (GObject *object) ++{ ++ KioskScreenshot *self = KIOSK_SCREENSHOT (object); ++ MetaDisplay *display = meta_plugin_get_display (META_PLUGIN (self->compositor)); ++ ++ G_OBJECT_CLASS (kiosk_screenshot_parent_class)->constructed (object); ++ ++ g_set_weak_pointer (&self->display, display); ++ g_set_weak_pointer (&self->context, meta_display_get_context (self->display)); ++ g_set_weak_pointer (&self->backend, meta_context_get_backend (self->context)); ++ g_set_weak_pointer (&self->stage, CLUTTER_ACTOR (meta_get_stage_for_display (self->display))); ++} ++ ++static void ++kiosk_screenshot_set_property (GObject *object, ++ guint property_id, ++ const GValue *value, ++ GParamSpec *param_spec) ++{ ++ KioskScreenshot *self = KIOSK_SCREENSHOT (object); ++ ++ switch (property_id) { ++ case PROP_COMPOSITOR: ++ g_set_weak_pointer (&self->compositor, g_value_get_object (value)); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, param_spec); ++ break; ++ } ++} ++ ++static void ++kiosk_screenshot_get_property (GObject *object, ++ guint property_id, ++ GValue *value, ++ GParamSpec *param_spec) ++{ ++ switch (property_id) { ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, param_spec); ++ break; ++ } ++} ++ ++static void ++kiosk_screenshot_class_init (KioskScreenshotClass *screenshot_class) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (screenshot_class); ++ ++ object_class->constructed = kiosk_screenshot_constructed; ++ object_class->set_property = kiosk_screenshot_set_property; ++ object_class->get_property = kiosk_screenshot_get_property; ++ object_class->dispose = kiosk_screenshot_dispose; ++ ++ kiosk_screenshot_properties[PROP_COMPOSITOR] = g_param_spec_object ("compositor", ++ "compositor", ++ "compositor", ++ KIOSK_TYPE_COMPOSITOR, ++ G_PARAM_CONSTRUCT_ONLY ++ | G_PARAM_WRITABLE ++ | G_PARAM_STATIC_NAME ++ | G_PARAM_STATIC_NICK ++ | G_PARAM_STATIC_BLURB); ++ g_object_class_install_properties (object_class, NUMBER_OF_PROPERTIES, kiosk_screenshot_properties); ++ ++ signals[SCREENSHOT_TAKEN] = ++ g_signal_new ("screenshot-taken", ++ G_TYPE_FROM_CLASS (object_class), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, NULL, NULL, ++ G_TYPE_NONE, ++ 1, ++ MTK_TYPE_RECTANGLE); ++} ++ ++static void ++kiosk_screenshot_init (KioskScreenshot *screenshot) ++{ ++ g_debug ("KiosScreenshot: Initializing"); ++} ++ ++static void ++on_screenshot_written (GObject *source, ++ GAsyncResult *task, ++ gpointer user_data) ++{ ++ KioskScreenshot *screenshot = KIOSK_SCREENSHOT (source); ++ GTask *result = user_data; ++ ++ g_task_return_boolean (result, g_task_propagate_boolean (G_TASK (task), NULL)); ++ g_object_unref (result); ++ ++ g_clear_pointer (&screenshot->image, cairo_surface_destroy); ++ g_clear_object (&screenshot->stream); ++ g_clear_pointer (&screenshot->datetime, g_date_time_unref); ++} ++ ++static cairo_format_t ++util_cairo_format_for_content (cairo_content_t content) ++{ ++ switch (content) { ++ case CAIRO_CONTENT_COLOR: ++ return CAIRO_FORMAT_RGB24; ++ case CAIRO_CONTENT_ALPHA: ++ return CAIRO_FORMAT_A8; ++ case CAIRO_CONTENT_COLOR_ALPHA: ++ default: ++ return CAIRO_FORMAT_ARGB32; ++ } ++} ++ ++static cairo_surface_t * ++util_cairo_surface_coerce_to_image (cairo_surface_t *surface, ++ cairo_content_t content, ++ int src_x, ++ int src_y, ++ int width, ++ int height) ++{ ++ cairo_surface_t *copy; ++ cairo_t *cr; ++ ++ copy = cairo_image_surface_create (util_cairo_format_for_content (content), ++ width, ++ height); ++ ++ cr = cairo_create (copy); ++ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); ++ cairo_set_source_surface (cr, surface, -src_x, -src_y); ++ cairo_paint (cr); ++ cairo_destroy (cr); ++ ++ return copy; ++} ++ ++static void ++convert_alpha (guchar *dest_data, ++ int dest_stride, ++ guchar *src_data, ++ int src_stride, ++ int src_x, ++ int src_y, ++ int width, ++ int height) ++{ ++ int x, y; ++ ++ src_data += src_stride * src_y + src_x * 4; ++ ++ for (y = 0; y < height; y++) { ++ uint32_t *src = (guint32 *) src_data; ++ ++ for (x = 0; x < width; x++) { ++ unsigned int alpha = src[x] >> 24; ++ ++ if (alpha == 0) { ++ dest_data[x * 4 + 0] = 0; ++ dest_data[x * 4 + 1] = 0; ++ dest_data[x * 4 + 2] = 0; ++ } else { ++ dest_data[x * 4 + 0] = (((src[x] & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; ++ dest_data[x * 4 + 1] = (((src[x] & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; ++ dest_data[x * 4 + 2] = (((src[x] & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha; ++ } ++ dest_data[x * 4 + 3] = alpha; ++ } ++ ++ src_data += src_stride; ++ dest_data += dest_stride; ++ } ++} ++ ++static void ++convert_no_alpha (guchar *dest_data, ++ int dest_stride, ++ guchar *src_data, ++ int src_stride, ++ int src_x, ++ int src_y, ++ int width, ++ int height) ++{ ++ int x, y; ++ ++ src_data += src_stride * src_y + src_x * 4; ++ ++ for (y = 0; y < height; y++) { ++ uint32_t *src = (uint32_t *) src_data; ++ ++ for (x = 0; x < width; x++) { ++ dest_data[x * 3 + 0] = src[x] >> 16; ++ dest_data[x * 3 + 1] = src[x] >> 8; ++ dest_data[x * 3 + 2] = src[x]; ++ } ++ ++ src_data += src_stride; ++ dest_data += dest_stride; ++ } ++} ++ ++static GdkPixbuf * ++util_pixbuf_from_surface (cairo_surface_t *surface, ++ gint src_x, ++ gint src_y, ++ gint width, ++ gint height) ++{ ++ cairo_content_t content; ++ GdkPixbuf *dest; ++ ++ /* General sanity checks */ ++ g_return_val_if_fail (surface != NULL, NULL); ++ g_return_val_if_fail (width > 0 && height > 0, NULL); ++ ++ content = cairo_surface_get_content (surface) | CAIRO_CONTENT_COLOR; ++ dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, ++ !!(content & CAIRO_CONTENT_ALPHA), ++ 8, ++ width, height); ++ ++ if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE && ++ cairo_image_surface_get_format (surface) == util_cairo_format_for_content (content)) { ++ surface = cairo_surface_reference (surface); ++ } else { ++ surface = util_cairo_surface_coerce_to_image (surface, content, ++ src_x, src_y, ++ width, height); ++ src_x = 0; ++ src_y = 0; ++ } ++ cairo_surface_flush (surface); ++ if (cairo_surface_status (surface) || dest == NULL) { ++ cairo_surface_destroy (surface); ++ g_clear_object (&dest); ++ return NULL; ++ } ++ ++ if (gdk_pixbuf_get_has_alpha (dest)) { ++ convert_alpha (gdk_pixbuf_get_pixels (dest), ++ gdk_pixbuf_get_rowstride (dest), ++ cairo_image_surface_get_data (surface), ++ cairo_image_surface_get_stride (surface), ++ src_x, src_y, ++ width, height); ++ } else { ++ convert_no_alpha (gdk_pixbuf_get_pixels (dest), ++ gdk_pixbuf_get_rowstride (dest), ++ cairo_image_surface_get_data (surface), ++ cairo_image_surface_get_stride (surface), ++ src_x, src_y, ++ width, height); ++ } ++ ++ cairo_surface_destroy (surface); ++ ++ return dest; ++} ++ ++static void ++write_screenshot_thread (GTask *result, ++ gpointer object, ++ gpointer task_data, ++ GCancellable *cancellable) ++{ ++ KioskScreenshot *screenshot = KIOSK_SCREENSHOT (object); ++ g_autoptr (GOutputStream) stream = NULL; ++ g_autoptr (GdkPixbuf) pixbuf = NULL; ++ g_autofree char *creation_time = NULL; ++ GError *error = NULL; ++ ++ g_assert (screenshot != NULL); ++ ++ stream = g_object_ref (screenshot->stream); ++ ++ pixbuf = util_pixbuf_from_surface (screenshot->image, ++ 0, 0, ++ cairo_image_surface_get_width (screenshot->image), ++ cairo_image_surface_get_height (screenshot->image)); ++ creation_time = g_date_time_format (screenshot->datetime, "%c"); ++ ++ if (!creation_time) ++ creation_time = g_date_time_format (screenshot->datetime, "%FT%T%z"); ++ ++ gdk_pixbuf_save_to_stream (pixbuf, stream, "png", NULL, &error, ++ "tEXt::Software", "gnome-screenshot", ++ "tEXt::Creation Time", creation_time, ++ NULL); ++ ++ if (error) ++ g_task_return_error (result, error); ++ else ++ g_task_return_boolean (result, TRUE); ++} ++ ++static void ++do_grab_screenshot (KioskScreenshot *screenshot, ++ int x, ++ int y, ++ int width, ++ int height, ++ KioskScreenshotFlag flags) ++{ ++ MtkRectangle screenshot_rect = { x, y, width, height }; ++ int image_width; ++ int image_height; ++ float scale; ++ cairo_surface_t *image; ++ ClutterPaintFlag paint_flags = CLUTTER_PAINT_FLAG_NONE; ++ g_autoptr (GError) error = NULL; ++ ++ clutter_stage_get_capture_final_size (CLUTTER_STAGE (screenshot->stage), ++ &screenshot_rect, ++ &image_width, ++ &image_height, ++ &scale); ++ image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, ++ image_width, image_height); ++ ++ if (flags & KIOSK_SCREENSHOT_FLAG_INCLUDE_CURSOR) ++ paint_flags |= CLUTTER_PAINT_FLAG_FORCE_CURSORS; ++ else ++ paint_flags |= CLUTTER_PAINT_FLAG_NO_CURSORS; ++ if (!clutter_stage_paint_to_buffer (CLUTTER_STAGE (screenshot->stage), ++ &screenshot_rect, scale, ++ cairo_image_surface_get_data (image), ++ cairo_image_surface_get_stride (image), ++ COGL_PIXEL_FORMAT_CAIRO_ARGB32_COMPAT, ++ paint_flags, ++ &error)) { ++ cairo_surface_destroy (image); ++ g_warning ("Failed to take screenshot: %s", error->message); ++ return; ++ } ++ ++ screenshot->image = image; ++ ++ screenshot->datetime = g_date_time_new_now_local (); ++} ++ ++static void ++draw_cursor_image (KioskScreenshot *screenshot, ++ cairo_surface_t *surface, ++ MtkRectangle area) ++{ ++ CoglTexture *texture; ++ int width, height; ++ int stride; ++ guint8 *data; ++ MetaCursorTracker *tracker; ++ cairo_surface_t *cursor_surface; ++ cairo_t *cr; ++ int x, y; ++ int xhot, yhot; ++ double xscale, yscale; ++ graphene_point_t point; ++ ++ tracker = meta_cursor_tracker_get_for_display (screenshot->display); ++ texture = meta_cursor_tracker_get_sprite (tracker); ++ ++ if (!texture) ++ return; ++ ++ meta_cursor_tracker_get_pointer (tracker, &point, NULL); ++ x = point.x; ++ y = point.y; ++ ++ if (!mtk_rectangle_contains_point (&area, point.x, point.y)) ++ return; ++ ++ meta_cursor_tracker_get_hot (tracker, &xhot, &yhot); ++ width = cogl_texture_get_width (texture); ++ height = cogl_texture_get_height (texture); ++ stride = 4 * width; ++ data = g_new (guint8, stride * height); ++ cogl_texture_get_data (texture, COGL_PIXEL_FORMAT_CAIRO_ARGB32_COMPAT, stride, data); ++ ++ /* FIXME: cairo-gl? */ ++ cursor_surface = cairo_image_surface_create_for_data (data, ++ CAIRO_FORMAT_ARGB32, ++ width, height, ++ stride); ++ ++ cairo_surface_get_device_scale (surface, &xscale, &yscale); ++ ++ if (xscale != 1.0 || yscale != 1.0) { ++ int monitor; ++ float monitor_scale; ++ MtkRectangle cursor_rect = { ++ .x = x, .y = y, .width = width, .height = height ++ }; ++ ++ monitor = meta_display_get_monitor_index_for_rect (screenshot->display, ++ &cursor_rect); ++ monitor_scale = meta_display_get_monitor_scale (screenshot->display, ++ monitor); ++ ++ cairo_surface_set_device_scale (cursor_surface, monitor_scale, monitor_scale); ++ } ++ ++ cr = cairo_create (surface); ++ cairo_set_source_surface (cr, ++ cursor_surface, ++ x - xhot - area.x, ++ y - yhot - area.y); ++ cairo_paint (cr); ++ ++ cairo_destroy (cr); ++ cairo_surface_destroy (cursor_surface); ++ g_free (data); ++} ++ ++static void ++grab_screenshot (KioskScreenshot *screenshot, ++ KioskScreenshotFlag flags, ++ GTask *result) ++{ ++ int width, height; ++ GTask *task; ++ ++ meta_display_get_size (screenshot->display, &width, &height); ++ ++ do_grab_screenshot (screenshot, ++ 0, 0, width, height, ++ flags); ++ ++ screenshot->screenshot_area.x = 0; ++ screenshot->screenshot_area.y = 0; ++ screenshot->screenshot_area.width = width; ++ screenshot->screenshot_area.height = height; ++ ++ task = g_task_new (screenshot, NULL, on_screenshot_written, result); ++ g_task_run_in_thread (task, write_screenshot_thread); ++ g_object_unref (task); ++} ++ ++static void ++grab_window_screenshot (KioskScreenshot *screenshot, ++ KioskScreenshotFlag flags, ++ GTask *result) ++{ ++ GTask *task; ++ MetaWindow *window = meta_display_get_focus_window (screenshot->display); ++ ClutterActor *window_actor; ++ gfloat actor_x, actor_y; ++ MtkRectangle rect; ++ ++ window_actor = CLUTTER_ACTOR (meta_window_get_compositor_private (window)); ++ clutter_actor_get_position (window_actor, &actor_x, &actor_y); ++ ++ meta_window_get_frame_rect (window, &rect); ++ ++ if (!screenshot->include_frame) ++ meta_window_frame_rect_to_client_rect (window, &rect, &rect); ++ ++ screenshot->screenshot_area = rect; ++ ++ screenshot->image = meta_window_actor_get_image (META_WINDOW_ACTOR (window_actor), ++ NULL); ++ ++ if (!screenshot->image) { ++ g_task_report_new_error (screenshot, on_screenshot_written, result, NULL, ++ G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Capturing window failed"); ++ return; ++ } ++ ++ screenshot->datetime = g_date_time_new_now_local (); ++ ++ if (flags & KIOSK_SCREENSHOT_FLAG_INCLUDE_CURSOR) { ++ if (meta_window_get_client_type (window) == META_WINDOW_CLIENT_TYPE_WAYLAND) { ++ float resource_scale; ++ resource_scale = clutter_actor_get_resource_scale (window_actor); ++ ++ cairo_surface_set_device_scale (screenshot->image, resource_scale, resource_scale); ++ } ++ ++ draw_cursor_image (screenshot, ++ screenshot->image, ++ screenshot->screenshot_area); ++ } ++ ++ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0, &rect); ++ ++ task = g_task_new (screenshot, NULL, on_screenshot_written, result); ++ g_task_run_in_thread (task, write_screenshot_thread); ++ g_object_unref (task); ++} ++ ++static gboolean ++finish_screenshot (KioskScreenshot *screenshot, ++ GAsyncResult *result, ++ MtkRectangle **area, ++ GError **error) ++{ ++ if (!g_task_propagate_boolean (G_TASK (result), error)) ++ return FALSE; ++ ++ if (area) ++ *area = &screenshot->screenshot_area; ++ ++ return TRUE; ++} ++ ++static void ++on_after_paint (ClutterStage *stage, ++ ClutterStageView *view, ++ ClutterFrame *frame, ++ GTask *result) ++{ ++ KioskScreenshot *screenshot = g_task_get_task_data (result); ++ GTask *task; ++ ++ g_signal_handlers_disconnect_by_func (stage, on_after_paint, result); ++ ++ if (screenshot->mode == KIOSK_SCREENSHOT_AREA) { ++ do_grab_screenshot (screenshot, ++ screenshot->screenshot_area.x, ++ screenshot->screenshot_area.y, ++ screenshot->screenshot_area.width, ++ screenshot->screenshot_area.height, ++ screenshot->flags); ++ ++ task = g_task_new (screenshot, NULL, on_screenshot_written, result); ++ g_task_run_in_thread (task, write_screenshot_thread); ++ } else { ++ grab_screenshot (screenshot, screenshot->flags, result); ++ } ++ ++ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0, ++ (MtkRectangle *) &screenshot->screenshot_area); ++ ++ meta_enable_unredirect_for_display (screenshot->display); ++} ++ ++/** ++ * kiosk_screenshot_screenshot: ++ * @screenshot: the #KioskScreenshot ++ * @include_cursor: Whether to include the cursor or not ++ * @stream: The stream for the screenshot ++ * @callback: (scope async): function to call returning success or failure ++ * of the async grabbing ++ * @user_data: the data to pass to callback function ++ * ++ * Takes a screenshot of the whole screen ++ * in @stream as png image. ++ * ++ */ ++void ++kiosk_screenshot_screenshot (KioskScreenshot *screenshot, ++ gboolean include_cursor, ++ GOutputStream *stream, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ GTask *result; ++ KioskScreenshotFlag flags; ++ ++ g_return_if_fail (KIOSK_IS_SCREENSHOT (screenshot)); ++ g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); ++ ++ if (screenshot->stream != NULL) { ++ if (callback) { ++ g_task_report_new_error (screenshot, ++ callback, ++ user_data, ++ kiosk_screenshot_screenshot, ++ G_IO_ERROR, ++ G_IO_ERROR_PENDING, ++ "Only one screenshot operation at a time " ++ "is permitted"); ++ } ++ return; ++ } ++ ++ result = g_task_new (screenshot, NULL, callback, user_data); ++ g_task_set_source_tag (result, kiosk_screenshot_screenshot); ++ g_task_set_task_data (result, screenshot, NULL); ++ ++ screenshot->stream = g_object_ref (stream); ++ ++ flags = KIOSK_SCREENSHOT_FLAG_NONE; ++ if (include_cursor) ++ flags |= KIOSK_SCREENSHOT_FLAG_INCLUDE_CURSOR; ++ ++ if (meta_is_wayland_compositor ()) { ++ grab_screenshot (screenshot, flags, result); ++ ++ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0, ++ (MtkRectangle *) &screenshot->screenshot_area); ++ } else { ++ meta_disable_unredirect_for_display (screenshot->display); ++ clutter_actor_queue_redraw (CLUTTER_ACTOR (screenshot->stage)); ++ screenshot->flags = flags; ++ screenshot->mode = KIOSK_SCREENSHOT_SCREEN; ++ g_signal_connect (screenshot->stage, "after-paint", ++ G_CALLBACK (on_after_paint), result); ++ } ++} ++ ++/** ++ * kiosk_screenshot_screenshot_finish: ++ * @screenshot: the #KioskScreenshot ++ * @result: the #GAsyncResult that was provided to the callback ++ * @area: (out) (transfer none): the area that was grabbed in screen coordinates ++ * @error: #GError for error reporting ++ * ++ * Finish the asynchronous operation started by kiosk_screenshot_screenshot() ++ * and obtain its result. ++ * ++ * Returns: whether the operation was successful ++ * ++ */ ++gboolean ++kiosk_screenshot_screenshot_finish (KioskScreenshot *screenshot, ++ GAsyncResult *result, ++ MtkRectangle **area, ++ GError **error) ++{ ++ g_return_val_if_fail (KIOSK_IS_SCREENSHOT (screenshot), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ g_return_val_if_fail (g_async_result_is_tagged (result, ++ kiosk_screenshot_screenshot), ++ FALSE); ++ return finish_screenshot (screenshot, result, area, error); ++} ++ ++/** ++ * kiosk_screenshot_screenshot_area: ++ * @screenshot: the #KioskScreenshot ++ * @x: The X coordinate of the area ++ * @y: The Y coordinate of the area ++ * @width: The width of the area ++ * @height: The height of the area ++ * @stream: The stream for the screenshot ++ * @callback: (scope async): function to call returning success or failure ++ * of the async grabbing ++ * @user_data: the data to pass to callback function ++ * ++ * Takes a screenshot of the passed in area and saves it ++ * in @stream as png image. ++ * ++ */ ++void ++kiosk_screenshot_screenshot_area (KioskScreenshot *screenshot, ++ int x, ++ int y, ++ int width, ++ int height, ++ GOutputStream *stream, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ GTask *result; ++ g_autoptr (GTask) task = NULL; ++ ++ g_return_if_fail (KIOSK_IS_SCREENSHOT (screenshot)); ++ g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); ++ ++ if (screenshot->stream != NULL) { ++ if (callback) { ++ g_task_report_new_error (screenshot, ++ callback, ++ NULL, ++ kiosk_screenshot_screenshot_area, ++ G_IO_ERROR, ++ G_IO_ERROR_PENDING, ++ "Only one screenshot operation at a time " ++ "is permitted"); ++ } ++ return; ++ } ++ ++ result = g_task_new (screenshot, NULL, callback, user_data); ++ g_task_set_source_tag (result, kiosk_screenshot_screenshot_area); ++ g_task_set_task_data (result, screenshot, NULL); ++ ++ screenshot->stream = g_object_ref (stream); ++ screenshot->screenshot_area.x = x; ++ screenshot->screenshot_area.y = y; ++ screenshot->screenshot_area.width = width; ++ screenshot->screenshot_area.height = height; ++ ++ ++ if (meta_is_wayland_compositor ()) { ++ do_grab_screenshot (screenshot, ++ screenshot->screenshot_area.x, ++ screenshot->screenshot_area.y, ++ screenshot->screenshot_area.width, ++ screenshot->screenshot_area.height, ++ KIOSK_SCREENSHOT_FLAG_NONE); ++ ++ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0, ++ (MtkRectangle *) &screenshot->screenshot_area); ++ ++ task = g_task_new (screenshot, NULL, on_screenshot_written, result); ++ g_task_run_in_thread (task, write_screenshot_thread); ++ } else { ++ meta_disable_unredirect_for_display (screenshot->display); ++ clutter_actor_queue_redraw (CLUTTER_ACTOR (screenshot->stage)); ++ screenshot->flags = KIOSK_SCREENSHOT_FLAG_NONE; ++ screenshot->mode = KIOSK_SCREENSHOT_AREA; ++ g_signal_connect (screenshot->stage, "after-paint", ++ G_CALLBACK (on_after_paint), result); ++ } ++} ++ ++/** ++ * kiosk_screenshot_screenshot_area_finish: ++ * @screenshot: the #KioskScreenshot ++ * @result: the #GAsyncResult that was provided to the callback ++ * @area: (out) (transfer none): the area that was grabbed in screen coordinates ++ * @error: #GError for error reporting ++ * ++ * Finish the asynchronous operation started by kiosk_screenshot_screenshot_area() ++ * and obtain its result. ++ * ++ * Returns: whether the operation was successful ++ * ++ */ ++gboolean ++kiosk_screenshot_screenshot_area_finish (KioskScreenshot *screenshot, ++ GAsyncResult *result, ++ MtkRectangle **area, ++ GError **error) ++{ ++ g_return_val_if_fail (KIOSK_IS_SCREENSHOT (screenshot), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ g_return_val_if_fail (g_async_result_is_tagged (result, ++ kiosk_screenshot_screenshot_area), ++ FALSE); ++ return finish_screenshot (screenshot, result, area, error); ++} ++ ++/** ++ * kiosk_screenshot_screenshot_window: ++ * @screenshot: the #KioskScreenshot ++ * @include_frame: Whether to include the frame or not ++ * @include_cursor: Whether to include the cursor or not ++ * @stream: The stream for the screenshot ++ * @callback: (scope async): function to call returning success or failure ++ * of the async grabbing ++ * @user_data: the data to pass to callback function ++ * ++ * Takes a screenshot of the focused window (optionally omitting the frame) ++ * in @stream as png image. ++ * ++ */ ++void ++kiosk_screenshot_screenshot_window (KioskScreenshot *screenshot, ++ gboolean include_frame, ++ gboolean include_cursor, ++ GOutputStream *stream, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ MetaWindow *window; ++ GTask *result; ++ ++ g_return_if_fail (KIOSK_IS_SCREENSHOT (screenshot)); ++ g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); ++ ++ window = meta_display_get_focus_window (screenshot->display); ++ ++ if (screenshot->stream != NULL || !window) { ++ if (callback) { ++ g_task_report_new_error (screenshot, ++ callback, ++ NULL, ++ kiosk_screenshot_screenshot_window, ++ G_IO_ERROR, ++ G_IO_ERROR_PENDING, ++ "Only one screenshot operation at a time " ++ "is permitted"); ++ } ++ return; ++ } ++ ++ result = g_task_new (screenshot, NULL, callback, user_data); ++ g_task_set_source_tag (result, kiosk_screenshot_screenshot_window); ++ ++ screenshot->stream = g_object_ref (stream); ++ screenshot->include_frame = include_frame; ++ ++ grab_window_screenshot (screenshot, include_cursor, result); ++} ++ ++/** ++ * kiosk_screenshot_screenshot_window_finish: ++ * @screenshot: the #KioskScreenshot ++ * @result: the #GAsyncResult that was provided to the callback ++ * @area: (out) (transfer none): the area that was grabbed in screen coordinates ++ * @error: #GError for error reporting ++ * ++ * Finish the asynchronous operation started by kiosk_screenshot_screenshot_window() ++ * and obtain its result. ++ * ++ * Returns: whether the operation was successful ++ * ++ */ ++gboolean ++kiosk_screenshot_screenshot_window_finish (KioskScreenshot *screenshot, ++ GAsyncResult *result, ++ MtkRectangle **area, ++ GError **error) ++{ ++ g_return_val_if_fail (KIOSK_IS_SCREENSHOT (screenshot), FALSE); ++ g_return_val_if_fail (G_IS_TASK (result), FALSE); ++ g_return_val_if_fail (g_async_result_is_tagged (result, ++ kiosk_screenshot_screenshot_window), ++ FALSE); ++ return finish_screenshot (screenshot, result, area, error); ++} ++ ++KioskScreenshot * ++kiosk_screenshot_new (KioskCompositor *compositor) ++{ ++ GObject *object; ++ ++ object = g_object_new (KIOSK_TYPE_SCREENSHOT, ++ "compositor", compositor, ++ NULL); ++ ++ return KIOSK_SCREENSHOT (object); ++} +diff --git a/compositor/kiosk-screenshot.h b/compositor/kiosk-screenshot.h +new file mode 100644 +index 0000000..ec03fa8 +--- /dev/null ++++ b/compositor/kiosk-screenshot.h +@@ -0,0 +1,59 @@ ++#pragma once ++ ++#include <glib-object.h> ++#include <gio/gio.h> ++#include <mtk/mtk.h> ++#include <clutter/clutter.h> ++#include <gdk-pixbuf/gdk-pixbuf.h> ++ ++typedef struct _KioskCompositor KioskCompositor; ++ ++/** ++ * KioskScreenshot: ++ * ++ * Grabs screenshots of areas and/or windows ++ * ++ * The #KioskScreenshot object is used to take screenshots of screen ++ * areas or windows and write them out as png files. ++ * ++ */ ++#define KIOSK_TYPE_SCREENSHOT (kiosk_screenshot_get_type ()) ++ ++G_DECLARE_FINAL_TYPE (KioskScreenshot, kiosk_screenshot, ++ KIOSK, SCREENSHOT, GObject) ++ ++KioskScreenshot *kiosk_screenshot_new (KioskCompositor * compositor); ++ ++void kiosk_screenshot_screenshot_area (KioskScreenshot *screenshot, ++ int x, ++ int y, ++ int width, ++ int height, ++ GOutputStream *stream, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean kiosk_screenshot_screenshot_area_finish (KioskScreenshot *screenshot, ++ GAsyncResult *result, ++ MtkRectangle **area, ++ GError **error); ++ ++void kiosk_screenshot_screenshot_window (KioskScreenshot *screenshot, ++ gboolean include_frame, ++ gboolean include_cursor, ++ GOutputStream *stream, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean kiosk_screenshot_screenshot_window_finish (KioskScreenshot *screenshot, ++ GAsyncResult *result, ++ MtkRectangle **area, ++ GError **error); ++ ++void kiosk_screenshot_screenshot (KioskScreenshot *screenshot, ++ gboolean include_cursor, ++ GOutputStream *stream, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++gboolean kiosk_screenshot_screenshot_finish (KioskScreenshot *screenshot, ++ GAsyncResult *result, ++ MtkRectangle **area, ++ GError **error); +diff --git a/meson.build b/meson.build +index fb9a76d..d1efcab 100644 +--- a/meson.build ++++ b/meson.build +@@ -123,6 +123,7 @@ compositor_dependencies += dependency('glib-2.0') + compositor_dependencies += dependency('gnome-desktop-4') + compositor_dependencies += dependency('gobject-2.0') + compositor_dependencies += dependency('ibus-1.0') ++compositor_dependencies += dependency('gdk-pixbuf-2.0') + compositor_dependencies += dependency(libmutter_cogl_name) + compositor_dependencies += dependency(libmutter_cogl_pango_name) + compositor_dependencies += dependency(libmutter_clutter_name) +@@ -153,6 +154,8 @@ compositor_sources += 'compositor/kiosk-input-source-group.c' + compositor_sources += 'compositor/kiosk-service.c' + compositor_sources += 'compositor/kiosk-shell-service.c' + compositor_sources += 'compositor/kiosk-shell-introspect-service.c' ++compositor_sources += 'compositor/kiosk-screenshot.c' ++ + if mutter_have_x11 + compositor_sources += 'compositor/kiosk-x-keyboard-manager.c' + endif +-- +2.48.1 + diff --git a/SOURCES/0002-compositor-Add-Shell-Screenshot-support.patch b/SOURCES/0002-compositor-Add-Shell-Screenshot-support.patch new file mode 100644 index 0000000..f3658ce --- /dev/null +++ b/SOURCES/0002-compositor-Add-Shell-Screenshot-support.patch @@ -0,0 +1,892 @@ +From 0ab97a3be415e7cc3937d68fd66a421f96f45417 Mon Sep 17 00:00:00 2001 +From: Olivier Fourdan <ofourdan@redhat.com> +Date: Fri, 24 Jan 2025 09:41:04 +0100 +Subject: [PATCH 2/2] compositor: Add Shell Screenshot support + +Add support for the org.gnome.Shell.Screenshot DBus API which is +required by the desktop portals. + +Not all of the org.gnome.Shell.Screenshot API is implemented though, +only the non-interactive part which require no UI interaction are +currently available. Also missing is the pick-color API. + +To have access to the Shell screenshot API from all other clients, +gnome-kiosk needs to be started in unsafe mode (using the command line +option "--unsafe-mode"). + +Part-of: <https://gitlab.gnome.org/GNOME/gnome-kiosk/-/merge_requests/61> +(cherry picked from commit 5338c8ab454b8cad607e2f696a9a352b375dff0d) +--- + compositor/kiosk-compositor.c | 4 + + compositor/kiosk-shell-screenshot-service.c | 599 ++++++++++++++++++ + compositor/kiosk-shell-screenshot-service.h | 23 + + .../org.gnome.Shell.Screenshot.xml | 161 +++++ + meson.build | 12 + + 5 files changed, 799 insertions(+) + create mode 100644 compositor/kiosk-shell-screenshot-service.c + create mode 100644 compositor/kiosk-shell-screenshot-service.h + create mode 100644 dbus-interfaces/org.gnome.Shell.Screenshot.xml + +diff --git a/compositor/kiosk-compositor.c b/compositor/kiosk-compositor.c +index d5a2b64..8432a24 100644 +--- a/compositor/kiosk-compositor.c ++++ b/compositor/kiosk-compositor.c +@@ -24,6 +24,7 @@ + #include "kiosk-app-system.h" + #include "kiosk-window-tracker.h" + #include "kiosk-shell-introspect-service.h" ++#include "kiosk-shell-screenshot-service.h" + + #include "org.gnome.DisplayManager.Manager.h" + +@@ -46,6 +47,7 @@ struct _KioskCompositor + KioskAppSystem *app_system; + KioskWindowTracker *tracker; + KioskShellIntrospectService *introspect_service; ++ KioskShellScreenshotService *screenshot_service; + }; + + enum +@@ -250,6 +252,8 @@ kiosk_compositor_start (MetaPlugin *plugin) + self->tracker = kiosk_window_tracker_new (self, self->app_system); + self->introspect_service = kiosk_shell_introspect_service_new (self); + kiosk_shell_introspect_service_start (self->introspect_service, &error); ++ self->screenshot_service = kiosk_shell_screenshot_service_new (self); ++ kiosk_shell_screenshot_service_start (self->screenshot_service, &error); + + if (error != NULL) { + g_debug ("KioskCompositor: Could not start D-Bus service: %s", error->message); +diff --git a/compositor/kiosk-shell-screenshot-service.c b/compositor/kiosk-shell-screenshot-service.c +new file mode 100644 +index 0000000..a961905 +--- /dev/null ++++ b/compositor/kiosk-shell-screenshot-service.c +@@ -0,0 +1,599 @@ ++#include "config.h" ++#include "kiosk-shell-screenshot-service.h" ++ ++#include <stdlib.h> ++#include <string.h> ++#include <meta/display.h> ++#include <meta/meta-context.h> ++ ++#include "kiosk-compositor.h" ++#include "kiosk-screenshot.h" ++ ++#define KIOSK_SHELL_SCREENSHOT_SERVICE_BUS_NAME "org.gnome.Shell.Screenshot" ++#define KIOSK_SHELL_SCREENSHOT_SERVICE_OBJECT_PATH "/org/gnome/Shell/Screenshot" ++ ++struct _KioskShellScreenshotService ++{ ++ KioskShellScreenshotDBusServiceSkeleton parent; ++ ++ /* weak references */ ++ KioskCompositor *compositor; ++ MetaDisplay *display; ++ MetaContext *context; ++ ++ /* strong references */ ++ GCancellable *cancellable; ++ KioskScreenshot *screenshot; ++ ++ /* handles */ ++ guint bus_id; ++}; ++ ++struct KioskShellScreenshotCompletion ++{ ++ KioskShellScreenshotDBusService *service; ++ GDBusMethodInvocation *invocation; ++ ++ gpointer data; ++}; ++ ++enum ++{ ++ PROP_COMPOSITOR = 1, ++ NUMBER_OF_PROPERTIES ++}; ++ ++static GParamSpec *kiosk_shell_screenshot_service_properties[NUMBER_OF_PROPERTIES] = { NULL, }; ++ ++static void kiosk_shell_screenshot_dbus_service_interface_init (KioskShellScreenshotDBusServiceIface *interface); ++ ++G_DEFINE_TYPE_WITH_CODE (KioskShellScreenshotService, ++ kiosk_shell_screenshot_service, ++ KIOSK_TYPE_SHELL_SCREENSHOT_DBUS_SERVICE_SKELETON, ++ G_IMPLEMENT_INTERFACE (KIOSK_TYPE_SHELL_SCREENSHOT_DBUS_SERVICE, ++ kiosk_shell_screenshot_dbus_service_interface_init)); ++ ++static void kiosk_shell_screenshot_service_set_property (GObject *object, ++ guint property_id, ++ const GValue *value, ++ GParamSpec *param_spec); ++ ++static void kiosk_shell_screenshot_service_constructed (GObject *object); ++static void kiosk_shell_screenshot_service_dispose (GObject *object); ++ ++static void ++kiosk_shell_screenshot_service_class_init (KioskShellScreenshotServiceClass *shell_service_class) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (shell_service_class); ++ ++ object_class->constructed = kiosk_shell_screenshot_service_constructed; ++ object_class->set_property = kiosk_shell_screenshot_service_set_property; ++ object_class->dispose = kiosk_shell_screenshot_service_dispose; ++ ++ kiosk_shell_screenshot_service_properties[PROP_COMPOSITOR] = ++ g_param_spec_object ("compositor", ++ NULL, ++ NULL, ++ KIOSK_TYPE_COMPOSITOR, ++ G_PARAM_CONSTRUCT_ONLY ++ | G_PARAM_WRITABLE ++ | G_PARAM_STATIC_NAME); ++ g_object_class_install_properties (object_class, ++ NUMBER_OF_PROPERTIES, ++ kiosk_shell_screenshot_service_properties); ++} ++ ++static void ++kiosk_shell_screenshot_service_set_property (GObject *object, ++ guint property_id, ++ const GValue *value, ++ GParamSpec *param_spec) ++{ ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (object); ++ ++ switch (property_id) { ++ case PROP_COMPOSITOR: ++ g_set_weak_pointer (&self->compositor, g_value_get_object (value)); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, param_spec); ++ break; ++ } ++} ++ ++static void ++kiosk_shell_screenshot_service_dispose (GObject *object) ++{ ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (object); ++ ++ kiosk_shell_screenshot_service_stop (self); ++ ++ g_clear_object (&self->screenshot); ++ g_clear_weak_pointer (&self->context); ++ g_clear_weak_pointer (&self->display); ++ g_clear_weak_pointer (&self->compositor); ++ ++ G_OBJECT_CLASS (kiosk_shell_screenshot_service_parent_class)->dispose (object); ++} ++ ++static void ++kiosk_shell_screenshot_service_constructed (GObject *object) ++{ ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (object); ++ ++ self->screenshot = kiosk_screenshot_new (self->compositor); ++ ++ g_set_weak_pointer (&self->display, meta_plugin_get_display (META_PLUGIN (self->compositor))); ++ g_set_weak_pointer (&self->context, meta_display_get_context (self->display)); ++ ++ G_OBJECT_CLASS (kiosk_shell_screenshot_service_parent_class)->constructed (object); ++} ++ ++static gboolean ++kiosk_shell_screenshot_check_access (KioskShellScreenshotService *self, ++ const char *client_unique_name) ++{ ++ GValue value = G_VALUE_INIT; ++ gboolean unsafe_mode; ++ ++ g_object_get_property (G_OBJECT (self->context), "unsafe-mode", &value); ++ unsafe_mode = g_value_get_boolean (&value); ++ g_debug ("KioskShellScreenshotService: unsafe-mode is %s", ++ unsafe_mode ? "TRUE" : "FALSE"); ++ ++ return unsafe_mode; ++} ++ ++static void ++completion_dispose (struct KioskShellScreenshotCompletion *completion) ++{ ++ g_object_unref (completion->service); ++ g_object_unref (completion->invocation); ++ g_free (completion->data); ++ ++ g_free (completion); ++} ++ ++static struct KioskShellScreenshotCompletion * ++completion_new (KioskShellScreenshotDBusService *service, ++ GDBusMethodInvocation *invocation, ++ const char *filename) ++{ ++ struct KioskShellScreenshotCompletion *completion; ++ ++ completion = g_new0 (struct KioskShellScreenshotCompletion, 1); ++ completion->service = g_object_ref (service); ++ completion->invocation = g_object_ref (invocation); ++ completion->data = g_strdup (filename); ++ ++ return completion; ++} ++ ++static gboolean ++kiosk_shell_screenshot_service_handle_flash_area (KioskShellScreenshotDBusService *object, ++ GDBusMethodInvocation *invocation, ++ gint arg_x, ++ gint arg_y, ++ gint arg_width, ++ gint arg_height) ++{ ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (object); ++ const char *client_unique_name = g_dbus_method_invocation_get_sender (invocation); ++ ++ g_debug ("KioskShellScreenshotService: Handling FlashArea(x=%i, y=%i, w=%i, h=%i) from %s", ++ arg_x, arg_y, arg_width, arg_height, client_unique_name); ++ ++ if (!kiosk_shell_screenshot_check_access (self, client_unique_name)) { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ "Permission denied"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_NOT_SUPPORTED, ++ "FlashArea is not supported"); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static gboolean ++kiosk_shell_screenshot_service_handle_interactive_screenshot (KioskShellScreenshotDBusService *object, ++ GDBusMethodInvocation *invocation) ++{ ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (object); ++ const char *client_unique_name = g_dbus_method_invocation_get_sender (invocation); ++ ++ g_debug ("KioskShellScreenshotService: Handling InteractiveScreenshot() from %s", ++ client_unique_name); ++ ++ if (!kiosk_shell_screenshot_check_access (self, client_unique_name)) { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ "Permission denied"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_NOT_SUPPORTED, ++ "InteractiveScreenshot is not supported"); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++screenshot_ready_callback (GObject *source_object, ++ GAsyncResult *result, ++ gpointer data) ++{ ++ struct KioskShellScreenshotCompletion *completion = data; ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (completion->service); ++ g_autoptr (GError) error = NULL; ++ gboolean success = TRUE; ++ ++ kiosk_screenshot_screenshot_finish (self->screenshot, ++ result, ++ NULL, ++ &error); ++ ++ if (error) { ++ g_warning ("Screenshot failed: %s", error->message); ++ success = FALSE; ++ } ++ ++ kiosk_shell_screenshot_dbus_service_complete_screenshot (completion->service, ++ completion->invocation, ++ success, ++ completion->data); ++ ++ completion_dispose (completion); ++} ++ ++static gboolean ++kiosk_shell_screenshot_service_handle_screenshot (KioskShellScreenshotDBusService *object, ++ GDBusMethodInvocation *invocation, ++ gboolean arg_include_cursor, ++ gboolean arg_flash, ++ const gchar *arg_filename) ++{ ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (object); ++ const char *client_unique_name = g_dbus_method_invocation_get_sender (invocation); ++ struct KioskShellScreenshotCompletion *completion; ++ g_autoptr (GFile) file = NULL; ++ g_autoptr (GFileOutputStream) stream = NULL; ++ g_autoptr (GError) error = NULL; ++ g_autoptr (GAsyncResult) result = NULL; ++ ++ g_debug ("KioskShellScreenshotService: Handling Screenshot(cursor=%i, flash=%i, file='%s') from %s", ++ arg_include_cursor, arg_flash, arg_filename, client_unique_name); ++ ++ if (!kiosk_shell_screenshot_check_access (self, client_unique_name)) { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ "Permission denied"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ file = g_file_new_for_path (arg_filename); ++ stream = g_file_create (file, G_FILE_CREATE_NONE, NULL, &error); ++ if (error) { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Error creating file: %s", ++ error->message); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ completion = completion_new (object, invocation, arg_filename); ++ kiosk_screenshot_screenshot (self->screenshot, ++ arg_include_cursor, ++ G_OUTPUT_STREAM (stream), ++ screenshot_ready_callback, ++ completion); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++screenshot_area_ready_callback (GObject *source_object, ++ GAsyncResult *result, ++ gpointer data) ++{ ++ struct KioskShellScreenshotCompletion *completion = data; ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (completion->service); ++ g_autoptr (GError) error = NULL; ++ gboolean success = TRUE; ++ ++ kiosk_screenshot_screenshot_area_finish (self->screenshot, ++ result, ++ NULL, ++ &error); ++ ++ if (error) { ++ g_warning ("Screenshot area failed: %s", error->message); ++ success = FALSE; ++ } ++ ++ kiosk_shell_screenshot_dbus_service_complete_screenshot_area (completion->service, ++ completion->invocation, ++ success, ++ completion->data); ++ ++ completion_dispose (completion); ++} ++ ++static gboolean ++kiosk_shell_screenshot_service_handle_screenshot_area (KioskShellScreenshotDBusService *object, ++ GDBusMethodInvocation *invocation, ++ gint arg_x, ++ gint arg_y, ++ gint arg_width, ++ gint arg_height, ++ gboolean arg_flash, ++ const gchar *arg_filename) ++{ ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (object); ++ const char *client_unique_name = g_dbus_method_invocation_get_sender (invocation); ++ struct KioskShellScreenshotCompletion *completion; ++ g_autoptr (GFile) file = NULL; ++ g_autoptr (GFileOutputStream) stream = NULL; ++ g_autoptr (GError) error = NULL; ++ g_autoptr (GAsyncResult) result = NULL; ++ ++ g_debug ("KioskShellScreenshotService: Handling ScreenshotArea(x=%i, y=%i, w=%i, h=%i, flash=%i, file='%s') from %s", ++ arg_x, arg_y, arg_width, arg_height, arg_flash, arg_filename, client_unique_name); ++ ++ if (!kiosk_shell_screenshot_check_access (self, client_unique_name)) { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ "Permission denied"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ file = g_file_new_for_path (arg_filename); ++ stream = g_file_create (file, G_FILE_CREATE_NONE, NULL, &error); ++ if (error) { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Error creating file: %s", ++ error->message); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ completion = completion_new (object, invocation, arg_filename); ++ kiosk_screenshot_screenshot_area (self->screenshot, ++ arg_x, ++ arg_y, ++ arg_width, ++ arg_height, ++ G_OUTPUT_STREAM (stream), ++ screenshot_area_ready_callback, ++ completion); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++screenshot_window_ready_callback (GObject *source_object, ++ GAsyncResult *result, ++ gpointer data) ++{ ++ struct KioskShellScreenshotCompletion *completion = data; ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (completion->service); ++ g_autoptr (GError) error = NULL; ++ gboolean success = TRUE; ++ ++ kiosk_screenshot_screenshot_window_finish (self->screenshot, ++ result, ++ NULL, ++ &error); ++ ++ if (error) { ++ g_warning ("Screenshot window failed: %s", error->message); ++ success = FALSE; ++ } ++ ++ kiosk_shell_screenshot_dbus_service_complete_screenshot_window (completion->service, ++ completion->invocation, ++ success, ++ completion->data); ++ ++ completion_dispose (completion); ++} ++ ++static gboolean ++kiosk_shell_screenshot_service_handle_screenshot_window (KioskShellScreenshotDBusService *object, ++ GDBusMethodInvocation *invocation, ++ gboolean arg_include_frame, ++ gboolean arg_include_cursor, ++ gboolean arg_flash, ++ const gchar *arg_filename) ++{ ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (object); ++ const char *client_unique_name = g_dbus_method_invocation_get_sender (invocation); ++ struct KioskShellScreenshotCompletion *completion; ++ g_autoptr (GFile) file = NULL; ++ g_autoptr (GFileOutputStream) stream = NULL; ++ g_autoptr (GError) error = NULL; ++ g_autoptr (GAsyncResult) result = NULL; ++ ++ g_debug ("KioskShellScreenshotService: Handling ScreenshotWindow(frame=%i, cursor=%i, flash=%i, file='%s') from %s", ++ arg_include_frame, arg_include_cursor, arg_flash, arg_filename, client_unique_name); ++ ++ if (!kiosk_shell_screenshot_check_access (self, client_unique_name)) { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ "Permission denied"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ file = g_file_new_for_path (arg_filename); ++ stream = g_file_create (file, G_FILE_CREATE_NONE, NULL, &error); ++ if (error) { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Error creating file: %s", ++ error->message); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ completion = completion_new (object, invocation, arg_filename); ++ kiosk_screenshot_screenshot_window (self->screenshot, ++ arg_include_frame, ++ arg_include_cursor, ++ G_OUTPUT_STREAM (stream), ++ screenshot_window_ready_callback, ++ completion); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static gboolean ++kiosk_shell_screenshot_service_handle_select_area (KioskShellScreenshotDBusService *object, ++ GDBusMethodInvocation *invocation) ++{ ++ KioskShellScreenshotService *self = KIOSK_SHELL_SCREENSHOT_SERVICE (object); ++ const char *client_unique_name = g_dbus_method_invocation_get_sender (invocation); ++ ++ g_debug ("KioskShellScreenshotService: Handling SelectArea() from %s", ++ client_unique_name); ++ ++ if (!kiosk_shell_screenshot_check_access (self, client_unique_name)) { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ "Permission denied"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_NOT_SUPPORTED, ++ "SelectArea is not supported"); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++kiosk_shell_screenshot_dbus_service_interface_init (KioskShellScreenshotDBusServiceIface *interface) ++{ ++ interface->handle_flash_area = ++ kiosk_shell_screenshot_service_handle_flash_area; ++ interface->handle_interactive_screenshot = ++ kiosk_shell_screenshot_service_handle_interactive_screenshot; ++ interface->handle_screenshot = ++ kiosk_shell_screenshot_service_handle_screenshot; ++ interface->handle_screenshot_area = ++ kiosk_shell_screenshot_service_handle_screenshot_area; ++ interface->handle_screenshot_window = ++ kiosk_shell_screenshot_service_handle_screenshot_window; ++ interface->handle_select_area = ++ kiosk_shell_screenshot_service_handle_select_area; ++} ++ ++static void ++kiosk_shell_screenshot_service_init (KioskShellScreenshotService *self) ++{ ++ g_debug ("KioskShellScreenshotService: Initializing"); ++} ++ ++KioskShellScreenshotService * ++kiosk_shell_screenshot_service_new (KioskCompositor *compositor) ++{ ++ GObject *object; ++ ++ object = g_object_new (KIOSK_TYPE_SHELL_SCREENSHOT_SERVICE, ++ "compositor", compositor, ++ NULL); ++ ++ return KIOSK_SHELL_SCREENSHOT_SERVICE (object); ++} ++ ++static void ++on_user_bus_acquired (GDBusConnection *connection, ++ const char *unique_name, ++ KioskShellScreenshotService *self) ++{ ++ g_autoptr (GError) error = NULL; ++ ++ g_debug ("KioskShellScreenshotService: Connected to user bus"); ++ ++ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self), ++ connection, ++ KIOSK_SHELL_SCREENSHOT_SERVICE_OBJECT_PATH, ++ &error); ++ ++ if (error != NULL) { ++ g_debug ("KioskShellScreenshotService: Could not export interface skeleton: %s", ++ error->message); ++ g_clear_error (&error); ++ } ++} ++ ++static void ++on_bus_name_acquired (GDBusConnection *connection, ++ const char *name, ++ KioskShellScreenshotService *self) ++{ ++ if (g_strcmp0 (name, KIOSK_SHELL_SCREENSHOT_SERVICE_BUS_NAME) != 0) { ++ return; ++ } ++ ++ g_debug ("KioskShellScreenshotService: Acquired name %s", name); ++} ++ ++static void ++on_bus_name_lost (GDBusConnection *connection, ++ const char *name, ++ KioskShellScreenshotService *self) ++{ ++ if (g_strcmp0 (name, KIOSK_SHELL_SCREENSHOT_SERVICE_BUS_NAME) != 0) { ++ return; ++ } ++ ++ g_debug ("KioskShellScreenshotService: Lost name %s", name); ++} ++ ++gboolean ++kiosk_shell_screenshot_service_start (KioskShellScreenshotService *self, ++ GError **error) ++{ ++ g_return_val_if_fail (KIOSK_IS_SHELL_SCREENSHOT_SERVICE (self), FALSE); ++ ++ g_debug ("KioskShellScreenshotService: Starting"); ++ self->bus_id = g_bus_own_name (G_BUS_TYPE_SESSION, ++ KIOSK_SHELL_SCREENSHOT_SERVICE_BUS_NAME, ++ G_BUS_NAME_OWNER_FLAGS_REPLACE, ++ (GBusAcquiredCallback) on_user_bus_acquired, ++ (GBusNameAcquiredCallback) on_bus_name_acquired, ++ (GBusNameVanishedCallback) on_bus_name_lost, ++ self, ++ NULL); ++ ++ return TRUE; ++} ++ ++void ++kiosk_shell_screenshot_service_stop (KioskShellScreenshotService *self) ++{ ++ g_return_if_fail (KIOSK_IS_SHELL_SCREENSHOT_SERVICE (self)); ++ ++ g_debug ("KioskShellScreenshotService: Stopping"); ++ ++ g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self)); ++ g_clear_handle_id (&self->bus_id, g_bus_unown_name); ++} +diff --git a/compositor/kiosk-shell-screenshot-service.h b/compositor/kiosk-shell-screenshot-service.h +new file mode 100644 +index 0000000..520c319 +--- /dev/null ++++ b/compositor/kiosk-shell-screenshot-service.h +@@ -0,0 +1,23 @@ ++#pragma once ++ ++#include <glib-object.h> ++ ++#include "org.gnome.Shell.Screenshot.h" ++ ++typedef struct _KioskCompositor KioskCompositor; ++ ++G_BEGIN_DECLS ++ ++#define KIOSK_TYPE_SHELL_SCREENSHOT_SERVICE (kiosk_shell_screenshot_service_get_type ()) ++ ++G_DECLARE_FINAL_TYPE (KioskShellScreenshotService, ++ kiosk_shell_screenshot_service, ++ KIOSK, SHELL_SCREENSHOT_SERVICE, ++ KioskShellScreenshotDBusServiceSkeleton); ++ ++KioskShellScreenshotService *kiosk_shell_screenshot_service_new (KioskCompositor *compositor); ++gboolean kiosk_shell_screenshot_service_start (KioskShellScreenshotService *service, ++ GError **error); ++void kiosk_shell_screenshot_service_stop (KioskShellScreenshotService *service); ++ ++G_END_DECLS +diff --git a/dbus-interfaces/org.gnome.Shell.Screenshot.xml b/dbus-interfaces/org.gnome.Shell.Screenshot.xml +new file mode 100644 +index 0000000..8e16a30 +--- /dev/null ++++ b/dbus-interfaces/org.gnome.Shell.Screenshot.xml +@@ -0,0 +1,161 @@ ++<!DOCTYPE node PUBLIC ++'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN' ++'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'> ++<node> ++ ++ <!-- ++ org.gnome.Shell.Screenshot: ++ @short_description: Screenshot interface ++ ++ The interface used to capture pictures of the screen contents. ++ --> ++ <interface name="org.gnome.Shell.Screenshot"> ++ ++ <!-- ++ InteractiveScreenshot: ++ @success: whether the screenshot was captured ++ @uri: the file where the screenshot was saved ++ ++ Shows Shell's interactive screenshot dialog, and lets the ++ user take an interactive screenshot, which is then returned ++ in @filename as png image. It returns a boolean indicating ++ whether the operation was successful or not. The URI of the ++ screenshot will be returned in @uri. ++ --> ++ <method name="InteractiveScreenshot"> ++ <arg type="b" direction="out" name="success"/> ++ <arg type="s" direction="out" name="uri"/> ++ </method> ++ ++ <!-- ++ Screenshot: ++ @filename: The filename for the screenshot ++ @include_cursor: Whether to include the cursor image or not ++ @flash: Whether to flash the screen or not ++ @success: whether the screenshot was captured ++ @filename_used: the file where the screenshot was saved ++ ++ Takes a screenshot of the whole screen and saves it ++ in @filename as png image, it returns a boolean ++ indicating whether the operation was successful or not. ++ @filename can either be an absolute path or a basename, in ++ which case the screenshot will be saved in the $XDG_PICTURES_DIR ++ or the home directory if it doesn't exist. The filename used ++ to save the screenshot will be returned in @filename_used. ++ --> ++ <method name="Screenshot"> ++ <arg type="b" direction="in" name="include_cursor"/> ++ <arg type="b" direction="in" name="flash"/> ++ <arg type="s" direction="in" name="filename"/> ++ <arg type="b" direction="out" name="success"/> ++ <arg type="s" direction="out" name="filename_used"/> ++ </method> ++ ++ <!-- ++ ScreenshotWindow: ++ @include_frame: Whether to include the frame or not ++ @include_cursor: Whether to include the cursor image or not ++ @flash: Whether to flash the window area or not ++ @filename: The filename for the screenshot ++ @success: whether the screenshot was captured ++ @filename_used: the file where the screenshot was saved ++ ++ Takes a screenshot of the focused window (optionally omitting the frame) ++ and saves it in @filename as png image, it returns a boolean ++ indicating whether the operation was successful or not. ++ @filename can either be an absolute path or a basename, in ++ which case the screenshot will be saved in the $XDG_PICTURES_DIR ++ or the home directory if it doesn't exist. The filename used ++ to save the screenshot will be returned in @filename_used. ++ --> ++ <method name="ScreenshotWindow"> ++ <arg type="b" direction="in" name="include_frame"/> ++ <arg type="b" direction="in" name="include_cursor"/> ++ <arg type="b" direction="in" name="flash"/> ++ <arg type="s" direction="in" name="filename"/> ++ <arg type="b" direction="out" name="success"/> ++ <arg type="s" direction="out" name="filename_used"/> ++ </method> ++ ++ <!-- ++ ScreenshotArea: ++ @x: the X coordinate of the area to capture ++ @y: the Y coordinate of the area to capture ++ @width: the width of the area to capture ++ @height: the height of the area to capture ++ @flash: whether to flash the area or not ++ @filename: the filename for the screenshot ++ @success: whether the screenshot was captured ++ @filename_used: the file where the screenshot was saved ++ ++ Takes a screenshot of the passed in area and saves it ++ in @filename as png image, it returns a boolean ++ indicating whether the operation was successful or not. ++ @filename can either be an absolute path or a basename, in ++ which case the screenshot will be saved in the $XDG_PICTURES_DIR ++ or the home directory if it doesn't exist. The filename used ++ to save the screenshot will be returned in @filename_used. ++ --> ++ <method name="ScreenshotArea"> ++ <arg type="i" direction="in" name="x"/> ++ <arg type="i" direction="in" name="y"/> ++ <arg type="i" direction="in" name="width"/> ++ <arg type="i" direction="in" name="height"/> ++ <arg type="b" direction="in" name="flash"/> ++ <arg type="s" direction="in" name="filename"/> ++ <arg type="b" direction="out" name="success"/> ++ <arg type="s" direction="out" name="filename_used"/> ++ </method> ++ ++ <!-- ++ PickColor: ++ ++ Picks a color and returns the result. ++ ++ The @result vardict contains: ++ <variablelist> ++ <varlistentry> ++ <term>color (ddd)</term> ++ <listitem><para>The color, RGB values in the range [0,1].</para></listitem> ++ </varlistentry> ++ </variablelist> ++ --> ++ <method name="PickColor"> ++ <arg type="a{sv}" direction="out" name="result"/> ++ </method> ++ ++ <!-- ++ FlashArea: ++ @x: the X coordinate of the area to flash ++ @y: the Y coordinate of the area to flash ++ @width: the width of the area to flash ++ @height: the height of the area to flash ++ ++ Renders a flash spot effect in the specified rectangle of the screen. ++ --> ++ <method name="FlashArea"> ++ <arg type="i" direction="in" name="x"/> ++ <arg type="i" direction="in" name="y"/> ++ <arg type="i" direction="in" name="width"/> ++ <arg type="i" direction="in" name="height"/> ++ </method> ++ ++ <!-- ++ SelectArea: ++ @x: the X coordinate of the selected area ++ @y: the Y coordinate of the selected area ++ @width: the width of the selected area ++ @height: the height of the selected area ++ ++ Interactively allows the user to select a rectangular area of ++ the screen, and returns its coordinates. ++ --> ++ <method name="SelectArea"> ++ <arg type="i" direction="out" name="x"/> ++ <arg type="i" direction="out" name="y"/> ++ <arg type="i" direction="out" name="width"/> ++ <arg type="i" direction="out" name="height"/> ++ </method> ++ ++ </interface> ++</node> +diff --git a/meson.build b/meson.build +index d1efcab..d5941bf 100644 +--- a/meson.build ++++ b/meson.build +@@ -116,6 +116,17 @@ sources = gnome.gdbus_codegen(dbus_interface, dbus_interface_file, + ) + dbus_interface_sources_map += { dbus_interface: sources } + ++dbus_interface = 'org.gnome.Shell.Screenshot' ++dbus_interface_file = join_paths('dbus-interfaces', dbus_interface + '.xml') ++sources = gnome.gdbus_codegen(dbus_interface, dbus_interface_file, ++ namespace: 'Kiosk', ++ interface_prefix: 'org.gnome', ++ annotations: [ ++ [ dbus_interface, 'org.gtk.GDBus.C.Name', 'ShellScreenshotDBusService' ] ++ ] ++) ++dbus_interface_sources_map += { dbus_interface: sources } ++ + compositor_dependencies = [] + compositor_dependencies += c_compiler.find_library('m') + compositor_dependencies += dependency('gio-2.0') +@@ -154,6 +165,7 @@ compositor_sources += 'compositor/kiosk-input-source-group.c' + compositor_sources += 'compositor/kiosk-service.c' + compositor_sources += 'compositor/kiosk-shell-service.c' + compositor_sources += 'compositor/kiosk-shell-introspect-service.c' ++compositor_sources += 'compositor/kiosk-shell-screenshot-service.c' + compositor_sources += 'compositor/kiosk-screenshot.c' + + if mutter_have_x11 +-- +2.48.1 + diff --git a/SPECS/gnome-kiosk.spec b/SPECS/gnome-kiosk.spec index fdd435a..85177fd 100644 --- a/SPECS/gnome-kiosk.spec +++ b/SPECS/gnome-kiosk.spec @@ -2,7 +2,7 @@ ## (rpmautospec version 0.7.3) ## RPMAUTOSPEC: autorelease, autochangelog %define autorelease(e:s:pb:n) %{?-p:0.}%{lua: - release_number = 6; + release_number = 7; base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}")); print(release_number + base_release_number - 1); }%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}} @@ -46,6 +46,7 @@ BuildRequires: pkgconfig(gnome-desktop-4) >= %{gnome_desktop_version} BuildRequires: pkgconfig(gtk4) >= %{gtk4_version} BuildRequires: pkgconfig(ibus-1.0) >= %{ibus_version} BuildRequires: pkgconfig(libmutter-15) >= %{mutter_version} +BuildRequires: pkgconfig(gdk-pixbuf-2.0) Requires: gnome-settings-daemon%{?_isa} >= %{gnome_settings_daemon_version} Requires: gsettings-desktop-schemas%{?_isa} >= %{gsettings_desktop_schemas_version} @@ -55,6 +56,9 @@ Patch0: 0001-kiosk-app-Do-not-add-the-window-in-kiosk_app_new_for.patch Patch1: 0001-search-app-Use-firefox-from-flatpak.patch # https://issues.redhat.com/browse/RHEL-71757 Patch2: 0001-kiosk-script-Copy-and-run-the-script-from-XDG_RUNTIM.patch +# https://issues.redhat.com/browse/RHEL-62420 +Patch3: 0001-compositor-Add-screenshot-utilities.patch +Patch4: 0002-compositor-Add-Shell-Screenshot-support.patch %description GNOME Kiosk provides a desktop enviroment suitable for fixed purpose, or @@ -123,6 +127,9 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.gnome.Kiosk.Searc %changelog ## START: Generated by rpmautospec +* Thu Feb 13 2025 Olivier Fourdan <ofourdan@redhat.com> - 47.0-7 +- Enable screenshots support + * Fri Dec 20 2024 Olivier Fourdan <ofourdan@redhat.com> - 47.0-6 - Copy and run the script from XDG_RUNTIME_DIR -- GitLab