diff options
author | Alexander Mikhaylenko <alexm@gnome.org> | 2020-04-19 01:55:24 +0500 |
---|---|---|
committer | Alexander Mikhaylenko <alexm@gnome.org> | 2020-04-22 13:32:31 +0000 |
commit | fce83df5317085de9f1d06b6e2a9ee9822d8b02e (patch) | |
tree | 907223ff857e90790e0b3c7bc569783dd24e63e0 | |
parent | 67f71e63b9b9dd344382d9b82d4e71915cb9b720 (diff) | |
download | gnome-screenshot-fce83df5317085de9f1d06b6e2a9ee9822d8b02e.tar.gz |
Split ScreenshotUtils
Introduce ScreenshotBackendShell and ScreenshotBackendX11, move the
code as appropriate.
-rw-r--r-- | src/meson.build | 2 | ||||
-rw-r--r-- | src/screenshot-backend-shell.c | 128 | ||||
-rw-r--r-- | src/screenshot-backend-shell.h | 33 | ||||
-rw-r--r-- | src/screenshot-backend-x11.c | 570 | ||||
-rw-r--r-- | src/screenshot-backend-x11.h | 33 | ||||
-rw-r--r-- | src/screenshot-utils.c | 601 |
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; } |