re-work X11 capturing from a window
authorTony Cook <tony@develop-help.com>
Thu, 5 Dec 2019 10:22:32 +0000 (10:22 +0000)
committerTony Cook <tony@develop-help.com>
Thu, 5 Dec 2019 10:22:32 +0000 (10:22 +0000)
Previously this would use XGetImage() on the window itself, but
this returns a completely black image in some cases on more modern
X11 servers.

Fetching a window now fetches from the root window instead, adjusting
co-ordinates to match.

Screenshot.pm
Screenshot.xs
imss.h
scx11.c

index dd38b28..dd5bb26 100644 (file)
@@ -29,6 +29,7 @@ sub screenshot {
      right => 0,
      bottom => 0,
      monitor => 0,
+     direct => 0,
      @_);
 
   my $result;
@@ -43,7 +44,7 @@ sub screenshot {
     defined &_x11
       or die "X11 driver not enabled\n";
     $result = _x11($opts{display}, $opts{id}, $opts{left}, $opts{top},
-                  $opts{right}, $opts{bottom});
+                  $opts{right}, $opts{bottom}, $opts{direct});
   }
   elsif (defined $opts{darwin}) { # as long as it's there
     defined &_darwin
@@ -213,6 +214,12 @@ window.  I<display object> is a integer version of an X11 C< Display *
 >, if this isn't supplied C<screenshot()> will attempt connect to the
 the display specified by $ENV{DISPLAY}.
 
+By default this works by always capturing from the root window,
+adjusting for the position of the supplied window if one is supplied.
+
+To capture directly from the window, which returns a completely black
+image on some platforms, supply a C<< direct => 1 >> parameter.
+
 Note: taking a screenshot of a remote display is slow.
 
 =item screenshot darwin => 0
index ecc5a3d..58793db 100644 (file)
@@ -34,13 +34,14 @@ imss_win32(hwnd, include_decor = 0, left = 0, top = 0, right = 0, bottom = 0, mo
 #ifdef SS_X11
 
 Imager::ImgRaw
-imss_x11(display, window_id, left = 0, top = 0, right = 0, bottom = 0)
+imss_x11(display, window_id, left = 0, top = 0, right = 0, bottom = 0, direct = 0)
         unsigned long display
        int window_id
        int left
        int top
        int right
        int bottom
+       int direct
 
 unsigned long
 imss_x11_open(display_name = NULL)
@@ -64,4 +65,4 @@ imss_darwin(left = 0, top = 0, right = 0, bottom = 0)
 #endif
 
 BOOT:
-       PERL_INITIALIZE_IMAGER_CALLBACKS;
\ No newline at end of file
+       PERL_INITIALIZE_IMAGER_CALLBACKS;
diff --git a/imss.h b/imss.h
index b8fdb81..ab1b50e 100644 (file)
--- a/imss.h
+++ b/imss.h
@@ -5,7 +5,7 @@ extern i_img *
 imss_win32(unsigned hwnd, int include_decor, int left, int top, int right, int bottom, int display);
 
 extern i_img *
-imss_x11(unsigned long display, int window_id, int left, int top, int right, int bottom);
+imss_x11(unsigned long display, int window_id, int left, int top, int right, int bottom, int direct);
 
 extern unsigned long
 imss_x11_open(char const *display_name);
diff --git a/scx11.c b/scx11.c
index d58cd4f..d13cac8 100644 (file)
--- a/scx11.c
+++ b/scx11.c
@@ -16,10 +16,10 @@ my_handler(Display *display, XErrorEvent *error) {
 
 i_img *
 imss_x11(unsigned long display_ul, int window_id,
-        int left, int top, int right, int bottom) {
+        int left, int top, int right, int bottom, int direct) {
   Display *display = (Display *)display_ul;
   int own_display = 0; /* non-zero if we connect */
-  XImage *image;
+  XImage *image = NULL;
   XWindowAttributes attr;
   i_img *result;
   i_color *line, *cp;
@@ -27,6 +27,8 @@ imss_x11(unsigned long display_ul, int window_id,
   XColor *colors;
   XErrorHandler old_handler;
   int width, height;
+  int root_id;
+  int screen;
 
   i_clear_error();
 
@@ -37,23 +39,20 @@ imss_x11(unsigned long display_ul, int window_id,
     display = XOpenDisplay(NULL);
     ++own_display;
     if (!display) {
-      XSetErrorHandler(old_handler);
       i_push_error(0, "No display supplied and cannot connect");
-      return NULL;
+      goto fail;
     }
   }
 
+  screen = DefaultScreen(display);
+  root_id = RootWindow(display, screen);
   if (!window_id) {
-    int screen = DefaultScreen(display);
-    window_id = RootWindow(display, screen);
+    window_id = root_id;
   }
 
   if (!XGetWindowAttributes(display, window_id, &attr)) {
-    XSetErrorHandler(old_handler);
-    if (own_display)
-      XCloseDisplay(display);
     i_push_error(0, "Cannot XGetWindowAttributes");
-    return NULL;
+    goto fail;
   }
 
   /* adjust negative/zero values to window size */
@@ -65,6 +64,8 @@ imss_x11(unsigned long display_ul, int window_id,
     right += attr.width;
   if (bottom <= 0)
     bottom += attr.height;
+
+  mm_log((3, "window @(%d,%d) %dx%d\n", attr.x, attr.y, attr.width, attr.height));
   
   /* clamp */
   if (left < 0)
@@ -78,22 +79,65 @@ imss_x11(unsigned long display_ul, int window_id,
 
   /* validate */
   if (right <= left || bottom <= top) {
-    XSetErrorHandler(old_handler);
-    if (own_display)
-      XCloseDisplay(display);
     i_push_error(0, "image would be empty");
-    return NULL;
+    goto fail;
   }
   width = right - left;
   height = bottom - top;
-  image = XGetImage(display, window_id, left, top, width, height,
-                    -1, ZPixmap);
+  if (direct) {
+    /* try to get the pixels directly, this returns black images in
+       some ill-determined cases, I suspect compositing.
+    */
+    image = XGetImage(display, window_id, left, top, width, height,
+                     -1, ZPixmap);
+  }
+  else {
+    int rootx = left, rooty = top;
+    Window child_id; /* ignored */
+
+    if (root_id != window_id) {
+      XWindowAttributes root_attr;
+
+      if (!XTranslateCoordinates(display, window_id, root_id, left, top,
+                                &rootx, &rooty, &child_id)) {
+       i_push_error(0, "could not translate co-ordinates");
+       goto fail;
+      }
+
+      if (!XGetWindowAttributes(display, root_id, &root_attr)) {
+       i_push_error(0, "Cannot XGetWindowAttributes for root");
+       goto fail;
+      }
+
+      /* clip the window to the root, in case it's partly off the edge
+        of the root window
+      */
+      if (rootx < 0) {
+       width += rootx;
+       rootx = 0;
+      }
+      if (rootx + width > root_attr.width) {
+       width = root_attr.width - rootx;
+      }
+      if (rooty < 0) {
+       height += rooty;
+       rooty = 0;
+      }
+      if (rooty + height > root_attr.height) {
+       height = root_attr.height - rooty;
+      }
+
+      if (width == 0 || height == 0) {
+       i_push_error(0, "window is completely clipped by the root window");
+       goto fail;
+      }
+    }
+    image = XGetImage(display, root_id, rootx, rooty,
+                     width, height, -1, ZPixmap);
+  }
   if (!image) {
-    XSetErrorHandler(old_handler);
-    if (own_display)
-      XCloseDisplay(display);
     i_push_error(0, "Cannot XGetImage");
-    return NULL;
+    goto fail;
   }
 
   result = i_img_8_new(width, height, 3);
@@ -130,6 +174,15 @@ imss_x11(unsigned long display_ul, int window_id,
   i_tags_setn(&result->tags, "ss_top", top);
 
   return result;
+
+ fail:
+  if (image)
+    XDestroyImage(image);
+
+  XSetErrorHandler(old_handler);
+  if (own_display)
+    XCloseDisplay(display);
+  return NULL;
 }
 
 unsigned long