1 /*---------------------------------------------------------------------------
2
3 rpng - simple PNG display program rpng-win.c
4
5 This program decodes and displays PNG images, with gamma correction and
6 optionally with a user-specified background color (in case the image has
7 transparency). It is very nearly the most basic PNG viewer possible.
8 This version is for 32-bit Windows; it may compile under 16-bit Windows
9 with a little tweaking (or maybe not).
10
11 to do:
12 - handle quoted command-line args (especially filenames with spaces)
13 - have minimum window width: oh well
14 - use %.1023s to simplify truncation of title-bar string?
15
16 ---------------------------------------------------------------------------
17
18 Changelog:
19 - 1.00: initial public release
20 - 1.01: modified to allow abbreviated options; fixed long/ulong mis-
21 match; switched to png_jmpbuf() macro
22 - 1.02: added extra set of parentheses to png_jmpbuf() macro; fixed
23 command-line parsing bug
24 - 1.10: enabled "message window"/console (thanks to David Geldreich)
25 - 2.00: dual-licensed (added GNU GPL)
26 - 2.01: fixed improper display of usage screen on PNG error(s)
27 - 2.02: check for integer overflow (Glenn R-P)
28
29 ---------------------------------------------------------------------------
30
31 Copyright (c) 1998-2008, 2017 Greg Roelofs. All rights reserved.
32
33 This software is provided "as is," without warranty of any kind,
34 express or implied. In no event shall the author or contributors
35 be held liable for any damages arising in any way from the use of
36 this software.
37
38 The contents of this file are DUAL-LICENSED. You may modify and/or
39 redistribute this software according to the terms of one of the
40 following two licenses (at your option):
41
42
43 LICENSE 1 ("BSD-like with advertising clause"):
44
45 Permission is granted to anyone to use this software for any purpose,
46 including commercial applications, and to alter it and redistribute
47 it freely, subject to the following restrictions:
48
49 1. Redistributions of source code must retain the above copyright
50 notice, disclaimer, and this list of conditions.
51 2. Redistributions in binary form must reproduce the above copyright
52 notice, disclaimer, and this list of conditions in the documenta-
53 tion and/or other materials provided with the distribution.
54 3. All advertising materials mentioning features or use of this
55 software must display the following acknowledgment:
56
57 This product includes software developed by Greg Roelofs
58 and contributors for the book, "PNG: The Definitive Guide,"
59 published by O'Reilly and Associates.
60
61
62 LICENSE 2 (GNU GPL v2 or later):
63
64 This program is free software; you can redistribute it and/or modify
65 it under the terms of the GNU General Public License as published by
66 the Free Software Foundation; either version 2 of the License, or
67 (at your option) any later version.
68
69 This program is distributed in the hope that it will be useful,
70 but WITHOUT ANY WARRANTY; without even the implied warranty of
71 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
72 GNU General Public License for more details.
73
74 You should have received a copy of the GNU General Public License
75 along with this program; if not, write to the Free Software Foundation,
76 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
77
78 ---------------------------------------------------------------------------*/
79
80 #define PROGNAME "rpng-win"
81 #define LONGNAME "Simple PNG Viewer for Windows"
82 #define VERSION "2.01 of 16 March 2008"
83
84 #include <stdio.h>
85 #include <stdlib.h>
86 #include <string.h>
87 #include <time.h>
88 #include <windows.h>
89 #ifdef __CYGWIN__
90 /* getch replacement. Turns out, we don't really need this,
91 * but leave it here if we ever enable any of the uses of
92 * _getch in the main code
93 */
94 #include <unistd.h>
95 #include <termio.h>
96 #include <sys/ioctl.h>
repl_getch(void)97 int repl_getch( void )
98 {
99 char ch;
100 int fd = fileno(stdin);
101 struct termio old_tty, new_tty;
102
103 ioctl(fd, TCGETA, &old_tty);
104 new_tty = old_tty;
105 new_tty.c_lflag &= ~(ICANON | ECHO | ISIG);
106 ioctl(fd, TCSETA, &new_tty);
107 fread(&ch, 1, sizeof(ch), stdin);
108 ioctl(fd, TCSETA, &old_tty);
109
110 return ch;
111 }
112 #define _getch repl_getch
113 #else
114 #include <conio.h> /* only for _getch() */
115 #endif
116
117 /* #define DEBUG : this enables the Trace() macros */
118
119 #include "readpng.h" /* typedefs, common macros, readpng prototypes */
120
121
122 /* could just include png.h, but this macro is the only thing we need
123 * (name and typedefs changed to local versions); note that side effects
124 * only happen with alpha (which could easily be avoided with
125 * "ush acopy = (alpha);") */
126
127 #define alpha_composite(composite, fg, alpha, bg) { \
128 ush temp = ((ush)(fg)*(ush)(alpha) + \
129 (ush)(bg)*(ush)(255 - (ush)(alpha)) + (ush)128); \
130 (composite) = (uch)((temp + (temp >> 8)) >> 8); \
131 }
132
133
134 /* local prototypes */
135 static int rpng_win_create_window(HINSTANCE hInst, int showmode);
136 static int rpng_win_display_image(void);
137 static void rpng_win_cleanup(void);
138 LRESULT CALLBACK rpng_win_wndproc(HWND, UINT, WPARAM, LPARAM);
139
140
141 static char titlebar[1024];
142 static char *progname = PROGNAME;
143 static char *appname = LONGNAME;
144 static char *filename;
145 static FILE *infile;
146
147 static char *bgstr;
148 static uch bg_red=0, bg_green=0, bg_blue=0;
149
150 static double display_exponent;
151
152 static ulg image_width, image_height, image_rowbytes;
153 static int image_channels;
154 static uch *image_data;
155
156 /* Windows-specific variables */
157 static ulg wimage_rowbytes;
158 static uch *dib;
159 static uch *wimage_data;
160 static BITMAPINFOHEADER *bmih;
161
162 static HWND global_hwnd;
163
164
165
166
WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,PSTR cmd,int showmode)167 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR cmd, int showmode)
168 {
169 char *args[1024]; /* arbitrary limit, but should suffice */
170 char *p, *q, **argv = args;
171 int argc = 0;
172 int rc, alen, flen;
173 int error = 0;
174 int have_bg = FALSE;
175 double LUT_exponent; /* just the lookup table */
176 double CRT_exponent = 2.2; /* just the monitor */
177 double default_display_exponent; /* whole display system */
178 MSG msg;
179
180
181 filename = (char *)NULL;
182
183 #ifndef __CYGWIN__
184 /* First reenable console output, which normally goes to the bit bucket
185 * for windowed apps. Closing the console window will terminate the
186 * app. Thanks to David.Geldreich at realviz.com for supplying the magical
187 * incantation. */
188
189 AllocConsole();
190 freopen("CONOUT$", "a", stderr);
191 freopen("CONOUT$", "a", stdout);
192 #endif
193
194
195 /* Next set the default value for our display-system exponent, i.e.,
196 * the product of the CRT exponent and the exponent corresponding to
197 * the frame-buffer's lookup table (LUT), if any. This is not an
198 * exhaustive list of LUT values (e.g., OpenStep has a lot of weird
199 * ones), but it should cover 99% of the current possibilities. And
200 * yes, these ifdefs are completely wasted in a Windows program... */
201
202 #if defined(NeXT)
203 LUT_exponent = 1.0 / 2.2;
204 /*
205 if (some_next_function_that_returns_gamma(&next_gamma))
206 LUT_exponent = 1.0 / next_gamma;
207 */
208 #elif defined(sgi)
209 LUT_exponent = 1.0 / 1.7;
210 /* there doesn't seem to be any documented function to get the
211 * "gamma" value, so we do it the hard way */
212 infile = fopen("/etc/config/system.glGammaVal", "r");
213 if (infile) {
214 double sgi_gamma;
215
216 fgets(tmpline, 80, infile);
217 fclose(infile);
218 sgi_gamma = atof(tmpline);
219 if (sgi_gamma > 0.0)
220 LUT_exponent = 1.0 / sgi_gamma;
221 }
222 #elif defined(Macintosh)
223 LUT_exponent = 1.8 / 2.61;
224 /*
225 if (some_mac_function_that_returns_gamma(&mac_gamma))
226 LUT_exponent = mac_gamma / 2.61;
227 */
228 #else
229 LUT_exponent = 1.0; /* assume no LUT: most PCs */
230 #endif
231
232 /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
233 default_display_exponent = LUT_exponent * CRT_exponent;
234
235
236 /* If the user has set the SCREEN_GAMMA environment variable as suggested
237 * (somewhat imprecisely) in the libpng documentation, use that; otherwise
238 * use the default value we just calculated. Either way, the user may
239 * override this via a command-line option. */
240
241 if ((p = getenv("SCREEN_GAMMA")) != NULL)
242 display_exponent = atof(p);
243 else
244 display_exponent = default_display_exponent;
245
246
247 /* Windows really hates command lines, so we have to set up our own argv.
248 * Note that we do NOT bother with quoted arguments here, so don't use
249 * filenames with spaces in 'em! */
250
251 argv[argc++] = PROGNAME;
252 p = cmd;
253 for (;;) {
254 if (*p == ' ')
255 while (*++p == ' ')
256 ;
257 /* now p points at the first non-space after some spaces */
258 if (*p == '\0')
259 break; /* nothing after the spaces: done */
260 argv[argc++] = q = p;
261 while (*q && *q != ' ')
262 ++q;
263 /* now q points at a space or the end of the string */
264 if (*q == '\0')
265 break; /* last argv already terminated; quit */
266 *q = '\0'; /* change space to terminator */
267 p = q + 1;
268 }
269 argv[argc] = NULL; /* terminate the argv array itself */
270
271
272 /* Now parse the command line for options and the PNG filename. */
273
274 while (*++argv && !error) {
275 if (!strncmp(*argv, "-gamma", 2)) {
276 if (!*++argv)
277 ++error;
278 else {
279 display_exponent = atof(*argv);
280 if (display_exponent <= 0.0)
281 ++error;
282 }
283 } else if (!strncmp(*argv, "-bgcolor", 2)) {
284 if (!*++argv)
285 ++error;
286 else {
287 bgstr = *argv;
288 if (strlen(bgstr) != 7 || bgstr[0] != '#')
289 ++error;
290 else
291 have_bg = TRUE;
292 }
293 } else {
294 if (**argv != '-') {
295 filename = *argv;
296 if (argv[1]) /* shouldn't be any more args after filename */
297 ++error;
298 } else
299 ++error; /* not expecting any other options */
300 }
301 }
302
303 if (!filename)
304 ++error;
305
306
307 /* print usage screen if any errors up to this point */
308
309 if (error) {
310 #ifndef __CYGWIN__
311 int ch;
312 #endif
313
314 fprintf(stderr, "\n%s %s: %s\n\n", PROGNAME, VERSION, appname);
315 readpng_version_info();
316 fprintf(stderr, "\n"
317 "Usage: %s [-gamma exp] [-bgcolor bg] file.png\n"
318 " exp \ttransfer-function exponent (``gamma'') of the display\n"
319 "\t\t system in floating-point format (e.g., ``%.1f''); equal\n"
320 "\t\t to the product of the lookup-table exponent (varies)\n"
321 "\t\t and the CRT exponent (usually 2.2); must be positive\n"
322 " bg \tdesired background color in 7-character hex RGB format\n"
323 "\t\t (e.g., ``#ff7700'' for orange: same as HTML colors);\n"
324 "\t\t used with transparent images\n"
325 "\nPress Q, Esc or mouse button 1 after image is displayed to quit.\n"
326 #ifndef __CYGWIN__
327 "Press Q or Esc to quit this usage screen.\n"
328 #endif
329 "\n", PROGNAME, default_display_exponent);
330 #ifndef __CYGWIN__
331 do
332 ch = _getch();
333 while (ch != 'q' && ch != 'Q' && ch != 0x1B);
334 #endif
335 exit(1);
336 }
337
338
339 if (!(infile = fopen(filename, "rb"))) {
340 fprintf(stderr, PROGNAME ": can't open PNG file [%s]\n", filename);
341 ++error;
342 } else {
343 if ((rc = readpng_init(infile, &image_width, &image_height)) != 0) {
344 switch (rc) {
345 case 1:
346 fprintf(stderr, PROGNAME
347 ": [%s] is not a PNG file: incorrect signature\n",
348 filename);
349 break;
350 case 2:
351 fprintf(stderr, PROGNAME
352 ": [%s] has bad IHDR (libpng longjmp)\n", filename);
353 break;
354 case 4:
355 fprintf(stderr, PROGNAME ": insufficient memory\n");
356 break;
357 default:
358 fprintf(stderr, PROGNAME
359 ": unknown readpng_init() error\n");
360 break;
361 }
362 ++error;
363 }
364 if (error)
365 fclose(infile);
366 }
367
368
369 if (error) {
370 #ifndef __CYGWIN__
371 int ch;
372 #endif
373
374 fprintf(stderr, PROGNAME ": aborting.\n");
375 #ifndef __CYGWIN__
376 do
377 ch = _getch();
378 while (ch != 'q' && ch != 'Q' && ch != 0x1B);
379 #endif
380 exit(2);
381 } else {
382 fprintf(stderr, "\n%s %s: %s\n", PROGNAME, VERSION, appname);
383 #ifndef __CYGWIN__
384 fprintf(stderr,
385 "\n [console window: closing this window will terminate %s]\n\n",
386 PROGNAME);
387 #endif
388 }
389
390
391 /* set the title-bar string, but make sure buffer doesn't overflow */
392
393 alen = strlen(appname);
394 flen = strlen(filename);
395 if (alen + flen + 3 > 1023)
396 sprintf(titlebar, "%s: ...%s", appname, filename+(alen+flen+6-1023));
397 else
398 sprintf(titlebar, "%s: %s", appname, filename);
399
400
401 /* if the user didn't specify a background color on the command line,
402 * check for one in the PNG file--if not, the initialized values of 0
403 * (black) will be used */
404
405 if (have_bg) {
406 unsigned r, g, b; /* this approach quiets compiler warnings */
407
408 sscanf(bgstr+1, "%2x%2x%2x", &r, &g, &b);
409 bg_red = (uch)r;
410 bg_green = (uch)g;
411 bg_blue = (uch)b;
412 } else if (readpng_get_bgcolor(&bg_red, &bg_green, &bg_blue) > 1) {
413 readpng_cleanup(TRUE);
414 fprintf(stderr, PROGNAME
415 ": libpng error while checking for background color\n");
416 exit(2);
417 }
418
419
420 /* do the basic Windows initialization stuff, make the window and fill it
421 * with the background color */
422
423 if (rpng_win_create_window(hInst, showmode))
424 exit(2);
425
426
427 /* decode the image, all at once */
428
429 Trace((stderr, "calling readpng_get_image()\n"))
430 image_data = readpng_get_image(display_exponent, &image_channels,
431 &image_rowbytes);
432 Trace((stderr, "done with readpng_get_image()\n"))
433
434
435 /* done with PNG file, so clean up to minimize memory usage (but do NOT
436 * nuke image_data!) */
437
438 readpng_cleanup(FALSE);
439 fclose(infile);
440
441 if (!image_data) {
442 fprintf(stderr, PROGNAME ": unable to decode PNG image\n");
443 exit(3);
444 }
445
446
447 /* display image (composite with background if requested) */
448
449 Trace((stderr, "calling rpng_win_display_image()\n"))
450 if (rpng_win_display_image()) {
451 free(image_data);
452 exit(4);
453 }
454 Trace((stderr, "done with rpng_win_display_image()\n"))
455
456
457 /* wait for the user to tell us when to quit */
458
459 printf(
460 #ifndef __CYGWIN__
461 "Done. Press Q, Esc or mouse button 1 (within image window) to quit.\n"
462 #else
463 "Done. Press mouse button 1 (within image window) to quit.\n"
464 #endif
465 );
466 fflush(stdout);
467
468 while (GetMessage(&msg, NULL, 0, 0)) {
469 TranslateMessage(&msg);
470 DispatchMessage(&msg);
471 }
472
473
474 /* OK, we're done: clean up all image and Windows resources and go away */
475
476 rpng_win_cleanup();
477
478 return msg.wParam;
479 }
480
481
482
483
484
rpng_win_create_window(HINSTANCE hInst,int showmode)485 static int rpng_win_create_window(HINSTANCE hInst, int showmode)
486 {
487 uch *dest;
488 int extra_width, extra_height;
489 ulg i, j;
490 WNDCLASSEX wndclass;
491
492
493 /*---------------------------------------------------------------------------
494 Allocate memory for the display-specific version of the image (round up
495 to multiple of 4 for Windows DIB).
496 ---------------------------------------------------------------------------*/
497
498 wimage_rowbytes = ((3*image_width + 3L) >> 2) << 2;
499
500 /* Guard against integer overflow */
501 if (image_height > ((size_t)(-1))/wimage_rowbytes) {
502 fprintf(stderr, PROGNAME ": image_data buffer would be too large\n");
503 return 4; /* fail */
504 }
505
506 if (!(dib = (uch *)malloc(sizeof(BITMAPINFOHEADER) +
507 wimage_rowbytes*image_height)))
508 {
509 return 4; /* fail */
510 }
511
512 /*---------------------------------------------------------------------------
513 Initialize the DIB. Negative height means to use top-down BMP ordering
514 (must be uncompressed, but that's what we want). Bit count of 1, 4 or 8
515 implies a colormap of RGBX quads, but 24-bit BMPs just use B,G,R values
516 directly => wimage_data begins immediately after BMP header.
517 ---------------------------------------------------------------------------*/
518
519 memset(dib, 0, sizeof(BITMAPINFOHEADER));
520 bmih = (BITMAPINFOHEADER *)dib;
521 bmih->biSize = sizeof(BITMAPINFOHEADER);
522 bmih->biWidth = image_width;
523 bmih->biHeight = -((long)image_height);
524 bmih->biPlanes = 1;
525 bmih->biBitCount = 24;
526 bmih->biCompression = 0;
527 wimage_data = dib + sizeof(BITMAPINFOHEADER);
528
529 /*---------------------------------------------------------------------------
530 Fill in background color (black by default); data are in BGR order.
531 ---------------------------------------------------------------------------*/
532
533 for (j = 0; j < image_height; ++j) {
534 dest = wimage_data + j*wimage_rowbytes;
535 for (i = image_width; i > 0; --i) {
536 *dest++ = bg_blue;
537 *dest++ = bg_green;
538 *dest++ = bg_red;
539 }
540 }
541
542 /*---------------------------------------------------------------------------
543 Set the window parameters.
544 ---------------------------------------------------------------------------*/
545
546 memset(&wndclass, 0, sizeof(wndclass));
547
548 wndclass.cbSize = sizeof(wndclass);
549 wndclass.style = CS_HREDRAW | CS_VREDRAW;
550 wndclass.lpfnWndProc = rpng_win_wndproc;
551 wndclass.hInstance = hInst;
552 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
553 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
554 wndclass.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);
555 wndclass.lpszMenuName = NULL;
556 wndclass.lpszClassName = progname;
557 wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
558
559 RegisterClassEx(&wndclass);
560
561 /*---------------------------------------------------------------------------
562 Finally, create the window.
563 ---------------------------------------------------------------------------*/
564
565 extra_width = 2*(GetSystemMetrics(SM_CXBORDER) +
566 GetSystemMetrics(SM_CXDLGFRAME));
567 extra_height = 2*(GetSystemMetrics(SM_CYBORDER) +
568 GetSystemMetrics(SM_CYDLGFRAME)) +
569 GetSystemMetrics(SM_CYCAPTION);
570
571 global_hwnd = CreateWindow(progname, titlebar, WS_OVERLAPPEDWINDOW,
572 CW_USEDEFAULT, CW_USEDEFAULT, image_width+extra_width,
573 image_height+extra_height, NULL, NULL, hInst, NULL);
574
575 ShowWindow(global_hwnd, showmode);
576 UpdateWindow(global_hwnd);
577
578 return 0;
579
580 } /* end function rpng_win_create_window() */
581
582
583
584
585
rpng_win_display_image()586 static int rpng_win_display_image()
587 {
588 uch *src, *dest;
589 uch r, g, b, a;
590 ulg i, row, lastrow;
591 RECT rect;
592
593
594 Trace((stderr, "beginning display loop (image_channels == %d)\n",
595 image_channels))
596 Trace((stderr, "(width = %ld, rowbytes = %ld, wimage_rowbytes = %d)\n",
597 image_width, image_rowbytes, wimage_rowbytes))
598
599
600 /*---------------------------------------------------------------------------
601 Blast image data to buffer. This whole routine takes place before the
602 message loop begins, so there's no real point in any pseudo-progressive
603 display...
604 ---------------------------------------------------------------------------*/
605
606 for (lastrow = row = 0; row < image_height; ++row) {
607 src = image_data + row*image_rowbytes;
608 dest = wimage_data + row*wimage_rowbytes;
609 if (image_channels == 3) {
610 for (i = image_width; i > 0; --i) {
611 r = *src++;
612 g = *src++;
613 b = *src++;
614 *dest++ = b;
615 *dest++ = g; /* note reverse order */
616 *dest++ = r;
617 }
618 } else /* if (image_channels == 4) */ {
619 for (i = image_width; i > 0; --i) {
620 r = *src++;
621 g = *src++;
622 b = *src++;
623 a = *src++;
624 if (a == 255) {
625 *dest++ = b;
626 *dest++ = g;
627 *dest++ = r;
628 } else if (a == 0) {
629 *dest++ = bg_blue;
630 *dest++ = bg_green;
631 *dest++ = bg_red;
632 } else {
633 /* this macro (copied from png.h) composites the
634 * foreground and background values and puts the
635 * result into the first argument; there are no
636 * side effects with the first argument */
637 alpha_composite(*dest++, b, a, bg_blue);
638 alpha_composite(*dest++, g, a, bg_green);
639 alpha_composite(*dest++, r, a, bg_red);
640 }
641 }
642 }
643 /* display after every 16 lines */
644 if (((row+1) & 0xf) == 0) {
645 rect.left = 0L;
646 rect.top = (LONG)lastrow;
647 rect.right = (LONG)image_width; /* possibly off by one? */
648 rect.bottom = (LONG)lastrow + 16L; /* possibly off by one? */
649 InvalidateRect(global_hwnd, &rect, FALSE);
650 UpdateWindow(global_hwnd); /* similar to XFlush() */
651 lastrow = row + 1;
652 }
653 }
654
655 Trace((stderr, "calling final image-flush routine\n"))
656 if (lastrow < image_height) {
657 rect.left = 0L;
658 rect.top = (LONG)lastrow;
659 rect.right = (LONG)image_width; /* possibly off by one? */
660 rect.bottom = (LONG)image_height; /* possibly off by one? */
661 InvalidateRect(global_hwnd, &rect, FALSE);
662 UpdateWindow(global_hwnd); /* similar to XFlush() */
663 }
664
665 /*
666 last param determines whether or not background is wiped before paint
667 InvalidateRect(global_hwnd, NULL, TRUE);
668 UpdateWindow(global_hwnd);
669 */
670
671 return 0;
672 }
673
674
675
676
677
rpng_win_cleanup()678 static void rpng_win_cleanup()
679 {
680 if (image_data) {
681 free(image_data);
682 image_data = NULL;
683 }
684
685 if (dib) {
686 free(dib);
687 dib = NULL;
688 }
689 }
690
691
692
693
694
rpng_win_wndproc(HWND hwnd,UINT iMsg,WPARAM wP,LPARAM lP)695 LRESULT CALLBACK rpng_win_wndproc(HWND hwnd, UINT iMsg, WPARAM wP, LPARAM lP)
696 {
697 HDC hdc;
698 PAINTSTRUCT ps;
699 int rc;
700
701 switch (iMsg) {
702 case WM_CREATE:
703 /* one-time processing here, if any */
704 return 0;
705
706 case WM_PAINT:
707 hdc = BeginPaint(hwnd, &ps);
708 /* dest */
709 rc = StretchDIBits(hdc, 0, 0, image_width, image_height,
710 /* source */
711 0, 0, image_width, image_height,
712 wimage_data, (BITMAPINFO *)bmih,
713 /* iUsage: no clue */
714 0, SRCCOPY);
715 EndPaint(hwnd, &ps);
716 return 0;
717
718 /* wait for the user to tell us when to quit */
719 case WM_CHAR:
720 switch (wP) { /* only need one, so ignore repeat count */
721 case 'q':
722 case 'Q':
723 case 0x1B: /* Esc key */
724 PostQuitMessage(0);
725 }
726 return 0;
727
728 case WM_LBUTTONDOWN: /* another way of quitting */
729 case WM_DESTROY:
730 PostQuitMessage(0);
731 return 0;
732 }
733
734 return DefWindowProc(hwnd, iMsg, wP, lP);
735 }
736