Fixing doc links
[privoxy.git] / w32log.c
1 const char w32log_rcs[] = "$Id: w32log.c,v 1.21 2002/03/24 12:07:35 jongfoster Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/w32log.c,v $
5  *
6  * Purpose     :  Functions for creating and destroying the log window,
7  *                ouputting strings, processing messages and so on.
8  *
9  * Copyright   :  Written by and Copyright (C) 2001-2002 members of
10  *                the Privoxy team.  http://privoxy.org/
11  *
12  *                Written by and Copyright (C) 1999 Adam Lock
13  *                <locka@iol.ie>
14  *
15  *                This program is free software; you can redistribute it 
16  *                and/or modify it under the terms of the GNU General
17  *                Public License as published by the Free Software
18  *                Foundation; either version 2 of the License, or (at
19  *                your option) any later version.
20  *
21  *                This program is distributed in the hope that it will
22  *                be useful, but WITHOUT ANY WARRANTY; without even the
23  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
24  *                PARTICULAR PURPOSE.  See the GNU General Public
25  *                License for more details.
26  *
27  *                The GNU General Public License should be included with
28  *                this file.  If not, you can view it at
29  *                http://www.gnu.org/copyleft/gpl.html
30  *                or write to the Free Software Foundation, Inc., 59
31  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
32  *
33  * Revisions   :
34  *    $Log: w32log.c,v $
35  *    Revision 1.21  2002/03/24 12:07:35  jongfoster
36  *    Consistern name for filters file
37  *
38  *    Revision 1.20  2002/03/24 12:03:47  jongfoster
39  *    Name change
40  *
41  *    Revision 1.19  2002/01/17 21:04:17  jongfoster
42  *    Replacing hard references to the URL of the config interface
43  *    with #defines from project.h
44  *
45  *    Revision 1.18  2001/11/30 23:37:24  jongfoster
46  *    Renaming the Win32 config file to config.txt - this is almost the
47  *    same as the corresponding UNIX name "config"
48  *
49  *    Revision 1.17  2001/11/16 00:46:31  jongfoster
50  *    Fixing compiler warnings
51  *
52  *    Revision 1.16  2001/08/01 19:58:12  jongfoster
53  *    Fixing documentation filenames in help menu, and making status
54  *    option work without needing the "Junkbuster Status.URL" file.
55  *
56  *    Revision 1.15  2001/07/30 22:08:36  jongfoster
57  *    Tidying up #defines:
58  *    - All feature #defines are now of the form FEATURE_xxx
59  *    - Permanently turned off WIN_GUI_EDIT
60  *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
61  *
62  *    Revision 1.14  2001/07/29 18:47:05  jongfoster
63  *    Adding missing #include "loadcfg.h"
64  *
65  *    Revision 1.13  2001/07/19 19:15:14  haroon
66  *    - Added a FIXME for EditFile but didn't fix :-)
67  *
68  *    Revision 1.12  2001/07/13 14:04:59  oes
69  *    Removed all #ifdef PCRS
70  *
71  *    Revision 1.11  2001/06/07 23:08:12  jongfoster
72  *    Forward and ACL edit options removed.
73  *
74  *    Revision 1.10  2001/05/31 21:37:11  jongfoster
75  *    GUI changes to rename "permissions file" to "actions file".
76  *
77  *    Revision 1.9  2001/05/31 17:33:13  oes
78  *
79  *    CRLF -> LF
80  *
81  *    Revision 1.8  2001/05/29 09:50:24  jongfoster
82  *    Unified blocklist/imagelist/permissionslist.
83  *    File format is still under discussion, but the internal changes
84  *    are (mostly) done.
85  *
86  *    Also modified interceptor behaviour:
87  *    - We now intercept all URLs beginning with one of the following
88  *      prefixes (and *only* these prefixes):
89  *        * http://i.j.b/
90  *        * http://ijbswa.sf.net/config/
91  *        * http://ijbswa.sourceforge.net/config/
92  *    - New interceptors "home page" - go to http://i.j.b/ to see it.
93  *    - Internal changes so that intercepted and fast redirect pages
94  *      are not replaced with an image.
95  *    - Interceptors now have the option to send a binary page direct
96  *      to the client. (i.e. ijb-send-banner uses this)
97  *    - Implemented show-url-info interceptor.  (Which is why I needed
98  *      the above interceptors changes - a typical URL is
99  *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
100  *      The previous mechanism would not have intercepted that, and
101  *      if it had been intercepted then it then it would have replaced
102  *      it with an image.)
103  *
104  *    Revision 1.7  2001/05/26 01:26:34  jongfoster
105  *    New #define, WIN_GUI_EDIT, enables the (embryonic) Win32 GUI editor.
106  *    This #define cannot be set from ./configure - there's no point, it
107  *    doesn't work yet.  See feature request # 425722
108  *
109  *    Revision 1.6  2001/05/26 00:31:30  jongfoster
110  *    Fixing compiler warning about comparing signed/unsigned.
111  *
112  *    Revision 1.5  2001/05/26 00:28:36  jongfoster
113  *    Automatic reloading of config file.
114  *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
115  *    Most of the global variables have been moved to a new
116  *    struct configuration_spec, accessed through csp->config->globalname
117  *    Most of the globals remaining are used by the Win32 GUI.
118  *
119  *    Revision 1.4  2001/05/22 18:56:28  oes
120  *    CRLF -> LF
121  *
122  *    Revision 1.3  2001/05/20 15:07:54  jongfoster
123  *    File is now ignored if _WIN_CONSOLE is defined.
124  *
125  *    Revision 1.2  2001/05/20 01:21:20  jongfoster
126  *    Version 2.9.4 checkin.
127  *    - Merged popupfile and cookiefile, and added control over PCRS
128  *      filtering, in new "permissionsfile".
129  *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
130  *      file error you now get a message box (in the Win32 GUI) rather
131  *      than the program exiting with no explanation.
132  *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
133  *      skipping.
134  *    - Removed tabs from "config"
135  *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
136  *    - Bumped up version number.
137  *
138  *    Revision 1.1.1.1  2001/05/15 13:59:07  oes
139  *    Initial import of version 2.9.3 source tree
140  *
141  *
142  *********************************************************************/
143 \f
144
145 #include "config.h"
146
147 #include <assert.h>
148 #include <stdio.h>
149
150 #include <windows.h>
151 #include <richedit.h>
152
153 #include "project.h"
154 #include "w32log.h"
155 #include "w32taskbar.h"
156 #include "win32.h"
157 #include "w32res.h"
158 #include "jcc.h"
159 #include "miscutil.h"
160 #include "errlog.h"
161 #include "loadcfg.h"
162
163 const char w32res_h_rcs[] = W32RES_H_VERSION;
164
165 #ifdef __MINGW32__
166 #include "cygwin.h"
167 const char cygwin_h_rcs[] = CYGWIN_H_VERSION;
168 #endif
169
170 const char w32log_h_rcs[] = W32LOG_H_VERSION;
171
172 #ifndef _WIN_CONSOLE /* entire file */
173
174 /*
175  * Timers and the various durations
176  */
177 #define TIMER_ANIM_ID               1
178 #define TIMER_ANIM_TIME             100
179 #define TIMER_ANIMSTOP_ID           2
180 #define TIMER_ANIMSTOP_TIME         1000
181 #define TIMER_CLIPBUFFER_ID         3
182 #define TIMER_CLIPBUFFER_TIME       1000
183 #define TIMER_CLIPBUFFER_FORCE_ID   4
184 #define TIMER_CLIPBUFFER_FORCE_TIME 5000
185
186 /*
187  * Styles of text that can be output
188  */
189 #define STYLE_NONE      0
190 #define STYLE_HIGHLIGHT 1
191 #define STYLE_LINK      2
192 #define STYLE_HEADER    3
193
194 /*
195  * Number of frames of animation in tray activity sequence
196  */
197 #define ANIM_FRAMES 8
198
199 #define DEFAULT_MAX_BUFFER_LINES    200
200 #define DEFAULT_LOG_FONT_NAME       "MS Sans Serif"
201 #define DEFAULT_LOG_FONT_SIZE       8
202
203 /*
204  * These values affect the way the log window behaves, they should be read
205  * from a file but for the moment, they are hardcoded here. Some options are
206  * configurable through the UI.
207  */
208
209 /* Indicates whether task bar shows activity animation */
210 BOOL g_bShowActivityAnimation = 1;
211
212 /* Indicates if the log window appears on the task bar */
213 BOOL g_bShowOnTaskBar = 0;
214
215 /* Indicates whether closing the log window really just hides it */
216 BOOL g_bCloseHidesWindow = 1;
217
218 /* Indicates if messages are logged at all */
219 BOOL g_bLogMessages = 1;
220
221 /* Indicates whether log messages are highlighted */
222 BOOL g_bHighlightMessages = 1;
223
224 /* Indicates if buffer is limited in size */
225 BOOL g_bLimitBufferSize = 1;
226
227 /* Maximum number of lines allowed in buffer when limited */
228 int g_nMaxBufferLines = DEFAULT_MAX_BUFFER_LINES;
229
230 /* Font to use */
231 char g_szFontFaceName[255] = DEFAULT_LOG_FONT_NAME;
232
233 /* Size of font to use */
234 int g_nFontSize = DEFAULT_LOG_FONT_SIZE;
235
236
237 /* FIXME: this is a kludge */
238
239 const char * g_actions_file = NULL;
240 const char * g_re_filterfile = NULL;
241 #ifdef FEATURE_TRUST
242 const char * g_trustfile = NULL;
243 #endif /* def FEATURE_TRUST */
244
245 /* FIXME: end kludge */
246
247
248 #ifdef REGEX
249 /* Regular expression for detected URLs */
250 #define RE_URL "http:[^ \n\r]*"
251
252 /*
253  * Regular expressions that are used to perform highlight in the log window
254  */
255 static struct _Pattern
256 {
257    const char *str;
258    int style;
259    regex_t buffer;
260 } patterns_to_highlight[] =
261 {
262    /* url headers */
263    { RE_URL,                STYLE_LINK },
264 /* { "[a-zA-Z0-9]+\\.[a-zA-Z0-9]+\\.[a-zA-Z0-9]+\\.[^ \n\r]*", STYLE_LINK }, */
265    /* interesting text to highlight */
266    { "crunch!",           STYLE_HIGHLIGHT },
267    /* http headers */
268    { "referer:",            STYLE_HEADER },
269    { "proxy-connection:",   STYLE_HEADER },
270    { "proxy-agent:",        STYLE_HEADER },
271    { "user-agent:",         STYLE_HEADER },
272    { "host:",               STYLE_HEADER },
273    { "accept:",             STYLE_HEADER },
274    { "accept-encoding:",    STYLE_HEADER },
275    { "accept-language:",    STYLE_HEADER },
276    { "accept-charset:",     STYLE_HEADER },
277    { "accept-ranges:",      STYLE_HEADER },
278    { "date:",               STYLE_HEADER },
279    { "cache-control:",      STYLE_HEADER },
280    { "cache-last-checked:", STYLE_HEADER },
281    { "connection:",         STYLE_HEADER },
282    { "content-type",        STYLE_HEADER },
283    { "content-length",      STYLE_HEADER },
284    { "cookie",              STYLE_HEADER },
285    { "last-modified:",      STYLE_HEADER },
286    { "pragma:",             STYLE_HEADER },
287    { "server:",             STYLE_HEADER },
288    { "etag:",               STYLE_HEADER },
289    { "expires:",            STYLE_HEADER },
290    { "warning:",            STYLE_HEADER },
291    /* this is the terminator statement - do not delete! */
292    { NULL,                  STYLE_NONE }
293 };
294 #endif /* def REGEX */
295
296
297 /*
298  * Public variables
299  */
300 HWND g_hwndLogFrame;
301
302 /*
303  * Private variables
304  */
305 static CRITICAL_SECTION g_criticalsection;
306 static HWND g_hwndTray;
307 static HWND g_hwndLogBox;
308 static WNDPROC g_fnLogBox;
309 static HICON g_hiconAnim[ANIM_FRAMES];
310 static HICON g_hiconIdle;
311 static HICON g_hiconApp;
312 static int g_nAnimFrame;
313 static BOOL g_bClipPending = FALSE;
314 static int g_nRichEditVersion = 0;
315
316 /*
317  * Private functions
318  */
319 static HWND CreateLogWindow(HINSTANCE hInstance, int nCmdShow);
320 static HWND CreateHiddenLogOwnerWindow(HINSTANCE hInstance);
321 static LRESULT CALLBACK LogWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
322 static LRESULT CALLBACK LogOwnerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
323 static LRESULT CALLBACK LogRichEditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
324 static BOOL InitRichEdit(void);
325 static void LogClipBuffer(void);
326 static void LogCreatePatternMatchingBuffers(void);
327 static void LogDestroyPatternMatchingBuffers(void);
328 static int LogPutStringNoMatch(const char *pszText, int style);
329
330
331 /*********************************************************************
332  *
333  * Function    :  InitLogWindow
334  *
335  * Description :  Initialise the log window.
336  *
337  * Parameters  :  None
338  *
339  * Returns     :  Always TRUE (there should be error checking on the resources).
340  *
341  *********************************************************************/
342 BOOL InitLogWindow(void)
343 {
344    int i;
345
346    /* Load the icons */
347    g_hiconIdle = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_IDLE));
348    for (i = 0; i < ANIM_FRAMES; i++)
349    {
350       g_hiconAnim[i] = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ANIMATED1 + i));
351    }
352    g_hiconApp = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_MAINICON));
353
354    /* Create the user interface */
355    g_hwndLogFrame = CreateLogWindow(g_hInstance, g_nCmdShow);
356    g_hwndTray = CreateTrayWindow(g_hInstance);
357    TrayAddIcon(g_hwndTray, 1, g_hiconApp, "Privoxy");
358
359    /* Create pattern matching buffers (for highlighting */
360    LogCreatePatternMatchingBuffers();
361
362    /* Create a critical section to protect multi-threaded access to certain things */
363    InitializeCriticalSection(&g_criticalsection);
364
365    return TRUE;
366
367 }
368
369
370 /*********************************************************************
371  *
372  * Function    :  TermLogWindow
373  *
374  * Description :  Cleanup the logwindow.
375  *
376  * Parameters  :  None
377  *
378  * Returns     :  N/A
379  *
380  *********************************************************************/
381 void TermLogWindow(void)
382 {
383    int i;
384
385    LogDestroyPatternMatchingBuffers();
386
387    TrayDeleteIcon(g_hwndTray, 1);
388    DeleteObject(g_hiconApp);
389    DeleteObject(g_hiconIdle);
390    for (i = 0; i < ANIM_FRAMES; i++)
391    {
392       DeleteObject(g_hiconAnim[i]);
393    }
394
395 }
396
397
398 /*********************************************************************
399  *
400  * Function    :  LogCreatePatternMatchingBuffers
401  *
402  * Description :  Compile the pattern matching buffers.
403  *
404  * Parameters  :  None
405  *
406  * Returns     :  N/A
407  *
408  *********************************************************************/
409 void LogCreatePatternMatchingBuffers(void)
410 {
411 #ifdef REGEX
412    int i;
413    for (i = 0; patterns_to_highlight[i].str != NULL; i++)
414    {
415       regcomp(&patterns_to_highlight[i].buffer, patterns_to_highlight[i].str, REG_ICASE);
416    }
417 #endif
418
419 }
420
421
422 /*********************************************************************
423  *
424  * Function    :  LogDestroyPatternMatchingBuffers
425  *
426  * Description :  Free up the pattern matching buffers.
427  *
428  * Parameters  :  None
429  *
430  * Returns     :  N/A
431  *
432  *********************************************************************/
433 void LogDestroyPatternMatchingBuffers(void)
434 {
435 #ifdef REGEX
436    int i;
437    for (i = 0; patterns_to_highlight[i].str != NULL; i++)
438    {
439       regfree(&patterns_to_highlight[i].buffer);
440    }
441 #endif
442
443 }
444
445
446 /*********************************************************************
447  *
448  * Function    :  LogGetURLUnderCursor
449  *
450  * Description :  Returns the URL from under the cursor (remember to free it!).
451  *
452  * Parameters  :  None
453  *
454  * Returns     :  NULL or a pointer to an URL string.
455  *
456  *********************************************************************/
457 char *LogGetURLUnderCursor(void)
458 {
459    char *szResult = NULL;
460 #ifdef REGEX
461    regex_t re;
462    POINT ptCursor;
463    POINTL ptl;
464    DWORD nPos;
465    DWORD nWordStart = 0;
466    DWORD nWordEnd = 0;
467
468    regcomp(&re, RE_URL, REG_ICASE);
469
470    /* Get the position of the cursor over the text window */
471    GetCursorPos(&ptCursor);
472    ScreenToClient(g_hwndLogBox, &ptCursor);
473    ptl.x = ptCursor.x;
474    ptl.y = ptCursor.y;
475
476    /* Search backwards and fowards to obtain the word that is highlighted */
477    nPos = LOWORD(SendMessage(g_hwndLogBox, EM_CHARFROMPOS, 0, (LPARAM) &ptl));
478    nWordStart = SendMessage(g_hwndLogBox, EM_FINDWORDBREAK, WB_LEFT, nPos);
479    nWordEnd = SendMessage(g_hwndLogBox, EM_FINDWORDBREAK, WB_RIGHTBREAK, nPos);
480
481    /* Compare the string to the pattern */
482    if (nWordEnd > nWordStart)
483    {
484       TEXTRANGE range;
485       regmatch_t match;
486
487       range.chrg.cpMin = nWordStart;
488       range.chrg.cpMax = nWordEnd;
489       range.lpstrText = (LPSTR)zalloc(nWordEnd - nWordStart + 1);
490       SendMessage(g_hwndLogBox, EM_GETTEXTRANGE, 0, (LPARAM) &range);
491
492       if (regexec(&re, range.lpstrText, 1, &match, 0) == 0)
493       {
494          szResult = range.lpstrText;
495       }
496       else
497       {
498          free(range.lpstrText);
499       }
500
501       regfree(&re);
502    }
503 #endif
504    return szResult;
505
506 }
507
508
509 /*********************************************************************
510  *
511  * Function    :  LogPutString
512  *
513  * Description :  Inserts text into the logging window.  This is really
514  *                a REGEXP aware wrapper function to `LogPutStringNoMatch'.
515  *
516  * Parameters  :
517  *          1  :  pszText = pointer to string going to the log window
518  *
519  * Returns     :  1 => success, else the return code from `LogPutStringNoMatch'.
520  *                FIXME: this is backwards to the rest of IJB and to common
521  *                programming practice.  Please use 0 => success instead.
522  *
523  *********************************************************************/
524 int LogPutString(const char *pszText)
525 {
526 #ifdef REGEX
527    int i;
528 #endif
529    int result = 0;
530
531    if (pszText == NULL || strlen(pszText) == 0)
532    {
533       return 1;
534    }
535
536    if (!g_bLogMessages)
537    {
538       return 1;
539    }
540
541    /* Critical section stops multiple threads doing nasty interactions that
542     * foul up the highlighting and output.
543     */
544    EnterCriticalSection(&g_criticalsection);
545
546 #ifdef REGEX
547    if (g_bHighlightMessages)
548    {
549       regmatch_t match;
550
551       /* First things first, regexp scan for various things that we would like highlighted */
552       for (i = 0; patterns_to_highlight[i].str != NULL; i++)
553       {
554          if (regexec(&patterns_to_highlight[i].buffer, pszText, 1, &match, 0) == 0)
555          {
556             char *pszBefore = NULL;
557             char *pszMatch = NULL;
558             char *pszAfter = NULL;
559             int nMatchSize;
560
561             /* Split the string up into pieces representing the strings, before
562                at and after the matching pattern
563              */
564             if (match.rm_so > 0)
565             {
566                pszBefore = (char *)malloc((match.rm_so + 1) * sizeof(char));
567                memset(pszBefore, 0, (match.rm_so + 1) * sizeof(char));
568                strncpy(pszBefore, pszText, match.rm_so);
569             }
570             if (match.rm_eo < (regoff_t)strlen(pszText))
571             {
572                pszAfter = strdup(&pszText[match.rm_eo]);
573             }
574             nMatchSize = match.rm_eo - match.rm_so;
575             pszMatch = (char *)malloc(nMatchSize + 1);
576             strncpy(pszMatch, &pszText[match.rm_so], nMatchSize);
577             pszMatch[nMatchSize] = '\0';
578
579             /* Recursively call LogPutString */
580             if (pszBefore)
581             {
582                LogPutString(pszBefore);
583                free(pszBefore);
584             }
585             if (pszMatch)
586             {
587                LogPutStringNoMatch(pszMatch, patterns_to_highlight[i].style);
588                free(pszMatch);
589             }
590             if (pszAfter)
591             {
592                LogPutString(pszAfter);
593                free(pszAfter);
594             }
595
596             result = 1;
597             goto end;
598          }
599       }
600    }
601 #endif
602
603    result = LogPutStringNoMatch(pszText, STYLE_NONE);
604
605 #ifdef REGEX
606 end:
607 #endif
608    LeaveCriticalSection(&g_criticalsection);
609
610    return result;
611
612 }
613
614
615 /*********************************************************************
616  *
617  * Function    :  LogPutStringNoMatch
618  *
619  * Description :  Puts a string into the logging window.
620  *
621  * Parameters  :
622  *          1  :  pszText = pointer to string going to the log window
623  *          2  :  style = STYLE_NONE, STYLE_HEADER, STYLE_HIGHLIGHT, or STYLE_LINK
624  *
625  * Returns     :  Always 1 => success.
626  *                FIXME: this is backwards to the rest of IJB and to common
627  *                programming practice.  Please use 0 => success instead.
628  *
629  *********************************************************************/
630 int LogPutStringNoMatch(const char *pszText, int style)
631 {
632    CHARRANGE range;
633    CHARFORMAT format;
634    int nTextLength;
635
636    assert(g_hwndLogBox);
637    if (g_hwndLogBox == NULL)
638    {
639       return 1;
640    }
641
642    /* TODO preserve existing selection */
643
644    /* Go to the end of the text */
645    nTextLength = GetWindowTextLength(g_hwndLogBox);
646    range.cpMin = nTextLength;
647    range.cpMax = nTextLength;
648    SendMessage(g_hwndLogBox, EM_EXSETSEL, 0, (LPARAM) &range);
649
650    /* Apply a formatting style */
651    memset(&format, 0, sizeof(format));
652    format.cbSize = sizeof(format);
653    format.dwMask = CFM_BOLD | CFM_UNDERLINE | CFM_STRIKEOUT | CFM_ITALIC | CFM_COLOR | CFM_FACE | CFM_SIZE;
654    format.yHeight = (g_nFontSize * 1440) / 72;
655    strcpy(format.szFaceName, g_szFontFaceName);
656    if (style == STYLE_NONE)
657    {
658       /* DO NOTHING */
659       format.dwEffects |= CFE_AUTOCOLOR;
660    }
661    else if (style == STYLE_HEADER)
662    {
663       format.dwEffects |= CFE_AUTOCOLOR | CFE_ITALIC;
664    }
665    else if (style == STYLE_HIGHLIGHT)
666    {
667       format.dwEffects |= CFE_AUTOCOLOR | CFE_BOLD;
668    }
669    else if (style == STYLE_LINK)
670    {
671       format.dwEffects |= CFE_UNDERLINE;
672       format.crTextColor = RGB(0, 0, 255);
673    }
674    SendMessage(g_hwndLogBox, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &format);
675
676    /* Append text to the end */
677    SendMessage(g_hwndLogBox, EM_REPLACESEL, FALSE, (LPARAM) pszText);
678
679    /* TODO Restore the old selection */
680
681    /* Purge buffer */
682    if (strchr(pszText, '\n') != NULL)
683    {
684       SetTimer(g_hwndLogFrame, TIMER_CLIPBUFFER_ID, TIMER_CLIPBUFFER_TIME, NULL);
685       if (!g_bClipPending)
686       {
687          /* Set the force clip timer going. This timer ensures clipping is done
688             intermittently even when there is a sustained burst of logging
689          */
690          SetTimer(g_hwndLogFrame, TIMER_CLIPBUFFER_FORCE_ID, TIMER_CLIPBUFFER_FORCE_TIME, NULL);
691       }
692       g_bClipPending = TRUE;
693    }
694
695    return 1;
696
697 }
698
699
700 /*********************************************************************
701  *
702  * Function    :  LogShowActivity
703  *
704  * Description :  Start the spinner.
705  *
706  * Parameters  :  None
707  *
708  * Returns     :  N/A
709  *
710  *********************************************************************/
711 void LogShowActivity(void)
712 {
713    /* Start some activity timers */
714    if (g_bShowActivityAnimation)
715    {
716       SetTimer(g_hwndLogFrame, TIMER_ANIM_ID, TIMER_ANIM_TIME, NULL);
717       SetTimer(g_hwndLogFrame, TIMER_ANIMSTOP_ID, TIMER_ANIMSTOP_TIME, NULL);
718    }
719
720 }
721
722
723 /*********************************************************************
724  *
725  * Function    :  LogClipBuffer
726  *
727  * Description :  Prunes old lines from the log.
728  *
729  * Parameters  :  None
730  *
731  * Returns     :  N/A
732  *
733  *********************************************************************/
734 void LogClipBuffer(void)
735 {
736    int nLines = SendMessage(g_hwndLogBox, EM_GETLINECOUNT, 0, 0);
737    if (g_bLimitBufferSize && nLines > g_nMaxBufferLines)
738    {
739       /* Compute the range representing the lines to be deleted */
740       LONG nLastLineToDelete = nLines - g_nMaxBufferLines;
741       LONG nLastChar = SendMessage(g_hwndLogBox, EM_LINEINDEX, nLastLineToDelete, 0);
742       CHARRANGE range;
743       range.cpMin = 0;
744       range.cpMax = nLastChar;
745
746       /* TODO get current selection */
747
748       /* TODO adjust and clip old selection against range to be deleted */
749
750       /* Select range and erase it (turning off autoscroll to prevent
751          nasty scrolling) */
752       SendMessage(g_hwndLogBox, EM_SETOPTIONS, ECOOP_XOR, ECO_AUTOVSCROLL);
753       SendMessage(g_hwndLogBox, EM_EXSETSEL, 0, (LPARAM) &range);
754       SendMessage(g_hwndLogBox, EM_REPLACESEL, FALSE, (LPARAM) "");
755       SendMessage(g_hwndLogBox, EM_SETOPTIONS, ECOOP_XOR, ECO_AUTOVSCROLL);
756
757       /* Restore old selection */
758    }
759
760 }
761
762
763 /*********************************************************************
764  *
765  * Function    :  CreateHiddenLogOwnerWindow
766  *
767  * Description :  Creates a hidden owner window that stops the log
768  *                window appearing in the task bar.
769  *
770  * Parameters  :
771  *          1  :  hInstance = application's instance handle
772  *
773  * Returns     :  Handle to newly created window.
774  *
775  *********************************************************************/
776 HWND CreateHiddenLogOwnerWindow(HINSTANCE hInstance)
777 {
778    static const char *szWndName = "PrivoxyLogOwner";
779    WNDCLASS wc;
780    HWND hwnd;
781
782    wc.style          = 0;
783    wc.lpfnWndProc    = LogOwnerWindowProc;
784    wc.cbClsExtra     = 0;
785    wc.cbWndExtra     = 0;
786    wc.hInstance      = hInstance;
787    wc.hIcon          = 0;
788    wc.hCursor        = 0;
789    wc.hbrBackground  = 0;
790    wc.lpszMenuName   = 0;
791    wc.lpszClassName  = szWndName;
792
793    RegisterClass(&wc);
794
795    hwnd = CreateWindow(szWndName, szWndName,
796       WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
797       CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
798
799    return hwnd;
800
801 }
802
803
804 /*********************************************************************
805  *
806  * Function    :  LogOwnerWindowProc
807  *
808  * Description :  Dummy procedure that does nothing special.
809  *
810  * Parameters  :
811  *          1  :  hwnd = window handle
812  *          2  :  uMsg = message number
813  *          3  :  wParam = first param for this message
814  *          4  :  lParam = next param for this message
815  *
816  * Returns     :  Same as `DefWindowProc'.
817  *
818  *********************************************************************/
819 LRESULT CALLBACK LogOwnerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
820 {
821    return DefWindowProc(hwnd, uMsg, wParam, lParam);
822
823 }
824
825
826 /*********************************************************************
827  *
828  * Function    :  CreateLogWindow
829  *
830  * Description :  Create the logging window.
831  *
832  * Parameters  :
833  *          1  :  hInstance = application's instance handle
834  *          2  :  nCmdShow = window show value (MIN, MAX, NORMAL, etc...)
835  *
836  * Returns     :  Handle to newly created window.
837  *
838  *********************************************************************/
839 HWND CreateLogWindow(HINSTANCE hInstance, int nCmdShow)
840 {
841    static const char *szWndName = "PrivoxyLogWindow";
842    static const char *szWndTitle = "Privoxy";
843
844    HWND hwnd = NULL;
845    HWND hwndOwner = (g_bShowOnTaskBar) ? NULL : CreateHiddenLogOwnerWindow(hInstance);
846    RECT rcClient;
847    WNDCLASSEX wc;
848
849    memset(&wc, 0, sizeof(wc));
850    wc.cbSize         = sizeof(wc);
851    wc.style          = CS_DBLCLKS;
852    wc.lpfnWndProc    = LogWindowProc;
853    wc.cbClsExtra     = 0;
854    wc.cbWndExtra     = 0;
855    wc.hInstance      = hInstance;
856    wc.hIcon          = g_hiconApp;
857    wc.hCursor        = 0;
858    wc.hbrBackground  = 0;
859    wc.lpszMenuName   = MAKEINTRESOURCE(IDR_LOGVIEW);
860    wc.lpszClassName  = szWndName;
861    wc.hbrBackground  = GetStockObject(WHITE_BRUSH);
862    RegisterClassEx(&wc);
863
864    hwnd = CreateWindowEx(WS_EX_APPWINDOW, szWndName, szWndTitle,
865       WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
866       CW_USEDEFAULT, hwndOwner, NULL, hInstance, NULL);
867
868    /* Now create a child list box */
869    GetClientRect(hwnd, &rcClient);
870
871    /* Create a rich edit control */
872    InitRichEdit();
873    g_hwndLogBox = CreateWindowEx(0, (g_nRichEditVersion == 0x0100) ? "RichEdit" : RICHEDIT_CLASS, "",
874       ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY | ES_NOHIDESEL | WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_VISIBLE,
875       rcClient.left, rcClient.top, rcClient.right, rcClient.bottom,
876       hwnd, NULL, hInstance, NULL);
877 /* SendMessage(g_hwndLogBox, EM_SETWORDWRAPMODE, 0, 0); */
878
879    /* Subclass the control to catch certain messages */
880    g_fnLogBox = (WNDPROC) GetWindowLong(g_hwndLogBox, GWL_WNDPROC);
881    SetWindowLong(g_hwndLogBox, GWL_WNDPROC, (LONG) LogRichEditProc);
882
883    /* Minimizing looks stupid when the log window is not on the task bar, so hide instead */
884    if (!g_bShowOnTaskBar &&
885          (nCmdShow == SW_SHOWMINIMIZED ||
886           nCmdShow == SW_MINIMIZE ||
887           nCmdShow == SW_SHOWMINNOACTIVE))
888    {
889       nCmdShow = SW_HIDE;
890    }
891
892    ShowWindow(hwnd, nCmdShow);
893    UpdateWindow(hwnd);
894
895    GetClientRect(g_hwndLogFrame, &rcClient);
896    SetWindowPos(g_hwndLogBox, NULL, rcClient.left, rcClient.top, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, SWP_NOZORDER);
897
898    return hwnd;
899
900 }
901
902
903 /*********************************************************************
904  *
905  * Function    :  InitRichEdit
906  *
907  * Description :  Initialise the rich edit control library.
908  *
909  * Parameters  :  None
910  *
911  * Returns     :  TRUE => success, FALSE => failure.
912  *                FIXME: this is backwards to the rest of IJB and to common
913  *                programming practice.  Please use 0 => success instead.
914  *
915  *********************************************************************/
916 BOOL InitRichEdit(void)
917 {
918    static HINSTANCE hInstRichEdit;
919    if (hInstRichEdit == NULL)
920    {
921       g_nRichEditVersion = 0;
922       hInstRichEdit = LoadLibraryA("RICHED20.DLL");
923       if (hInstRichEdit)
924       {
925          g_nRichEditVersion = _RICHEDIT_VER;
926       }
927       else
928       {
929          hInstRichEdit = LoadLibraryA("RICHED32.DLL");
930          if (hInstRichEdit)
931          {
932             g_nRichEditVersion = 0x0100;
933          }
934       }
935    }
936    return (hInstRichEdit != NULL) ? TRUE : FALSE;
937
938 }
939
940
941 /*********************************************************************
942  *
943  * Function    :  ShowLogWindow
944  *
945  * Description :  Shows or hides the log window.  We will also raise the
946  *                window on a show command in case it is buried.
947  *
948  * Parameters  :
949  *          1  :  bShow = TRUE to show, FALSE to mimize/hide
950  *
951  * Returns     :  N/A
952  *
953  *********************************************************************/
954 void ShowLogWindow(BOOL bShow)
955 {
956    if (bShow)
957    {
958       SetForegroundWindow(g_hwndLogFrame);
959       SetWindowPos(g_hwndLogFrame, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
960    }
961    else if (g_bShowOnTaskBar)
962    {
963       ShowWindow(g_hwndLogFrame, SW_MINIMIZE);
964    }
965    else
966    {
967       ShowWindow(g_hwndLogFrame, SW_HIDE);
968    }
969
970 }
971
972
973 /*********************************************************************
974  *
975  * Function    :  EditFile
976  *
977  * Description :  Opens the specified setting file for editing.
978  * FIXME: What if the file has no associated application. Check for return values
979 *        from ShellExecute??
980  *
981  * Parameters  :
982  *          1  :  filename = filename from the config (aka config.txt) file.
983  *
984  * Returns     :  N/A
985  *
986  *********************************************************************/
987 void EditFile(const char *filename)
988 {
989    if (filename)
990    {
991       ShellExecute(g_hwndLogFrame, "open", filename, NULL, NULL, SW_SHOWNORMAL);
992    }
993
994 }
995
996
997 /*--------------------------------------------------------------------------*/
998 /* Windows message handlers                                                 */
999 /*--------------------------------------------------------------------------*/
1000
1001
1002 /*********************************************************************
1003  *
1004  * Function    :  OnLogRButtonUp
1005  *
1006  * Description :  Handler for WM_RBUTTONUP messages.
1007  *
1008  * Parameters  :
1009  *          1  :  nModifier = wParam from mouse message (unused)
1010  *          2  :  x = x coordinate of the mouse event
1011  *          3  :  y = y coordinate of the mouse event
1012  *
1013  * Returns     :  N/A
1014  *
1015  *********************************************************************/
1016 void OnLogRButtonUp(int nModifier, int x, int y)
1017 {
1018    HMENU hMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_POPUP_SELECTION));
1019    if (hMenu != NULL)
1020    {
1021       HMENU hMenuPopup = GetSubMenu(hMenu, 0);
1022
1023       /* Check if there is a selection */
1024       CHARRANGE range;
1025       SendMessage(g_hwndLogBox, EM_EXGETSEL, 0, (LPARAM) &range);
1026       if (range.cpMin == range.cpMax)
1027       {
1028          EnableMenuItem(hMenuPopup, ID_EDIT_COPY, MF_BYCOMMAND | MF_GRAYED);
1029       }
1030       else
1031       {
1032          EnableMenuItem(hMenuPopup, ID_EDIT_COPY, MF_BYCOMMAND | MF_ENABLED);
1033       }
1034
1035       /* Display the popup */
1036       TrackPopupMenu(hMenuPopup, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON, x, y, 0, g_hwndLogFrame, NULL);
1037       DestroyMenu(hMenu);
1038    }
1039
1040 }
1041
1042
1043 /*********************************************************************
1044  *
1045  * Function    :  OnLogCommand
1046  *
1047  * Description :  Handler for WM_COMMAND messages.
1048  *
1049  * Parameters  :
1050  *          1  :  nCommand = the command portion of the menu selection event
1051  *
1052  * Returns     :  N/A
1053  *
1054  *********************************************************************/
1055 void OnLogCommand(int nCommand)
1056 {
1057    switch (nCommand)
1058    {
1059       case ID_SHOWWINDOW:
1060          ShowLogWindow(TRUE);
1061          break;
1062
1063       case ID_FILE_EXIT:
1064          PostMessage(g_hwndLogFrame, WM_CLOSE, 0, 0);
1065          break;
1066
1067       case ID_EDIT_COPY:
1068          SendMessage(g_hwndLogBox, WM_COPY, 0, 0);
1069          break;
1070
1071       case ID_VIEW_CLEARLOG:
1072          SendMessage(g_hwndLogBox, WM_SETTEXT, 0, (LPARAM) "");
1073          break;
1074
1075       case ID_VIEW_LOGMESSAGES:
1076          g_bLogMessages = !g_bLogMessages;
1077          /* SaveLogSettings(); */
1078          break;
1079
1080       case ID_VIEW_MESSAGEHIGHLIGHTING:
1081          g_bHighlightMessages = !g_bHighlightMessages;
1082          /* SaveLogSettings(); */
1083          break;
1084
1085       case ID_VIEW_LIMITBUFFERSIZE:
1086          g_bLimitBufferSize = !g_bLimitBufferSize;
1087          /* SaveLogSettings(); */
1088          break;
1089
1090       case ID_VIEW_ACTIVITYANIMATION:
1091          g_bShowActivityAnimation = !g_bShowActivityAnimation;
1092          /* SaveLogSettings(); */
1093          break;
1094
1095 #ifdef FEATURE_TOGGLE
1096       /* by haroon - change toggle to its opposite value */
1097       case ID_TOGGLE_ENABLED:
1098          g_bToggleIJB = !g_bToggleIJB;
1099          if (g_bToggleIJB)
1100          {
1101             log_error(LOG_LEVEL_INFO, "Now toggled ON.");
1102          }
1103          else
1104          {
1105             log_error(LOG_LEVEL_INFO, "Now toggled OFF.");
1106          }
1107          break;
1108 #endif /* def FEATURE_TOGGLE */
1109
1110       case ID_TOOLS_EDITCONFIG:
1111          EditFile(configfile);
1112          break;
1113
1114       case ID_TOOLS_EDITACTIONS:
1115          EditFile(g_actions_file);
1116          break;
1117
1118       case ID_TOOLS_EDITFILTERS:
1119          EditFile(g_re_filterfile);
1120          break;
1121
1122 #ifdef FEATURE_TRUST
1123       case ID_TOOLS_EDITTRUST:
1124          EditFile(g_trustfile);
1125          break;
1126 #endif /* def FEATURE_TRUST */
1127
1128       case ID_HELP_GPL:
1129          ShellExecute(g_hwndLogFrame, "open", "LICENSE.txt", NULL, NULL, SW_SHOWNORMAL);
1130          break;
1131
1132       case ID_HELP_FAQ:
1133          ShellExecute(g_hwndLogFrame, "open", "doc\\faq\\index.html", NULL, NULL, SW_SHOWNORMAL);
1134          break;
1135
1136       case ID_HELP_MANUAL:
1137          ShellExecute(g_hwndLogFrame, "open", "doc\\user-manual\\index.html", NULL, NULL, SW_SHOWNORMAL);
1138          break;
1139
1140       case ID_HELP_STATUS:
1141          ShellExecute(g_hwndLogFrame, "open", CGI_PREFIX "show-status", NULL, NULL, SW_SHOWNORMAL);
1142          break;
1143
1144       case ID_HELP_ABOUT:
1145          MessageBox(g_hwndLogFrame, win32_blurb, "About Privoxy", MB_OK);
1146          break;
1147
1148       default:
1149          /* DO NOTHING */
1150          break;
1151    }
1152
1153 }
1154
1155
1156 /*********************************************************************
1157  *
1158  * Function    :  OnLogInitMenu
1159  *
1160  * Description :  Handler for WM_INITMENU messages.  Enable, disable,
1161  *                check, and/or uncheck menu options as apropos.
1162  *
1163  * Parameters  :
1164  *          1  :  hmenu = handle to menu to "make current"
1165  *
1166  * Returns     :  N/A
1167  *
1168  *********************************************************************/
1169 void OnLogInitMenu(HMENU hmenu)
1170 {
1171    /* Only enable editors if there is a file to edit */
1172    EnableMenuItem(hmenu, ID_TOOLS_EDITACTIONS, MF_BYCOMMAND | (g_actions_file ? MF_ENABLED : MF_GRAYED));
1173    EnableMenuItem(hmenu, ID_TOOLS_EDITFILTERS, MF_BYCOMMAND | (g_re_filterfile ? MF_ENABLED : MF_GRAYED));
1174 #ifdef FEATURE_TRUST
1175    EnableMenuItem(hmenu, ID_TOOLS_EDITTRUST, MF_BYCOMMAND | (g_trustfile ? MF_ENABLED : MF_GRAYED));
1176 #endif /* def FEATURE_TRUST */
1177
1178    /* Check/uncheck options */
1179    CheckMenuItem(hmenu, ID_VIEW_LOGMESSAGES, MF_BYCOMMAND | (g_bLogMessages ? MF_CHECKED : MF_UNCHECKED));
1180    CheckMenuItem(hmenu, ID_VIEW_MESSAGEHIGHLIGHTING, MF_BYCOMMAND | (g_bHighlightMessages ? MF_CHECKED : MF_UNCHECKED));
1181    CheckMenuItem(hmenu, ID_VIEW_LIMITBUFFERSIZE, MF_BYCOMMAND | (g_bLimitBufferSize ? MF_CHECKED : MF_UNCHECKED));
1182    CheckMenuItem(hmenu, ID_VIEW_ACTIVITYANIMATION, MF_BYCOMMAND | (g_bShowActivityAnimation ? MF_CHECKED : MF_UNCHECKED));
1183 #ifdef FEATURE_TOGGLE
1184    /* by haroon - menu item for Enable toggle on/off */
1185    CheckMenuItem(hmenu, ID_TOGGLE_ENABLED, MF_BYCOMMAND | (g_bToggleIJB ? MF_CHECKED : MF_UNCHECKED));
1186 #endif /* def FEATURE_TOGGLE */
1187
1188 }
1189
1190
1191 /*********************************************************************
1192  *
1193  * Function    :  OnLogTimer
1194  *
1195  * Description :  Handler for WM_TIMER messages.
1196  *
1197  * Parameters  :
1198  *          1  :  nTimer = timer id (animation start/stop or clip buffer)
1199  *
1200  * Returns     :  N/A
1201  *
1202  *********************************************************************/
1203 void OnLogTimer(int nTimer)
1204 {
1205    switch (nTimer)
1206    {
1207       case TIMER_ANIM_ID:
1208          TraySetIcon(g_hwndTray, 1, g_hiconAnim[g_nAnimFrame++ % ANIM_FRAMES]);
1209          break;
1210
1211       case TIMER_ANIMSTOP_ID:
1212          g_nAnimFrame = 0;
1213          TraySetIcon(g_hwndTray, 1, g_hiconIdle);
1214          KillTimer(g_hwndLogFrame, TIMER_ANIM_ID);
1215          KillTimer(g_hwndLogFrame, TIMER_ANIMSTOP_ID);
1216          break;
1217
1218       case TIMER_CLIPBUFFER_ID:
1219       case TIMER_CLIPBUFFER_FORCE_ID:
1220          LogClipBuffer();
1221          g_bClipPending = FALSE;
1222          KillTimer(g_hwndLogFrame, TIMER_CLIPBUFFER_ID);
1223          KillTimer(g_hwndLogFrame, TIMER_CLIPBUFFER_FORCE_ID);
1224          break;
1225
1226       default:
1227          /* DO NOTHING */
1228          break;
1229    }
1230
1231 }
1232
1233
1234 /*********************************************************************
1235  *
1236  * Function    :  LogRichEditProc
1237  *
1238  * Description :  Window subclass routine handles some events for the rich edit control.
1239  *
1240  * Parameters  :
1241  *          1  :  hwnd = window handle of the rich edit control
1242  *          2  :  uMsg = message number
1243  *          3  :  wParam = first param for this message
1244  *          4  :  lParam = next param for this message
1245  *
1246  * Returns     :  Appropriate M$ window message handler codes.
1247  *
1248  *********************************************************************/
1249 LRESULT CALLBACK LogRichEditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1250 {
1251    switch (uMsg)
1252    {
1253       case WM_RBUTTONUP:
1254       {
1255          POINT pt;
1256          pt.x = LOWORD(lParam);
1257          pt.y = HIWORD(lParam);
1258          ClientToScreen(hwnd, &pt);
1259          OnLogRButtonUp(wParam, pt.x, pt.y);
1260       }
1261       return 0;
1262    }
1263    return CallWindowProc(g_fnLogBox, hwnd, uMsg, wParam, lParam);
1264
1265 }
1266
1267
1268 /*********************************************************************
1269  *
1270  * Function    :  LogWindowProc
1271  *
1272  * Description :  Windows call back routine handles events on the log window.
1273  *
1274  * Parameters  :
1275  *          1  :  hwnd = handle of the logging window
1276  *          2  :  uMsg = message number
1277  *          3  :  wParam = first param for this message
1278  *          4  :  lParam = next param for this message
1279  *
1280  * Returns     :  Appropriate M$ window message handler codes.
1281  *
1282  *********************************************************************/
1283 LRESULT CALLBACK LogWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1284 {
1285    switch (uMsg)
1286    {
1287       case WM_CREATE:
1288          return 0;
1289
1290       case WM_CLOSE:
1291          /* This is the end - beautiful friend - the end */
1292          DestroyWindow(g_hwndLogBox);
1293          DestroyWindow(g_hwndLogFrame);
1294          return 0;
1295
1296       case WM_DESTROY:
1297          PostQuitMessage(0);
1298          return 0;
1299
1300       case WM_SHOWWINDOW:
1301       case WM_SIZE:
1302          /* Resize the logging window to fit the new frame */
1303          if (g_hwndLogBox)
1304          {
1305             RECT rc;
1306             GetClientRect(g_hwndLogFrame, &rc);
1307             SetWindowPos(g_hwndLogBox, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER);
1308          }
1309          return 0;
1310
1311       case WM_INITMENU:
1312          OnLogInitMenu((HMENU) wParam);
1313          return 0;
1314
1315       case WM_TIMER:
1316          OnLogTimer(wParam);
1317          return 0;
1318
1319       case WM_COMMAND:
1320          OnLogCommand(LOWORD(wParam));
1321          return 0;
1322
1323       case WM_SYSCOMMAND:
1324          switch (wParam)
1325          {
1326             case SC_CLOSE:
1327                if (g_bCloseHidesWindow)
1328                {
1329                   ShowLogWindow(FALSE);
1330                   return 0;
1331                }
1332                break;
1333             case SC_MINIMIZE:
1334                ShowLogWindow(FALSE);
1335                return 0;
1336          }
1337          break;
1338    }
1339
1340    return DefWindowProc(hwnd, uMsg, wParam, lParam);
1341
1342 }
1343
1344 #endif /* ndef _WIN_CONSOLE - entire file */
1345
1346 /*
1347   Local Variables:
1348   tab-width: 3
1349   end:
1350 */