1 /* $Id: win_w3mimg.cpp,v 1.2 2010/12/24 09:52:06 htrb Exp $ */
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <ctype.h>
6 #include "config.h"
7 #include <assert.h>
8 #include <locale.h>
9
10 #include <new>
11 #include <algorithm>
12
13 #include "w3mimg/w3mimg.h"
14 #include <windows.h>
15 #include <gdiplus.h>
16 #include <unistd.h>
17 #include <sys/cygwin.h>
18 /* GDI+ can handle BMP, GIF, JPEG, PNG and TIFF by itself. */
19
20 #define OFFSET_X 2
21 #define OFFSET_Y 2
22 #define DEBUG
23
24 #ifdef DEBUG
25 #define THROW_NONE throw()
26 #else
27 #define THROW_NONE
28 #endif
29
30 struct win_info {
31 HWND window;
32 Gdiplus::ARGB background_pixel;
33 ULONG_PTR gdiplus_token;
34 FILE *logfile;
35 };
36
37 struct window_list {
38 HWND *wnd;
39 size_t nwnd;
40 size_t capacity;
41 };
42
43 typedef Gdiplus::CachedBitmap *cache_handle;
44 class win_image {
45 private:
46 win_image(const win_image &); // decl only
47 win_image &operator=(const win_image &); // decl only
48
49 Gdiplus::Bitmap *gpbitmap;
50 unsigned int nframe;
51 unsigned int current;
52 unsigned long tick;
53 unsigned long loopcount; // zero = infinite
54 unsigned long *delay; // unit: millisecond
55 cache_handle *cache;
56
57 public:
58 win_image() THROW_NONE;
59 ~win_image() THROW_NONE;
60 int load(w3mimg_op *wop, Gdiplus::Bitmap **p_gpbitmap,
61 int *wreturn, int *hreturn) THROW_NONE;
62 int show(w3mimg_op *wop, int sx, int sy, int sw, int sh, int x, int y) THROW_NONE;
63 int animate(w3mimg_op *wop) THROW_NONE;
64 };
65
66 static int win_init(w3mimg_op * wop) THROW_NONE;
67 static int win_finish(w3mimg_op * wop) THROW_NONE;
68 static int win_active(w3mimg_op * wop) THROW_NONE;
69 static void win_set_background(w3mimg_op * wop, char *background) THROW_NONE;
70 static void win_sync(w3mimg_op * wop) THROW_NONE;
71 static void win_close(w3mimg_op * wop) THROW_NONE;
72
73 static int win_load_image(w3mimg_op * wop, W3MImage * img, char *fname,
74 int w, int h) THROW_NONE;
75 static int win_show_image(w3mimg_op * wop, W3MImage * img,
76 int sx, int sy, int sw, int sh, int x, int y) THROW_NONE;
77 static void win_free_image(w3mimg_op * wop, W3MImage * img) THROW_NONE;
78 static int win_get_image_size(w3mimg_op * wop, W3MImage * img,
79 char *fname, int *w, int *h) THROW_NONE;
80 static int win_clear(w3mimg_op * wop, int x, int y, int w, int h) THROW_NONE;
81
82 static int window_alive(w3mimg_op *wop) THROW_NONE;
83 static Gdiplus::Bitmap *read_image_file(w3mimg_op *wop, const char *fname) THROW_NONE;
84 static BOOL CALLBACK store_to_window_list(HWND hWnd, LPARAM wndlist) THROW_NONE;
85 static void clear_window_list(struct window_list *wl) THROW_NONE;
86 static const char *gdip_strerror(Gdiplus::Status status) THROW_NONE;
87 static void gdip_perror(w3mimg_op *wop, Gdiplus::Status status, const char *func) THROW_NONE;
88 static char *win32_strerror_alloc(DWORD status) THROW_NONE;
89 static void win32_perror(w3mimg_op *wop, DWORD status, const char *func) THROW_NONE;
90 #if 0 /* unused */
91 static WCHAR *mb2wstr_alloc(const char *) THROW_NONE;
92 static char *wstr2mb_alloc(const WCHAR *) THROW_NONE;
93 #endif
94
95 #define PRELUDE(wop, xi) \
96 assert(wop); \
97 struct win_info *xi = static_cast<struct win_info *>(wop->priv); \
98 assert(xi)
99
win_image()100 win_image::win_image() THROW_NONE
101 : gpbitmap(NULL), nframe(0)
102 {}
103
~win_image()104 win_image::~win_image() THROW_NONE
105 {
106 if (this->cache) {
107 for (size_t i = 0; i != this->nframe; ++i) {
108 delete this->cache[i];
109 }
110 delete[] this->cache;
111 }
112 delete[] this->delay;
113 delete this->gpbitmap;
114 }
115
116 int
load(w3mimg_op * wop,Gdiplus::Bitmap ** p_gpbitmap,int * wreturn,int * hreturn)117 win_image::load(w3mimg_op *wop, Gdiplus::Bitmap **p_gpbitmap, int *wreturn, int *hreturn) THROW_NONE
118 {
119 PRELUDE(wop, xi);
120 Gdiplus::Bitmap *gpbitmap = *p_gpbitmap;
121 assert(gpbitmap);
122 Gdiplus::Status status = Gdiplus::Ok;
123 int retval = 0;
124
125 Gdiplus::PropertyItem *loopcountbuf = NULL;
126 Gdiplus::PropertyItem *delaybuf = NULL;
127 unsigned long *delay = NULL;
128 cache_handle *cache = NULL;
129
130 if (xi->logfile) {
131 fprintf(xi->logfile, "win_image::load(%p, %p, %p, %p) start\n",
132 wop, gpbitmap, wreturn, hreturn);
133 }
134 {
135 unsigned int width = gpbitmap->GetWidth();
136 unsigned int height = gpbitmap->GetHeight();
137 unsigned int nframe = gpbitmap->GetFrameCount(&Gdiplus::FrameDimensionTime);
138 unsigned long loopcount = 0;
139 unsigned int first_frame = 0;
140
141 if (xi->logfile)
142 fprintf(xi->logfile, "win_image::load(): size[0]=%ux%u\n", width, height);
143 if (nframe == 0) {
144 // Not an animated picture
145 if (xi->logfile)
146 fprintf(xi->logfile, "win_image::load(): zero frame count\n");
147 nframe = 1;
148 delay = new(std::nothrow) unsigned long[1];
149 if (delay == NULL)
150 goto last;
151 delay[0] = 0;
152 } else {
153 unsigned int loopcountsize = gpbitmap->GetPropertyItemSize(PropertyTagLoopCount);
154 unsigned int delaysize = gpbitmap->GetPropertyItemSize(PropertyTagFrameDelay);
155
156 // Get loop count
157 if (loopcountsize != 0) {
158 loopcountbuf = (Gdiplus::PropertyItem *)malloc(loopcountsize);
159 if (loopcountbuf == NULL)
160 goto last;
161 status = gpbitmap->GetPropertyItem(PropertyTagLoopCount, loopcountsize, loopcountbuf);
162 if (status != Gdiplus::Ok)
163 goto gdip_error;
164 if (loopcountbuf->type == PropertyTagTypeShort &&
165 loopcountbuf->length >= sizeof(unsigned short)) {
166 loopcount = *(unsigned short *)loopcountbuf->value;
167 } else if (loopcountbuf->type == PropertyTagTypeLong &&
168 loopcountbuf->length >= sizeof(unsigned long)) {
169 loopcount = *(unsigned long *)loopcountbuf->value;
170 }
171 }
172 if (xi->logfile)
173 fprintf(xi->logfile, "win_image::load(): loopcount=%lu\n", loopcount);
174 // Get delay times
175 if (delaysize != 0) {
176 delaybuf = (Gdiplus::PropertyItem *)malloc(delaysize);
177 if (delaybuf == NULL)
178 goto last;
179 status = gpbitmap->GetPropertyItem(PropertyTagFrameDelay, delaysize, delaybuf);
180 if (status != Gdiplus::Ok)
181 goto gdip_error;
182 delay = new(std::nothrow) unsigned long[nframe];
183 if (delay == NULL)
184 goto last;
185 std::fill(delay, delay + nframe, 0);
186 if (delaybuf->type == PropertyTagTypeShort) {
187 unsigned int count = delaybuf->length / sizeof(unsigned short);
188 for (unsigned int i = 0; i != count; ++i)
189 delay[i] = ((unsigned short *)delaybuf->value)[i] * 10;
190 } else if (delaybuf->type == PropertyTagTypeLong) {
191 unsigned int count = delaybuf->length / sizeof(unsigned long);
192 for (unsigned int i = 0; i != count; ++i)
193 delay[i] = ((unsigned long *)delaybuf->value)[i] * 10;
194 }
195 }
196 if (xi->logfile) {
197 for (unsigned int i = 0; i != nframe; ++i)
198 fprintf(xi->logfile, "win_image::load(): delay[%u]=%lu\n", i, delay[i]);
199 }
200 // Get dimensions
201 for (unsigned int nextframe = 1; nextframe != nframe; ++nextframe) {
202 status = gpbitmap->SelectActiveFrame(&Gdiplus::FrameDimensionTime, nextframe);
203 if (status != Gdiplus::Ok) {
204 if (xi->logfile)
205 fprintf(xi->logfile, "win_image::load(): SelectActiveFrame() to %u failed = %d: %s\n",
206 nextframe, (int)status, gdip_strerror(status));
207 goto last;
208 }
209 unsigned int iw = gpbitmap->GetWidth();
210 unsigned int ih = gpbitmap->GetHeight();
211 if (iw > width)
212 width = iw;
213 if (ih > height)
214 height = ih;
215 if (xi->logfile)
216 fprintf(xi->logfile, "win_image::load(): size[%u]=%ux%u\n", nextframe, iw, ih);
217 }
218 // Go to the first frame
219 first_frame = (0 < -wop->max_anim && -wop->max_anim < nframe) ? (nframe + wop->max_anim) : 0;
220 status = gpbitmap->SelectActiveFrame(&Gdiplus::FrameDimensionTime, first_frame);
221 if (status != Gdiplus::Ok) {
222 if (xi->logfile)
223 fprintf(xi->logfile, "win_image::load(): SelectActiveFrame() to %u frame = %d: %s\n",
224 first_frame, (int)status, gdip_strerror(status));
225 goto last;
226 }
227 }
228 // Allocate cache array
229 cache = new(std::nothrow) cache_handle[nframe];
230 if (cache == NULL)
231 goto last;
232 std::fill(cache, cache + nframe, (cache_handle)NULL);
233 // Sanity check
234 if (width > SHRT_MAX || height > SHRT_MAX) {
235 if (xi->logfile)
236 fprintf(xi->logfile, "win_image::load(): too big image: %ux%u\n", width, height);
237 goto last;
238 }
239 // Store the results
240 if (wreturn)
241 *wreturn = (int)width;
242 if (hreturn)
243 *hreturn = (int)height;
244 this->gpbitmap = gpbitmap;
245 *p_gpbitmap = NULL; // ownership transfer
246 this->nframe = nframe;
247 this->current = first_frame;
248 this->tick = 0;
249 this->loopcount = loopcount;
250 this->delay = delay;
251 delay = NULL; // ownership transfer
252 this->cache = cache;
253 cache = NULL; // ownership transfer
254 retval = 1;
255 }
256 goto last;
257
258 gdip_error:
259 gdip_perror(wop, status, "win_image::load");
260 goto last;
261 last:
262 delete[] cache;
263 delete[] delay;
264 free(delaybuf);
265 free(loopcountbuf);
266 if (xi->logfile)
267 fprintf(xi->logfile, "win_image::load() = %d\n", retval);
268 return retval;
269 }
270
271 int
show(w3mimg_op * wop,int sx,int sy,int sw,int sh,int x,int y)272 win_image::show(w3mimg_op *wop, int sx, int sy, int sw, int sh, int x, int y) THROW_NONE
273 {
274 PRELUDE(wop, xi);
275 int retval = 0;
276 Gdiplus::Status status = Gdiplus::Ok;
277 cache_handle newcache = NULL;
278
279 if (xi->logfile)
280 fprintf(xi->logfile, "win_image::show(%p, %d, %d, %d, %d, %d, %d) start current=%u\n",
281 wop, sx, sy, sw, sh, x, y, this->current);
282 if (!window_alive(wop))
283 goto last;
284 {
285 int xx = x + wop->offset_x;
286 int yy = y + wop->offset_y;
287
288 // Prepare the Graphics object for painting
289 Gdiplus::Graphics graphics(xi->window);
290 if ((status = graphics.GetLastStatus()) != Gdiplus::Ok)
291 goto gdip_error;
292 Gdiplus::Rect clip(xx, yy, sw, sh);
293 status = graphics.SetClip(clip);
294 if (status != Gdiplus::Ok)
295 goto gdip_error;
296
297 unsigned int retry_count = 2;
298 do {
299 if (this->cache[this->current] == NULL) {
300 // Cache the image
301 Gdiplus::Bitmap tmp_bitmap(sw, sh, &graphics);
302 if ((status = tmp_bitmap.GetLastStatus()) != Gdiplus::Ok)
303 goto gdip_error;
304 Gdiplus::Graphics tmp_graphics(&tmp_bitmap);
305 if ((status = tmp_graphics.GetLastStatus()) != Gdiplus::Ok)
306 goto gdip_error;
307 status = tmp_graphics.Clear(Gdiplus::Color(xi->background_pixel));
308 if (status != Gdiplus::Ok)
309 goto gdip_error;
310 status = tmp_graphics.DrawImage(this->gpbitmap, 0, 0, sw, sh);
311 if (status != Gdiplus::Ok)
312 goto gdip_error;
313 Gdiplus::CachedBitmap *newcache = new Gdiplus::CachedBitmap(&tmp_bitmap, &graphics);
314 if (newcache == NULL)
315 goto last;
316 if ((status = newcache->GetLastStatus()) != Gdiplus::Ok)
317 goto gdip_error;
318 this->cache[this->current] = newcache;
319 newcache = NULL; // ownership transfer
320 --retry_count;
321 }
322 // Draw it
323 status = graphics.DrawCachedBitmap(this->cache[this->current], xx - sx, yy - sy);
324 if (status == Gdiplus::Ok)
325 break;
326 // maybe the user altered the display configuration
327 if (xi->logfile)
328 fprintf(xi->logfile, "win_image::show(): stale cache = %d: %s\n",
329 (int)status, gdip_strerror(status));
330 delete this->cache[this->current];
331 this->cache[this->current] = NULL;
332 if (retry_count == 0)
333 goto last;
334 } while (1);
335
336 retval = 1;
337 }
338 goto last;
339 gdip_error:
340 gdip_perror(wop, status, "win_image::show");
341 goto last;
342 last:
343 delete newcache;
344 if (xi->logfile)
345 fprintf(xi->logfile, "win_image::show() = %d\n", retval);
346 return retval;
347 }
348
349 int
animate(w3mimg_op * wop)350 win_image::animate(w3mimg_op * wop) THROW_NONE
351 {
352 PRELUDE(wop, xi);
353 int retval = 0;
354 Gdiplus::Status status = Gdiplus::Ok;
355
356 if (xi->logfile)
357 fprintf(xi->logfile, "win_image::animate(%p) start\n", wop);
358 {
359 if (this->nframe <= 1)
360 goto animation_end;
361 #define UNIT_TICK 50
362 #define MIN_DELAY (UNIT_TICK*2)
363 this->tick += UNIT_TICK;
364 if (this->tick >= MIN_DELAY && this->tick >= this->delay[this->current]) {
365 this->tick = 0;
366 unsigned int nextframe = this->current + 1;
367 if (wop->max_anim == nextframe)
368 goto animation_end;
369 if (nextframe >= this->nframe) {
370 if (this->loopcount == 1 || wop->max_anim < 0) // end of the loop
371 goto animation_end;
372 nextframe = 0;
373 }
374 status = this->gpbitmap->SelectActiveFrame(&Gdiplus::FrameDimensionTime, nextframe);
375 if (status != Gdiplus::Ok)
376 goto gdip_error;
377 this->current = nextframe;
378 if (nextframe == 0 && this->loopcount > 1)
379 --this->loopcount;
380 }
381 animation_end:
382 retval = 1;
383 }
384 goto last;
385 gdip_error:
386 gdip_perror(wop, status, "win_image::animate");
387 goto last;
388 last:
389 if (xi->logfile)
390 fprintf(xi->logfile, "win_image::animate() = %d\n", retval);
391 return retval;
392 }
393
394 static int
window_alive(w3mimg_op * wop)395 window_alive(w3mimg_op *wop) THROW_NONE
396 {
397 PRELUDE(wop, xi);
398 if (xi->window == NULL)
399 return 0;
400 if (IsWindow(xi->window))
401 return 1;
402 xi->window = NULL;
403 fputs("w3mimgdisplay: target window disappeared\n", stderr);
404 if (xi->logfile)
405 fputs("w3mimgdisplay: target window disappeared\n", xi->logfile);
406 return 0;
407 }
408
409 static int
win_init(w3mimg_op *)410 win_init(w3mimg_op *) THROW_NONE
411 {
412 // nothing to do
413 return 1;
414 }
415
416 static int
win_finish(w3mimg_op *)417 win_finish(w3mimg_op *) THROW_NONE
418 {
419 // nothing to do
420 return 1;
421 }
422
423 static int
win_clear(w3mimg_op * wop,int x,int y,int w,int h)424 win_clear(w3mimg_op *wop, int x, int y, int w, int h) THROW_NONE
425 {
426 PRELUDE(wop, xi);
427 Gdiplus::Status status = Gdiplus::Ok;
428 int retval = 0;
429
430 if (xi->logfile)
431 fprintf(xi->logfile, "win_clear(%p, %d, %d, %d, %d) start\n",
432 wop, x, y, w, h);
433 if (!window_alive(wop))
434 goto last;
435 {
436 if (x < 0)
437 x = 0;
438 if (y < 0)
439 y = 0;
440 Gdiplus::SolidBrush brush(Gdiplus::Color(xi->background_pixel));
441 if ((status = brush.GetLastStatus()) != Gdiplus::Ok)
442 goto gdip_error;
443 Gdiplus::Graphics graphics(xi->window);
444 if ((status = graphics.GetLastStatus()) != Gdiplus::Ok)
445 goto gdip_error;
446 status = graphics.FillRectangle(&brush, x + wop->offset_x, y + wop->offset_y, w, h);
447 if (status != Gdiplus::Ok)
448 goto gdip_error;
449 retval = 1;
450 }
451 goto last;
452 gdip_error:
453 gdip_perror(wop, status, "win_clear");
454 goto last;
455 last:
456 if (xi->logfile)
457 fprintf(xi->logfile, "win_clear() = %d\n", retval);
458 return retval;
459 }
460
461 static int
win_active(w3mimg_op * wop)462 win_active(w3mimg_op * wop) THROW_NONE
463 {
464 return window_alive(wop);
465 }
466
467 static void
win_set_background(w3mimg_op * wop,char * background)468 win_set_background(w3mimg_op * wop, char *background) THROW_NONE
469 {
470 PRELUDE(wop, xi);
471
472 HDC windc = NULL;
473
474 if (xi->logfile)
475 fprintf(xi->logfile, "win_set_background(%p, \"%s\")\n", wop, background ? background : "(auto)");
476 {
477 // Fallback value
478 // xi->background_pixel = Gdiplus::Color::White;
479 xi->background_pixel = Gdiplus::Color::Black;
480
481 // Explicit
482 if (background) {
483 unsigned int r, g, b;
484 if (sscanf(background, "#%02x%02x%02x", &r, &g, &b) == 3) {
485 xi->background_pixel = Gdiplus::Color::MakeARGB((BYTE)255, (BYTE)r, (BYTE)g, (BYTE)b);
486 goto last;
487 }
488 }
489
490 // Auto detect
491 if (xi->window == NULL || !IsWindow(xi->window))
492 goto last;
493 windc = GetDC(xi->window);
494 if (windc == NULL)
495 goto win32_error;
496 COLORREF c = GetPixel(windc,
497 (wop->offset_x >= 1) ? (wop->offset_x - 1) : 0,
498 (wop->offset_y >= 1) ? (wop->offset_y - 1) : 0);
499 xi->background_pixel = Gdiplus::Color::MakeARGB(
500 (BYTE)255, GetRValue(c), GetGValue(c), GetBValue(c));
501 }
502 goto last;
503 win32_error:
504 win32_perror(wop, GetLastError(), "win_set_background");
505 goto last;
506 last:
507 if (xi->logfile)
508 fprintf(xi->logfile, "win_set_background() result = #%06x\n",
509 (unsigned int)xi->background_pixel);
510 if (windc)
511 ReleaseDC(xi->window, windc);
512 }
513
514 static void
win_sync(w3mimg_op *)515 win_sync(w3mimg_op *) THROW_NONE
516 {
517 // nothing to do
518 return;
519 }
520
521 static void
win_close(w3mimg_op * wop)522 win_close(w3mimg_op * wop) THROW_NONE
523 {
524 PRELUDE(wop, xi);
525
526 if (xi->gdiplus_token)
527 Gdiplus::GdiplusShutdown(xi->gdiplus_token);
528 if (xi->logfile) {
529 fprintf(xi->logfile, "win_close(%p)\n", wop);
530 fclose(xi->logfile);
531 }
532 delete xi;
533 delete wop;
534 }
535
536 static Gdiplus::Bitmap *
read_image_file(w3mimg_op * wop,const char * fname)537 read_image_file(w3mimg_op *wop, const char *fname) THROW_NONE
538 {
539 PRELUDE(wop, xi);
540 Gdiplus::Status status = Gdiplus::Ok;
541 Gdiplus::Bitmap *retval = NULL;
542
543 WCHAR *wfname = NULL;
544 Gdiplus::Bitmap *gpbitmap = NULL;
545
546 if (xi->logfile)
547 fprintf(xi->logfile, "read_image_file(%p, \"%s\") start\n", wop, fname);
548 {
549 wfname = (WCHAR *)cygwin_create_path(CCP_POSIX_TO_WIN_W, fname);
550 if (wfname == NULL)
551 goto last;
552 gpbitmap = new Gdiplus::Bitmap(wfname);
553 if (gpbitmap == NULL)
554 goto last;
555 status = gpbitmap->GetLastStatus();
556 switch (status) {
557 case Gdiplus::Ok:
558 break;
559 case Gdiplus::UnknownImageFormat:
560 case Gdiplus::FileNotFound:
561 goto last; // fail silently
562 default:
563 goto gdip_error;
564 }
565 retval = gpbitmap;
566 gpbitmap = NULL; // ownership transfer
567 }
568 goto last;
569 gdip_error:
570 gdip_perror(wop, status, "read_image_file");
571 last:
572 delete gpbitmap;
573 free(wfname);
574 if (xi->logfile)
575 fprintf(xi->logfile, "read_image_file() = %p\n", retval);
576 return retval;
577 }
578
579 static int
win_load_image(w3mimg_op * wop,W3MImage * img,char * fname,int w,int h)580 win_load_image(w3mimg_op * wop, W3MImage * img, char *fname, int w, int h) THROW_NONE
581 {
582 PRELUDE(wop, xi);
583 int retval = 0;
584 Gdiplus::Bitmap *gpbitmap = NULL;
585 win_image *wimg = NULL;
586
587 assert(img);
588 if (xi->logfile) {
589 fprintf(xi->logfile, "win_load_image(%p, %p, \"%s\", %d, %d) start\n",
590 wop, img, fname, w, h);
591 }
592 {
593 gpbitmap = read_image_file(wop, fname);
594 if (gpbitmap == NULL)
595 goto last;
596 int iw, ih;
597 wimg = new(std::nothrow) win_image;
598 if (!wimg->load(wop, &gpbitmap, &iw, &ih))
599 goto last;
600 img->pixmap = wimg;
601 wimg = NULL; // ownership transfer
602 img->width = (0 <= w && w < iw) ? w : iw;
603 img->height = (0 <= h && h < ih) ? h : ih;
604 retval = 1;
605 }
606 goto last;
607 last:
608 delete wimg;
609 delete gpbitmap;
610 if (xi->logfile)
611 fprintf(xi->logfile, "win_load_image() = %d\n", retval);
612 return retval;
613 }
614
615 static int
win_show_image(w3mimg_op * wop,W3MImage * img,int sx,int sy,int sw,int sh,int x,int y)616 win_show_image(w3mimg_op * wop, W3MImage * img, int sx, int sy, int sw,
617 int sh, int x, int y) THROW_NONE
618 {
619 PRELUDE(wop, xi);
620 int retval = 0;
621
622 assert(img);
623 win_image *wimg = static_cast<win_image *>(img->pixmap);
624 assert(wimg);
625
626 if (xi->logfile)
627 fprintf(xi->logfile, "win_show_image(%p, %p, %d, %d, %d, %d, %d, %d) start\n",
628 wop, img, sx, sy, sw, sh, x, y);
629 int sww = sw ? sw : img->width;
630 int shh = sh ? sh : img->height;
631 retval = wimg->show(wop, sx, sy, sww, shh, x, y)
632 && wimg->animate(wop);
633 if (xi->logfile)
634 fprintf(xi->logfile, "win_show_image() = %d\n", retval);
635 return retval;
636 }
637
638 static void
win_free_image(w3mimg_op * wop,W3MImage * img)639 win_free_image(w3mimg_op * wop, W3MImage * img) THROW_NONE
640 {
641 PRELUDE(wop, xi);
642
643 assert(img);
644 if (xi->logfile)
645 fprintf(xi->logfile, "win_free_image(%p, %p) pixmap=%p\n", wop, img, img->pixmap);
646 delete static_cast<win_image *>(img->pixmap);
647 img->pixmap = NULL;
648 img->width = 0;
649 img->height = 0;
650 }
651
652 static int
win_get_image_size(w3mimg_op * wop,W3MImage * img_unused,char * fname,int * w,int * h)653 win_get_image_size(w3mimg_op * wop, W3MImage *img_unused, char *fname, int *w, int *h) THROW_NONE
654 {
655 PRELUDE(wop, xi);
656 int retval = 0;
657 Gdiplus::Bitmap *gpbitmap = NULL;
658 win_image *wimg = NULL;
659
660 if (xi->logfile) {
661 fprintf(xi->logfile, "win_get_image_size(%p, %p, \"%s\", %p, %p) start\n",
662 wop, img_unused, fname, w, h);
663 }
664 {
665 gpbitmap = read_image_file(wop, fname);
666 if (gpbitmap == NULL)
667 goto last;
668 wimg = new(std::nothrow) win_image;
669 if (wimg == NULL)
670 goto last;
671 retval = wimg->load(wop, &gpbitmap, w, h);;
672 }
673 goto last;
674 last:
675 delete wimg;
676 delete gpbitmap;
677 if (xi->logfile)
678 fprintf(xi->logfile, "win_get_image_size() = %d\n", retval);
679 return retval;
680 }
681
682 extern "C" w3mimg_op *
w3mimg_winopen()683 w3mimg_winopen()
684 {
685 w3mimg_op *retval = NULL;
686 Gdiplus::Status status = Gdiplus::Ok;
687
688 w3mimg_op *wop = NULL;
689 struct win_info *xi = NULL;
690 struct window_list children = { NULL, 0, 0 };
691
692 {
693 // Quit if running on X
694 const char *display_name;
695 if ((display_name = getenv("DISPLAY")) != NULL &&
696 display_name[0] && strcmp(display_name, ":0") != 0)
697 return NULL;
698
699 // Allocate the context objects
700 wop = new(std::nothrow) w3mimg_op(); // call the default ctor instead of "new w3mimg_op;"
701 if (wop == NULL)
702 return NULL;
703 wop->priv = xi = new(std::nothrow) win_info();
704 if (xi == NULL)
705 goto last;
706
707 // Debug logging
708 const char *logging_dir;
709 if ((logging_dir = getenv("W3MIMG_LOGDIR")) != NULL &&
710 logging_dir[0]) {
711 size_t l = strlen(logging_dir) + sizeof "/w3mimgXXXXXXXXXX.log";
712 char *fname = (char *)malloc(l);
713 snprintf(fname, l, "%s/w3mimg%d.log", logging_dir, (int)getpid());
714 xi->logfile = fopen(fname, "a");
715 if (xi->logfile) {
716 setvbuf(xi->logfile, NULL, _IONBF, 0);
717 fprintf(xi->logfile, "\nw3mimg_winopen() start pid=%d\n", (int)getpid());
718 }
719 }
720
721 // Look for the window to draw the image
722 xi->window = NULL;
723 const char *windowid;
724 if ((windowid = getenv("WINDOWID")) != NULL)
725 xi->window = FindWindowA(windowid, NULL);
726 if (!xi->window)
727 xi->window = GetForegroundWindow();
728 if (!xi->window)
729 goto win32_error;
730
731 WINDOWINFO winfo = WINDOWINFO();
732 winfo.cbSize = sizeof winfo;
733 GetWindowInfo(xi->window, &winfo);
734 wop->width = (int)(winfo.rcClient.right - winfo.rcClient.left);
735 wop->height = (int)(winfo.rcClient.bottom - winfo.rcClient.top);
736
737 // Search decendant windows and find out which is the text window
738 while (1) {
739 HWND p_window = xi->window;
740
741 clear_window_list(&children);
742 EnumChildWindows(xi->window, &store_to_window_list, (LPARAM)&children);
743 for (unsigned int i = 0; i < children.nwnd; i++) {
744 int width, height;
745
746 GetWindowInfo(children.wnd[i], &winfo);
747 width = (int)(winfo.rcClient.right - winfo.rcClient.left);
748 height = (int)(winfo.rcClient.bottom - winfo.rcClient.top);
749 if (width > wop->width * 0.7 &&
750 height > wop->height * 0.7) {
751 /* maybe text window */
752 wop->width = width;
753 wop->height = height;
754 xi->window = children.wnd[i];
755 }
756 }
757 if (p_window == xi->window)
758 break;
759 }
760
761 // Terminal may leave some border pixels
762 wop->offset_x = OFFSET_X;
763 wop->offset_y = OFFSET_Y;
764
765 // Start up the GDI+
766 Gdiplus::GdiplusStartupInput startup_input; /// default ctor
767 status = Gdiplus::GdiplusStartup(&xi->gdiplus_token, &startup_input, NULL);
768 if (status != Gdiplus::Ok)
769 goto gdip_error;
770
771 // Fill the context object
772 wop->init = win_init;
773 wop->finish = win_finish;
774 wop->active = win_active;
775 wop->set_background = win_set_background;
776 wop->sync = win_sync;
777 wop->close = win_close;
778 wop->clear = win_clear;
779
780 wop->load_image = win_load_image;
781 wop->show_image = win_show_image;
782 wop->free_image = win_free_image;
783 wop->get_image_size = win_get_image_size;
784
785 retval = wop; // take care of the object lifetime
786 }
787 goto last;
788 win32_error:
789 win32_perror(wop, GetLastError(), "w3mimg_winopen");
790 goto last;
791 gdip_error:
792 gdip_perror(wop, status, "w3mimg_winopen");
793 goto last;
794 last:
795 if (xi && xi->logfile)
796 fprintf(xi->logfile, "w3mimg_winopen() = %p\n", retval);
797 clear_window_list(&children);
798 if (!retval) {
799 if (xi) {
800 if (xi->gdiplus_token)
801 Gdiplus::GdiplusShutdown(xi->gdiplus_token);
802 if (xi->logfile)
803 fclose(xi->logfile);
804 delete xi;
805 }
806 delete wop;
807 }
808 return retval;
809 }
810
811 static BOOL CALLBACK
store_to_window_list(HWND hWnd,LPARAM wndlist)812 store_to_window_list(HWND hWnd, LPARAM wndlist) THROW_NONE
813 {
814 struct window_list *wl = (struct window_list *)wndlist;
815
816 if (wl->nwnd >= wl->capacity) {
817 size_t newsize = (wl->capacity < 4 ) ? 4 : (wl->capacity * 2);
818 HWND *newbuf = (HWND *)realloc(wl->wnd, newsize * sizeof newbuf[0]);
819 if (newbuf == NULL) {
820 clear_window_list(wl);
821 return FALSE;
822 }
823 wl->wnd = newbuf;
824 wl->capacity = newsize;
825 }
826 wl->wnd[wl->nwnd++] = hWnd;
827 return TRUE;
828 }
829
830 static void
clear_window_list(struct window_list * wl)831 clear_window_list(struct window_list *wl) THROW_NONE
832 {
833 free(wl->wnd);
834 wl->wnd = NULL;
835 wl->nwnd = 0;
836 wl->capacity = 0;
837 }
838
839 static const char *
gdip_strerror(Gdiplus::Status status)840 gdip_strerror(Gdiplus::Status status) THROW_NONE
841 {
842 size_t i;
843 struct status_rec {
844 Gdiplus::Status code;
845 const char *str;
846 };
847 static const struct status_rec table[] = {
848 #define ERRITEM(s) { Gdiplus::s, #s }
849 ERRITEM(Ok),
850 ERRITEM(GenericError),
851 ERRITEM(InvalidParameter),
852 ERRITEM(OutOfMemory),
853 ERRITEM(ObjectBusy),
854 ERRITEM(InsufficientBuffer),
855 ERRITEM(NotImplemented),
856 ERRITEM(Win32Error),
857 ERRITEM(WrongState),
858 ERRITEM(Aborted),
859 ERRITEM(FileNotFound),
860 ERRITEM(ValueOverflow),
861 ERRITEM(AccessDenied),
862 ERRITEM(UnknownImageFormat),
863 ERRITEM(FontFamilyNotFound),
864 ERRITEM(FontStyleNotFound),
865 ERRITEM(NotTrueTypeFont),
866 ERRITEM(UnsupportedGdiplusVersion),
867 ERRITEM(GdiplusNotInitialized),
868 ERRITEM(PropertyNotFound),
869 ERRITEM(PropertyNotSupported),
870 ERRITEM(ProfileNotFound),
871 #undef ERRITEM
872 };
873 for (i = 0; i != sizeof table / sizeof table[0]; ++i)
874 if (table[i].code == status)
875 return table[i].str;
876 return "unknown";
877 }
878
879 static void
gdip_perror(w3mimg_op * wop,Gdiplus::Status status,const char * func)880 gdip_perror(w3mimg_op *wop, Gdiplus::Status status, const char *func) THROW_NONE
881 {
882 const char *s = gdip_strerror(status);
883 fprintf(stderr, "w3mimgdisplay: GDI+ error %d: %s\n", (int)status, s);
884 if (wop && wop->priv) {
885 struct win_info *xi = (struct win_info *)wop->priv;
886 if (xi->logfile) {
887 fprintf(xi->logfile, "%s(): GDI+ error %d: %s\n", func, (int)status, s);
888 }
889 }
890 }
891
892 // Don't free() the result; use LocalFree() instead
893 static char *
win32_strerror_alloc(DWORD status)894 win32_strerror_alloc(DWORD status) THROW_NONE
895 {
896 char *errbuf = NULL;
897
898 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
899 FORMAT_MESSAGE_FROM_SYSTEM |
900 FORMAT_MESSAGE_IGNORE_INSERTS,
901 NULL, status, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
902 (LPSTR)&errbuf, 0, NULL);
903 if (errbuf) {
904 size_t len = strlen(errbuf);
905 if (len > 0 && errbuf[len - 1] == '\n')
906 errbuf[len - 1] = '\0';
907 }
908 return errbuf;
909 }
910
911 static void
win32_perror(w3mimg_op * wop,DWORD status,const char * func)912 win32_perror(w3mimg_op *wop, DWORD status, const char *func) THROW_NONE
913 {
914 char *errbuf = win32_strerror_alloc(status);
915 const char *s = errbuf ? errbuf : "(unknown)";
916
917 fprintf(stderr, "w3mimgdisplay: Win32 error %u: %s\n", (unsigned int)status, s);
918 if (wop && wop->priv) {
919 struct win_info *xi = (struct win_info *)wop->priv;
920 if (xi->logfile) {
921 fprintf(xi->logfile, "%s(): Win32 error %u: %s\n",
922 func, (unsigned int)status, s);
923 }
924 }
925 LocalFree(errbuf);
926 }
927
928 #if 0 /* unused */
929 static WCHAR *
930 mb2wstr_alloc(const char *s) THROW_NONE
931 {
932 int len;
933 WCHAR *buf = NULL;
934
935 len = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, s, -1, NULL, 0);
936 if (len <= 0) {
937 fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
938 (unsigned int)GetLastError());
939 goto error;
940 }
941 buf = (WCHAR *)malloc(len * sizeof(WCHAR)); /* including L'\0' */
942 if (!buf)
943 goto error;
944 len = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, s, -1, buf, len);
945 if (len <= 0) {
946 fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
947 (unsigned int)GetLastError());
948 goto error;
949 }
950 return buf;
951 error:
952 free(buf);
953 return NULL;
954 }
955
956 static char *
957 wstr2mb_alloc(const WCHAR *ws) THROW_NONE
958 {
959 int len;
960 char *buf = NULL;
961
962 len = WideCharToMultiByte(CP_OEMCP, WC_COMPOSITECHECK, ws, -1, NULL, 0, NULL, NULL);
963 if (len <= 0) {
964 fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
965 (unsigned int)GetLastError());
966 goto error;
967 }
968 buf = (char *)malloc(len); /* including '\0' */
969 if (!buf)
970 goto error;
971 len = WideCharToMultiByte(CP_OEMCP, WC_COMPOSITECHECK, ws, -1, buf, len, NULL, NULL);
972 if (len <= 0) {
973 fprintf(stderr, "w3mimgdisplay: unable to convert string ecode=%u\n",
974 (unsigned int)GetLastError());
975 goto error;
976 }
977 return buf;
978 error:
979 free(buf);
980 return NULL;
981 }
982 #endif /* unused */
983