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> 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 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 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 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 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 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