summaryrefslogtreecommitdiffstats
path: root/src/shell-screenshot.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:54:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:54:43 +0000
commite4283f6d48b98e764b988b43bbc86b9d52e6ec94 (patch)
treec8f7f7a6c2f5faa2942d27cefc6fd46cca492656 /src/shell-screenshot.c
parentInitial commit. (diff)
downloadgnome-shell-e4283f6d48b98e764b988b43bbc86b9d52e6ec94.tar.xz
gnome-shell-e4283f6d48b98e764b988b43bbc86b9d52e6ec94.zip
Adding upstream version 43.9.upstream/43.9upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/shell-screenshot.c1217
1 files changed, 1217 insertions, 0 deletions
diff --git a/src/shell-screenshot.c b/src/shell-screenshot.c
new file mode 100644
index 0000000..f7132ee
--- /dev/null
+++ b/src/shell-screenshot.c
@@ -0,0 +1,1217 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <clutter/clutter.h>
+#include <cogl/cogl.h>
+#include <meta/display.h>
+#include <meta/util.h>
+#include <meta/meta-plugin.h>
+#include <meta/meta-cursor-tracker.h>
+#include <st/st.h>
+
+#include "shell-global.h"
+#include "shell-screenshot.h"
+#include "shell-util.h"
+
+typedef enum _ShellScreenshotFlag
+{
+ SHELL_SCREENSHOT_FLAG_NONE,
+ SHELL_SCREENSHOT_FLAG_INCLUDE_CURSOR,
+} ShellScreenshotFlag;
+
+typedef enum _ShellScreenshotMode
+{
+ SHELL_SCREENSHOT_SCREEN,
+ SHELL_SCREENSHOT_WINDOW,
+ SHELL_SCREENSHOT_AREA,
+} ShellScreenshotMode;
+
+enum
+{
+ SCREENSHOT_TAKEN,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+typedef struct _ShellScreenshotPrivate ShellScreenshotPrivate;
+
+struct _ShellScreenshot
+{
+ GObject parent_instance;
+
+ ShellScreenshotPrivate *priv;
+};
+
+struct _ShellScreenshotPrivate
+{
+ ShellGlobal *global;
+
+ GOutputStream *stream;
+ ShellScreenshotFlag flags;
+ ShellScreenshotMode mode;
+
+ GDateTime *datetime;
+
+ cairo_surface_t *image;
+ cairo_rectangle_int_t screenshot_area;
+
+ gboolean include_frame;
+
+ float scale;
+ ClutterContent *cursor_content;
+ graphene_point_t cursor_point;
+ float cursor_scale;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellScreenshot, shell_screenshot, G_TYPE_OBJECT);
+
+static void
+shell_screenshot_class_init (ShellScreenshotClass *screenshot_class)
+{
+ signals[SCREENSHOT_TAKEN] =
+ g_signal_new ("screenshot-taken",
+ G_TYPE_FROM_CLASS(screenshot_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ META_TYPE_RECTANGLE);
+}
+
+static void
+shell_screenshot_init (ShellScreenshot *screenshot)
+{
+ screenshot->priv = shell_screenshot_get_instance_private (screenshot);
+ screenshot->priv->global = shell_global_get ();
+}
+
+static void
+on_screenshot_written (GObject *source,
+ GAsyncResult *task,
+ gpointer user_data)
+{
+ ShellScreenshot *screenshot = SHELL_SCREENSHOT (source);
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ GTask *result = user_data;
+
+ g_task_return_boolean (result, g_task_propagate_boolean (G_TASK (task), NULL));
+ g_object_unref (result);
+
+ g_clear_pointer (&priv->image, cairo_surface_destroy);
+ g_clear_object (&priv->stream);
+ g_clear_pointer (&priv->datetime, g_date_time_unref);
+}
+
+static void
+write_screenshot_thread (GTask *result,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ ShellScreenshot *screenshot = SHELL_SCREENSHOT (object);
+ ShellScreenshotPrivate *priv;
+ g_autoptr (GOutputStream) stream = NULL;
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ g_autofree char *creation_time = NULL;
+ GError *error = NULL;
+
+ g_assert (screenshot != NULL);
+
+ priv = screenshot->priv;
+
+ stream = g_object_ref (priv->stream);
+
+ pixbuf = gdk_pixbuf_get_from_surface (priv->image,
+ 0, 0,
+ cairo_image_surface_get_width (priv->image),
+ cairo_image_surface_get_height (priv->image));
+ creation_time = g_date_time_format (priv->datetime, "%c");
+
+ if (!creation_time)
+ creation_time = g_date_time_format (priv->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 (ShellScreenshot *screenshot,
+ int x,
+ int y,
+ int width,
+ int height,
+ ShellScreenshotFlag flags)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ ClutterStage *stage = shell_global_get_stage (priv->global);
+ cairo_rectangle_int_t 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 (stage, &screenshot_rect,
+ &image_width,
+ &image_height,
+ &scale);
+ image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ image_width, image_height);
+
+ if (flags & SHELL_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 (stage, &screenshot_rect, scale,
+ cairo_image_surface_get_data (image),
+ cairo_image_surface_get_stride (image),
+ CLUTTER_CAIRO_FORMAT_ARGB32,
+ paint_flags,
+ &error))
+ {
+ cairo_surface_destroy (image);
+ g_warning ("Failed to take screenshot: %s", error->message);
+ return;
+ }
+
+ priv->image = image;
+
+ priv->datetime = g_date_time_new_now_local ();
+}
+
+static void
+draw_cursor_image (cairo_surface_t *surface,
+ cairo_rectangle_int_t area)
+{
+ CoglTexture *texture;
+ int width, height;
+ int stride;
+ guint8 *data;
+ MetaDisplay *display;
+ MetaCursorTracker *tracker;
+ cairo_surface_t *cursor_surface;
+ cairo_region_t *screenshot_region;
+ cairo_t *cr;
+ int x, y;
+ int xhot, yhot;
+ double xscale, yscale;
+ graphene_point_t point;
+
+ display = shell_global_get_display (shell_global_get ());
+ tracker = meta_cursor_tracker_get_for_display (display);
+ texture = meta_cursor_tracker_get_sprite (tracker);
+
+ if (!texture)
+ return;
+
+ screenshot_region = cairo_region_create_rectangle (&area);
+ meta_cursor_tracker_get_pointer (tracker, &point, NULL);
+ x = point.x;
+ y = point.y;
+
+ if (!cairo_region_contains_point (screenshot_region, point.x, point.y))
+ {
+ cairo_region_destroy (screenshot_region);
+ 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, CLUTTER_CAIRO_FORMAT_ARGB32, 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;
+ MetaRectangle cursor_rect = {
+ .x = x, .y = y, .width = width, .height = height
+ };
+
+ monitor = meta_display_get_monitor_index_for_rect (display, &cursor_rect);
+ monitor_scale = meta_display_get_monitor_scale (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);
+ cairo_region_destroy (screenshot_region);
+ g_free (data);
+}
+
+static void
+grab_screenshot (ShellScreenshot *screenshot,
+ ShellScreenshotFlag flags,
+ GTask *result)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ MetaDisplay *display;
+ int width, height;
+ GTask *task;
+
+ display = shell_global_get_display (priv->global);
+ meta_display_get_size (display, &width, &height);
+
+ do_grab_screenshot (screenshot,
+ 0, 0, width, height,
+ flags);
+
+ priv->screenshot_area.x = 0;
+ priv->screenshot_area.y = 0;
+ priv->screenshot_area.width = width;
+ priv->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_screenshot_content (ShellScreenshot *screenshot,
+ GTask *result)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ MetaDisplay *display;
+ int width, height;
+ cairo_rectangle_int_t screenshot_rect;
+ ClutterStage *stage;
+ int image_width;
+ int image_height;
+ float scale;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (ClutterContent) content = NULL;
+ g_autoptr (GTask) task = result;
+ MetaCursorTracker *tracker;
+ CoglTexture *cursor_texture;
+ int cursor_hot_x, cursor_hot_y;
+
+ display = shell_global_get_display (priv->global);
+ meta_display_get_size (display, &width, &height);
+ screenshot_rect = (cairo_rectangle_int_t) {
+ .x = 0,
+ .y = 0,
+ .width = width,
+ .height = height,
+ };
+
+ stage = shell_global_get_stage (priv->global);
+
+ clutter_stage_get_capture_final_size (stage, &screenshot_rect,
+ &image_width,
+ &image_height,
+ &scale);
+
+ priv->scale = scale;
+
+ content = clutter_stage_paint_to_content (stage, &screenshot_rect, scale,
+ CLUTTER_PAINT_FLAG_NO_CURSORS,
+ &error);
+ if (!content)
+ {
+ g_task_return_error (result, g_steal_pointer (&error));
+ return;
+ }
+
+ tracker = meta_cursor_tracker_get_for_display (display);
+ cursor_texture = meta_cursor_tracker_get_sprite (tracker);
+
+ // If the cursor is invisible, the texture is NULL.
+ if (cursor_texture)
+ {
+ unsigned int width, height;
+ CoglContext *ctx;
+ CoglPipeline *pipeline;
+ CoglTexture2D *texture;
+ CoglOffscreen *offscreen;
+ ClutterStageView *view;
+
+ // Copy the texture to prevent it from changing shortly after.
+ width = cogl_texture_get_width (cursor_texture);
+ height = cogl_texture_get_height (cursor_texture);
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+ texture = cogl_texture_2d_new_with_size (ctx, width, height);
+ offscreen = cogl_offscreen_new_with_texture (texture);
+ cogl_framebuffer_clear4f (COGL_FRAMEBUFFER (offscreen),
+ COGL_BUFFER_BIT_COLOR,
+ 0, 0, 0, 0);
+
+ pipeline = cogl_pipeline_new (ctx);
+ cogl_pipeline_set_layer_texture (pipeline, 0, cursor_texture);
+
+ cogl_framebuffer_draw_textured_rectangle (COGL_FRAMEBUFFER (offscreen),
+ pipeline,
+ -1, 1, 1, -1,
+ 0, 0, 1, 1);
+ cogl_object_unref (pipeline);
+ g_object_unref (offscreen);
+
+ priv->cursor_content =
+ clutter_texture_content_new_from_texture (texture, NULL);
+ cogl_object_unref (texture);
+
+ priv->cursor_scale = meta_cursor_tracker_get_scale (tracker);
+
+ meta_cursor_tracker_get_pointer (tracker, &priv->cursor_point, NULL);
+
+ view = clutter_stage_get_view_at (stage,
+ priv->cursor_point.x,
+ priv->cursor_point.y);
+
+ meta_cursor_tracker_get_hot (tracker, &cursor_hot_x, &cursor_hot_y);
+ priv->cursor_point.x -= cursor_hot_x * priv->cursor_scale;
+ priv->cursor_point.y -= cursor_hot_y * priv->cursor_scale;
+
+ // Align the coordinates to the pixel grid the same way it's done in
+ // MetaCursorRenderer.
+ if (view)
+ {
+ cairo_rectangle_int_t view_layout;
+ float view_scale;
+
+ clutter_stage_view_get_layout (view, &view_layout);
+ view_scale = clutter_stage_view_get_scale (view);
+
+ priv->cursor_point.x -= view_layout.x;
+ priv->cursor_point.y -= view_layout.y;
+
+ priv->cursor_point.x =
+ floorf (priv->cursor_point.x * view_scale) / view_scale;
+ priv->cursor_point.y =
+ floorf (priv->cursor_point.y * view_scale) / view_scale;
+
+ priv->cursor_point.x += view_layout.x;
+ priv->cursor_point.y += view_layout.y;
+ }
+ }
+
+ g_task_return_pointer (result, g_steal_pointer (&content), g_object_unref);
+}
+
+static void
+grab_window_screenshot (ShellScreenshot *screenshot,
+ ShellScreenshotFlag flags,
+ GTask *result)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ GTask *task;
+ MetaDisplay *display = shell_global_get_display (priv->global);
+ MetaWindow *window = meta_display_get_focus_window (display);
+ ClutterActor *window_actor;
+ gfloat actor_x, actor_y;
+ MetaRectangle 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 (!priv->include_frame)
+ meta_window_frame_rect_to_client_rect (window, &rect, &rect);
+
+ priv->screenshot_area = rect;
+
+ priv->image = meta_window_actor_get_image (META_WINDOW_ACTOR (window_actor),
+ NULL);
+
+ if (!priv->image)
+ {
+ g_task_report_new_error (screenshot, on_screenshot_written, result, NULL,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Capturing window failed");
+ return;
+ }
+
+ priv->datetime = g_date_time_new_now_local ();
+
+ if (flags & SHELL_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 (priv->image, resource_scale, resource_scale);
+ }
+
+ draw_cursor_image (priv->image, priv->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 (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+
+ if (!g_task_propagate_boolean (G_TASK (result), error))
+ return FALSE;
+
+ if (area)
+ *area = &priv->screenshot_area;
+
+ return TRUE;
+}
+
+static void
+on_after_paint (ClutterStage *stage,
+ ClutterStageView *view,
+ GTask *result)
+{
+ ShellScreenshot *screenshot = g_task_get_task_data (result);
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ MetaDisplay *display = shell_global_get_display (priv->global);
+ GTask *task;
+
+ g_signal_handlers_disconnect_by_func (stage, on_after_paint, result);
+
+ if (priv->mode == SHELL_SCREENSHOT_AREA)
+ {
+ do_grab_screenshot (screenshot,
+ priv->screenshot_area.x,
+ priv->screenshot_area.y,
+ priv->screenshot_area.width,
+ priv->screenshot_area.height,
+ priv->flags);
+
+ task = g_task_new (screenshot, NULL, on_screenshot_written, result);
+ g_task_run_in_thread (task, write_screenshot_thread);
+ }
+ else
+ {
+ grab_screenshot (screenshot, priv->flags, result);
+ }
+
+ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0,
+ (cairo_rectangle_int_t *) &priv->screenshot_area);
+
+ meta_enable_unredirect_for_display (display);
+}
+
+/**
+ * shell_screenshot_screenshot:
+ * @screenshot: the #ShellScreenshot
+ * @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
+shell_screenshot_screenshot (ShellScreenshot *screenshot,
+ gboolean include_cursor,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellScreenshotPrivate *priv;
+ GTask *result;
+ ShellScreenshotFlag flags;
+
+ g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
+ g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+
+ priv = screenshot->priv;
+
+ if (priv->stream != NULL) {
+ if (callback)
+ g_task_report_new_error (screenshot,
+ callback,
+ user_data,
+ shell_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, shell_screenshot_screenshot);
+ g_task_set_task_data (result, screenshot, NULL);
+
+ priv->stream = g_object_ref (stream);
+
+ flags = SHELL_SCREENSHOT_FLAG_NONE;
+ if (include_cursor)
+ flags |= SHELL_SCREENSHOT_FLAG_INCLUDE_CURSOR;
+
+ if (meta_is_wayland_compositor ())
+ {
+ grab_screenshot (screenshot, flags, result);
+
+ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0,
+ (cairo_rectangle_int_t *) &priv->screenshot_area);
+ }
+ else
+ {
+ MetaDisplay *display = shell_global_get_display (priv->global);
+ ClutterStage *stage = shell_global_get_stage (priv->global);
+
+ meta_disable_unredirect_for_display (display);
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+ priv->flags = flags;
+ priv->mode = SHELL_SCREENSHOT_SCREEN;
+ g_signal_connect (stage, "after-paint",
+ G_CALLBACK (on_after_paint), result);
+ }
+}
+
+/**
+ * shell_screenshot_screenshot_finish:
+ * @screenshot: the #ShellScreenshot
+ * @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 shell_screenshot_screenshot()
+ * and obtain its result.
+ *
+ * Returns: whether the operation was successful
+ *
+ */
+gboolean
+shell_screenshot_screenshot_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error)
+{
+ g_return_val_if_fail (SHELL_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,
+ shell_screenshot_screenshot),
+ FALSE);
+ return finish_screenshot (screenshot, result, area, error);
+}
+
+static void
+screenshot_stage_to_content_on_after_paint (ClutterStage *stage,
+ ClutterStageView *view,
+ GTask *result)
+{
+ ShellScreenshot *screenshot = g_task_get_task_data (result);
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ MetaDisplay *display = shell_global_get_display (priv->global);
+
+ g_signal_handlers_disconnect_by_func (stage,
+ screenshot_stage_to_content_on_after_paint,
+ result);
+
+ meta_enable_unredirect_for_display (display);
+
+ grab_screenshot_content (screenshot, result);
+}
+
+/**
+ * shell_screenshot_screenshot_stage_to_content:
+ * @screenshot: the #ShellScreenshot
+ * @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 as #ClutterContent.
+ *
+ */
+void
+shell_screenshot_screenshot_stage_to_content (ShellScreenshot *screenshot,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellScreenshotPrivate *priv;
+ GTask *result;
+
+ g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
+
+ result = g_task_new (screenshot, NULL, callback, user_data);
+ g_task_set_source_tag (result, shell_screenshot_screenshot_stage_to_content);
+ g_task_set_task_data (result, screenshot, NULL);
+
+ if (meta_is_wayland_compositor ())
+ {
+ grab_screenshot_content (screenshot, result);
+ }
+ else
+ {
+ priv = screenshot->priv;
+
+ MetaDisplay *display = shell_global_get_display (priv->global);
+ ClutterStage *stage = shell_global_get_stage (priv->global);
+
+ meta_disable_unredirect_for_display (display);
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+ g_signal_connect (stage, "after-paint",
+ G_CALLBACK (screenshot_stage_to_content_on_after_paint),
+ result);
+ }
+}
+
+/**
+ * shell_screenshot_screenshot_stage_to_content_finish:
+ * @screenshot: the #ShellScreenshot
+ * @result: the #GAsyncResult that was provided to the callback
+ * @scale: (out) (optional): location to store the content scale
+ * @cursor_content: (out) (optional): location to store the cursor content
+ * @cursor_point: (out) (optional): location to store the point at which to
+ * draw the cursor content
+ * @cursor_scale: (out) (optional): location to store the cursor scale
+ * @error: #GError for error reporting
+ *
+ * Finish the asynchronous operation started by
+ * shell_screenshot_screenshot_stage_to_content() and obtain its result.
+ *
+ * Returns: (transfer full): the #ClutterContent, or NULL
+ *
+ */
+ClutterContent *
+shell_screenshot_screenshot_stage_to_content_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ float *scale,
+ ClutterContent **cursor_content,
+ graphene_point_t *cursor_point,
+ float *cursor_scale,
+ GError **error)
+{
+ ShellScreenshotPrivate *priv = screenshot->priv;
+ ClutterContent *content;
+
+ g_return_val_if_fail (SHELL_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,
+ shell_screenshot_screenshot_stage_to_content),
+ FALSE);
+
+ content = g_task_propagate_pointer (G_TASK (result), error);
+ if (!content)
+ return NULL;
+
+ if (scale)
+ *scale = priv->scale;
+
+ if (cursor_content)
+ *cursor_content = g_steal_pointer (&priv->cursor_content);
+ else
+ g_clear_pointer (&priv->cursor_content, g_object_unref);
+
+ if (cursor_point)
+ *cursor_point = priv->cursor_point;
+
+ if (cursor_scale)
+ *cursor_scale = priv->cursor_scale;
+
+ return content;
+}
+
+/**
+ * shell_screenshot_screenshot_area:
+ * @screenshot: the #ShellScreenshot
+ * @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
+shell_screenshot_screenshot_area (ShellScreenshot *screenshot,
+ int x,
+ int y,
+ int width,
+ int height,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellScreenshotPrivate *priv;
+ GTask *result;
+ g_autoptr (GTask) task = NULL;
+
+ g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
+ g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+
+ priv = screenshot->priv;
+
+ if (priv->stream != NULL) {
+ if (callback)
+ g_task_report_new_error (screenshot,
+ callback,
+ NULL,
+ shell_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, shell_screenshot_screenshot_area);
+ g_task_set_task_data (result, screenshot, NULL);
+
+ priv->stream = g_object_ref (stream);
+ priv->screenshot_area.x = x;
+ priv->screenshot_area.y = y;
+ priv->screenshot_area.width = width;
+ priv->screenshot_area.height = height;
+
+
+ if (meta_is_wayland_compositor ())
+ {
+ do_grab_screenshot (screenshot,
+ priv->screenshot_area.x,
+ priv->screenshot_area.y,
+ priv->screenshot_area.width,
+ priv->screenshot_area.height,
+ SHELL_SCREENSHOT_FLAG_NONE);
+
+ g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0,
+ (cairo_rectangle_int_t *) &priv->screenshot_area);
+
+ task = g_task_new (screenshot, NULL, on_screenshot_written, result);
+ g_task_run_in_thread (task, write_screenshot_thread);
+ }
+ else
+ {
+ MetaDisplay *display = shell_global_get_display (priv->global);
+ ClutterStage *stage = shell_global_get_stage (priv->global);
+
+ meta_disable_unredirect_for_display (display);
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+ priv->flags = SHELL_SCREENSHOT_FLAG_NONE;
+ priv->mode = SHELL_SCREENSHOT_AREA;
+ g_signal_connect (stage, "after-paint",
+ G_CALLBACK (on_after_paint), result);
+ }
+}
+
+/**
+ * shell_screenshot_screenshot_area_finish:
+ * @screenshot: the #ShellScreenshot
+ * @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 shell_screenshot_screenshot_area()
+ * and obtain its result.
+ *
+ * Returns: whether the operation was successful
+ *
+ */
+gboolean
+shell_screenshot_screenshot_area_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error)
+{
+ g_return_val_if_fail (SHELL_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,
+ shell_screenshot_screenshot_area),
+ FALSE);
+ return finish_screenshot (screenshot, result, area, error);
+}
+
+/**
+ * shell_screenshot_screenshot_window:
+ * @screenshot: the #ShellScreenshot
+ * @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
+shell_screenshot_screenshot_window (ShellScreenshot *screenshot,
+ gboolean include_frame,
+ gboolean include_cursor,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellScreenshotPrivate *priv;
+ MetaDisplay *display;
+ MetaWindow *window;
+ GTask *result;
+
+ g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
+ g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+
+ priv = screenshot->priv;
+ display = shell_global_get_display (priv->global);
+ window = meta_display_get_focus_window (display);
+
+ if (priv->stream != NULL || !window) {
+ if (callback)
+ g_task_report_new_error (screenshot,
+ callback,
+ NULL,
+ shell_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, shell_screenshot_screenshot_window);
+
+ priv->stream = g_object_ref (stream);
+ priv->include_frame = include_frame;
+
+ grab_window_screenshot (screenshot, include_cursor, result);
+}
+
+/**
+ * shell_screenshot_screenshot_window_finish:
+ * @screenshot: the #ShellScreenshot
+ * @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 shell_screenshot_screenshot_window()
+ * and obtain its result.
+ *
+ * Returns: whether the operation was successful
+ *
+ */
+gboolean
+shell_screenshot_screenshot_window_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ cairo_rectangle_int_t **area,
+ GError **error)
+{
+ g_return_val_if_fail (SHELL_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,
+ shell_screenshot_screenshot_window),
+ FALSE);
+ return finish_screenshot (screenshot, result, area, error);
+}
+
+/**
+ * shell_screenshot_pick_color:
+ * @screenshot: the #ShellScreenshot
+ * @x: The X coordinate to pick
+ * @y: The Y coordinate to pick
+ * @callback: (scope async): function to call returning success or failure
+ * of the async grabbing
+ *
+ * Picks the pixel at @x, @y and returns its color as #ClutterColor.
+ *
+ */
+void
+shell_screenshot_pick_color (ShellScreenshot *screenshot,
+ int x,
+ int y,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ShellScreenshotPrivate *priv;
+ g_autoptr (GTask) result = NULL;
+
+ g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
+
+ result = g_task_new (screenshot, NULL, callback, user_data);
+ g_task_set_source_tag (result, shell_screenshot_pick_color);
+
+ priv = screenshot->priv;
+
+ priv->screenshot_area.x = x;
+ priv->screenshot_area.y = y;
+ priv->screenshot_area.width = 1;
+ priv->screenshot_area.height = 1;
+
+ do_grab_screenshot (screenshot,
+ priv->screenshot_area.x,
+ priv->screenshot_area.y,
+ 1,
+ 1,
+ SHELL_SCREENSHOT_FLAG_NONE);
+
+ g_task_return_boolean (result, TRUE);
+}
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+#define INDEX_A 3
+#define INDEX_R 2
+#define INDEX_G 1
+#define INDEX_B 0
+#else
+#define INDEX_A 0
+#define INDEX_R 1
+#define INDEX_G 2
+#define INDEX_B 3
+#endif
+
+/**
+ * shell_screenshot_pick_color_finish:
+ * @screenshot: the #ShellScreenshot
+ * @result: the #GAsyncResult that was provided to the callback
+ * @color: (out caller-allocates): the picked color
+ * @error: #GError for error reporting
+ *
+ * Finish the asynchronous operation started by shell_screenshot_pick_color()
+ * and obtain its result.
+ *
+ * Returns: whether the operation was successful
+ *
+ */
+gboolean
+shell_screenshot_pick_color_finish (ShellScreenshot *screenshot,
+ GAsyncResult *result,
+ ClutterColor *color,
+ GError **error)
+{
+ ShellScreenshotPrivate *priv;
+
+ g_return_val_if_fail (SHELL_IS_SCREENSHOT (screenshot), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+ g_return_val_if_fail (color != NULL, FALSE);
+ g_return_val_if_fail (g_async_result_is_tagged (result,
+ shell_screenshot_pick_color),
+ FALSE);
+
+ if (!g_task_propagate_boolean (G_TASK (result), error))
+ return FALSE;
+
+ priv = screenshot->priv;
+
+ /* protect against mutter changing the format used for stage captures */
+ g_assert (cairo_image_surface_get_format (priv->image) == CAIRO_FORMAT_ARGB32);
+
+ if (color)
+ {
+ uint8_t *data = cairo_image_surface_get_data (priv->image);
+
+ color->alpha = data[INDEX_A];
+ color->red = data[INDEX_R];
+ color->green = data[INDEX_G];
+ color->blue = data[INDEX_B];
+ }
+
+ return TRUE;
+}
+
+#undef INDEX_A
+#undef INDEX_R
+#undef INDEX_G
+#undef INDEX_B
+
+static void
+composite_to_stream_on_png_saved (GObject *pixbuf,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GTask *task = G_TASK (user_data);
+ GError *error = NULL;
+
+ if (!gdk_pixbuf_save_to_stream_finish (result, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, g_object_ref (pixbuf), g_object_unref);
+
+ g_object_unref (task);
+}
+
+/**
+ * shell_screenshot_composite_to_stream:
+ * @texture: the source texture
+ * @x: x coordinate of the rectangle
+ * @y: y coordinate of the rectangle
+ * @width: width of the rectangle, or -1 to use the full texture
+ * @height: height of the rectangle, or -1 to use the full texture
+ * @scale: scale of the source texture
+ * @cursor: (nullable): the cursor texture
+ * @cursor_x: x coordinate to put the cursor texture at, relative to the full
+ * source texture
+ * @cursor_y: y coordinate to put the cursor texture at, relative to the full
+ * source texture
+ * @cursor_scale: scale of the cursor texture
+ * @stream: the stream to write the PNG image into
+ * @callback: (scope async): function to call returning success or failure
+ * @user_data: the data to pass to callback function
+ *
+ * Composite a rectangle defined by x, y, width, height from the texture to a
+ * pixbuf and write it as a PNG image into the stream.
+ *
+ */
+void
+shell_screenshot_composite_to_stream (CoglTexture *texture,
+ int x,
+ int y,
+ int width,
+ int height,
+ float scale,
+ CoglTexture *cursor,
+ int cursor_x,
+ int cursor_y,
+ float cursor_scale,
+ GOutputStream *stream,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ CoglContext *ctx;
+ CoglTexture *sub_texture;
+ cairo_surface_t *surface;
+ cairo_surface_t *cursor_surface;
+ cairo_t *cr;
+ g_autoptr (GTask) task = NULL;
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ g_autofree char *creation_time = NULL;
+ g_autoptr (GDateTime) date_time = NULL;
+
+ task = g_task_new (NULL, NULL, callback, user_data);
+ g_task_set_source_tag (task, shell_screenshot_composite_to_stream);
+
+ if (width == -1 || height == -1)
+ {
+ x = 0;
+ y = 0;
+ width = cogl_texture_get_width (texture);
+ height = cogl_texture_get_height (texture);
+ }
+
+ ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ sub_texture = cogl_sub_texture_new (ctx, texture, x, y, width, height);
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ cogl_texture_get_width (sub_texture),
+ cogl_texture_get_height (sub_texture));
+
+ cogl_texture_get_data (sub_texture, CLUTTER_CAIRO_FORMAT_ARGB32,
+ cairo_image_surface_get_stride (surface),
+ cairo_image_surface_get_data (surface));
+ cairo_surface_mark_dirty (surface);
+
+ cogl_object_unref (sub_texture);
+
+ cairo_surface_set_device_scale (surface, scale, scale);
+
+ if (cursor != NULL)
+ {
+ // Paint the cursor on top.
+ cursor_surface =
+ cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ cogl_texture_get_width (cursor),
+ cogl_texture_get_height (cursor));
+ cogl_texture_get_data (cursor, CLUTTER_CAIRO_FORMAT_ARGB32,
+ cairo_image_surface_get_stride (cursor_surface),
+ cairo_image_surface_get_data (cursor_surface));
+ cairo_surface_mark_dirty (cursor_surface);
+
+ cairo_surface_set_device_scale (cursor_surface,
+ 1 / cursor_scale,
+ 1 / cursor_scale);
+
+ cr = cairo_create (surface);
+ cairo_set_source_surface (cr, cursor_surface,
+ (cursor_x - x) / scale,
+ (cursor_y - y) / scale);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ cairo_surface_destroy (cursor_surface);
+ }
+
+ // Save to an image.
+ pixbuf = gdk_pixbuf_get_from_surface (surface,
+ 0, 0,
+ cairo_image_surface_get_width (surface),
+ cairo_image_surface_get_height (surface));
+ cairo_surface_destroy (surface);
+
+ date_time = g_date_time_new_now_local ();
+ creation_time = g_date_time_format (date_time, "%c");
+
+ if (!creation_time)
+ creation_time = g_date_time_format (date_time, "%FT%T%z");
+
+ gdk_pixbuf_save_to_stream_async (pixbuf, stream, "png", NULL,
+ composite_to_stream_on_png_saved,
+ g_steal_pointer (&task),
+ "tEXt::Software", "gnome-screenshot",
+ "tEXt::Creation Time", creation_time,
+ NULL);
+}
+
+/**
+ * shell_screenshot_composite_to_stream_finish:
+ * @result: the #GAsyncResult that was provided to the callback
+ * @error: #GError for error reporting
+ *
+ * Finish the asynchronous operation started by
+ * shell_screenshot_composite_to_stream () and obtain its result.
+ *
+ * Returns: (transfer full) (nullable): a GdkPixbuf with the final image if the
+ * operation was successful, or NULL on error.
+ *
+ */
+GdkPixbuf *
+shell_screenshot_composite_to_stream_finish (GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+ g_return_val_if_fail (g_async_result_is_tagged (result,
+ shell_screenshot_composite_to_stream),
+ FALSE);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+ShellScreenshot *
+shell_screenshot_new (void)
+{
+ return g_object_new (SHELL_TYPE_SCREENSHOT, NULL);
+}