diff --git a/.gitignore b/.gitignore index 5a66f9c..a50c2a2 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ /gnome-kiosk-47.alpha.tar.xz /gnome-kiosk-47.rc.tar.xz /gnome-kiosk-47.0.tar.xz +/gnome-kiosk-49.0.tar.xz diff --git a/0001-compositor-Add-screenshot-utilities.patch b/0001-compositor-Add-screenshot-utilities.patch deleted file mode 100644 index 025b5c4..0000000 --- a/0001-compositor-Add-screenshot-utilities.patch +++ /dev/null @@ -1,1046 +0,0 @@ -From 52f9b3792196e405d7ce9917e735c707d369b8f9 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -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: -(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 -+#include -+ -+#include -+ -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* 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 -+#include -+#include -+#include -+#include -+ -+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/0001-compositor-Use-the-meta-window-API-for-set-above.patch b/0001-compositor-Use-the-meta-window-API-for-set-above.patch deleted file mode 100644 index c2cf142..0000000 --- a/0001-compositor-Use-the-meta-window-API-for-set-above.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 231001c019b379f573baa0e869811fa8c429775b Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Wed, 13 Nov 2024 11:36:25 +0100 -Subject: [PATCH 1/5] compositor: Use the meta window API for set-above - -Currently, GNOME Kiosk would place windows that need to be above in a -special window group on top. - -Unfortunately, that means windows in that group will remain there, and -there is no way to send these back to the notmal layer using the -existing mutter API. - -To avoid the problem, use the meta_window_make_above() API. - -See-also: https://gitlab.gnome.org/GNOME/gnome-kiosk/-/issues/26 -(cherry picked from commit 1c2994e5eada4286655d007f91c22452c27f8ead) ---- - compositor/kiosk-compositor.c | 14 +++----------- - 1 file changed, 3 insertions(+), 11 deletions(-) - -diff --git a/compositor/kiosk-compositor.c b/compositor/kiosk-compositor.c -index 8432a24..37588da 100644 ---- a/compositor/kiosk-compositor.c -+++ b/compositor/kiosk-compositor.c -@@ -377,18 +377,10 @@ kiosk_compositor_map (MetaPlugin *plugin, - meta_window_make_fullscreen (window); - easing_duration = 3000; - } else { -- ClutterActor *window_group; -- - g_debug ("KioskCompositor: Mapping window that does not need to be fullscreened"); -- window_group = meta_get_top_window_group_for_display (self->display); -- -- if (kiosk_compositor_wants_window_above (self, window)) { -- g_object_ref (G_OBJECT (actor)); -- clutter_actor_remove_child (clutter_actor_get_parent (CLUTTER_ACTOR (actor)), CLUTTER_ACTOR (actor)); -- clutter_actor_add_child (window_group, CLUTTER_ACTOR (actor)); -- clutter_actor_set_child_above_sibling (window_group, CLUTTER_ACTOR (actor), NULL); -- g_object_unref (G_OBJECT (actor)); -- } -+ -+ if (kiosk_compositor_wants_window_above (self, window)) -+ meta_window_make_above (window); - - easing_duration = 500; - } --- -2.49.0 - diff --git a/0001-input-sources-manager-Do-not-crash-if-there-is-no-X1.patch b/0001-input-sources-manager-Do-not-crash-if-there-is-no-X1.patch deleted file mode 100644 index 5a7cfff..0000000 --- a/0001-input-sources-manager-Do-not-crash-if-there-is-no-X1.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 269cf451feb18206f9a8326d8fa36bc36f9b1897 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Wed, 19 Mar 2025 14:41:15 +0100 -Subject: [PATCH] input-sources-manager: Do not crash if there is no X11Layouts - -When queried for the X11Layout property, localed will try to get the -configuration from /etc/X11/xorg.conf.d/00-keyboard.conf. - -If for some reason (the system was misconfigured or the installation was -altered), that directory is not accessible or not present, localed will -return a NULL string instead of the actual X11 layouts or even an empty -string if no X11 layout was ever configured. - -In such a case, gnome-kiosk will segfault. - -To avoid that potential problem, check for the X11 layouts to be not -NULL or return early if that's not the case. - -Fixes: 38407233 - input-sources-manager: Add APIs for setting keymap -Part-of: ---- - compositor/kiosk-input-sources-manager.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/compositor/kiosk-input-sources-manager.c b/compositor/kiosk-input-sources-manager.c -index 31dab12..6200af5 100644 ---- a/compositor/kiosk-input-sources-manager.c -+++ b/compositor/kiosk-input-sources-manager.c -@@ -869,6 +869,10 @@ kiosk_input_sources_manager_set_input_sources_from_system_configuration (KioskIn - g_debug ("KioskInputSourcesManager: Setting keymap from system configuration"); - - layouts_string = sd_locale1_get_x11_layout (self->locale_proxy); -+ if (layouts_string == NULL) { -+ g_debug ("KioskInputSourcesManager: No layouts defined"); -+ return FALSE; -+ } - g_debug ("KioskInputSourcesManager: System layout is '%s'", layouts_string); - - layouts = g_strsplit (layouts_string, ",", -1); --- -2.49.0 - diff --git a/0001-kiosk-app-Do-not-add-the-window-in-kiosk_app_new_for.patch b/0001-kiosk-app-Do-not-add-the-window-in-kiosk_app_new_for.patch deleted file mode 100644 index 88e4bf9..0000000 --- a/0001-kiosk-app-Do-not-add-the-window-in-kiosk_app_new_for.patch +++ /dev/null @@ -1,99 +0,0 @@ -From 67cb8748dbc8c237d7b7486c7cec9e7902dcb51f Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Mon, 7 Oct 2024 18:09:32 +0200 -Subject: [PATCH] kiosk-app: Do not add the window in - kiosk_app_new_for_window() - -When a window has no matching application (like, when there is no -corresponding desktop file), a KioskApp is created for the window and -the window would be added to the newly-created KioskApp. - -However, by doing so, we add the window before the calling window -tracker had a change to setup the state notify handler. - -As a result, the window tracker is never notified of the state change -and the KioskApp is never added to the list of running applications in -the KioskAppState. - -Too bad, since the introspection iterates on the running applications to -report the windows. - -To avoid the issue, simply do not add the window so soon in -kiosk_app_new_for_window(), it will be added in time in track_window() -and the notifications will be correctly triggered. - -Fixes: commit 50ae635dc - kiosk-app: Add a new KioskApp class ---- - compositor/kiosk-app.c | 2 -- - 1 file changed, 2 deletions(-) - -diff --git a/compositor/kiosk-app.c b/compositor/kiosk-app.c -index acc9d9e..5b19e4e 100644 ---- a/compositor/kiosk-app.c -+++ b/compositor/kiosk-app.c -@@ -441,62 +441,60 @@ kiosk_app_remove_window (KioskApp *app, - if (!meta_window_is_skip_taskbar (window)) - app->running_state->number_of_interesting_windows--; - kiosk_app_sync_running_state (app); - - if (app->running_state->windows == NULL) - g_clear_pointer (&app->running_state, unref_running_state); - - g_signal_handlers_disconnect_by_func (window, - G_CALLBACK (kiosk_app_on_user_time_changed), - app); - g_signal_handlers_disconnect_by_func (window, - G_CALLBACK (kiosk_app_on_skip_taskbar_changed), - app); - - g_object_unref (window); - - g_signal_emit (app, kiosk_app_signals[WINDOWS_CHANGED], 0); - } - - KioskApp * - kiosk_app_new_for_window (KioskCompositor *compositor, - MetaWindow *window) - { - KioskApp *app; - - app = g_object_new (KIOSK_TYPE_APP, "compositor", compositor, NULL); - - app->window_id_string = g_strdup_printf ("window:%d", - meta_window_get_stable_sequence (window)); - -- kiosk_app_add_window (app, window); -- - return app; - } - - KioskApp * - kiosk_app_new (KioskCompositor *compositor, - GDesktopAppInfo *info) - { - KioskApp *app; - - app = g_object_new (KIOSK_TYPE_APP, - "compositor", compositor, "app-info", info, NULL); - - return app; - } - - const char * - kiosk_app_get_sandbox_id (KioskApp *app) - { - KioskAppWindowIter iter; - MetaWindow *window; - - kiosk_app_window_iter_init (&iter, app); - while (kiosk_app_window_iter_next (&iter, &window)) { - const char *sandbox_id = meta_window_get_sandboxed_app_id (window); - - if (sandbox_id) - return sandbox_id; - } - - return NULL; --- -2.46.0 - diff --git a/0001-kiosk-script-Copy-and-run-the-script-from-XDG_RUNTIM.patch b/0001-kiosk-script-Copy-and-run-the-script-from-XDG_RUNTIM.patch deleted file mode 100644 index e15afb0..0000000 --- a/0001-kiosk-script-Copy-and-run-the-script-from-XDG_RUNTIM.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 9497651214baaae6dabe7cc1971a1799633983a6 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Fri, 22 Nov 2024 17:04:09 +0100 -Subject: [PATCH] kiosk-script: Copy and run the script from XDG_RUNTIME_DIR - -Some setup may enforce the noexec flag on the HOME directories. - -That prevents any executable placed in the HOME directory from running, -including the GNOME Kiosk script session. - -To work around such an issue, copy and execute the script from the -XDG_RUNTIME_DIR instead. - -Signed-off-by: Olivier Fourdan ---- - kiosk-script/gnome-kiosk-script | 18 +++++++++++++----- - 1 file changed, 13 insertions(+), 5 deletions(-) - -diff --git a/kiosk-script/gnome-kiosk-script b/kiosk-script/gnome-kiosk-script -index e45438d..c4ab176 100755 ---- a/kiosk-script/gnome-kiosk-script -+++ b/kiosk-script/gnome-kiosk-script -@@ -1,8 +1,10 @@ - #!/usr/bin/sh - --if [ ! -e ~/.local/bin/gnome-kiosk-script ]; then -- mkdir -p ~/.local/bin ~/.config -- cat > ~/.local/bin/gnome-kiosk-script <<- "EOF" -+EXECDIR=~/.local/bin -+ -+if [ ! -e $EXECDIR/gnome-kiosk-script ]; then -+ mkdir -p $EXECDIR ~/.config -+ cat > $EXECDIR/gnome-kiosk-script <<- "EOF" - #!/bin/sh - # This script is located in ~/.local/bin. - # It's provided as an example script to show how -@@ -16,8 +18,14 @@ if [ ! -e ~/.local/bin/gnome-kiosk-script ]; then - exec "$0" "$@" - EOF - -- chmod +x ~/.local/bin/gnome-kiosk-script -+ chmod +x $EXECDIR/gnome-kiosk-script - touch ~/.config/gnome-initial-setup-done - fi - --exec ~/.local/bin/gnome-kiosk-script "$@" -+# Copy and run the script from the XDG_RUNTIME_DIR directory if that exists -+if [ -d $XDG_RUNTIME_DIR ]; then -+ cp $EXECDIR/gnome-kiosk-script $XDG_RUNTIME_DIR/ -+ EXECDIR=$XDG_RUNTIME_DIR -+fi -+ -+exec $EXECDIR/gnome-kiosk-script "$@" --- -2.47.1 - diff --git a/0001-search-app-Add-systemd-session-files.patch b/0001-search-app-Add-systemd-session-files.patch deleted file mode 100644 index c849397..0000000 --- a/0001-search-app-Add-systemd-session-files.patch +++ /dev/null @@ -1,68 +0,0 @@ -From fea0d74ff155f05e9f1e8e0351562af6c7c15b3c Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Tue, 4 Mar 2025 16:21:30 +0100 -Subject: [PATCH 1/2] search-app: Add systemd session files - -The search appliance session would fail to start as a systemd session. - -Add the required systemd plumbing to fix the search appliance session. - -Part-of: - -(cherry picked from commit a40ac8d8dc132d9c94506419158ec4034eaaf2c8) ---- - meson.build | 12 ++++++++++++ - .../systemd/org.gnome.Kiosk.SearchApp.service.in | 8 ++++++++ - search-app/systemd/session.conf | 3 +++ - 3 files changed, 23 insertions(+) - create mode 100644 search-app/systemd/org.gnome.Kiosk.SearchApp.service.in - create mode 100644 search-app/systemd/session.conf - -diff --git a/meson.build b/meson.build -index fb9a76d..2024b76 100644 ---- a/meson.build -+++ b/meson.build -@@ -350,5 +350,17 @@ i18n.merge_file( - type: 'desktop' - ) - -+configure_file( -+ input: 'search-app/systemd/org.gnome.Kiosk.SearchApp.service.in', -+ output: '@BASENAME@', -+ configuration: systemd_service_config_data, -+ install_dir: systemd_user_unit_dir -+) -+ -+kiosk_search_appliance_systemd_target_dir = join_paths(systemd_user_unit_dir, 'gnome-session@org.gnome.Kiosk.SearchApp.target.d') -+install_data('search-app/systemd/session.conf', -+ install_dir: kiosk_search_appliance_systemd_target_dir -+) -+ - test('check-format', find_program('scripts/check-format.sh')) - -diff --git a/search-app/systemd/org.gnome.Kiosk.SearchApp.service.in b/search-app/systemd/org.gnome.Kiosk.SearchApp.service.in -new file mode 100644 -index 0000000..29ddcd8 ---- /dev/null -+++ b/search-app/systemd/org.gnome.Kiosk.SearchApp.service.in -@@ -0,0 +1,8 @@ -+[Unit] -+Description=Kiosk Search Appliance -+BindsTo=gnome-session.target -+After=gnome-session.target -+ -+[Service] -+ExecStart=@bindir@/firefox --kiosk --private-window --new-instance https://www.google.com -+Restart=always -diff --git a/search-app/systemd/session.conf b/search-app/systemd/session.conf -new file mode 100644 -index 0000000..1ab41da ---- /dev/null -+++ b/search-app/systemd/session.conf -@@ -0,0 +1,3 @@ -+[Unit] -+Requires=org.gnome.Kiosk.target -+Requires=org.gnome.Kiosk.SearchApp.service --- -2.48.1 - diff --git a/0001-search-app-Use-firefox-from-flatpak.patch b/0001-search-app-Use-firefox-from-flatpak.patch index e206d7c..7ac6fe9 100644 --- a/0001-search-app-Use-firefox-from-flatpak.patch +++ b/0001-search-app-Use-firefox-from-flatpak.patch @@ -1,4 +1,4 @@ -From bad81e556eca3acad1b97f6dbc00f3788e24d25f Mon Sep 17 00:00:00 2001 +From c8c4aac6d68bc864fa197d0bef0a747c70dd67f3 Mon Sep 17 00:00:00 2001 From: Olivier Fourdan Date: Thu, 12 Dec 2024 14:05:04 +0100 Subject: [PATCH] search-app: Use firefox from flatpak @@ -37,5 +37,5 @@ index 29ddcd8..d4f5d0f 100644 +ExecStart=@bindir@/flatpak run --command=firefox --file-forwarding org.mozilla.firefox --kiosk --private-window --new-instance https://www.google.com Restart=always -- -2.48.1 +2.51.1 diff --git a/0001-shell-service-Fix-GrabAccelerators-return-value.patch b/0001-shell-service-Fix-GrabAccelerators-return-value.patch deleted file mode 100644 index 5091ec7..0000000 --- a/0001-shell-service-Fix-GrabAccelerators-return-value.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 108aa947aaf8bf14a26756ba078bcf4141b6434b Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Wed, 30 Jul 2025 15:15:34 +0200 -Subject: [PATCH 1/3] shell-service: Fix GrabAccelerators return value - -The DBus definitions expects an array of uint32, not an array of -variants. - -(cherry picked from commit e9477c3cbdd81a14888b0b08ef38aa70188f736e) ---- - compositor/kiosk-shell-service.c | 3 +-- - 1 file changed, 1 insertion(+), 2 deletions(-) - -diff --git a/compositor/kiosk-shell-service.c b/compositor/kiosk-shell-service.c -index a6b4258..4f21ae2 100644 ---- a/compositor/kiosk-shell-service.c -+++ b/compositor/kiosk-shell-service.c -@@ -332,8 +332,7 @@ kiosk_shell_service_handle_grab_accelerators (KioskShellDBusService *object, - guint action_id; - - action_id = grab_accelerator_for_client (self, accelerator, mode_flags, grab_flags, client_unique_name); -- -- g_variant_builder_add (&builder, "u", g_variant_new_uint32 (action_id)); -+ g_variant_builder_add (&builder, "u", action_id); - } - - kiosk_shell_dbus_service_complete_grab_accelerators (KIOSK_SHELL_DBUS_SERVICE (self), --- -2.50.1 - diff --git a/0001-systemd-session-Enable-remote-session.patch b/0001-systemd-session-Enable-remote-session.patch deleted file mode 100644 index 4f36cb6..0000000 --- a/0001-systemd-session-Enable-remote-session.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 9dd06017bf85eb779579342f7f5e7955017aa893 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Wed, 16 Jul 2025 11:51:49 +0200 -Subject: [PATCH] systemd/session: Enable remote session - -To be able to get something on screen when starting a session remotely, -using remote login via gdm, the session has to pull in gsd-sharing, i.e. -the session wants the SettingsDaemon Sharing target. ---- - kiosk-script/systemd/session.conf | 1 + - search-app/systemd/session.conf | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/kiosk-script/systemd/session.conf b/kiosk-script/systemd/session.conf -index a948efb..aeb722c 100644 ---- a/kiosk-script/systemd/session.conf -+++ b/kiosk-script/systemd/session.conf -@@ -1,3 +1,4 @@ - [Unit] - Requires=org.gnome.Kiosk.target - Requires=org.gnome.Kiosk.Script.service -+Wants=org.gnome.SettingsDaemon.Sharing.target -diff --git a/search-app/systemd/session.conf b/search-app/systemd/session.conf -index 1ab41da..d038670 100644 ---- a/search-app/systemd/session.conf -+++ b/search-app/systemd/session.conf -@@ -1,3 +1,4 @@ - [Unit] - Requires=org.gnome.Kiosk.target - Requires=org.gnome.Kiosk.SearchApp.service -+Wants=org.gnome.SettingsDaemon.Sharing.target --- -2.50.1 - diff --git a/0002-compositor-Add-Shell-Screenshot-support.patch b/0002-compositor-Add-Shell-Screenshot-support.patch deleted file mode 100644 index f3658ce..0000000 --- a/0002-compositor-Add-Shell-Screenshot-support.patch +++ /dev/null @@ -1,892 +0,0 @@ -From 0ab97a3be415e7cc3937d68fd66a421f96f45417 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -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: -(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 -+#include -+#include -+#include -+ -+#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 -+ -+#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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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/0002-compositor-Add-a-window-configuration-file.patch b/0002-compositor-Add-a-window-configuration-file.patch deleted file mode 100644 index bc1cc7b..0000000 --- a/0002-compositor-Add-a-window-configuration-file.patch +++ /dev/null @@ -1,517 +0,0 @@ -From f30b19d09bf521f3b731e08551428c5a7afb26c6 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Fri, 11 Oct 2024 14:36:02 +0200 -Subject: [PATCH 2/5] compositor: Add a window configuration file - -This adds a configuration file to specify the windows configuration at -start-up. - -The file is an "ini" style file with sections and keys/values. - -There can be as many sections as desired, each section gets evaluated. - -There are two categories of keys, the "match" keys and the "set" keys. - -The "match" keys are used to filter the windows before applying the -values from the "set" keys. - -The "match" keys can take wildcards and patterns. - -Currently the following "match" keys as supported: - - * match-title (string) - Matches the window title - * match-class (string) - Matches the window class - * match-sandboxed-app-id (string) - Matches the sandboxed application id - -The following "set" keys are supported: - - * set-fullscreen (boolean) - Whether the window should be fullscreen - * set-x (integer) - the X position - * set-y (integer) - the Y position - * set-width (integer) - the width - * set-height (integer) - the height - -E.g.: - - # Place all windows at (0,0) by default - [all] - set-x=0 - set-y=0 - - # Make all Mozilla windows fullscreen - [mozilla] - match-class=org.mozilla.* - set-fullscreen=true - - # All other windows will be set fullscreen automatically using the - # existing GNOME Kiosk heuristic, as before. - -see-also: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4076 -(cherry picked from commit 9cb4f91724e489ab8b849ffb83f5a6c01e0ac552) -(cherry picked from commit 047bd8c83f4a6e950b0ce608417cba178df19f10) ---- - CONFIG.md | 62 ++++++ - compositor/kiosk-window-config.c | 315 +++++++++++++++++++++++++++++++ - compositor/kiosk-window-config.h | 21 +++ - meson.build | 1 + - window-config.ini | 17 ++ - 5 files changed, 416 insertions(+) - create mode 100644 CONFIG.md - create mode 100644 compositor/kiosk-window-config.c - create mode 100644 compositor/kiosk-window-config.h - create mode 100644 window-config.ini - -diff --git a/CONFIG.md b/CONFIG.md -new file mode 100644 -index 0000000..b0045ee ---- /dev/null -+++ b/CONFIG.md -@@ -0,0 +1,62 @@ -+# Configuration file -+ -+GNOME Kiosk takes a configuration file to specify the windows configuration at start-up. -+ -+The configuration file called `window-config.ini` is searched in multiple places on the -+system. The first instance of the file found is used. -+ -+ * The base directory in which user-specific application configuration is stored -+ `$XDG_CONFIG_HOME/gnome-kiosk/window-config.ini` (usually `$HOME/.config/gnome-kiosk/window-config.ini`) -+ * The system-wide list of directories in which system-wide application data is stored `$XDG_DATA_DIRS` -+ This list usually includes: -+ - `/var/lib/flatpak/exports/share/gnome-kiosk/window-config.ini` -+ - `/usr/local/share/gnome-kiosk/window-config.ini` -+ - `/usr/share/gnome-kiosk/window-config.ini` -+ -+# Syntax -+ -+The configuration file is an "ini" style file with sections and keys/values. -+ -+There can be as many sections as desired. -+ -+The name of the sections does not matter, there is no special name of section, -+each section gets evaluated. -+ -+There are two categories of keys, the "*match*" keys and the "*set*" keys. -+ -+The "*match*" keys are used to filter the windows before applying the -+values from the "*set*" keys. -+ -+The "*match*" keys can take wildcards and patterns. -+ -+The following "*match*" keys as supported: -+ -+ * `match-title` (string) - Matches the window title -+ * `match-class` (string) - Matches the window class -+ * `match-sandboxed-app-id` (string) - Matches the sandboxed application id -+ -+The following "*set*" keys are supported: -+ -+ * `set-fullscreen` (boolean) - Whether the window should be fullscreen -+ * `set-x` (integer) - the X position -+ * `set-y` (integer) - the Y position -+ * `set-width` (integer) - the width -+ * `set-height` (integer) - the height -+ -+# Example -+ -+``` -+ # Place all windows at (0,0) by default, not fullscreen -+ [all] -+ set-x=0 -+ set-y=0 -+ set-fullscreen=false -+ -+ # Make all Mozilla windows fullscreen -+ [mozilla] -+ match-class=org.mozilla.* -+ set-fullscreen=true -+ -+ # All other windows will be set fullscreen automatically using the -+ # existing GNOME Kiosk heuristic, as before. -+``` -diff --git a/compositor/kiosk-window-config.c b/compositor/kiosk-window-config.c -new file mode 100644 -index 0000000..5e6c830 ---- /dev/null -+++ b/compositor/kiosk-window-config.c -@@ -0,0 +1,315 @@ -+#include "config.h" -+ -+#include -+#include -+ -+#include "kiosk-window-config.h" -+ -+#define KIOSK_WINDOW_CONFIG_DIR "gnome-kiosk" -+#define KIOSK_WINDOW_CONFIG_FILENAME "window-config.ini" -+#define KIOSK_WINDOW_CONFIG_GET_KEY_VALUE(f) ((KioskWindowConfigGetKeyValue) (f)) -+ -+typedef gpointer (*KioskWindowConfigGetKeyValue) (GKeyFile *key_file, -+ const char *section_name, -+ const char *key_name, -+ GError **error); -+ -+struct _KioskWindowConfig -+{ -+ GObject parent; -+ -+ GKeyFile *config_key_file; -+}; -+ -+G_DEFINE_TYPE (KioskWindowConfig, kiosk_window_config, G_TYPE_OBJECT) -+ -+static gboolean -+kiosk_window_config_try_load_file (KioskWindowConfig *kiosk_window_config, -+ char *filename) -+{ -+ g_autoptr (GError) error = NULL; -+ -+ if (!g_key_file_load_from_file (kiosk_window_config->config_key_file, -+ filename, -+ G_KEY_FILE_NONE, -+ &error)) { -+ g_debug ("KioskWindowConfig: Error loading key file %s: %s", -+ filename, error->message); -+ -+ return FALSE; -+ } -+ -+ return TRUE; -+} -+ -+static gboolean -+kiosk_window_config_load (KioskWindowConfig *kiosk_window_config) -+{ -+ const char * const *xdg_data_dirs; -+ g_autofree gchar *filename = NULL; -+ int i; -+ -+ /* Try user config first */ -+ filename = g_build_filename (g_get_user_config_dir (), -+ KIOSK_WINDOW_CONFIG_DIR, -+ KIOSK_WINDOW_CONFIG_FILENAME, NULL); -+ -+ if (kiosk_window_config_try_load_file (kiosk_window_config, filename)) -+ goto out; -+ -+ /* Then system config */ -+ xdg_data_dirs = g_get_system_data_dirs (); -+ for (i = 0; xdg_data_dirs[i]; i++) { -+ filename = g_build_filename (xdg_data_dirs[i], -+ KIOSK_WINDOW_CONFIG_DIR, -+ KIOSK_WINDOW_CONFIG_FILENAME, NULL); -+ -+ if (kiosk_window_config_try_load_file (kiosk_window_config, filename)) -+ goto out; -+ } -+ -+ g_debug ("KioskWindowConfig: No configuration file found"); -+ -+ return FALSE; -+out: -+ g_debug ("KioskWindowConfig: Loading key file %s", filename); -+ -+ return TRUE; -+} -+ -+static void -+kiosk_window_config_init (KioskWindowConfig *self) -+{ -+ self->config_key_file = g_key_file_new (); -+ kiosk_window_config_load (self); -+} -+ -+static void -+kiosk_window_config_dispose (GObject *object) -+{ -+ KioskWindowConfig *self = KIOSK_WINDOW_CONFIG (object); -+ -+ g_clear_pointer (&self->config_key_file, g_key_file_free); -+ -+ G_OBJECT_CLASS (kiosk_window_config_parent_class)->dispose (object); -+} -+ -+static void -+kiosk_window_config_class_init (KioskWindowConfigClass *klass) -+{ -+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass); -+ -+ gobject_class->dispose = kiosk_window_config_dispose; -+} -+ -+#define KIOSK_WINDOW_CONFIG_CHECK_FUNC_VALUE(self, section, key, func, value) \ -+ G_STMT_START { \ -+ g_autoptr (GError) error = NULL; \ -+ if (!g_key_file_has_key (self->config_key_file, section, key, NULL)) { \ -+ g_debug ("KioskWindowConfig: No key '%s' in section [%s]", \ -+ key, section); \ -+ return FALSE; \ -+ } \ -+ *value = func (self->config_key_file, section, key, &error); \ -+ if (error) { \ -+ g_debug ("KioskWindowConfig: Error with key '%s' in section [%s]: %s", \ -+ key, section, error->message); \ -+ return FALSE; \ -+ } \ -+ return TRUE; \ -+ } G_STMT_END -+ -+static gboolean -+kiosk_window_config_check_for_string_value (KioskWindowConfig *kiosk_window_config, -+ const char *section_name, -+ const char *key_name, -+ char **value) -+{ -+ KIOSK_WINDOW_CONFIG_CHECK_FUNC_VALUE (kiosk_window_config, -+ section_name, -+ key_name, -+ g_key_file_get_string, -+ value); -+} -+ -+static gboolean -+kiosk_window_config_check_for_integer_value (KioskWindowConfig *kiosk_window_config, -+ const char *section_name, -+ const char *key_name, -+ int *value) -+{ -+ KIOSK_WINDOW_CONFIG_CHECK_FUNC_VALUE (kiosk_window_config, -+ section_name, -+ key_name, -+ g_key_file_get_integer, -+ value); -+} -+ -+static gboolean -+kiosk_window_config_check_for_boolean_value (KioskWindowConfig *kiosk_window_config, -+ const char *section_name, -+ const char *key_name, -+ gboolean *value) -+{ -+ KIOSK_WINDOW_CONFIG_CHECK_FUNC_VALUE (kiosk_window_config, -+ section_name, -+ key_name, -+ g_key_file_get_boolean, -+ value); -+} -+ -+static void -+kiosk_window_config_apply_config (KioskWindowConfig *kiosk_window_config, -+ MetaWindowConfig *window_config, -+ const char *section_name) -+{ -+ MtkRectangle rect; -+ int new_x, new_y, new_width, new_height; -+ gboolean fullscreen; -+ -+ if (kiosk_window_config_check_for_boolean_value (kiosk_window_config, -+ section_name, -+ "set-fullscreen", -+ &fullscreen)) { -+ g_debug ("KioskWindowConfig: Using 'set-fullscreen=%s' from section [%s]", -+ fullscreen ? "TRUE" : "FALSE", section_name); -+ meta_window_config_set_is_fullscreen (window_config, fullscreen); -+ } -+ -+ rect = meta_window_config_get_rect (window_config); -+ -+ if (kiosk_window_config_check_for_integer_value (kiosk_window_config, -+ section_name, -+ "set-x", -+ &new_x)) { -+ g_debug ("KioskWindowConfig: Using 'set-x=%i' from section [%s]", -+ new_x, section_name); -+ rect.x = new_x; -+ } -+ -+ if (kiosk_window_config_check_for_integer_value (kiosk_window_config, -+ section_name, -+ "set-y", -+ &new_y)) { -+ g_debug ("KioskWindowConfig: Using 'set-y=%i' from section [%s]", -+ new_y, section_name); -+ rect.y = new_y; -+ } -+ -+ if (kiosk_window_config_check_for_integer_value (kiosk_window_config, -+ section_name, -+ "set-width", -+ &new_width)) { -+ g_debug ("KioskWindowConfig: Using 'set-width=%i' from section [%s]", -+ new_width, section_name); -+ rect.width = new_width; -+ } -+ -+ if (kiosk_window_config_check_for_integer_value (kiosk_window_config, -+ section_name, -+ "set-height", -+ &new_height)) { -+ g_debug ("KioskWindowConfig: Using 'set-height=%i' from section [%s]", -+ new_height, section_name); -+ rect.height = new_height; -+ } -+ -+ meta_window_config_set_rect (window_config, rect); -+} -+ -+static gboolean -+kiosk_window_config_match_string_key (KioskWindowConfig *kiosk_window_config, -+ const char *section_name, -+ const char *key_name, -+ const char *value) -+{ -+ g_autofree gchar *key_value = NULL; -+ g_autoptr (GError) error = NULL; -+ gboolean is_a_match = TRUE; -+ -+ /* Keys are used to filter out, no key means we have a match */ -+ if (!kiosk_window_config_check_for_string_value (kiosk_window_config, -+ section_name, -+ key_name, -+ &key_value)) -+ return TRUE; -+ -+ is_a_match = g_pattern_match_simple (key_value, value); -+ g_debug ("KioskWindowConfig: Value '%s' %s key '%s=%s' from section [%s]", -+ value, -+ is_a_match ? "matches" : "does not match", -+ key_name, -+ key_value, -+ section_name); -+ -+ return is_a_match; -+} -+ -+static gboolean -+kiosk_window_config_match_window (KioskWindowConfig *kiosk_window_config, -+ MetaWindow *window, -+ const char *section_name) -+{ -+ const char *match_value; -+ -+ g_debug ("KioskWindowConfig: Checking section [%s]", section_name); -+ -+ match_value = meta_window_get_title (window); -+ if (match_value && -+ !kiosk_window_config_match_string_key (kiosk_window_config, -+ section_name, -+ "match-title", -+ match_value)) -+ return FALSE; -+ -+ match_value = meta_window_get_wm_class (window); -+ if (match_value && -+ !kiosk_window_config_match_string_key (kiosk_window_config, -+ section_name, -+ "match-class", -+ match_value)) -+ return FALSE; -+ -+ match_value = meta_window_get_sandboxed_app_id (window); -+ if (match_value && -+ !kiosk_window_config_match_string_key (kiosk_window_config, -+ section_name, -+ "match-sandboxed-app-id", -+ match_value)) -+ return FALSE; -+ -+ return TRUE; -+} -+ -+void -+kiosk_window_config_update_window (KioskWindowConfig *kiosk_window_config, -+ MetaWindow *window, -+ MetaWindowConfig *window_config) -+{ -+ g_auto (GStrv) sections; -+ gsize length; -+ int i; -+ -+ sections = g_key_file_get_groups (kiosk_window_config->config_key_file, &length); -+ for (i = 0; i < length; i++) { -+ if (!kiosk_window_config_match_window (kiosk_window_config, -+ window, -+ sections[i])) -+ continue; -+ -+ kiosk_window_config_apply_config (kiosk_window_config, -+ window_config, -+ sections[i]); -+ } -+} -+ -+KioskWindowConfig * -+kiosk_window_config_new (void) -+{ -+ KioskWindowConfig *kiosk_window_config; -+ -+ kiosk_window_config = g_object_new (KIOSK_TYPE_WINDOW_CONFIG, -+ NULL); -+ -+ return kiosk_window_config; -+} -diff --git a/compositor/kiosk-window-config.h b/compositor/kiosk-window-config.h -new file mode 100644 -index 0000000..1c7e8ea ---- /dev/null -+++ b/compositor/kiosk-window-config.h -@@ -0,0 +1,21 @@ -+#pragma once -+ -+#include -+#include -+ -+#include -+#include -+ -+G_BEGIN_DECLS -+ -+#define KIOSK_TYPE_WINDOW_CONFIG (kiosk_window_config_get_type ()) -+G_DECLARE_FINAL_TYPE (KioskWindowConfig, kiosk_window_config, -+ KIOSK, WINDOW_CONFIG, GObject) -+ -+KioskWindowConfig *kiosk_window_config_new (void); -+ -+void kiosk_window_config_update_window (KioskWindowConfig *kiosk_window_config, -+ MetaWindow *window, -+ MetaWindowConfig *window_config); -+ -+G_END_DECLS -diff --git a/meson.build b/meson.build -index 23aaff7..c3eb74c 100644 ---- a/meson.build -+++ b/meson.build -@@ -166,6 +166,7 @@ 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-window-config.c' - compositor_sources += 'compositor/kiosk-screenshot.c' - - if mutter_have_x11 -diff --git a/window-config.ini b/window-config.ini -new file mode 100644 -index 0000000..c4c825e ---- /dev/null -+++ b/window-config.ini -@@ -0,0 +1,17 @@ -+# This is just an example for a window configuration file. -+# Copy this file to $HOME/.config/gnome-kiosk/window-config.ini -+ -+# The section names are free and all sections get evaluated. -+ -+# Place all windows at (0,0) by default -+[all] -+set-x=0 -+set-y=0 -+ -+# Make all Mozilla windows fullscreen -+[browser] -+match-class=org.mozilla* -+set-fullscreen=true -+ -+# All other windows will be set fullscreen automatically using the -+# existing GNOME Kiosk heuristic, as before. --- -2.49.0 - diff --git a/0002-compositor-Use-the-Shell-service.patch b/0002-compositor-Use-the-Shell-service.patch deleted file mode 100644 index fa5cd24..0000000 --- a/0002-compositor-Use-the-Shell-service.patch +++ /dev/null @@ -1,55 +0,0 @@ -From a695282b8da73fe7df50293f177855a9b3c92416 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Wed, 30 Jul 2025 15:16:54 +0200 -Subject: [PATCH 2/3] compositor: Use the Shell service - -The Shell service is used by gnome-settings-daemon to implement media -keys. - -That in turn allows to start the screen reader, audio control, etc. - -(cherry picked from commit 722055b88747d5b6cc4de0875f4b9f0169fcf9c2) ---- - compositor/kiosk-compositor.c | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/compositor/kiosk-compositor.c b/compositor/kiosk-compositor.c -index aa5603d..73f5524 100644 ---- a/compositor/kiosk-compositor.c -+++ b/compositor/kiosk-compositor.c -@@ -24,6 +24,7 @@ - #include "kiosk-service.h" - #include "kiosk-app-system.h" - #include "kiosk-window-tracker.h" -+#include "kiosk-shell-service.h" - #include "kiosk-shell-introspect-service.h" - #include "kiosk-shell-screenshot-service.h" - #include "kiosk-window-config.h" -@@ -51,6 +52,7 @@ struct _KioskCompositor - KioskShellIntrospectService *introspect_service; - KioskShellScreenshotService *screenshot_service; - KioskWindowConfig *kiosk_window_config; -+ KioskShellService *shell_service; - }; - - enum -@@ -84,6 +86,7 @@ kiosk_compositor_dispose (GObject *object) - - g_clear_object (&self->backgrounds); - g_clear_object (&self->automount_manager); -+ g_clear_object (&self->shell_service); - - G_OBJECT_CLASS (kiosk_compositor_parent_class)->dispose (object); - } -@@ -293,6 +296,8 @@ kiosk_compositor_start (MetaPlugin *plugin) - 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); -+ self->shell_service = kiosk_shell_service_new (self); -+ kiosk_shell_service_start (self->shell_service, &error); - - if (error != NULL) { - g_debug ("KioskCompositor: Could not start D-Bus service: %s", error->message); --- -2.50.1 - diff --git a/0002-search-app-Update-desktop-file-definition.patch b/0002-search-app-Update-desktop-file-definition.patch deleted file mode 100644 index 95be95f..0000000 --- a/0002-search-app-Update-desktop-file-definition.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 30d48384d2b98dc6ba9534ef41ce445f6da41d7f Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Wed, 5 Mar 2025 14:23:49 +0100 -Subject: [PATCH 2/2] search-app: Update desktop file definition - -Firefox itself has nothing to do with neither Gtk or GNOME, no need to -list those in the categories. - -Part-of: - -(cherry picked from commit 5441999573a07c3513eeda5150683448bdd71c50) ---- - search-app/org.gnome.Kiosk.SearchApp.desktop.in.in | 5 ++--- - 1 file changed, 2 insertions(+), 3 deletions(-) - -diff --git a/search-app/org.gnome.Kiosk.SearchApp.desktop.in.in b/search-app/org.gnome.Kiosk.SearchApp.desktop.in.in -index 360e6df..478b328 100644 ---- a/search-app/org.gnome.Kiosk.SearchApp.desktop.in.in -+++ b/search-app/org.gnome.Kiosk.SearchApp.desktop.in.in -@@ -3,7 +3,6 @@ Type=Application - Name=Search Appliance - Comment=Sample Search Appliance Application for GNOME Kiosk - Exec=@bindir@/firefox --kiosk --private-window --new-instance https://www.google.com --Categories=GNOME;GTK;Core; --OnlyShowIn=GNOME; -+Categories=Core;System; - NoDisplay=true --X-GNOME-AutoRestart=true -+X-GNOME-HiddenUnderSystemd=true --- -2.48.1 - diff --git a/0003-compositor-Apply-the-window-configuration.patch b/0003-compositor-Apply-the-window-configuration.patch deleted file mode 100644 index 3f01dd3..0000000 --- a/0003-compositor-Apply-the-window-configuration.patch +++ /dev/null @@ -1,124 +0,0 @@ -From bf79fe519d55a5fc6d31775164e0a4c5831a9020 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Fri, 25 Oct 2024 14:04:17 +0200 -Subject: [PATCH 3/5] compositor: Apply the window configuration - -Use the "MetaWindow::configure" signal in mutter-16 to apply the GNOME -Kiosk window configuration. - -(cherry picked from commit 30d0c4233f84234bd6d470fc42715593360e115a) ---- - compositor/kiosk-compositor.c | 47 +++++++++++++++++++++++++++++++++-- - 1 file changed, 45 insertions(+), 2 deletions(-) - -diff --git a/compositor/kiosk-compositor.c b/compositor/kiosk-compositor.c -index 37588da..650ac50 100644 ---- a/compositor/kiosk-compositor.c -+++ b/compositor/kiosk-compositor.c -@@ -12,6 +12,7 @@ - #include - #include - #include -+#include - #include - - #include -@@ -25,6 +26,7 @@ - #include "kiosk-window-tracker.h" - #include "kiosk-shell-introspect-service.h" - #include "kiosk-shell-screenshot-service.h" -+#include "kiosk-window-config.h" - - #include "org.gnome.DisplayManager.Manager.h" - -@@ -48,6 +50,7 @@ struct _KioskCompositor - KioskWindowTracker *tracker; - KioskShellIntrospectService *introspect_service; - KioskShellScreenshotService *screenshot_service; -+ KioskWindowConfig *kiosk_window_config; - }; - - enum -@@ -61,6 +64,8 @@ static guint signals[NUMBER_OF_SIGNALS] = { 0, }; - G_DEFINE_TYPE (KioskCompositor, kiosk_compositor, META_TYPE_PLUGIN) - - static void kiosk_compositor_dispose (GObject *object); -+static gboolean kiosk_compositor_wants_window_fullscreen (KioskCompositor *self, -+ MetaWindow *window); - - static void - kiosk_compositor_dispose (GObject *object) -@@ -220,6 +225,39 @@ neuter_builtin_keybindings (KioskCompositor *self) - } - } - -+static void -+kiosk_compositor_on_window_configure (MetaWindow *window, -+ MetaWindowConfig *window_config, -+ gpointer user_data) -+{ -+ KioskCompositor *self = KIOSK_COMPOSITOR (user_data); -+ gboolean fullscreen; -+ -+ if (!meta_window_config_get_is_initial (window_config)) { -+ g_debug ("KioskCompositor: Ignoring configure for window: %s", -+ meta_window_get_description (window)); -+ return; -+ } -+ -+ g_debug ("KioskCompositor: configure window: %s", meta_window_get_description (window)); -+ -+ fullscreen = kiosk_compositor_wants_window_fullscreen (self, window); -+ meta_window_config_set_is_fullscreen (window_config, fullscreen); -+ kiosk_window_config_update_window (self->kiosk_window_config, -+ window, -+ window_config); -+} -+ -+static void -+kiosk_compositor_on_window_created (MetaDisplay *display, -+ MetaWindow *window, -+ gpointer user_data) -+{ -+ g_signal_connect (window, "configure", -+ G_CALLBACK (kiosk_compositor_on_window_configure), -+ user_data); -+} -+ - static void - kiosk_compositor_start (MetaPlugin *plugin) - { -@@ -250,6 +288,7 @@ kiosk_compositor_start (MetaPlugin *plugin) - self->input_sources_manager = kiosk_input_sources_manager_new (self); - self->app_system = kiosk_app_system_new (self); - self->tracker = kiosk_window_tracker_new (self, self->app_system); -+ self->kiosk_window_config = kiosk_window_config_new (); - 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); -@@ -265,6 +304,11 @@ kiosk_compositor_start (MetaPlugin *plugin) - self->cancellable, - KIOSK_OBJECT_CALLBACK (register_session), - NULL); -+ -+ g_signal_connect_object (self->display, "window-created", -+ G_CALLBACK (kiosk_compositor_on_window_created), -+ self, -+ G_CONNECT_DEFAULT); - } - - static void -@@ -372,9 +416,8 @@ kiosk_compositor_map (MetaPlugin *plugin, - - window = meta_window_actor_get_meta_window (actor); - -- if (kiosk_compositor_wants_window_fullscreen (self, window)) { -+ if (meta_window_is_fullscreen (window)) { - g_debug ("KioskCompositor: Mapping window that does need to be fullscreened"); -- meta_window_make_fullscreen (window); - easing_duration = 3000; - } else { - g_debug ("KioskCompositor: Mapping window that does not need to be fullscreened"); --- -2.49.0 - diff --git a/0003-systemd-sessions-Add-Mediakeys-support.patch b/0003-systemd-sessions-Add-Mediakeys-support.patch deleted file mode 100644 index 33f9ac0..0000000 --- a/0003-systemd-sessions-Add-Mediakeys-support.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 3bec84a57c51906880a1cf25d3255158ed6e9825 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Tue, 29 Jul 2025 18:08:24 +0200 -Subject: [PATCH 3/3] systemd/sessions: Add Mediakeys support - -That allows to start the screen reader using the default key shortcut. - -Closes: https://gitlab.gnome.org/GNOME/gnome-kiosk/-/issues/46 -(cherry picked from commit eacd8f733cdf8f53923268de43ff09cace758e50) ---- - kiosk-script/systemd/session.conf | 1 + - search-app/systemd/session.conf | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/kiosk-script/systemd/session.conf b/kiosk-script/systemd/session.conf -index aeb722c..5652e83 100644 ---- a/kiosk-script/systemd/session.conf -+++ b/kiosk-script/systemd/session.conf -@@ -2,3 +2,4 @@ - Requires=org.gnome.Kiosk.target - Requires=org.gnome.Kiosk.Script.service - Wants=org.gnome.SettingsDaemon.Sharing.target -+Wants=org.gnome.SettingsDaemon.MediaKeys.target -diff --git a/search-app/systemd/session.conf b/search-app/systemd/session.conf -index d038670..e37baf7 100644 ---- a/search-app/systemd/session.conf -+++ b/search-app/systemd/session.conf -@@ -2,3 +2,4 @@ - Requires=org.gnome.Kiosk.target - Requires=org.gnome.Kiosk.SearchApp.service - Wants=org.gnome.SettingsDaemon.Sharing.target -+Wants=org.gnome.SettingsDaemon.MediaKeys.target --- -2.50.1 - diff --git a/0004-compositor-Add-an-API-to-retrieve-a-boolean-setting.patch b/0004-compositor-Add-an-API-to-retrieve-a-boolean-setting.patch deleted file mode 100644 index 017786d..0000000 --- a/0004-compositor-Add-an-API-to-retrieve-a-boolean-setting.patch +++ /dev/null @@ -1,78 +0,0 @@ -From fed3a11e0f9c21f2361b3fea564c51957cd24fa5 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Tue, 12 Nov 2024 10:38:19 +0100 -Subject: [PATCH 4/5] compositor: Add an API to retrieve a boolean setting - -This adds a direct search API to the window configuration to retrieve -the boolean value for a matching window. - -This is preparation work for the following commit. - -(cherry picked from commit 62a7053d14b8432ef8dbc3ad78cd043ea95cf043) ---- - compositor/kiosk-window-config.c | 32 ++++++++++++++++++++++++++++++++ - compositor/kiosk-window-config.h | 5 +++++ - 2 files changed, 37 insertions(+) - -diff --git a/compositor/kiosk-window-config.c b/compositor/kiosk-window-config.c -index 5e6c830..a07798b 100644 ---- a/compositor/kiosk-window-config.c -+++ b/compositor/kiosk-window-config.c -@@ -281,6 +281,38 @@ kiosk_window_config_match_window (KioskWindowConfig *kiosk_window_config, - return TRUE; - } - -+gboolean -+kiosk_window_config_get_boolean_for_window (KioskWindowConfig *kiosk_window_config, -+ MetaWindow *window, -+ const char *key_name, -+ gboolean *value) -+{ -+ g_auto (GStrv) sections; -+ gsize length; -+ gboolean key_found = FALSE; -+ int i; -+ -+ sections = g_key_file_get_groups (kiosk_window_config->config_key_file, &length); -+ for (i = 0; i < length; i++) { -+ if (!kiosk_window_config_match_window (kiosk_window_config, -+ window, -+ sections[i])) -+ continue; -+ -+ if (kiosk_window_config_check_for_boolean_value (kiosk_window_config, -+ sections[i], -+ key_name, -+ value)) { -+ g_debug ("KioskWindowConfig: Using '%s=%s' from section [%s]", -+ key_name, *value ? "TRUE" : "FALSE", sections[i]); -+ -+ key_found = TRUE; -+ } -+ } -+ -+ return key_found; -+} -+ - void - kiosk_window_config_update_window (KioskWindowConfig *kiosk_window_config, - MetaWindow *window, -diff --git a/compositor/kiosk-window-config.h b/compositor/kiosk-window-config.h -index 1c7e8ea..3482c43 100644 ---- a/compositor/kiosk-window-config.h -+++ b/compositor/kiosk-window-config.h -@@ -14,6 +14,11 @@ G_DECLARE_FINAL_TYPE (KioskWindowConfig, kiosk_window_config, - - KioskWindowConfig *kiosk_window_config_new (void); - -+gboolean kiosk_window_config_get_boolean_for_window (KioskWindowConfig *kiosk_window_config, -+ MetaWindow *window, -+ const char *key_name, -+ gboolean *value); -+ - void kiosk_window_config_update_window (KioskWindowConfig *kiosk_window_config, - MetaWindow *window, - MetaWindowConfig *window_config); --- -2.49.0 - diff --git a/0005-compositor-Make-set-above-configurable.patch b/0005-compositor-Make-set-above-configurable.patch deleted file mode 100644 index 48a83d4..0000000 --- a/0005-compositor-Make-set-above-configurable.patch +++ /dev/null @@ -1,81 +0,0 @@ -From 8a9121bb11f0423aefe5a51252986765e10214b0 Mon Sep 17 00:00:00 2001 -From: Olivier Fourdan -Date: Fri, 8 Nov 2024 18:18:32 +0100 -Subject: [PATCH 5/5] compositor: Make set-above configurable - -GNOME Kiosk has heuristics to place windows on the top group. - -However, in some cases, it may desirable to avoid that behavior as that -prevents the windows stacking from being changed by the user. - -Add a new option "set-above" to the configuration file to control that -behaviour. - -If not specified, the existing heuristics will be applied, not breaking -possible existing setups. - -Closes: https://gitlab.gnome.org/GNOME/gnome-kiosk/-/issues/26 -(cherry picked from commit b50c25fe739c00f4a1984919ee93cd351f58dfc1) ---- - CONFIG.md | 3 +++ - compositor/kiosk-compositor.c | 9 +++++++++ - window-config.ini | 2 ++ - 3 files changed, 14 insertions(+) - -diff --git a/CONFIG.md b/CONFIG.md -index b0045ee..652fb71 100644 ---- a/CONFIG.md -+++ b/CONFIG.md -@@ -42,6 +42,7 @@ The following "*set*" keys are supported: - * `set-y` (integer) - the Y position - * `set-width` (integer) - the width - * `set-height` (integer) - the height -+ * `set-above` (boolean) - Whether the window should be placed on a layer above - - # Example - -@@ -51,6 +52,8 @@ The following "*set*" keys are supported: - set-x=0 - set-y=0 - set-fullscreen=false -+ # The following will place all windows on the same layer -+ set-above=false - - # Make all Mozilla windows fullscreen - [mozilla] -diff --git a/compositor/kiosk-compositor.c b/compositor/kiosk-compositor.c -index 650ac50..aa5603d 100644 ---- a/compositor/kiosk-compositor.c -+++ b/compositor/kiosk-compositor.c -@@ -385,6 +385,15 @@ static gboolean - kiosk_compositor_wants_window_above (KioskCompositor *self, - MetaWindow *window) - { -+ gboolean set_above; -+ -+ if (kiosk_window_config_get_boolean_for_window (self->kiosk_window_config, -+ window, -+ "set-above", -+ &set_above)) -+ return set_above; -+ -+ /* If not specified in the config, use the heuristics */ - if (meta_window_is_screen_sized (window)) { - return FALSE; - } -diff --git a/window-config.ini b/window-config.ini -index c4c825e..f4f37b3 100644 ---- a/window-config.ini -+++ b/window-config.ini -@@ -7,6 +7,8 @@ - [all] - set-x=0 - set-y=0 -+# Uncomment the following to have all windows on the same layer -+# set-above=false - - # Make all Mozilla windows fullscreen - [browser] --- -2.49.0 - diff --git a/gnome-kiosk.spec b/gnome-kiosk.spec index 058035d..c34c13c 100644 --- a/gnome-kiosk.spec +++ b/gnome-kiosk.spec @@ -5,13 +5,13 @@ %global gnome_desktop_version 44.0 %global glib2_version 2.68.0 %global gtk4_version 3.24.27 -%global mutter_version 47.5-9 +%global mutter_version 49~beta %global gsettings_desktop_schemas_version 40~rc %global ibus_version 1.5.24 %global gnome_settings_daemon_version 40~rc Name: gnome-kiosk -Version: 47.0 +Version: 49.0 Release: %{autorelease} Summary: Window management and application launching for GNOME @@ -35,7 +35,7 @@ BuildRequires: pkgconfig(gio-2.0) >= %{glib2_version} 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(libmutter-17) >= %{mutter_version} BuildRequires: pkgconfig(gdk-pixbuf-2.0) Requires: gnome-settings-daemon%{?_isa} >= %{gnome_settings_daemon_version} @@ -44,32 +44,8 @@ Requires: mutter%{?_isa} >= %{mutter_version} Recommends: xorg-x11-server-Xwayland Recommends: dbus-daemon -Patch: 0001-kiosk-app-Do-not-add-the-window-in-kiosk_app_new_for.patch -# https://issues.redhat.com/browse/RHEL-71757 -Patch: 0001-kiosk-script-Copy-and-run-the-script-from-XDG_RUNTIM.patch -# https://issues.redhat.com/browse/RHEL-62420 -Patch: 0001-compositor-Add-screenshot-utilities.patch -Patch: 0002-compositor-Add-Shell-Screenshot-support.patch -# https://issues.redhat.com/browse/RHEL-82250 -# https://issues.redhat.com/browse/RHEL-82404 -Patch: 0001-search-app-Add-systemd-session-files.patch -Patch: 0002-search-app-Update-desktop-file-definition.patch # https://issues.redhat.com/browse/RHEL-36521 Patch: 0001-search-app-Use-firefox-from-flatpak.patch -# https://issues.redhat.com/browse/RHEL-84829 -Patch: 0001-input-sources-manager-Do-not-crash-if-there-is-no-X1.patch -# https://issues.redhat.com/browse/RHEL-84697 -Patch: 0001-compositor-Use-the-meta-window-API-for-set-above.patch -Patch: 0002-compositor-Add-a-window-configuration-file.patch -Patch: 0003-compositor-Apply-the-window-configuration.patch -Patch: 0004-compositor-Add-an-API-to-retrieve-a-boolean-setting.patch -Patch: 0005-compositor-Make-set-above-configurable.patch -# https://issues.redhat.com/browse/RHEL-103639 -Patch: 0001-systemd-session-Enable-remote-session.patch -# https://issues.redhat.com/browse/RHEL-106584 -Patch: 0001-shell-service-Fix-GrabAccelerators-return-value.patch -Patch: 0002-compositor-Use-the-Shell-service.patch -Patch: 0003-systemd-sessions-Add-Mediakeys-support.patch %description GNOME Kiosk provides a desktop enviroment suitable for fixed purpose, or diff --git a/sources b/sources index b38fdd3..5ac974c 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (gnome-kiosk-47.0.tar.xz) = 497b7e3500d4afc38f9d57a4a40c2ea2ad834365989893d71742ce02c1e67cd7976bf884d02248b269a2141ca865845beac97e130bfe1032dbebaa95cfe52e4f +SHA512 (gnome-kiosk-49.0.tar.xz) = a2d92b39516b7f70074f4ef210ddf98215820699d717973f4addeafa9e68875d0e8b49913a5f175027878aab5eca7b810abd20c8df1a7b011ac9e573560feacb