capture multiple monitors with transparency on Win32
[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 struct monitor_ctx {
12   i_img *out;
13   i_img_dim orig_x, orig_y;
14   int good;
15 };
16
17 static int
18 display_to_img(HDC dc, i_img *im, const RECT *src, int dest_x, int dest_y) {
19   HBITMAP work_bmp;
20   HDC bmdc;
21   HBITMAP old_dc_bmp;
22   i_img_dim width = src->right - src->left;
23   i_img_dim height = src->bottom - src->top;
24   int result = 0;
25   BITMAPINFO bmi;
26   unsigned char *di_bits;
27
28   work_bmp = CreateCompatibleBitmap(dc, width, height);
29   bmdc = CreateCompatibleDC(dc);
30   old_dc_bmp = SelectObject(bmdc, work_bmp);
31   BitBlt(bmdc, 0, 0, width, height, dc, src->left, src->top, SRCCOPY);
32
33   /* make a dib */
34   memset(&bmi, 0, sizeof(bmi));
35   bmi.bmiHeader.biSize = sizeof(bmi);
36   bmi.bmiHeader.biWidth = width;
37   bmi.bmiHeader.biHeight = -height;
38   bmi.bmiHeader.biPlanes = 1;
39   bmi.bmiHeader.biBitCount = 32;
40   bmi.bmiHeader.biCompression = BI_RGB;
41
42   di_bits = mymalloc(4 * width * height);
43   if (GetDIBits(bmdc, work_bmp, 0, height, di_bits, &bmi, DIB_RGB_COLORS)) {
44     i_color *line = mymalloc(sizeof(i_color) * width);
45     i_color *cp;
46     int x, y;
47     unsigned char *ch_pp = di_bits;
48     for (y = 0; y < height; ++y) {
49       cp = line;
50       for (x = 0; x < width; ++x) {
51         cp->rgba.b = *ch_pp++;
52         cp->rgba.g = *ch_pp++;
53         cp->rgba.r = *ch_pp++;
54         cp->rgba.a = 255;
55         ch_pp++;
56         cp++;
57       }
58       i_plin(im, dest_x, dest_x+width, dest_y + y, line);
59     }
60     myfree(line);
61     result = 1;
62   }
63   else {
64     i_push_errorf(0, "GetDIBits() failure %d", (long)GetLastError());
65   }
66
67   myfree(di_bits);
68   SelectObject(bmdc, old_dc_bmp);
69   DeleteDC(bmdc);
70   DeleteObject(work_bmp);
71
72   return result;
73 }
74
75 static BOOL CALLBACK
76 monitor_enum(HMONITOR hmon, HDC dc, LPRECT r, LPARAM lp_ctx) {
77   struct monitor_ctx *ctx = (struct monitor_ctx *)lp_ctx;
78
79   if (!display_to_img(dc, ctx->out, r,
80                       r->left - ctx->orig_x, r->top - ctx->orig_y)) {
81     ctx->good = 0;
82   }
83
84   return ctx->good;
85 }
86
87
88 i_img *
89 imss_win32(unsigned hwnd_u, int include_decor, int left, int top, 
90            int right, int bottom, int display) {
91   HWND hwnd = (HWND)hwnd_u;
92   HDC cdc = 0, wdc;
93   int orig_x = 0;
94   int orig_y = 0;
95   int window_width, window_height;
96   i_img *result = NULL;
97   int width, height;
98   int channels = 3;
99
100   i_clear_error();
101
102   if (hwnd) {
103     RECT rect;
104     if (include_decor) {
105       wdc = GetWindowDC(hwnd);
106       GetWindowRect(hwnd, &rect);
107     }
108     else {
109       wdc = GetDC(hwnd);
110       GetClientRect(hwnd, &rect);
111     }
112     if (!wdc) {
113       i_push_error(0, "Cannot get window DC - invalid hwnd?");
114       return NULL;
115     }
116
117     window_width = rect.right - rect.left;
118     window_height = rect.bottom - rect.top;
119   }
120   else {
121     if (display == -1) {
122       cdc = CreateDC("DISPLAY", NULL, NULL, NULL);
123       orig_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
124       orig_y = GetSystemMetrics(SM_YVIRTUALSCREEN);
125       window_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
126       window_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
127       channels = 4;
128     }
129     else {
130       DISPLAY_DEVICE dd;
131       dd.cb = sizeof(dd);
132
133       if (EnumDisplayDevices(NULL, display, &dd, 0)) {
134         printf("Flags %lx\n", (unsigned long)dd.StateFlags);
135         if (dd.StateFlags & DISPLAY_DEVICE_ACTIVE) {
136           cdc = CreateDC(dd.DeviceName, dd.DeviceName, NULL, NULL);
137         }
138         else {
139           i_push_errorf(0, "Display device %d not active", display);
140           return NULL;
141         }
142       }
143       else {
144         i_push_errorf(0, "Cannot enumerate device %d: %ld", display, (long)GetLastError());
145         return NULL;
146       }
147
148       window_width = GetDeviceCaps(cdc, HORZRES);
149       window_height = GetDeviceCaps(cdc, VERTRES);
150     }
151
152     wdc = cdc;
153   }
154
155   /* adjust negative/zero values to window size */
156   if (left < 0)
157     left += window_width;
158   if (top < 0)
159     top += window_height;
160   if (right <= 0)
161     right += window_width;
162   if (bottom <= 0)
163     bottom += window_height;
164   
165   /* clamp */
166   if (left < 0)
167     left = 0;
168   if (right > window_width)
169     right = window_width;
170   if (top < 0)
171     top = 0;
172   if (bottom > window_height)
173     bottom = window_height;
174
175   /* validate */
176   if (right <= left || bottom <= top) {
177     i_push_error(0, "image would be empty");
178     if (cdc)
179       DeleteDC(cdc);
180     else
181       ReleaseDC(hwnd, wdc);
182     return NULL;
183   }
184   width = right - left;
185   height = bottom - top;
186   
187   result = i_img_8_new(width, height, channels);
188   
189   if (result) {
190     RECT r;
191     r.left = orig_x + left;
192     r.top = orig_y + top;
193     r.right = r.left + width;
194     r.bottom = r.top + height;
195     
196     if (display == -1) {
197       struct monitor_ctx ctx;
198       ctx.out = result;
199       ctx.orig_x = orig_x;
200       ctx.orig_y = orig_y;
201       ctx.good = 1;
202
203       if (!EnumDisplayMonitors(wdc, &r, monitor_enum, (LPARAM)&ctx)
204           || !ctx.good) {
205         i_img_destroy(result);
206         result = NULL;
207       }
208     }
209     else {
210       if (!display_to_img(wdc, result, &r, 0, 0)) {
211         i_img_destroy(result);
212         result = NULL;
213       }
214     }
215     if (result) {
216       i_tags_setn(&result->tags, "ss_window_width", window_width);
217       i_tags_setn(&result->tags, "ss_window_height", window_height);
218       i_tags_set(&result->tags, "ss_type", "Win32", 5);
219       i_tags_setn(&result->tags, "ss_left", left);
220       i_tags_setn(&result->tags, "ss_top", top);
221     }
222   }
223   /* clean up */
224   if (cdc) {
225     DeleteDC(cdc);
226   }
227   else {
228     ReleaseDC(hwnd, wdc);
229   }
230
231   return result;
232 }