shell-screenshot: Change to use clutter_stage_paint_to*() API

This eliminates the need to wait for redraws, drawing cursors, and
stiching together cairo images in case the screenshot covers multiple
monitors.

All of that is now handled by mutter itself.

https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1383
This commit is contained in:
Jonas Ådahl 2020-07-30 09:51:06 +02:00 committed by Georges Basile Stavracas Neto
parent c2b70101f2
commit 757e4b6731

View File

@ -12,6 +12,12 @@
#include "shell-screenshot.h" #include "shell-screenshot.h"
#include "shell-util.h" #include "shell-util.h"
typedef enum _ShellScreenshotFlag
{
SHELL_SCREENSHOT_FLAG_NONE,
SHELL_SCREENSHOT_FLAG_INCLUDE_CURSOR,
} ShellScreenshotFlag;
typedef struct _ShellScreenshotPrivate ShellScreenshotPrivate; typedef struct _ShellScreenshotPrivate ShellScreenshotPrivate;
struct _ShellScreenshot struct _ShellScreenshot
@ -32,7 +38,6 @@ struct _ShellScreenshotPrivate
cairo_surface_t *image; cairo_surface_t *image;
cairo_rectangle_int_t screenshot_area; cairo_rectangle_int_t screenshot_area;
gboolean include_cursor;
gboolean include_frame; gboolean include_frame;
}; };
@ -45,10 +50,6 @@ typedef enum
G_DEFINE_TYPE_WITH_PRIVATE (ShellScreenshot, shell_screenshot, G_TYPE_OBJECT); G_DEFINE_TYPE_WITH_PRIVATE (ShellScreenshot, shell_screenshot, G_TYPE_OBJECT);
static void
grab_screenshot (ClutterActor *stage,
GTask *result);
static void static void
shell_screenshot_class_init (ShellScreenshotClass *screenshot_class) shell_screenshot_class_init (ShellScreenshotClass *screenshot_class)
{ {
@ -77,8 +78,6 @@ on_screenshot_written (GObject *source,
g_clear_pointer (&priv->image, cairo_surface_destroy); g_clear_pointer (&priv->image, cairo_surface_destroy);
g_clear_object (&priv->stream); g_clear_object (&priv->stream);
g_clear_pointer (&priv->datetime, g_date_time_unref); g_clear_pointer (&priv->datetime, g_date_time_unref);
meta_enable_unredirect_for_display (shell_global_get_display (priv->global));
} }
static void static void
@ -122,62 +121,48 @@ write_screenshot_thread (GTask *result,
static void static void
do_grab_screenshot (ShellScreenshot *screenshot, do_grab_screenshot (ShellScreenshot *screenshot,
ClutterStage *stage,
int x, int x,
int y, int y,
int width, int width,
int height) int height,
ShellScreenshotFlag flags)
{ {
ShellScreenshotPrivate *priv = screenshot->priv; ShellScreenshotPrivate *priv = screenshot->priv;
ClutterStage *stage = shell_global_get_stage (priv->global);
cairo_rectangle_int_t screenshot_rect = { x, y, width, height }; cairo_rectangle_int_t screenshot_rect = { x, y, width, height };
ClutterCapture *captures; int image_width;
int n_captures; int image_height;
int i; float scale;
cairo_surface_t *image;
if (!clutter_stage_capture (stage, FALSE, ClutterPaintFlag paint_flags = CLUTTER_PAINT_FLAG_NONE;
&screenshot_rect, g_autoptr (GError) error = NULL;
&captures,
&n_captures))
return;
if (n_captures == 1)
priv->image = cairo_surface_reference (captures[0].image);
else
{
float target_scale;
clutter_stage_get_capture_final_size (stage, &screenshot_rect, clutter_stage_get_capture_final_size (stage, &screenshot_rect,
&width, &height, &target_scale); &image_width,
priv->image = shell_util_composite_capture_images (captures, &image_height,
n_captures, &scale);
x, y, image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
width, height, image_width, image_height);
target_scale);
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 (); priv->datetime = g_date_time_new_now_local ();
for (i = 0; i < n_captures; i++)
cairo_surface_destroy (captures[i].image);
g_free (captures);
}
static gboolean
should_draw_cursor_image (ShellScreenshotMode mode)
{
if (mode == SHELL_SCREENSHOT_WINDOW || !meta_is_wayland_compositor ())
{
StSettings *settings = st_settings_get ();
gboolean magnifier_active = FALSE;
g_object_get (settings, "magnifier-active", &magnifier_active, NULL);
if (!magnifier_active)
return TRUE;
}
return FALSE;
} }
static void static void
@ -256,116 +241,37 @@ draw_cursor_image (cairo_surface_t *surface,
} }
static void static void
on_paint (ClutterActor *actor, grab_screenshot (ShellScreenshot *screenshot,
ClutterPaintContext *paint_context, ShellScreenshotFlag flags,
GTask *result)
{
grab_screenshot (actor, result);
}
static void
on_actors_painted (ClutterActor *actor,
GTask *result)
{
grab_screenshot (actor, result);
}
static void
grab_screenshot (ClutterActor *stage,
GTask *result) GTask *result)
{ {
ShellScreenshotPrivate *priv = screenshot->priv;
MetaDisplay *display; MetaDisplay *display;
int width, height; int width, height;
ShellScreenshot *screenshot = g_task_get_source_object (result);
ShellScreenshotPrivate *priv = screenshot->priv;
GTask *task; GTask *task;
display = shell_global_get_display (priv->global); display = shell_global_get_display (priv->global);
meta_display_get_size (display, &width, &height); meta_display_get_size (display, &width, &height);
do_grab_screenshot (screenshot, CLUTTER_STAGE (stage), 0, 0, width, height); do_grab_screenshot (screenshot,
0, 0, width, height,
if (meta_display_get_n_monitors (display) > 1) flags);
{
cairo_region_t *screen_region = cairo_region_create ();
cairo_region_t *stage_region;
MetaRectangle monitor_rect;
cairo_rectangle_int_t stage_rect;
int i;
cairo_t *cr;
for (i = meta_display_get_n_monitors (display) - 1; i >= 0; i--)
{
meta_display_get_monitor_geometry (display, i, &monitor_rect);
cairo_region_union_rectangle (screen_region,
(const cairo_rectangle_int_t *) &monitor_rect);
}
stage_rect.x = 0;
stage_rect.y = 0;
stage_rect.width = width;
stage_rect.height = height;
stage_region = cairo_region_create_rectangle ((const cairo_rectangle_int_t *) &stage_rect);
cairo_region_xor (stage_region, screen_region);
cairo_region_destroy (screen_region);
cr = cairo_create (priv->image);
for (i = 0; i < cairo_region_num_rectangles (stage_region); i++)
{
cairo_rectangle_int_t rect;
cairo_region_get_rectangle (stage_region, i, &rect);
cairo_rectangle (cr, (double) rect.x, (double) rect.y, (double) rect.width, (double) rect.height);
cairo_fill (cr);
}
cairo_destroy (cr);
cairo_region_destroy (stage_region);
}
priv->screenshot_area.x = 0; priv->screenshot_area.x = 0;
priv->screenshot_area.y = 0; priv->screenshot_area.y = 0;
priv->screenshot_area.width = width; priv->screenshot_area.width = width;
priv->screenshot_area.height = height; priv->screenshot_area.height = height;
if (priv->include_cursor)
draw_cursor_image (priv->image, priv->screenshot_area);
g_signal_handlers_disconnect_by_func (stage, on_paint, result);
g_signal_handlers_disconnect_by_func (stage, on_actors_painted, result);
task = g_task_new (screenshot, NULL, on_screenshot_written, result); task = g_task_new (screenshot, NULL, on_screenshot_written, result);
g_task_run_in_thread (task, write_screenshot_thread); g_task_run_in_thread (task, write_screenshot_thread);
g_object_unref (task); g_object_unref (task);
} }
static void static void
grab_area_screenshot (ClutterActor *stage, grab_window_screenshot (ShellScreenshot *screenshot,
ShellScreenshotFlag flags,
GTask *result) GTask *result)
{ {
ShellScreenshot *screenshot = g_task_get_source_object (result);
ShellScreenshotPrivate *priv = screenshot->priv;
GTask *task;
do_grab_screenshot (screenshot,
CLUTTER_STAGE (stage),
priv->screenshot_area.x,
priv->screenshot_area.y,
priv->screenshot_area.width,
priv->screenshot_area.height);
g_signal_handlers_disconnect_by_func (stage, grab_area_screenshot, result);
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 (ClutterActor *stage,
GTask *result)
{
ShellScreenshot *screenshot = g_task_get_source_object (result);
ShellScreenshotPrivate *priv = screenshot->priv; ShellScreenshotPrivate *priv = screenshot->priv;
GTask *task; GTask *task;
MetaDisplay *display = shell_global_get_display (priv->global); MetaDisplay *display = shell_global_get_display (priv->global);
@ -388,7 +294,7 @@ grab_window_screenshot (ClutterActor *stage,
NULL); NULL);
priv->datetime = g_date_time_new_now_local (); priv->datetime = g_date_time_new_now_local ();
if (priv->include_cursor) if (flags & SHELL_SCREENSHOT_FLAG_INCLUDE_CURSOR)
{ {
if (meta_window_get_client_type (window) == META_WINDOW_CLIENT_TYPE_WAYLAND) if (meta_window_get_client_type (window) == META_WINDOW_CLIENT_TYPE_WAYLAND)
{ {
@ -401,33 +307,11 @@ grab_window_screenshot (ClutterActor *stage,
draw_cursor_image (priv->image, priv->screenshot_area); draw_cursor_image (priv->image, priv->screenshot_area);
} }
g_signal_handlers_disconnect_by_func (stage, grab_window_screenshot, result);
task = g_task_new (screenshot, NULL, on_screenshot_written, result); task = g_task_new (screenshot, NULL, on_screenshot_written, result);
g_task_run_in_thread (task, write_screenshot_thread); g_task_run_in_thread (task, write_screenshot_thread);
g_object_unref (task); g_object_unref (task);
} }
static void
grab_pixel (ClutterActor *stage,
GTask *result)
{
ShellScreenshot *screenshot = g_task_get_source_object (result);
ShellScreenshotPrivate *priv = screenshot->priv;
do_grab_screenshot (screenshot,
CLUTTER_STAGE (stage),
priv->screenshot_area.x,
priv->screenshot_area.y,
1,
1);
meta_enable_unredirect_for_display (shell_global_get_display (priv->global));
g_signal_handlers_disconnect_by_func (stage, grab_pixel, result);
g_task_return_boolean (result, TRUE);
g_object_unref (result);
}
static gboolean static gboolean
finish_screenshot (ShellScreenshot *screenshot, finish_screenshot (ShellScreenshot *screenshot,
GAsyncResult *result, GAsyncResult *result,
@ -465,10 +349,9 @@ shell_screenshot_screenshot (ShellScreenshot *screenshot,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
gpointer user_data) gpointer user_data)
{ {
ClutterActor *stage;
ShellScreenshotPrivate *priv; ShellScreenshotPrivate *priv;
gboolean use_paint_signal = FALSE;
GTask *result; GTask *result;
ShellScreenshotFlag flags;
g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot)); g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
@ -492,34 +375,12 @@ shell_screenshot_screenshot (ShellScreenshot *screenshot,
g_task_set_source_tag (result, shell_screenshot_screenshot); g_task_set_source_tag (result, shell_screenshot_screenshot);
priv->stream = g_object_ref (stream); priv->stream = g_object_ref (stream);
priv->include_cursor = FALSE;
stage = CLUTTER_ACTOR (shell_global_get_stage (priv->global));
meta_disable_unredirect_for_display (shell_global_get_display (priv->global));
flags = SHELL_SCREENSHOT_FLAG_NONE;
if (include_cursor) if (include_cursor)
{ flags |= SHELL_SCREENSHOT_FLAG_INCLUDE_CURSOR;
if (should_draw_cursor_image (SHELL_SCREENSHOT_SCREEN))
priv->include_cursor = TRUE;
else
use_paint_signal = TRUE;
}
if (use_paint_signal) grab_screenshot (screenshot, flags, result);
{
g_signal_connect_after (stage, "paint",
G_CALLBACK (on_paint),
result);
}
else
{
g_signal_connect_after (stage, "actors-painted",
G_CALLBACK (on_actors_painted),
result);
}
clutter_actor_queue_redraw (stage);
} }
/** /**
@ -575,9 +436,9 @@ shell_screenshot_screenshot_area (ShellScreenshot *screenshot,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
gpointer user_data) gpointer user_data)
{ {
ClutterActor *stage;
ShellScreenshotPrivate *priv; ShellScreenshotPrivate *priv;
GTask *result; GTask *result;
g_autoptr (GTask) task = NULL;
g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot)); g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
g_return_if_fail (G_IS_OUTPUT_STREAM (stream)); g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
@ -606,13 +467,15 @@ shell_screenshot_screenshot_area (ShellScreenshot *screenshot,
priv->screenshot_area.width = width; priv->screenshot_area.width = width;
priv->screenshot_area.height = height; priv->screenshot_area.height = height;
stage = CLUTTER_ACTOR (shell_global_get_stage (priv->global)); do_grab_screenshot (screenshot,
priv->screenshot_area.x,
priv->screenshot_area.y,
priv->screenshot_area.width,
priv->screenshot_area.height,
SHELL_SCREENSHOT_FLAG_NONE);
meta_disable_unredirect_for_display (shell_global_get_display (shell_global_get ())); task = g_task_new (screenshot, NULL, on_screenshot_written, result);
g_task_run_in_thread (task, write_screenshot_thread);
g_signal_connect_after (stage, "actors-painted", G_CALLBACK (grab_area_screenshot), result);
clutter_actor_queue_redraw (stage);
} }
/** /**
@ -666,7 +529,6 @@ shell_screenshot_screenshot_window (ShellScreenshot *screenshot,
{ {
ShellScreenshotPrivate *priv; ShellScreenshotPrivate *priv;
MetaDisplay *display; MetaDisplay *display;
ClutterActor *stage;
MetaWindow *window; MetaWindow *window;
GTask *result; GTask *result;
@ -695,16 +557,8 @@ shell_screenshot_screenshot_window (ShellScreenshot *screenshot,
priv->stream = g_object_ref (stream); priv->stream = g_object_ref (stream);
priv->include_frame = include_frame; priv->include_frame = include_frame;
priv->include_cursor = include_cursor &&
should_draw_cursor_image (SHELL_SCREENSHOT_WINDOW);
stage = CLUTTER_ACTOR (shell_global_get_stage (priv->global)); grab_window_screenshot (screenshot, include_cursor, result);
meta_disable_unredirect_for_display (shell_global_get_display (shell_global_get ()));
g_signal_connect_after (stage, "actors-painted", G_CALLBACK (grab_window_screenshot), result);
clutter_actor_queue_redraw (stage);
} }
/** /**
@ -753,9 +607,7 @@ shell_screenshot_pick_color (ShellScreenshot *screenshot,
gpointer user_data) gpointer user_data)
{ {
ShellScreenshotPrivate *priv; ShellScreenshotPrivate *priv;
MetaDisplay *display; g_autoptr (GTask) result = NULL;
ClutterActor *stage;
GTask *result;
g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot)); g_return_if_fail (SHELL_IS_SCREENSHOT (screenshot));
@ -769,14 +621,14 @@ shell_screenshot_pick_color (ShellScreenshot *screenshot,
priv->screenshot_area.width = 1; priv->screenshot_area.width = 1;
priv->screenshot_area.height = 1; priv->screenshot_area.height = 1;
display = shell_global_get_display (priv->global); do_grab_screenshot (screenshot,
stage = CLUTTER_ACTOR (shell_global_get_stage (priv->global)); priv->screenshot_area.x,
priv->screenshot_area.y,
1,
1,
SHELL_SCREENSHOT_FLAG_NONE);
meta_disable_unredirect_for_display (display); g_task_return_boolean (result, TRUE);
g_signal_connect_after (stage, "actors-painted", G_CALLBACK (grab_pixel), result);
clutter_actor_queue_redraw (stage);
} }
#if G_BYTE_ORDER == G_LITTLE_ENDIAN #if G_BYTE_ORDER == G_LITTLE_ENDIAN