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>
97int 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 */
135static int        rpng_win_create_window(HINSTANCE hInst, int showmode);
136static int        rpng_win_display_image(void);
137static void       rpng_win_cleanup(void);
138LRESULT CALLBACK  rpng_win_wndproc(HWND, UINT, WPARAM, LPARAM);
139
140
141static char titlebar[1024];
142static char *progname = PROGNAME;
143static char *appname = LONGNAME;
144static char *filename;
145static FILE *infile;
146
147static char *bgstr;
148static uch bg_red=0, bg_green=0, bg_blue=0;
149
150static double display_exponent;
151
152static ulg image_width, image_height, image_rowbytes;
153static int image_channels;
154static uch *image_data;
155
156/* Windows-specific variables */
157static ulg wimage_rowbytes;
158static uch *dib;
159static uch *wimage_data;
160static BITMAPINFOHEADER *bmih;
161
162static HWND global_hwnd;
163
164
165
166
167int 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
485static 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
586static 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
678static 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
695LRESULT 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