summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Mikhaylenko <alexm@gnome.org>2020-04-19 01:55:24 +0500
committerAlexander Mikhaylenko <alexm@gnome.org>2020-04-22 13:32:31 +0000
commitfce83df5317085de9f1d06b6e2a9ee9822d8b02e (patch)
tree907223ff857e90790e0b3c7bc569783dd24e63e0
parent67f71e63b9b9dd344382d9b82d4e71915cb9b720 (diff)
downloadgnome-screenshot-fce83df5317085de9f1d06b6e2a9ee9822d8b02e.tar.gz
Split ScreenshotUtils
Introduce ScreenshotBackendShell and ScreenshotBackendX11, move the code as appropriate.
-rw-r--r--src/meson.build2
-rw-r--r--src/screenshot-backend-shell.c128
-rw-r--r--src/screenshot-backend-shell.h33
-rw-r--r--src/screenshot-backend-x11.c570
-rw-r--r--src/screenshot-backend-x11.h33
-rw-r--r--src/screenshot-utils.c601
6 files changed, 780 insertions, 587 deletions
diff --git a/src/meson.build b/src/meson.build
index 6a026c3..b82ab49 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -6,6 +6,8 @@ sources = [
'screenshot-application.c',
'screenshot-area-selection.c',
'screenshot-backend.c',
+ 'screenshot-backend-shell.c',
+ 'screenshot-backend-x11.c',
'screenshot-config.c',
'screenshot-dialog.c',
'screenshot-filename-builder.c',
diff --git a/src/screenshot-backend-shell.c b/src/screenshot-backend-shell.c
new file mode 100644
index 0000000..84c5ead
--- /dev/null
+++ b/src/screenshot-backend-shell.c
@@ -0,0 +1,128 @@
+/* screenshot-backend-shell.c - GNOME Shell backend
+ *
+ * Copyright (C) 2001-2006 Jonathan Blandford <jrb@alum.mit.edu>
+ * Copyright (C) 2008 Cosimo Cecchi <cosimoc@gnome.org>
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ */
+
+#include "config.h"
+
+#include "screenshot-backend-shell.h"
+
+#include "screenshot-config.h"
+
+#include <glib/gstdio.h>
+
+struct _ScreenshotBackendShell
+{
+ GObject parent_instance;
+};
+
+static void screenshot_backend_shell_backend_init (ScreenshotBackendInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (ScreenshotBackendShell, screenshot_backend_shell, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (SCREENSHOT_TYPE_BACKEND, screenshot_backend_shell_backend_init))
+
+static GdkPixbuf *
+screenshot_backend_shell_get_pixbuf (ScreenshotBackend *backend,
+ GdkRectangle *rectangle)
+{
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *path = NULL, *filename = NULL, *tmpname = NULL;
+ GdkPixbuf *screenshot = NULL;
+ const gchar *method_name;
+ GVariant *method_params;
+ GDBusConnection *connection;
+
+ path = g_build_filename (g_get_user_cache_dir (), "gnome-screenshot", NULL);
+ g_mkdir_with_parents (path, 0700);
+
+ tmpname = g_strdup_printf ("scr-%d.png", g_random_int ());
+ filename = g_build_filename (path, tmpname, NULL);
+
+ if (screenshot_config->take_window_shot)
+ {
+ method_name = "ScreenshotWindow";
+ method_params = g_variant_new ("(bbbs)",
+ TRUE,
+ screenshot_config->include_pointer,
+ TRUE, /* flash */
+ filename);
+ }
+ else if (rectangle != NULL)
+ {
+ method_name = "ScreenshotArea";
+ method_params = g_variant_new ("(iiiibs)",
+ rectangle->x, rectangle->y,
+ rectangle->width, rectangle->height,
+ TRUE, /* flash */
+ filename);
+ }
+ else
+ {
+ method_name = "Screenshot";
+ method_params = g_variant_new ("(bbs)",
+ screenshot_config->include_pointer,
+ TRUE, /* flash */
+ filename);
+ }
+
+ connection = g_application_get_dbus_connection (g_application_get_default ());
+ g_dbus_connection_call_sync (connection,
+ "org.gnome.Shell.Screenshot",
+ "/org/gnome/Shell/Screenshot",
+ "org.gnome.Shell.Screenshot",
+ method_name,
+ method_params,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (error == NULL)
+ {
+ screenshot = gdk_pixbuf_new_from_file (filename, &error);
+
+ /* remove the temporary file created by the shell */
+ g_unlink (filename);
+ }
+
+ return screenshot;
+}
+
+static void
+screenshot_backend_shell_class_init (ScreenshotBackendShellClass *klass)
+{
+}
+
+static void
+screenshot_backend_shell_init (ScreenshotBackendShell *self)
+{
+}
+
+static void
+screenshot_backend_shell_backend_init (ScreenshotBackendInterface *iface)
+{
+ iface->get_pixbuf = screenshot_backend_shell_get_pixbuf;
+}
+
+ScreenshotBackend *
+screenshot_backend_shell_new (void)
+{
+ return g_object_new (SCREENSHOT_TYPE_BACKEND_SHELL, NULL);
+}
diff --git a/src/screenshot-backend-shell.h b/src/screenshot-backend-shell.h
new file mode 100644
index 0000000..4d86898
--- /dev/null
+++ b/src/screenshot-backend-shell.h
@@ -0,0 +1,33 @@
+/* screenshot-backend-shell.h - GNOME Shell backend
+ *
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include "screenshot-backend.h"
+
+G_BEGIN_DECLS
+
+#define SCREENSHOT_TYPE_BACKEND_SHELL (screenshot_backend_shell_get_type())
+
+G_DECLARE_FINAL_TYPE (ScreenshotBackendShell, screenshot_backend_shell, SCREENSHOT, BACKEND_SHELL, GObject)
+
+ScreenshotBackend *screenshot_backend_shell_new (void);
+
+G_END_DECLS
diff --git a/src/screenshot-backend-x11.c b/src/screenshot-backend-x11.c
new file mode 100644
index 0000000..b49a383
--- /dev/null
+++ b/src/screenshot-backend-x11.c
@@ -0,0 +1,570 @@
+/* screenshot-backend-x11.c - Fallback X11 backend
+ *
+ * Copyright (C) 2001-2006 Jonathan Blandford <jrb@alum.mit.edu>
+ * Copyright (C) 2008 Cosimo Cecchi <cosimoc@gnome.org>
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ */
+
+#include "config.h"
+
+#ifdef HAVE_X11
+
+#include "screenshot-backend-x11.h"
+
+#include "screenshot-config.h"
+
+#include "cheese-flash.h"
+
+#include <gdk/gdkx.h>
+
+#ifdef HAVE_X11_EXTENSIONS_SHAPE_H
+#include <X11/extensions/shape.h>
+#endif
+
+struct _ScreenshotBackendX11
+{
+ GObject parent_instance;
+};
+
+static void screenshot_backend_x11_backend_init (ScreenshotBackendInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (ScreenshotBackendX11, screenshot_backend_x11, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (SCREENSHOT_TYPE_BACKEND, screenshot_backend_x11_backend_init))
+
+static GdkWindow *
+screenshot_find_active_window (void)
+{
+ GdkWindow *window;
+ GdkScreen *default_screen;
+
+ default_screen = gdk_screen_get_default ();
+ window = gdk_screen_get_active_window (default_screen);
+
+ return window;
+}
+
+static gboolean
+screenshot_window_is_desktop (GdkWindow *window)
+{
+ GdkWindow *root_window = gdk_get_default_root_window ();
+ GdkWindowTypeHint window_type_hint;
+
+ if (window == root_window)
+ return TRUE;
+
+ window_type_hint = gdk_window_get_type_hint (window);
+ if (window_type_hint == GDK_WINDOW_TYPE_HINT_DESKTOP)
+ return TRUE;
+
+ return FALSE;
+}
+
+static Window
+find_wm_window (GdkWindow *window)
+{
+ Window xid, root, parent, *children;
+ unsigned int nchildren;
+
+ if (window == gdk_get_default_root_window ())
+ return None;
+
+ xid = GDK_WINDOW_XID (window);
+
+ do
+ {
+ if (XQueryTree (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ xid, &root, &parent, &children, &nchildren) == 0)
+ {
+ g_warning ("Couldn't find window manager window");
+ return None;
+ }
+
+ if (root == parent)
+ return xid;
+
+ xid = parent;
+ }
+ while (TRUE);
+}
+
+static cairo_region_t *
+make_region_with_monitors (GdkScreen *screen)
+{
+ cairo_region_t *region;
+ int num_monitors;
+ int i;
+
+ num_monitors = gdk_screen_get_n_monitors (screen);
+
+ region = cairo_region_create ();
+
+ for (i = 0; i < num_monitors; i++)
+ {
+ GdkRectangle rect;
+
+ gdk_screen_get_monitor_geometry (screen, i, &rect);
+ cairo_region_union_rectangle (region, &rect);
+ }
+
+ return region;
+}
+
+static void
+blank_rectangle_in_pixbuf (GdkPixbuf *pixbuf, GdkRectangle *rect)
+{
+ int x, y;
+ int x2, y2;
+ guchar *pixels;
+ int rowstride;
+ int n_channels;
+ guchar *row;
+ gboolean has_alpha;
+
+ g_assert (gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB);
+
+ x2 = rect->x + rect->width;
+ y2 = rect->y + rect->height;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
+ n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+ for (y = rect->y; y < y2; y++)
+ {
+ guchar *p;
+
+ row = pixels + y * rowstride;
+ p = row + rect->x * n_channels;
+
+ for (x = rect->x; x < x2; x++)
+ {
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+
+ if (has_alpha)
+ *p++ = 255; /* opaque black */
+ }
+ }
+}
+
+static void
+blank_region_in_pixbuf (GdkPixbuf *pixbuf, cairo_region_t *region)
+{
+ int n_rects;
+ int i;
+ int width, height;
+ cairo_rectangle_int_t pixbuf_rect;
+
+ n_rects = cairo_region_num_rectangles (region);
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ pixbuf_rect.x = 0;
+ pixbuf_rect.y = 0;
+ pixbuf_rect.width = width;
+ pixbuf_rect.height = height;
+
+ for (i = 0; i < n_rects; i++)
+ {
+ cairo_rectangle_int_t rect, dest;
+
+ cairo_region_get_rectangle (region, i, &rect);
+ if (gdk_rectangle_intersect (&rect, &pixbuf_rect, &dest))
+ blank_rectangle_in_pixbuf (pixbuf, &dest);
+ }
+}
+
+/* When there are multiple monitors with different resolutions, the visible area
+ * within the root window may not be rectangular (it may have an L-shape, for
+ * example). In that case, mask out the areas of the root window which would
+ * not be visible in the monitors, so that screenshot do not end up with content
+ * that the user won't ever see.
+ */
+static void
+mask_monitors (GdkPixbuf *pixbuf, GdkWindow *root_window)
+{
+ GdkScreen *screen;
+ cairo_region_t *region_with_monitors;
+ cairo_region_t *invisible_region;
+ cairo_rectangle_int_t rect;
+
+ screen = gdk_window_get_screen (root_window);
+
+ region_with_monitors = make_region_with_monitors (screen);
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = gdk_screen_get_width (screen);
+ rect.height = gdk_screen_get_height (screen);
+
+ invisible_region = cairo_region_create_rectangle (&rect);
+ cairo_region_subtract (invisible_region, region_with_monitors);
+
+ blank_region_in_pixbuf (pixbuf, invisible_region);
+
+ cairo_region_destroy (region_with_monitors);
+ cairo_region_destroy (invisible_region);
+}
+
+static void
+screenshot_fallback_get_window_rect_coords (GdkWindow *window,
+ GdkRectangle *real_coordinates_out,
+ GdkRectangle *screenshot_coordinates_out)
+{
+ gint x_orig, y_orig;
+ gint width, height;
+ GdkRectangle real_coordinates;
+
+ gdk_window_get_frame_extents (window, &real_coordinates);
+
+ x_orig = real_coordinates.x;
+ y_orig = real_coordinates.y;
+ width = real_coordinates.width;
+ height = real_coordinates.height;
+
+ if (real_coordinates_out != NULL)
+ *real_coordinates_out = real_coordinates;
+
+ if (x_orig < 0)
+ {
+ width = width + x_orig;
+ x_orig = 0;
+ }
+
+ if (y_orig < 0)
+ {
+ height = height + y_orig;
+ y_orig = 0;
+ }
+
+ if (x_orig + width > gdk_screen_width ())
+ width = gdk_screen_width () - x_orig;
+
+ if (y_orig + height > gdk_screen_height ())
+ height = gdk_screen_height () - y_orig;
+
+ if (screenshot_coordinates_out != NULL)
+ {
+ screenshot_coordinates_out->x = x_orig;
+ screenshot_coordinates_out->y = y_orig;
+ screenshot_coordinates_out->width = width;
+ screenshot_coordinates_out->height = height;
+ }
+}
+
+static void
+screenshot_fallback_fire_flash (GdkWindow *window,
+ GdkRectangle *rectangle)
+{
+ GdkRectangle rect;
+ CheeseFlash *flash = NULL;
+
+ if (rectangle != NULL)
+ rect = *rectangle;
+ else
+ screenshot_fallback_get_window_rect_coords (window, NULL, &rect);
+
+ flash = cheese_flash_new ();
+ cheese_flash_fire (flash, &rect);
+}
+
+GdkWindow *
+do_find_current_window (void)
+{
+ GdkWindow *current_window;
+ GdkDeviceManager *manager;
+ GdkDevice *device;
+
+ current_window = screenshot_find_active_window ();
+ manager = gdk_display_get_device_manager (gdk_display_get_default ());
+ device = gdk_device_manager_get_client_pointer (manager);
+
+ /* If there's no active window, we fall back to returning the
+ * window that the cursor is in.
+ */
+ if (!current_window)
+ current_window = gdk_device_get_window_at_position (device, NULL, NULL);
+
+ if (current_window)
+ {
+ if (screenshot_window_is_desktop (current_window))
+ /* if the current window is the desktop (e.g. nautilus), we
+ * return NULL, as getting the whole screen makes more sense.
+ */
+ return NULL;
+
+ /* Once we have a window, we take the toplevel ancestor. */
+ current_window = gdk_window_get_toplevel (current_window);
+ }
+
+ return current_window;
+}
+
+static GdkWindow *
+screenshot_fallback_find_current_window (void)
+{
+ GdkWindow *window = NULL;
+
+ if (screenshot_config->take_window_shot)
+ {
+ window = do_find_current_window ();
+
+ if (window == NULL)
+ screenshot_config->take_window_shot = FALSE;
+ }
+
+ if (window == NULL)
+ window = gdk_get_default_root_window ();
+
+ return window;
+}
+
+static GdkPixbuf *
+screenshot_backend_x11_get_pixbuf (ScreenshotBackend *backend,
+ GdkRectangle *rectangle)
+{
+ GdkWindow *root, *wm_window = NULL;
+ GdkPixbuf *screenshot = NULL;
+ GdkRectangle real_coords, screenshot_coords;
+ Window wm;
+ GtkBorder frame_offset = { 0, 0, 0, 0 };
+ GdkWindow *window;
+
+ window = screenshot_fallback_find_current_window ();
+
+ screenshot_fallback_get_window_rect_coords (window,
+ &real_coords,
+ &screenshot_coords);
+
+ wm = find_wm_window (window);
+ if (wm != None)
+ {
+ GdkRectangle wm_real_coords;
+
+ wm_window = gdk_x11_window_foreign_new_for_display
+ (gdk_window_get_display (window), wm);
+
+ screenshot_fallback_get_window_rect_coords (wm_window,
+ &wm_real_coords,
+ NULL);
+
+ frame_offset.left = (gdouble) (real_coords.x - wm_real_coords.x);
+ frame_offset.top = (gdouble) (real_coords.y - wm_real_coords.y);
+ frame_offset.right = (gdouble) (wm_real_coords.width - real_coords.width - frame_offset.left);
+ frame_offset.bottom = (gdouble) (wm_real_coords.height - real_coords.height - frame_offset.top);
+ }
+
+ if (rectangle)
+ {
+ screenshot_coords.x = rectangle->x - screenshot_coords.x;
+ screenshot_coords.y = rectangle->y - screenshot_coords.y;
+ screenshot_coords.width = rectangle->width;
+ screenshot_coords.height = rectangle->height;
+ }
+
+ root = gdk_get_default_root_window ();
+ screenshot = gdk_pixbuf_get_from_window (root,
+ screenshot_coords.x, screenshot_coords.y,
+ screenshot_coords.width, screenshot_coords.height);
+
+ if (!screenshot_config->take_window_shot &&
+ !screenshot_config->take_area_shot)
+ mask_monitors (screenshot, root);
+
+#ifdef HAVE_X11_EXTENSIONS_SHAPE_H
+ if (wm != None)
+ {
+ XRectangle *rectangles;
+ int rectangle_count, rectangle_order, i;
+
+ /* we must use XShape to avoid showing what's under the rounder corners
+ * of the WM decoration.
+ */
+ rectangles = XShapeGetRectangles (GDK_DISPLAY_XDISPLAY (gdk_display_get_default()),
+ wm,
+ ShapeBounding,
+ &rectangle_count,
+ &rectangle_order);
+ if (rectangles && rectangle_count > 0)
+ {
+ int scale_factor = gdk_window_get_scale_factor (wm_window);
+ gboolean has_alpha = gdk_pixbuf_get_has_alpha (screenshot);
+ GdkPixbuf *tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ gdk_pixbuf_get_width (screenshot),
+ gdk_pixbuf_get_height (screenshot));
+ gdk_pixbuf_fill (tmp, 0);
+
+ for (i = 0; i < rectangle_count; i++)
+ {
+ gint rec_x, rec_y;
+ gint rec_width, rec_height;
+ gint y;
+
+ /* If we're using invisible borders, the ShapeBounding might not
+ * have the same size as the frame extents, as it would include the
+ * areas for the invisible borders themselves.
+ * In that case, trim every rectangle we get by the offset between the
+ * WM window size and the frame extents.
+ *
+ * Note that the XShape values are in actual pixels, whereas the GDK
+ * ones are in display pixels (i.e. scaled), so we need to apply the
+ * scale factor to the former to use display pixels for all our math.
+ */
+ rec_x = rectangles[i].x / scale_factor;
+ rec_y = rectangles[i].y / scale_factor;
+ rec_width = rectangles[i].width / scale_factor - (frame_offset.left + frame_offset.right);
+ rec_height = rectangles[i].height / scale_factor - (frame_offset.top + frame_offset.bottom);
+
+ if (real_coords.x < 0)
+ {
+ rec_x += real_coords.x;
+ rec_x = MAX(rec_x, 0);
+ rec_width += real_coords.x;
+ }
+
+ if (real_coords.y < 0)
+ {
+ rec_y += real_coords.y;
+ rec_y = MAX(rec_y, 0);
+ rec_height += real_coords.y;
+ }
+
+ if (screenshot_coords.x + rec_x + rec_width > gdk_screen_width ())
+ rec_width = gdk_screen_width () - screenshot_coords.x - rec_x;
+
+ if (screenshot_coords.y + rec_y + rec_height > gdk_screen_height ())
+ rec_height = gdk_screen_height () - screenshot_coords.y - rec_y;
+
+ /* Undo the scale factor in order to copy the pixbuf data pixel-wise */
+ for (y = rec_y * scale_factor; y < (rec_y + rec_height) * scale_factor; y++)
+ {
+ guchar *src_pixels, *dest_pixels;
+ gint x;
+
+ src_pixels = gdk_pixbuf_get_pixels (screenshot)
+ + y * gdk_pixbuf_get_rowstride(screenshot)
+ + rec_x * scale_factor * (has_alpha ? 4 : 3);
+ dest_pixels = gdk_pixbuf_get_pixels (tmp)
+ + y * gdk_pixbuf_get_rowstride (tmp)
+ + rec_x * scale_factor * 4;
+
+ for (x = 0; x < rec_width * scale_factor; x++)
+ {
+ *dest_pixels++ = *src_pixels++;
+ *dest_pixels++ = *src_pixels++;
+ *dest_pixels++ = *src_pixels++;
+
+ if (has_alpha)
+ *dest_pixels++ = *src_pixels++;
+ else
+ *dest_pixels++ = 255;
+ }
+ }
+ }
+
+ g_set_object (&screenshot, tmp);
+
+ XFree (rectangles);
+ }
+ }
+#endif /* HAVE_X11_EXTENSIONS_SHAPE_H */
+
+ /* if we have a selected area, there were by definition no cursor in the
+ * screenshot */
+ if (screenshot_config->include_pointer && !rectangle)
+ {
+ g_autoptr(GdkCursor) cursor = NULL;
+ g_autoptr(GdkPixbuf) cursor_pixbuf = NULL;
+
+ cursor = gdk_cursor_new_for_display (gdk_display_get_default (), GDK_LEFT_PTR);
+ cursor_pixbuf = gdk_cursor_get_image (cursor);
+
+ if (cursor_pixbuf != NULL)
+ {
+ GdkDeviceManager *manager;
+ GdkDevice *device;
+ GdkRectangle rect;
+ gint cx, cy, xhot, yhot;
+
+ manager = gdk_display_get_device_manager (gdk_display_get_default ());
+ device = gdk_device_manager_get_client_pointer (manager);
+
+ if (wm_window != NULL)
+ gdk_window_get_device_position (wm_window, device,
+ &cx, &cy, NULL);
+ else
+ gdk_window_get_device_position (window, device,
+ &cx, &cy, NULL);
+
+ sscanf (gdk_pixbuf_get_option (cursor_pixbuf, "x_hot"), "%d", &xhot);
+ sscanf (gdk_pixbuf_get_option (cursor_pixbuf, "y_hot"), "%d", &yhot);
+
+ /* in rect we have the cursor window coordinates */
+ rect.x = cx + real_coords.x;
+ rect.y = cy + real_coords.y;
+ rect.width = gdk_pixbuf_get_width (cursor_pixbuf);
+ rect.height = gdk_pixbuf_get_height (cursor_pixbuf);
+
+ /* see if the pointer is inside the window */
+ if (gdk_rectangle_intersect (&real_coords, &rect, &rect))
+ {
+ gint cursor_x, cursor_y;
+
+ cursor_x = cx - xhot - frame_offset.left;
+ cursor_y = cy - yhot - frame_offset.top;
+ gdk_pixbuf_composite (cursor_pixbuf, screenshot,
+ cursor_x, cursor_y,
+ rect.width, rect.height,
+ cursor_x, cursor_y,
+ 1.0, 1.0,
+ GDK_INTERP_BILINEAR,
+ 255);
+ }
+ }
+ }
+
+ screenshot_fallback_fire_flash (window, rectangle);
+
+ return screenshot;
+}
+
+static void
+screenshot_backend_x11_class_init (ScreenshotBackendX11Class *klass)
+{
+}
+
+static void
+screenshot_backend_x11_init (ScreenshotBackendX11 *self)
+{
+}
+
+static void
+screenshot_backend_x11_backend_init (ScreenshotBackendInterface *iface)
+{
+ iface->get_pixbuf = screenshot_backend_x11_get_pixbuf;
+}
+
+ScreenshotBackend *
+screenshot_backend_x11_new (void)
+{
+ return g_object_new (SCREENSHOT_TYPE_BACKEND_X11, NULL);
+}
+
+#endif /* HAVE_X11 */
diff --git a/src/screenshot-backend-x11.h b/src/screenshot-backend-x11.h
new file mode 100644
index 0000000..3b52b1e
--- /dev/null
+++ b/src/screenshot-backend-x11.h
@@ -0,0 +1,33 @@
+/* screenshot-backend-x11.h - Fallback X11 backend
+ *
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include "screenshot-backend.h"
+
+G_BEGIN_DECLS
+
+#define SCREENSHOT_TYPE_BACKEND_X11 (screenshot_backend_x11_get_type())
+
+G_DECLARE_FINAL_TYPE (ScreenshotBackendX11, screenshot_backend_x11, SCREENSHOT, BACKEND_X11, GObject)
+
+ScreenshotBackend *screenshot_backend_x11_new (void);
+
+G_END_DECLS
diff --git a/src/screenshot-utils.c b/src/screenshot-utils.c
index f8dab9b..26ca026 100644
--- a/src/screenshot-utils.c
+++ b/src/screenshot-utils.c
@@ -20,254 +20,18 @@
#include "config.h"
-#include <gdk/gdkkeysyms.h>
-
-#if HAVE_X11
-#include <gdk/gdkx.h>
-#endif
+#include "screenshot-utils.h"
#include <gtk/gtk.h>
#include <glib.h>
#include <glib/gi18n.h>
-#include <glib/gstdio.h>
#include <canberra-gtk.h>
-#include <stdlib.h>
-
-#ifdef HAVE_X11_EXTENSIONS_SHAPE_H
-#include <X11/extensions/shape.h>
-#endif
-
-#include "cheese-flash.h"
-#include "screenshot-application.h"
-#include "screenshot-config.h"
-#include "screenshot-utils.h"
+#include "screenshot-backend-shell.h"
#ifdef HAVE_X11
-static GdkWindow *
-screenshot_find_active_window (void)
-{
- GdkWindow *window;
- GdkScreen *default_screen;
-
- default_screen = gdk_screen_get_default ();
- window = gdk_screen_get_active_window (default_screen);
-
- return window;
-}
-
-static gboolean
-screenshot_window_is_desktop (GdkWindow *window)
-{
- GdkWindow *root_window = gdk_get_default_root_window ();
- GdkWindowTypeHint window_type_hint;
-
- if (window == root_window)
- return TRUE;
-
- window_type_hint = gdk_window_get_type_hint (window);
- if (window_type_hint == GDK_WINDOW_TYPE_HINT_DESKTOP)
- return TRUE;
-
- return FALSE;
-}
-
-static Window
-find_wm_window (GdkWindow *window)
-{
- Window xid, root, parent, *children;
- unsigned int nchildren;
-
- if (window == gdk_get_default_root_window ())
- return None;
-
- xid = GDK_WINDOW_XID (window);
-
- do
- {
- if (XQueryTree (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
- xid, &root, &parent, &children, &nchildren) == 0)
- {
- g_warning ("Couldn't find window manager window");
- return None;
- }
-
- if (root == parent)
- return xid;
-
- xid = parent;
- }
- while (TRUE);
-}
-
-static cairo_region_t *
-make_region_with_monitors (GdkScreen *screen)
-{
- cairo_region_t *region;
- int num_monitors;
- int i;
-
- num_monitors = gdk_screen_get_n_monitors (screen);
-
- region = cairo_region_create ();
-
- for (i = 0; i < num_monitors; i++)
- {
- GdkRectangle rect;
-
- gdk_screen_get_monitor_geometry (screen, i, &rect);
- cairo_region_union_rectangle (region, &rect);
- }
-
- return region;
-}
-
-static void
-blank_rectangle_in_pixbuf (GdkPixbuf *pixbuf, GdkRectangle *rect)
-{
- int x, y;
- int x2, y2;
- guchar *pixels;
- int rowstride;
- int n_channels;
- guchar *row;
- gboolean has_alpha;
-
- g_assert (gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB);
-
- x2 = rect->x + rect->width;
- y2 = rect->y + rect->height;
-
- pixels = gdk_pixbuf_get_pixels (pixbuf);
- rowstride = gdk_pixbuf_get_rowstride (pixbuf);
- has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
- n_channels = gdk_pixbuf_get_n_channels (pixbuf);
-
- for (y = rect->y; y < y2; y++)
- {
- guchar *p;
-
- row = pixels + y * rowstride;
- p = row + rect->x * n_channels;
-
- for (x = rect->x; x < x2; x++)
- {
- *p++ = 0;
- *p++ = 0;
- *p++ = 0;
-
- if (has_alpha)
- *p++ = 255; /* opaque black */
- }
- }
-}
-
-static void
-blank_region_in_pixbuf (GdkPixbuf *pixbuf, cairo_region_t *region)
-{
- int n_rects;
- int i;
- int width, height;
- cairo_rectangle_int_t pixbuf_rect;
-
- n_rects = cairo_region_num_rectangles (region);
-
- width = gdk_pixbuf_get_width (pixbuf);
- height = gdk_pixbuf_get_height (pixbuf);
-
- pixbuf_rect.x = 0;
- pixbuf_rect.y = 0;
- pixbuf_rect.width = width;
- pixbuf_rect.height = height;
-
- for (i = 0; i < n_rects; i++)
- {
- cairo_rectangle_int_t rect, dest;
-
- cairo_region_get_rectangle (region, i, &rect);
- if (gdk_rectangle_intersect (&rect, &pixbuf_rect, &dest))
- blank_rectangle_in_pixbuf (pixbuf, &dest);
- }
-}
-
-/* When there are multiple monitors with different resolutions, the visible area
- * within the root window may not be rectangular (it may have an L-shape, for
- * example). In that case, mask out the areas of the root window which would
- * not be visible in the monitors, so that screenshot do not end up with content
- * that the user won't ever see.
- */
-static void
-mask_monitors (GdkPixbuf *pixbuf, GdkWindow *root_window)
-{
- GdkScreen *screen;
- cairo_region_t *region_with_monitors;
- cairo_region_t *invisible_region;
- cairo_rectangle_int_t rect;
-
- screen = gdk_window_get_screen (root_window);
-
- region_with_monitors = make_region_with_monitors (screen);
-
- rect.x = 0;
- rect.y = 0;
- rect.width = gdk_screen_get_width (screen);
- rect.height = gdk_screen_get_height (screen);
-
- invisible_region = cairo_region_create_rectangle (&rect);
- cairo_region_subtract (invisible_region, region_with_monitors);
-
- blank_region_in_pixbuf (pixbuf, invisible_region);
-
- cairo_region_destroy (region_with_monitors);
- cairo_region_destroy (invisible_region);
-}
-
-static void
-screenshot_fallback_get_window_rect_coords (GdkWindow *window,
- GdkRectangle *real_coordinates_out,
- GdkRectangle *screenshot_coordinates_out)
-{
- gint x_orig, y_orig;
- gint width, height;
- GdkRectangle real_coordinates;
-
- gdk_window_get_frame_extents (window, &real_coordinates);
-
- x_orig = real_coordinates.x;
- y_orig = real_coordinates.y;
- width = real_coordinates.width;
- height = real_coordinates.height;
-
- if (real_coordinates_out != NULL)
- *real_coordinates_out = real_coordinates;
-
- if (x_orig < 0)
- {
- width = width + x_orig;
- x_orig = 0;
- }
-
- if (y_orig < 0)
- {
- height = height + y_orig;
- y_orig = 0;
- }
-
- if (x_orig + width > gdk_screen_width ())
- width = gdk_screen_width () - x_orig;
-
- if (y_orig + height > gdk_screen_height ())
- height = gdk_screen_height () - y_orig;
-
- if (screenshot_coordinates_out != NULL)
- {
- screenshot_coordinates_out->x = x_orig;
- screenshot_coordinates_out->y = y_orig;
- screenshot_coordinates_out->width = width;
- screenshot_coordinates_out->height = height;
- }
-}
-#endif /* HAVE_X11 */
+#include "screenshot-backend-x11.h"
+#endif
void
screenshot_play_sound_effect (const gchar *event_id,
@@ -302,362 +66,21 @@ screenshot_play_sound_effect (const gchar *event_id,
ca_proplist_destroy (p);
}
-#ifdef HAVE_X11
-static void
-screenshot_fallback_fire_flash (GdkWindow *window,
- GdkRectangle *rectangle)
-{
- GdkRectangle rect;
- CheeseFlash *flash = NULL;
-
- if (rectangle != NULL)
- rect = *rectangle;
- else
- screenshot_fallback_get_window_rect_coords (window, NULL, &rect);
-
- flash = cheese_flash_new ();
- cheese_flash_fire (flash, &rect);
-}
-
-GdkWindow *
-do_find_current_window (void)
-{
- GdkWindow *current_window;
- GdkDeviceManager *manager;
- GdkDevice *device;
-
- current_window = screenshot_find_active_window ();
- manager = gdk_display_get_device_manager (gdk_display_get_default ());
- device = gdk_device_manager_get_client_pointer (manager);
-
- /* If there's no active window, we fall back to returning the
- * window that the cursor is in.
- */
- if (!current_window)
- current_window = gdk_device_get_window_at_position (device, NULL, NULL);
-
- if (current_window)
- {
- if (screenshot_window_is_desktop (current_window))
- /* if the current window is the desktop (e.g. nautilus), we
- * return NULL, as getting the whole screen makes more sense.
- */
- return NULL;
-
- /* Once we have a window, we take the toplevel ancestor. */
- current_window = gdk_window_get_toplevel (current_window);
- }
-
- return current_window;
-}
-
-static GdkWindow *
-screenshot_fallback_find_current_window (void)
-{
- GdkWindow *window = NULL;
-
- if (screenshot_config->take_window_shot)
- {
- window = do_find_current_window ();
-
- if (window == NULL)
- screenshot_config->take_window_shot = FALSE;
- }
-
- if (window == NULL)
- window = gdk_get_default_root_window ();
-
- return window;
-}
-
-static GdkPixbuf *
-screenshot_fallback_get_pixbuf (GdkRectangle *rectangle)
-{
- GdkWindow *root, *wm_window = NULL;
- GdkPixbuf *screenshot = NULL;
- GdkRectangle real_coords, screenshot_coords;
- Window wm;
- GtkBorder frame_offset = { 0, 0, 0, 0 };
- GdkWindow *window;
-
- window = screenshot_fallback_find_current_window ();
-
- screenshot_fallback_get_window_rect_coords (window,
- &real_coords,
- &screenshot_coords);
-
- wm = find_wm_window (window);
- if (wm != None)
- {
- GdkRectangle wm_real_coords;
-
- wm_window = gdk_x11_window_foreign_new_for_display
- (gdk_window_get_display (window), wm);
-
- screenshot_fallback_get_window_rect_coords (wm_window,
- &wm_real_coords,
- NULL);
-
- frame_offset.left = (gdouble) (real_coords.x - wm_real_coords.x);
- frame_offset.top = (gdouble) (real_coords.y - wm_real_coords.y);
- frame_offset.right = (gdouble) (wm_real_coords.width - real_coords.width - frame_offset.left);
- frame_offset.bottom = (gdouble) (wm_real_coords.height - real_coords.height - frame_offset.top);
- }
-
- if (rectangle)
- {
- screenshot_coords.x = rectangle->x - screenshot_coords.x;
- screenshot_coords.y = rectangle->y - screenshot_coords.y;
- screenshot_coords.width = rectangle->width;
- screenshot_coords.height = rectangle->height;
- }
-
- root = gdk_get_default_root_window ();
- screenshot = gdk_pixbuf_get_from_window (root,
- screenshot_coords.x, screenshot_coords.y,
- screenshot_coords.width, screenshot_coords.height);
-
- if (!screenshot_config->take_window_shot &&
- !screenshot_config->take_area_shot)
- mask_monitors (screenshot, root);
-
-#ifdef HAVE_X11_EXTENSIONS_SHAPE_H
- if (wm != None)
- {
- XRectangle *rectangles;
- int rectangle_count, rectangle_order, i;
-
- /* we must use XShape to avoid showing what's under the rounder corners
- * of the WM decoration.
- */
- rectangles = XShapeGetRectangles (GDK_DISPLAY_XDISPLAY (gdk_display_get_default()),
- wm,
- ShapeBounding,
- &rectangle_count,
- &rectangle_order);
- if (rectangles && rectangle_count > 0)
- {
- int scale_factor = gdk_window_get_scale_factor (wm_window);
- gboolean has_alpha = gdk_pixbuf_get_has_alpha (screenshot);
- GdkPixbuf *tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
- gdk_pixbuf_get_width (screenshot),
- gdk_pixbuf_get_height (screenshot));
- gdk_pixbuf_fill (tmp, 0);
-
- for (i = 0; i < rectangle_count; i++)
- {
- gint rec_x, rec_y;
- gint rec_width, rec_height;
- gint y;
-
- /* If we're using invisible borders, the ShapeBounding might not
- * have the same size as the frame extents, as it would include the
- * areas for the invisible borders themselves.
- * In that case, trim every rectangle we get by the offset between the
- * WM window size and the frame extents.
- *
- * Note that the XShape values are in actual pixels, whereas the GDK
- * ones are in display pixels (i.e. scaled), so we need to apply the
- * scale factor to the former to use display pixels for all our math.
- */
- rec_x = rectangles[i].x / scale_factor;
- rec_y = rectangles[i].y / scale_factor;
- rec_width = rectangles[i].width / scale_factor - (frame_offset.left + frame_offset.right);
- rec_height = rectangles[i].height / scale_factor - (frame_offset.top + frame_offset.bottom);
-
- if (real_coords.x < 0)
- {
- rec_x += real_coords.x;
- rec_x = MAX(rec_x, 0);
- rec_width += real_coords.x;
- }
-
- if (real_coords.y < 0)
- {
- rec_y += real_coords.y;
- rec_y = MAX(rec_y, 0);
- rec_height += real_coords.y;
- }
-
- if (screenshot_coords.x + rec_x + rec_width > gdk_screen_width ())
- rec_width = gdk_screen_width () - screenshot_coords.x - rec_x;
-
- if (screenshot_coords.y + rec_y + rec_height > gdk_screen_height ())
- rec_height = gdk_screen_height () - screenshot_coords.y - rec_y;
-
- /* Undo the scale factor in order to copy the pixbuf data pixel-wise */
- for (y = rec_y * scale_factor; y < (rec_y + rec_height) * scale_factor; y++)
- {
- guchar *src_pixels, *dest_pixels;
- gint x;
-
- src_pixels = gdk_pixbuf_get_pixels (screenshot)
- + y * gdk_pixbuf_get_rowstride(screenshot)
- + rec_x * scale_factor * (has_alpha ? 4 : 3);
- dest_pixels = gdk_pixbuf_get_pixels (tmp)
- + y * gdk_pixbuf_get_rowstride (tmp)
- + rec_x * scale_factor * 4;
-
- for (x = 0; x < rec_width * scale_factor; x++)
- {
- *dest_pixels++ = *src_pixels++;
- *dest_pixels++ = *src_pixels++;
- *dest_pixels++ = *src_pixels++;
-
- if (has_alpha)
- *dest_pixels++ = *src_pixels++;
- else
- *dest_pixels++ = 255;
- }
- }
- }
-
- g_set_object (&screenshot, tmp);
-
- XFree (rectangles);
- }
- }
-#endif /* HAVE_X11_EXTENSIONS_SHAPE_H */
-
- /* if we have a selected area, there were by definition no cursor in the
- * screenshot */
- if (screenshot_config->include_pointer && !rectangle)
- {
- g_autoptr(GdkCursor) cursor = NULL;
- g_autoptr(GdkPixbuf) cursor_pixbuf = NULL;
-
- cursor = gdk_cursor_new_for_display (gdk_display_get_default (), GDK_LEFT_PTR);
- cursor_pixbuf = gdk_cursor_get_image (cursor);
-
- if (cursor_pixbuf != NULL)
- {
- GdkDeviceManager *manager;
- GdkDevice *device;
- GdkRectangle rect;
- gint cx, cy, xhot, yhot;
-
- manager = gdk_display_get_device_manager (gdk_display_get_default ());
- device = gdk_device_manager_get_client_pointer (manager);
-
- if (wm_window != NULL)
- gdk_window_get_device_position (wm_window, device,
- &cx, &cy, NULL);
- else
- gdk_window_get_device_position (window, device,
- &cx, &cy, NULL);
-
- sscanf (gdk_pixbuf_get_option (cursor_pixbuf, "x_hot"), "%d", &xhot);
- sscanf (gdk_pixbuf_get_option (cursor_pixbuf, "y_hot"), "%d", &yhot);
-
- /* in rect we have the cursor window coordinates */
- rect.x = cx + real_coords.x;
- rect.y = cy + real_coords.y;
- rect.width = gdk_pixbuf_get_width (cursor_pixbuf);
- rect.height = gdk_pixbuf_get_height (cursor_pixbuf);
-
- /* see if the pointer is inside the window */
- if (gdk_rectangle_intersect (&real_coords, &rect, &rect))
- {
- gint cursor_x, cursor_y;
-
- cursor_x = cx - xhot - frame_offset.left;
- cursor_y = cy - yhot - frame_offset.top;
- gdk_pixbuf_composite (cursor_pixbuf, screenshot,
- cursor_x, cursor_y,
- rect.width, rect.height,
- cursor_x, cursor_y,
- 1.0, 1.0,
- GDK_INTERP_BILINEAR,
- 255);
- }
- }
- }
-
- screenshot_fallback_fire_flash (window, rectangle);
-
- return screenshot;
-}
-#endif /* HAVE_X11 */
-
-static GdkPixbuf *
-screenshot_shell_get_pixbuf (GdkRectangle *rectangle)
-{
- g_autoptr(GError) error = NULL;
- g_autofree gchar *path = NULL, *filename = NULL, *tmpname = NULL;
- GdkPixbuf *screenshot = NULL;
- const gchar *method_name;
- GVariant *method_params;
- GDBusConnection *connection;
-
- path = g_build_filename (g_get_user_cache_dir (), "gnome-screenshot", NULL);
- g_mkdir_with_parents (path, 0700);
-
- tmpname = g_strdup_printf ("scr-%d.png", g_random_int ());
- filename = g_build_filename (path, tmpname, NULL);
-
- if (screenshot_config->take_window_shot)
- {
- method_name = "ScreenshotWindow";
- method_params = g_variant_new ("(bbbs)",
- TRUE,
- screenshot_config->include_pointer,
- TRUE, /* flash */
- filename);
- }
- else if (rectangle != NULL)
- {
- method_name = "ScreenshotArea";
- method_params = g_variant_new ("(iiiibs)",
- rectangle->x, rectangle->y,
- rectangle->width, rectangle->height,
- TRUE, /* flash */
- filename);
- }
- else
- {
- method_name = "Screenshot";
- method_params = g_variant_new ("(bbs)",
- screenshot_config->include_pointer,
- TRUE, /* flash */
- filename);
- }
-
- connection = g_application_get_dbus_connection (g_application_get_default ());
- g_dbus_connection_call_sync (connection,
- "org.gnome.Shell.Screenshot",
- "/org/gnome/Shell/Screenshot",
- "org.gnome.Shell.Screenshot",
- method_name,
- method_params,
- NULL,
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- NULL,
- &error);
-
- if (error == NULL)
- {
- screenshot = gdk_pixbuf_new_from_file (filename, &error);
-
- /* remove the temporary file created by the shell */
- g_unlink (filename);
- }
-
- return screenshot;
-}
-
GdkPixbuf *
screenshot_get_pixbuf (GdkRectangle *rectangle)
{
GdkPixbuf *screenshot = NULL;
gboolean force_fallback = FALSE;
+ g_autoptr (ScreenshotBackend) backend = NULL;
#ifdef HAVE_X11
force_fallback = g_getenv ("GNOME_SCREENSHOT_FORCE_FALLBACK") != NULL;
#endif
+
if (!force_fallback)
{
- screenshot = screenshot_shell_get_pixbuf (rectangle);
+ backend = screenshot_backend_shell_new ();
+ screenshot = screenshot_backend_get_pixbuf (backend, rectangle);
if (!screenshot)
#ifdef HAVE_X11
g_message ("Unable to use GNOME Shell's builtin screenshot interface, "
@@ -671,8 +94,12 @@ screenshot_get_pixbuf (GdkRectangle *rectangle)
#ifdef HAVE_X11
if (!screenshot)
- screenshot = screenshot_fallback_get_pixbuf (rectangle);
-#endif /* HAVE_X11 */
+ {
+ g_clear_object (&backend);
+ backend = screenshot_backend_x11_new ();
+ screenshot = screenshot_backend_get_pixbuf (backend, rectangle);
+ }
+#endif
return screenshot;
}