update to a less ancient Devel::CheckLib
[imager-screenshot.git] / scwin32.c
1 #include "imext.h"
2 #include <windows.h>
3 #include <string.h>
4 #include "imss.h"
5
6 /* the SDK headers supplied with cygwin, and with some older strawberry perls */
7 #ifndef DISPLAY_DEVICE_ACTIVE
8 #define DISPLAY_DEVICE_ACTIVE 1
9 #endif
10
11 static void
12 i_push_win32_errorf(DWORD msg_id, char const *fmt, ...) {
13   va_list args;
14   LPSTR msg;
15   char buf[1000];
16
17   va_start(args, fmt);
18   vsprintf(buf, fmt, args);
19   if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
20                     NULL,
21                     msg_id,
22                     0, /* LANGID */
23                     (LPSTR)&msg,
24                     0,
25                     NULL)) {
26     strcat(buf, msg);
27     LocalFree(msg);
28   }
29   else {
30     sprintf(buf+strlen(buf), "%#010x", msg_id);
31   }
32   i_push_error(msg_id, buf);
33   va_end(args);
34 }
35
36 struct monitor_ctx {
37   i_img *out;
38   i_img_dim orig_x, orig_y;
39   int good;
40 };
41
42 static int
43 display_to_img(HDC dc, i_img *im, const RECT *src, int dest_x, int dest_y) {
44   HBITMAP work_bmp;
45   HDC bmdc;
46   HBITMAP old_dc_bmp;
47   i_img_dim width = src->right - src->left;
48   i_img_dim height = src->bottom - src->top;
49   int result = 0;
50   BITMAPINFO bmi;
51   unsigned char *di_bits;
52
53   work_bmp = CreateCompatibleBitmap(dc, width, height);
54   bmdc = CreateCompatibleDC(dc);
55   old_dc_bmp = SelectObject(bmdc, work_bmp);
56   BitBlt(bmdc, 0, 0, width, height, dc, src->left, src->top, SRCCOPY);
57
58   /* make a dib */
59   memset(&bmi, 0, sizeof(bmi));
60   bmi.bmiHeader.biSize = sizeof(bmi);
61   bmi.bmiHeader.biWidth = width;
62   bmi.bmiHeader.biHeight = -height;
63   bmi.bmiHeader.biPlanes = 1;
64   bmi.bmiHeader.biBitCount = 32;
65   bmi.bmiHeader.biCompression = BI_RGB;
66
67   di_bits = mymalloc(4 * width * height);
68   if (GetDIBits(bmdc, work_bmp, 0, height, di_bits, &bmi, DIB_RGB_COLORS)) {
69     i_color *line = mymalloc(sizeof(i_color) * width);
70     i_color *cp;
71     int x, y;
72     unsigned char *ch_pp = di_bits;
73     for (y = 0; y < height; ++y) {
74       cp = line;
75       for (x = 0; x < width; ++x) {
76         cp->rgba.b = *ch_pp++;
77         cp->rgba.g = *ch_pp++;
78         cp->rgba.r = *ch_pp++;
79         cp->rgba.a = 255;
80         ch_pp++;
81         cp++;
82       }
83       i_plin(im, dest_x, dest_x+width, dest_y + y, line);
84     }
85     myfree(line);
86     result = 1;
87   }
88   else {
89     i_push_win32_errorf(GetLastError(), "GetDIBits() failure: ");
90   }
91
92   myfree(di_bits);
93   SelectObject(bmdc, old_dc_bmp);
94   DeleteDC(bmdc);
95   DeleteObject(work_bmp);
96
97   return result;
98 }
99
100 static BOOL CALLBACK
101 monitor_enum(HMONITOR hmon, HDC dc, LPRECT r, LPARAM lp_ctx) {
102   struct monitor_ctx *ctx = (struct monitor_ctx *)lp_ctx;
103
104   if (!display_to_img(dc, ctx->out, r,
105                       r->left - ctx->orig_x, r->top - ctx->orig_y)) {
106     ctx->good = 0;
107   }
108
109   return ctx->good;
110 }
111
112 i_img *
113 imss_win32(unsigned hwnd_u, int include_decor, int left, int top, 
114            int right, int bottom, int display) {
115   HWND hwnd = (HWND)hwnd_u;
116   HDC cdc = 0, wdc;
117   int orig_x = 0;
118   int orig_y = 0;
119   int window_width, window_height;
120   i_img *result = NULL;
121   int width, height;
122   int channels = 3;
123
124   i_clear_error();
125
126   if (hwnd) {
127     RECT rect;
128     if (include_decor) {
129       wdc = GetWindowDC(hwnd);
130       GetWindowRect(hwnd, &rect);
131     }
132     else {
133       wdc = GetDC(hwnd);
134       GetClientRect(hwnd, &rect);
135     }
136     if (!wdc) {
137       i_push_error(0, "Cannot get window DC - invalid hwnd?");
138       return NULL;
139     }
140
141     window_width = rect.right - rect.left;
142     window_height = rect.bottom - rect.top;
143   }
144   else {
145     if (display == -1) {
146       cdc = CreateDC("DISPLAY", NULL, NULL, NULL);
147       orig_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
148       orig_y = GetSystemMetrics(SM_YVIRTUALSCREEN);
149       window_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
150       window_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
151       channels = 4;
152     }
153     else {
154       DISPLAY_DEVICE dd;
155       int work_display = 0;
156       int primary_display = -1;
157       int real_display = -1;
158
159       dd.cb = sizeof(dd);
160       /* look for the primary display, we need a simulate a gap to put the
161          primary at 0 */
162       while (work_display < 100
163              && EnumDisplayDevices(NULL, work_display, &dd, 0)) {
164         if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
165           primary_display = work_display;
166           break;
167         }
168         else if (display && display-1 == work_display) {
169           real_display = work_display;
170           break;
171         }
172         
173         dd.cb = sizeof(dd);
174         ++work_display;
175       }
176
177       if (!work_display && real_display == -1 && primary_display == -1) {
178         /* EnumDisplayDevices() failed for the first call */
179         i_push_win32_errorf(GetLastError(), "Cannot enumerate device %d(0): ", work_display);
180         return NULL;
181       }
182
183       if (primary_display != -1 && display == 0) {
184         real_display = primary_display;
185       }
186       
187       if (real_display == -1) {
188         /* haven't enumerated the display we want yet */
189         /* we're after the primary */
190         real_display = display;
191         dd.cb = sizeof(dd);
192         if (!EnumDisplayDevices(NULL, real_display, &dd, 0)) {
193           i_push_win32_errorf(GetLastError(), "Cannot enumerate device %d(%d): ",
194                         real_display, display);
195           return NULL;
196         }
197       }
198
199       if (!(dd.StateFlags & DISPLAY_DEVICE_ACTIVE)) {
200         i_push_errorf(0, "Display device %d not active", display);
201         return NULL;
202       }
203       cdc = CreateDC(dd.DeviceName, dd.DeviceName, NULL, NULL);
204       if (!cdc) {
205         i_push_win32_errorf(GetLastError(), "Cannot CreateDC(%s): ", dd.DeviceName);
206         return NULL;
207       }
208
209       window_width = GetDeviceCaps(cdc, HORZRES);
210       window_height = GetDeviceCaps(cdc, VERTRES);
211     }
212
213     wdc = cdc;
214   }
215
216   /* adjust negative/zero values to window size */
217   if (left < 0)
218     left += window_width;
219   if (top < 0)
220     top += window_height;
221   if (right <= 0)
222     right += window_width;
223   if (bottom <= 0)
224     bottom += window_height;
225   
226   /* clamp */
227   if (left < 0)
228     left = 0;
229   if (right > window_width)
230     right = window_width;
231   if (top < 0)
232     top = 0;
233   if (bottom > window_height)
234     bottom = window_height;
235
236   /* validate */
237   if (right <= left || bottom <= top) {
238     i_push_error(0, "image would be empty");
239     if (cdc)
240       DeleteDC(cdc);
241     else
242       ReleaseDC(hwnd, wdc);
243     return NULL;
244   }
245   width = right - left;
246   height = bottom - top;
247   
248   result = i_img_8_new(width, height, channels);
249   
250   if (result) {
251     RECT r;
252     r.left = orig_x + left;
253     r.top = orig_y + top;
254     r.right = r.left + width;
255     r.bottom = r.top + height;
256     
257     if (display == -1) {
258       struct monitor_ctx ctx;
259       ctx.out = result;
260       ctx.orig_x = orig_x;
261       ctx.orig_y = orig_y;
262       ctx.good = 1;
263
264       if (!EnumDisplayMonitors(wdc, &r, monitor_enum, (LPARAM)&ctx)
265           || !ctx.good) {
266         i_img_destroy(result);
267         result = NULL;
268       }
269     }
270     else {
271       if (!display_to_img(wdc, result, &r, 0, 0)) {
272         i_img_destroy(result);
273         result = NULL;
274       }
275     }
276     if (result) {
277       i_tags_setn(&result->tags, "ss_window_width", window_width);
278       i_tags_setn(&result->tags, "ss_window_height", window_height);
279       i_tags_set(&result->tags, "ss_type", "Win32", 5);
280       i_tags_setn(&result->tags, "ss_left", left);
281       i_tags_setn(&result->tags, "ss_top", top);
282     }
283   }
284   /* clean up */
285   if (cdc) {
286     DeleteDC(cdc);
287   }
288   else {
289     ReleaseDC(hwnd, wdc);
290   }
291
292   return result;
293 }