/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * Screenshot plug-in * Copyright 1998-2007 Sven Neumann * Copyright 2003 Henrik Brix Andersen * Copyright 2012 Simone Karin Lehmann - OS X patches * * 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 3 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, see . */ #include "config.h" #include #include #ifdef GDK_WINDOWING_X11 #include #include #ifdef HAVE_X11_EXTENSIONS_SHAPE_H #include #endif #ifdef HAVE_X11_XMU_WINUTIL_H #include #endif #ifdef HAVE_XFIXES #include #endif #include "screenshot.h" #include "screenshot-x11.h" #include "libgimp/stdplugins-intl.h" static guint32 select_window (ScreenshotValues *shootvals, GdkScreen *screen); static gint32 create_image (cairo_surface_t *surface, cairo_region_t *shape, const gchar *name); /* Allow the user to select a window or a region with the mouse */ static guint32 select_window (ScreenshotValues *shootvals, GdkScreen *screen) { Display *x_dpy = GDK_SCREEN_XDISPLAY (screen); gint x_scr = GDK_SCREEN_XNUMBER (screen); Window x_root = RootWindow (x_dpy, x_scr); Window x_win = None; GC x_gc = NULL; Cursor x_cursor = XCreateFontCursor (x_dpy, GDK_CROSSHAIR); GdkKeymap *keymap; GdkKeymapKey *keys = NULL; gint status; gint num_keys; gint i; gint buttons = 0; gint mask = ButtonPressMask | ButtonReleaseMask; gboolean cancel = FALSE; if (shootvals->shoot_type == SHOOT_REGION) mask |= PointerMotionMask; status = XGrabPointer (x_dpy, x_root, False, mask, GrabModeSync, GrabModeAsync, x_root, x_cursor, CurrentTime); if (status != GrabSuccess) { gint x, y; guint xmask; /* if we can't grab the pointer, return the window under the pointer */ XQueryPointer (x_dpy, x_root, &x_root, &x_win, &x, &y, &x, &y, &xmask); if (x_win == None || x_win == x_root) g_message (_("Error selecting the window")); } if (shootvals->shoot_type == SHOOT_REGION) { XGCValues gc_values; gc_values.function = GXxor; gc_values.plane_mask = AllPlanes; gc_values.foreground = WhitePixel (x_dpy, x_scr); gc_values.background = BlackPixel (x_dpy, x_scr); gc_values.line_width = 0; gc_values.line_style = LineSolid; gc_values.fill_style = FillSolid; gc_values.cap_style = CapButt; gc_values.join_style = JoinMiter; gc_values.graphics_exposures = FALSE; gc_values.clip_x_origin = 0; gc_values.clip_y_origin = 0; gc_values.clip_mask = None; gc_values.subwindow_mode = IncludeInferiors; x_gc = XCreateGC (x_dpy, x_root, GCFunction | GCPlaneMask | GCForeground | GCLineWidth | GCLineStyle | GCCapStyle | GCJoinStyle | GCGraphicsExposures | GCBackground | GCFillStyle | GCClipXOrigin | GCClipYOrigin | GCClipMask | GCSubwindowMode, &gc_values); } keymap = gdk_keymap_get_for_display (gdk_screen_get_display (screen)); if (gdk_keymap_get_entries_for_keyval (keymap, GDK_KEY_Escape, &keys, &num_keys)) { gdk_error_trap_push (); #define X_GRAB_KEY(index, modifiers) \ XGrabKey (x_dpy, keys[index].keycode, modifiers, x_root, False, \ GrabModeAsync, GrabModeAsync) for (i = 0; i < num_keys; i++) { X_GRAB_KEY (i, 0); X_GRAB_KEY (i, LockMask); /* CapsLock */ X_GRAB_KEY (i, Mod2Mask); /* NumLock */ X_GRAB_KEY (i, Mod5Mask); /* ScrollLock */ X_GRAB_KEY (i, LockMask | Mod2Mask); /* CapsLock + NumLock */ X_GRAB_KEY (i, LockMask | Mod5Mask); /* CapsLock + ScrollLock */ X_GRAB_KEY (i, Mod2Mask | Mod5Mask); /* NumLock + ScrollLock */ X_GRAB_KEY (i, LockMask | Mod2Mask | Mod5Mask); /* all */ } #undef X_GRAB_KEY gdk_flush (); if (gdk_error_trap_pop ()) { /* ignore errors */ } } while (! cancel && ((x_win == None) || (buttons != 0))) { XEvent x_event; gint x, y, w, h; XAllowEvents (x_dpy, SyncPointer, CurrentTime); XWindowEvent (x_dpy, x_root, mask | KeyPressMask, &x_event); switch (x_event.type) { case ButtonPress: if (x_win == None) { x_win = x_event.xbutton.subwindow; if (x_win == None) x_win = x_root; #ifdef HAVE_X11_XMU_WINUTIL_H else if (! shootvals->decorate) x_win = XmuClientWindow (x_dpy, x_win); #endif shootvals->x2 = shootvals->x1 = x_event.xbutton.x_root; shootvals->y2 = shootvals->y1 = x_event.xbutton.y_root; } buttons++; break; case ButtonRelease: if (buttons > 0) buttons--; if (! buttons && shootvals->shoot_type == SHOOT_REGION) { x = MIN (shootvals->x1, shootvals->x2); y = MIN (shootvals->y1, shootvals->y2); w = ABS (shootvals->x2 - shootvals->x1); h = ABS (shootvals->y2 - shootvals->y1); if (w > 0 && h > 0) XDrawRectangle (x_dpy, x_root, x_gc, x, y, w, h); shootvals->x2 = x_event.xbutton.x_root; shootvals->y2 = x_event.xbutton.y_root; } break; case MotionNotify: if (buttons > 0) { x = MIN (shootvals->x1, shootvals->x2); y = MIN (shootvals->y1, shootvals->y2); w = ABS (shootvals->x2 - shootvals->x1); h = ABS (shootvals->y2 - shootvals->y1); if (w > 0 && h > 0) XDrawRectangle (x_dpy, x_root, x_gc, x, y, w, h); shootvals->x2 = x_event.xmotion.x_root; shootvals->y2 = x_event.xmotion.y_root; x = MIN (shootvals->x1, shootvals->x2); y = MIN (shootvals->y1, shootvals->y2); w = ABS (shootvals->x2 - shootvals->x1); h = ABS (shootvals->y2 - shootvals->y1); if (w > 0 && h > 0) XDrawRectangle (x_dpy, x_root, x_gc, x, y, w, h); } break; case KeyPress: { guint *keyvals; gint n; if (gdk_keymap_get_entries_for_keycode (NULL, x_event.xkey.keycode, NULL, &keyvals, &n)) { gint i; for (i = 0; i < n && ! cancel; i++) if (keyvals[i] == GDK_KEY_Escape) cancel = TRUE; g_free (keyvals); } } break; default: break; } } if (keys) { #define X_UNGRAB_KEY(index, modifiers) \ XUngrabKey (x_dpy, keys[index].keycode, modifiers, x_root) for (i = 0; i < num_keys; i++) { X_UNGRAB_KEY (i, 0); X_UNGRAB_KEY (i, LockMask); /* CapsLock */ X_UNGRAB_KEY (i, Mod2Mask); /* NumLock */ X_UNGRAB_KEY (i, Mod5Mask); /* ScrollLock */ X_UNGRAB_KEY (i, LockMask | Mod2Mask); /* CapsLock + NumLock */ X_UNGRAB_KEY (i, LockMask | Mod5Mask); /* CapsLock + ScrollLock */ X_UNGRAB_KEY (i, Mod2Mask | Mod5Mask); /* NumLock + ScrollLock */ X_UNGRAB_KEY (i, LockMask | Mod2Mask | Mod5Mask); /* all */ } #undef X_UNGRAB_KEY g_free (keys); } if (status == GrabSuccess) XUngrabPointer (x_dpy, CurrentTime); XFreeCursor (x_dpy, x_cursor); if (x_gc != NULL) XFreeGC (x_dpy, x_gc); return x_win; } static gchar * window_get_utf8_property (GdkDisplay *display, guint32 window, const gchar *name) { gchar *retval = NULL; Atom utf8_string; Atom type = None; guchar *val = NULL; gulong nitems = 0; gulong after = 0; gint format = 0; utf8_string = gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"); XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), window, gdk_x11_get_xatom_by_name_for_display (display, name), 0, G_MAXLONG, False, utf8_string, &type, &format, &nitems, &after, &val); if (type != utf8_string || format != 8 || nitems == 0) { if (val) XFree (val); return NULL; } if (g_utf8_validate ((const gchar *) val, nitems, NULL)) retval = g_strndup ((const gchar *) val, nitems); XFree (val); return retval; } static gchar * window_get_title (GdkDisplay *display, guint window) { #ifdef HAVE_X11_XMU_WINUTIL_H window = XmuClientWindow (GDK_DISPLAY_XDISPLAY (display), window); #endif return window_get_utf8_property (display, window, "_NET_WM_NAME"); } static cairo_region_t * window_get_shape (GdkScreen *screen, guint32 window) { cairo_region_t *shape = NULL; #if defined(HAVE_X11_EXTENSIONS_SHAPE_H) XRectangle *rects; gint rect_count; gint rect_order; rects = XShapeGetRectangles (GDK_SCREEN_XDISPLAY (screen), window, ShapeBounding, &rect_count, &rect_order); if (rects) { if (rect_count > 1) { gint i; shape = cairo_region_create (); for (i = 0; i < rect_count; i++) { cairo_rectangle_int_t rect = { rects[i].x, rects[i].y, rects[i].width, rects[i].height }; cairo_region_union_rectangle (shape, &rect); } } XFree (rects); } #endif return shape; } static void image_select_shape (gint32 image, cairo_region_t *shape) { gint num_rects; gint i; gimp_selection_none (image); num_rects = cairo_region_num_rectangles (shape); for (i = 0; i < num_rects; i++) { cairo_rectangle_int_t rect; cairo_region_get_rectangle (shape, i, &rect); gimp_image_select_rectangle (image, GIMP_CHANNEL_OP_ADD, rect.x, rect.y, rect.width, rect.height); } gimp_selection_invert (image); } /* Create a GimpImage from a GdkPixbuf */ static gint32 create_image (cairo_surface_t *surface, cairo_region_t *shape, const gchar *name) { gint32 image; gint32 layer; gdouble xres, yres; gint width, height; gimp_progress_init (_("Importing screenshot")); width = cairo_image_surface_get_width (surface); height = cairo_image_surface_get_height (surface); image = gimp_image_new (width, height, GIMP_RGB); gimp_image_undo_disable (image); gimp_get_monitor_resolution (&xres, &yres); gimp_image_set_resolution (image, xres, yres); layer = gimp_layer_new_from_surface (image, name ? name : _("Screenshot"), surface, 0.0, 1.0); gimp_image_insert_layer (image, layer, -1, 0); if (shape && ! cairo_region_is_empty (shape)) { image_select_shape (image, shape); if (! gimp_selection_is_empty (image)) { gimp_layer_add_alpha (layer); gimp_drawable_edit_clear (layer); gimp_selection_none (image); } } gimp_image_undo_enable (image); return image; } static void add_cursor_image (gint32 image, GdkDisplay *display) { #ifdef HAVE_XFIXES XFixesCursorImage *cursor; GeglBuffer *buffer; GeglBufferIterator *iter; GeglRectangle *roi; gint32 layer; gint32 active; cursor = XFixesGetCursorImage (GDK_DISPLAY_XDISPLAY (display)); if (!cursor) return; active = gimp_image_get_active_layer (image); layer = gimp_layer_new (image, _("Mouse Pointer"), cursor->width, cursor->height, GIMP_RGBA_IMAGE, 100.0, gimp_image_get_default_new_layer_mode (image)); buffer = gimp_drawable_get_buffer (layer); iter = gegl_buffer_iterator_new (buffer, GEGL_RECTANGLE (0, 0, gimp_drawable_width (layer), gimp_drawable_height (layer)), 0, babl_format ("R'G'B'A u8"), GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1); roi = &iter->items[0].roi; while (gegl_buffer_iterator_next (iter)) { const gulong *src = cursor->pixels + roi->y * cursor->width + roi->x; guchar *dest = iter->items[0].data; gint x, y; for (y = 0; y < roi->height; y++) { const gulong *s = src; guchar *d = dest; for (x = 0; x < roi->width; x++) { /* the cursor pixels are pre-multiplied ARGB */ guint a = (*s >> 24) & 0xff; guint r = (*s >> 16) & 0xff; guint g = (*s >> 8) & 0xff; guint b = (*s >> 0) & 0xff; d[0] = a ? (r * 255) / a : r; d[1] = a ? (g * 255) / a : g; d[2] = a ? (b * 255) / a : b; d[3] = a; s++; d += 4; } src += cursor->width; dest += 4 * roi->width; } } g_object_unref (buffer); gimp_image_insert_layer (image, layer, -1, -1); gimp_layer_set_offsets (layer, cursor->x - cursor->xhot, cursor->y - cursor->yhot); gimp_image_set_active_layer (image, active); #endif } /* The main Screenshot function */ gboolean screenshot_x11_available (void) { return TRUE; } ScreenshotCapabilities screenshot_x11_get_capabilities (void) { ScreenshotCapabilities capabilities = SCREENSHOT_CAN_PICK_NONINTERACTIVELY; #ifdef HAVE_X11_XMU_WINUTIL_H capabilities |= SCREENSHOT_CAN_SHOOT_DECORATIONS; #endif #ifdef HAVE_XFIXES capabilities |= SCREENSHOT_CAN_SHOOT_POINTER; #endif capabilities |= SCREENSHOT_CAN_SHOOT_REGION | SCREENSHOT_CAN_SHOOT_WINDOW | SCREENSHOT_CAN_PICK_WINDOW | SCREENSHOT_CAN_DELAY_WINDOW_SHOT; return capabilities; } GimpPDBStatusType screenshot_x11_shoot (ScreenshotValues *shootvals, GdkScreen *screen, gint32 *image_ID, GError **error) { GdkDisplay *display; GdkWindow *window; cairo_surface_t *screenshot; cairo_region_t *shape = NULL; cairo_t *cr; GimpColorProfile *profile; GdkRectangle rect; GdkRectangle screen_rect; gchar *name = NULL; gint screen_x; gint screen_y; gint monitor = shootvals->monitor; gint x, y; /* use default screen if we are running non-interactively */ if (screen == NULL) screen = gdk_screen_get_default (); if (shootvals->shoot_type != SHOOT_ROOT && ! shootvals->window_id) { if (shootvals->select_delay > 0) screenshot_delay (shootvals->select_delay); shootvals->window_id = select_window (shootvals, screen); if (! shootvals->window_id) return GIMP_PDB_CANCEL; } if (shootvals->screenshot_delay > 0) screenshot_delay (shootvals->screenshot_delay); display = gdk_screen_get_display (screen); screen_rect.x = 0; screen_rect.y = 0; screen_rect.width = gdk_screen_get_width (screen); screen_rect.height = gdk_screen_get_height (screen); if (shootvals->shoot_type == SHOOT_REGION) { rect.x = MIN (shootvals->x1, shootvals->x2); rect.y = MIN (shootvals->y1, shootvals->y2); rect.width = ABS (shootvals->x2 - shootvals->x1); rect.height = ABS (shootvals->y2 - shootvals->y1); monitor = gdk_screen_get_monitor_at_point (screen, rect.x + rect.width / 2, rect.y + rect.height / 2); } else { if (shootvals->shoot_type == SHOOT_ROOT) { window = gdk_screen_get_root_window (screen); /* FIXME: figure monitor */ } else { window = gdk_x11_window_foreign_new_for_display (display, shootvals->window_id); monitor = gdk_screen_get_monitor_at_window (screen, window); } if (! window) { g_set_error_literal (error, 0, 0, _("Specified window not found")); return GIMP_PDB_EXECUTION_ERROR; } rect.width = gdk_window_get_width (window); rect.height = gdk_window_get_height (window); gdk_window_get_origin (window, &x, &y); rect.x = x; rect.y = y; } if (! gdk_rectangle_intersect (&rect, &screen_rect, &rect)) return GIMP_PDB_EXECUTION_ERROR; window = gdk_screen_get_root_window (screen); gdk_window_get_origin (window, &screen_x, &screen_y); screenshot = cairo_image_surface_create (CAIRO_FORMAT_RGB24, rect.width, rect.height); cr = cairo_create (screenshot); gdk_cairo_set_source_window (cr, window, - (rect.x - screen_x), - (rect.y - screen_y)); cairo_paint (cr); cairo_destroy (cr); gdk_display_beep (display); if (shootvals->shoot_type == SHOOT_WINDOW) { name = window_get_title (display, shootvals->window_id); shape = window_get_shape (screen, shootvals->window_id); if (shape) cairo_region_translate (shape, x - rect.x, y - rect.y); } *image_ID = create_image (screenshot, shape, name); cairo_surface_destroy (screenshot); if (shape) cairo_region_destroy (shape); g_free (name); /* FIXME: Some time might have passed until we get here. * The cursor image should be grabbed together with the screenshot. */ if ((shootvals->shoot_type == SHOOT_ROOT || shootvals->shoot_type == SHOOT_WINDOW) && shootvals->show_cursor) add_cursor_image (*image_ID, display); profile = gimp_screen_get_color_profile (screen, monitor); if (profile) { gimp_image_set_color_profile (*image_ID, profile); g_object_unref (profile); } return GIMP_PDB_SUCCESS; } #endif /* GDK_WINDOWING_X11 */