re-work X11 capturing from a window
[imager-screenshot.git] / scx11.c
1 #include "imext.h"
2 #include <X11/Xlib.h>
3 #include <X11/Xutil.h>
4 #include "imss.h"
5
6 static
7 int
8 my_handler(Display *display, XErrorEvent *error) {
9   char buffer[500];
10
11   XGetErrorText(display, error->error_code, buffer, sizeof(buffer));
12   i_push_error(error->error_code, buffer);
13
14   return 0;
15 }
16
17 i_img *
18 imss_x11(unsigned long display_ul, int window_id,
19          int left, int top, int right, int bottom, int direct) {
20   Display *display = (Display *)display_ul;
21   int own_display = 0; /* non-zero if we connect */
22   XImage *image = NULL;
23   XWindowAttributes attr;
24   i_img *result;
25   i_color *line, *cp;
26   int x, y;
27   XColor *colors;
28   XErrorHandler old_handler;
29   int width, height;
30   int root_id;
31   int screen;
32
33   i_clear_error();
34
35   /* we don't want the default noisy error handling */
36   old_handler = XSetErrorHandler(my_handler);
37
38   if (!display) {
39     display = XOpenDisplay(NULL);
40     ++own_display;
41     if (!display) {
42       i_push_error(0, "No display supplied and cannot connect");
43       goto fail;
44     }
45   }
46
47   screen = DefaultScreen(display);
48   root_id = RootWindow(display, screen);
49   if (!window_id) {
50     window_id = root_id;
51   }
52
53   if (!XGetWindowAttributes(display, window_id, &attr)) {
54     i_push_error(0, "Cannot XGetWindowAttributes");
55     goto fail;
56   }
57
58   /* adjust negative/zero values to window size */
59   if (left < 0)
60     left += attr.width;
61   if (top < 0)
62     top += attr.height;
63   if (right <= 0)
64     right += attr.width;
65   if (bottom <= 0)
66     bottom += attr.height;
67
68   mm_log((3, "window @(%d,%d) %dx%d\n", attr.x, attr.y, attr.width, attr.height));
69   
70   /* clamp */
71   if (left < 0)
72     left = 0;
73   if (right > attr.width)
74     right = attr.width;
75   if (top < 0)
76     top = 0;
77   if (bottom > attr.height)
78     bottom = attr.height;
79
80   /* validate */
81   if (right <= left || bottom <= top) {
82     i_push_error(0, "image would be empty");
83     goto fail;
84   }
85   width = right - left;
86   height = bottom - top;
87   if (direct) {
88     /* try to get the pixels directly, this returns black images in
89        some ill-determined cases, I suspect compositing.
90     */
91     image = XGetImage(display, window_id, left, top, width, height,
92                       -1, ZPixmap);
93   }
94   else {
95     int rootx = left, rooty = top;
96     Window child_id; /* ignored */
97
98     if (root_id != window_id) {
99       XWindowAttributes root_attr;
100
101       if (!XTranslateCoordinates(display, window_id, root_id, left, top,
102                                  &rootx, &rooty, &child_id)) {
103         i_push_error(0, "could not translate co-ordinates");
104         goto fail;
105       }
106
107       if (!XGetWindowAttributes(display, root_id, &root_attr)) {
108         i_push_error(0, "Cannot XGetWindowAttributes for root");
109         goto fail;
110       }
111
112       /* clip the window to the root, in case it's partly off the edge
113          of the root window
114       */
115       if (rootx < 0) {
116         width += rootx;
117         rootx = 0;
118       }
119       if (rootx + width > root_attr.width) {
120         width = root_attr.width - rootx;
121       }
122       if (rooty < 0) {
123         height += rooty;
124         rooty = 0;
125       }
126       if (rooty + height > root_attr.height) {
127         height = root_attr.height - rooty;
128       }
129
130       if (width == 0 || height == 0) {
131         i_push_error(0, "window is completely clipped by the root window");
132         goto fail;
133       }
134     }
135     image = XGetImage(display, root_id, rootx, rooty,
136                       width, height, -1, ZPixmap);
137   }
138   if (!image) {
139     i_push_error(0, "Cannot XGetImage");
140     goto fail;
141   }
142
143   result = i_img_8_new(width, height, 3);
144   line = mymalloc(sizeof(i_color) * width);
145   colors = mymalloc(sizeof(XColor) * width);
146   for (y = 0; y < height; ++y) {
147     cp = line;
148     /* XQueryColors seems to be a round-trip, so do one big request
149        instead of one per pixel */
150     for (x = 0; x < width; ++x) {
151       colors[x].pixel = XGetPixel(image, x, y);
152     }
153     XQueryColors(display, attr.colormap, colors, width);
154     for (x = 0; x < width; ++x) {
155       cp->rgb.r = colors[x].red >> 8;
156       cp->rgb.g = colors[x].green >> 8;
157       cp->rgb.b = colors[x].blue >> 8;
158       ++cp;
159     }
160     i_plin(result, 0, width, y, line);
161   }
162   myfree(line);
163   myfree(colors);
164   XDestroyImage(image);
165
166   XSetErrorHandler(old_handler);
167   if (own_display)
168     XCloseDisplay(display);
169
170   i_tags_setn(&result->tags, "ss_window_width", attr.width);
171   i_tags_setn(&result->tags, "ss_window_height", attr.height);
172   i_tags_set(&result->tags, "ss_type", "X11", 3);
173   i_tags_setn(&result->tags, "ss_left", left);
174   i_tags_setn(&result->tags, "ss_top", top);
175
176   return result;
177
178  fail:
179   if (image)
180     XDestroyImage(image);
181
182   XSetErrorHandler(old_handler);
183   if (own_display)
184     XCloseDisplay(display);
185   return NULL;
186 }
187
188 unsigned long
189 imss_x11_open(char const *display_name) {
190   XErrorHandler old_handler;
191   Display *display;
192
193   i_clear_error();
194   old_handler = XSetErrorHandler(my_handler);
195   display = XOpenDisplay(display_name);
196   if (!display)
197     i_push_errorf(0, "Cannot connect to X server %s", XDisplayName(display_name));
198   
199   XSetErrorHandler(old_handler);
200
201   return (unsigned long)display;
202 }
203
204 void
205 imss_x11_close(unsigned long display) {
206   XCloseDisplay((Display *)display);
207 }