6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2009-2013, MinGW.org Project
10 * Implementation of GUI specific API extensions, providing support
11 * for the DMH_SUBSYSTEM_GUI diagnostic message handling subsystem.
14 * This is free software. Permission is granted to copy, modify and
15 * redistribute this software, under the provisions of the GNU General
16 * Public License, Version 3, (or, at your option, any later version),
17 * as published by the Free Software Foundation; see the file COPYING
18 * for licensing details.
20 * Note, in particular, that this software is provided "as is", in the
21 * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
22 * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
23 * PARTICULAR PURPOSE. Under no circumstances will the author, or the
24 * MinGW Project, accept liability for any damages, however caused,
25 * arising from the use of this software.
35 #define WIN32_LEAN_AND_MEAN
38 /* Establish limits on the buffer size for any EDITTEXT control which is
39 * used to emulate the VDU device of a pseudo-TTY diagnostic console.
41 #define DMH_PTY_MIN_BUFSIZ 4096
42 #define DMH_PTY_MAX_BUFSIZ 32768
43 #define DMH_PTY_GUARDBYTES 1
47 /* An auxiliary class, which may be associated with a DMH controller,
48 * to control the emulated VDU device of a pseudo-TTY console.
52 int printf( const char *, va_list );
57 static HFONT console_font;
58 char *console_buffer, *caret; size_t max;
62 /* When we assign an emulated PTY display to a DMH data stream, in a
63 * GUI context, we will wish to also assign a monospaced font object,
64 * (Lucida Console, for preference). We will create this font object
65 * once, on making the first PTY assignment, and will maintain this
66 * static reference to locate it; we initialise the reference to NULL,
67 * indicating the requirement to create the font object.
69 HFONT dmhTypePTY::console_font = NULL;
71 static int CALLBACK dmhWordBreak( const char *buf, int cur, int max, int op )
73 /* This trivial word-break call-back is assigned in place of the default
74 * handler for any EDITTEXT control used as a pseudo-TTY; it emulates the
75 * behaviour of a dumb VT-100 display, wrapping text at the right extent
76 * of the "screen", regardless of proximity to any natural word-break.
78 * Note that this call-back function must conform to the prototype which
79 * has been specified by Microsoft, even though it uses neither the "buf"
80 * nor the "max" arguments; we simply confirm, on request, that each and
81 * every character in "buf" may be considered as a word-break delimiter,
82 * and that the "cur" position in "buf" is a potential word-break point.
84 return (op == WB_ISDELIMITER) ? TRUE : cur;
87 dmhTypePTY::dmhTypePTY( HWND console ): console_hook( console )
89 /* Construct a PTY controller and assign it to the EDITTEXT control which
90 * we assume, without checking, has been passed as "console".
92 if( console_font == NULL )
94 /* The font object for use with PTY diagnostic streams has yet to be
95 * created; create it now.
98 console_font = (HFONT)(SendMessage( console, WM_GETFONT, 0, 0 ));
99 GetObject( console_font, sizeof( LOGFONT ), &font_info );
100 strcpy( (char *)(&(font_info.lfFaceName)), "Lucida Console" );
101 font_info.lfHeight = (font_info.lfHeight * 9) / 10;
102 font_info.lfWidth = (font_info.lfWidth * 9) / 10;
103 console_font = CreateFontIndirect( &font_info );
105 if( console_font != NULL )
107 * When we have a valid font object, we instruct the EDITTEXT control
108 * to use it, within its device context.
110 SendMessage( console, WM_SETFONT, (WPARAM)(console_font), TRUE );
112 /* Override the default EDITTEXT word-wrapping behaviour, so that we
113 * more accurately emulate the display behaviour of a VT-100 device.
115 SendMessage( console, EM_SETWORDBREAKPROC, 0, (LPARAM)(dmhWordBreak) );
117 /* Finally, establish a working buffer for output to the PTY, and set
118 * the initial caret position for the output text stream.
120 caret = console_buffer = (char *)(malloc( max = DMH_PTY_MIN_BUFSIZ ));
123 int dmhTypePTY::printf( const char *fmt, va_list argv )
125 /* Handler for printf() style output to a DMH pseudo-TTY stream.
127 * We begin by formatting the specified arguments, while saving
128 * the result into a local transfer buffer; (as a side effect,
129 * this also sets the output character count)...
131 char buf[1 + vsnprintf( NULL, 0, fmt, argv )];
132 int retval = vsnprintf( buf, sizeof( buf ), fmt, argv );
133 for( char *bufptr = buf; *bufptr; ++bufptr )
135 /* We transfer the content of the local buffer to the "device"
136 * buffer; (we do this character by character, because the DMH
137 * output stream encodes line breaks as God intended -- using
138 * '\n' only -- but the EDITTEXT control which emulates the PTY
139 * "device" requires Lucifer's "\r\n" encoding; thus '\r' and
140 * '\n' require special handling).
142 if( *bufptr == '\r' )
144 /* An explicit '\r' in the data stream should return the
145 * caret to the start of the current PHYSICAL line in the
149 while( (mark > console_buffer) && (*--mark != '\n') )
152 /* The '\r' character isn't actually written to the
153 * output buffer; discount it.
158 { /* Any other character is transferred to the "device" buffer...
160 if( *bufptr == '\n' )
162 /* ...inserting, (and counting), an implied '\r' before each
163 * '\n', to comply with Lucifer's encoding standard.
171 /* When all output has been transferred to the "device" buffer, we
172 * terminate and flush it to the EDITTEXT control.
175 SendMessage( console_hook, WM_SETTEXT, 0, (LPARAM)(console_buffer) );
177 /* As we write it, ensure that the last line written remains visible,
178 * with the caret positioned after it.
180 SendMessage( console_hook, EM_SETSEL, caret - console_buffer, (LPARAM)(-1) );
181 SendMessage( console_hook, EM_SCROLLCARET, (WPARAM)(0), (LPARAM)(0) );
183 /* Finally, we repaint the display window, and return the output
186 InvalidateRect( console_hook, NULL, FALSE );
187 UpdateWindow( console_hook );
191 int dmhTypePTY::putchar( int charval )
193 /* Helper method for appending a single character to the PTY "device"
194 * buffer; this will allocate an expanding buffer, (up to the maximum
195 * specified size limit, with reservation of sufficient guard bytes to
196 * ensure that output may be safely terminated), as may be required.
199 const size_t guarded_max = max - DMH_PTY_GUARDBYTES;
200 if( (offset = caret - console_buffer) >= guarded_max )
202 /* The current "device" buffer is full; compute a new size, for
203 * possible buffer expansion.
206 if( (newsize = max + DMH_PTY_MIN_BUFSIZ) > DMH_PTY_MAX_BUFSIZ )
207 newsize = DMH_PTY_MAX_BUFSIZ;
211 /* The buffer has not yet grown to its maximum allowed size;
212 * attempt to allocate additional memory, to expand it.
215 if( (newbuf = (char *)(realloc( console_buffer, newsize ))) != NULL )
217 /* Allocation was successful; complete assignment of the new
218 * buffer, and adjust the caret to preserve its position, at
219 * its original offset within the new buffer.
222 caret = (console_buffer = newbuf) + offset;
225 if( offset >= guarded_max )
227 /* The buffer has reached its maximum permitted size, (or there
228 * was insufficient free memory to expand it), and there still
229 * isn't room to accommodate another character; locate the start
230 * of the SECOND logical line within it...
232 char *mark = console_buffer, *endptr = caret;
233 while( *mark && (mark < caret) && (*mark++ != '\n') )
235 /* ...then copy it, and all following lines, to the start of the
236 * buffer, so deleting the FIRST logical line, and thus free up
237 * an equivalent amount of space at the end.
239 * Note: we might get away with a single memcpy() of the entire
240 * portion of the buffer, from the SECOND line onward, backwards
241 * to overwrite the FIRST line, but that would entail copying of
242 * overlapping memory regions, which is technically described as
243 * causing undefined behaviour; to avoid any possible problems,
244 * we identify a block size equal to the space occupied by the
247 size_t residual = caret - mark;
248 size_t block_size = mark - (caret = console_buffer);
249 while( residual >= block_size )
251 /* ...then copy the residual, in chunks of that size, (so that
252 * there is never any overlap), until we reduce the residual to
253 * less than the block size...
255 memcpy( caret, mark, block_size );
256 residual -= block_size; caret = mark; mark += block_size;
260 /* ...finishing up by copying any lesser sized final residual,
261 * and leaving the caret at the start of the space so freed.
263 memcpy( caret, mark, residual );
268 /* Finally, store the current character into the "device" buffer, and
269 * adjust the caret position in readiness for the next.
271 return *caret++ = charval;
274 dmhTypePTY::~dmhTypePTY()
276 /* When closing a PTY controller handle, we may free the memory which
277 * we allocated for its "device" buffer. (Note that we DO NOT delete
278 * the assocaiated font object, because the associated EDITTEXT control
279 * may outlive its controller, and will continue to need it; however,
280 * it does not need the buffer, because it maintains its own private
281 * copy of the content).
283 free( (void *)(console_buffer) );
286 class dmhTypeGUI: public dmhTypeGeneric
288 /* Diagnostic message handler for use in GUI applications.
291 dmhTypeGUI( const char* );
292 virtual uint16_t control( const uint16_t, const uint16_t );
293 virtual int notify( const dmh_severity, const char*, va_list );
294 virtual int printf( const char*, va_list );
295 virtual void set_console_hook( void * );
298 HWND owner; uint16_t mode;
299 char *msgbuf; size_t msglen;
300 int dispatch_message( int );
301 dmhTypePTY *console_hook;
304 #if IMPLEMENTATION_LEVEL == PACKAGE_BASE_COMPONENT
307 dmhTypeGeneric *dmh_init_gui( const char * ) __declspec(dllexport);
308 dmhTypeGeneric *dmh_init_gui( const char *progname )
310 /* Hook required by the dmh_init() function, to support run-time
311 * binding of this DMH_SUBSYSTEM_GUI implementation.
313 return new dmhTypeGUI( progname );
316 #endif /* IMPLEMENTATION_LEVEL == PACKAGE_BASE_COMPONENT */
318 /* Constructor serves to initialise the message handler,
319 * creating the class instance, and storing the specified
320 * program name within it.
322 dmhTypeGUI::dmhTypeGUI( const char* name ):dmhTypeGeneric( name ),
324 * This handler also requires initialisation of the
325 * message box content collector...
327 owner( NULL ), mode( 0 ), msgbuf( NULL ), msglen( 0 ),
329 * ...and must set the default state for pseudo-console
330 * association to "none".
332 console_hook( NULL ){}
334 uint16_t dmhTypeGUI::control( const uint16_t request, const uint16_t mask )
336 /* Select optional features of the GUI class message handler...
338 if( request & DMH_DISPATCH_DIGEST )
340 * ...and dispatch "flush" requests for messages which have
341 * been collected, when operating in "digest mode".
343 dispatch_message( msglen );
344 return mode = request | (mode & mask);
347 void dmhTypeGUI::set_console_hook( void *console )
349 /* Helper method to assign an emulated PTY "device" to the DMH stream;
350 * (note that any prior PTY assignment is first discarded).
353 console_hook = (console == NULL) ? NULL : new dmhTypePTY( (HWND)(console) );
356 int dmhTypeGUI::notify( const dmh_severity code, const char *fmt, va_list argv )
358 /* Message dispatcher for GUI applications; this formats messages
359 * for display within a conventional MessageBox dialogue.
361 if( console_hook != NULL )
363 /* When the DMH stream has an associated PTY, then we direct this
364 * notification to it, analogously to the CLI use of a TTY.
366 return dmh_printf( notification_format, progname, severity_tag( code ) )
367 + console_hook->printf( fmt, argv );
370 /* Conversely, when there is no associated PTY, then we lay out the
371 * notification for display in a windows message box.
373 int newlen = 1 + vsnprintf( NULL, 0, fmt, argv );
374 if( mode & DMH_COMPILE_DIGEST )
376 /* The message handler is operating in "digest mode"; individual
377 * messages are collected into a dynamically extensible buffer,
378 * until a control request flushes the entire collection to a
382 if( (newbuf = (char *)(realloc( msgbuf, newlen + msglen ))) != NULL )
384 /* The message buffer has been successfully extended
385 * to accommodate the incoming message...
387 if( (mode & DMH_SEVERITY_MASK) < code )
389 * Adjust the severity code to match the maximum
390 * of all messages currently collected...
392 mode = code | (mode & ~DMH_SEVERITY_MASK);
394 /* ...and append the text of the incoming message
395 * to the buffered collection.
397 msglen += vsprintf( (msgbuf = newbuf) + msglen, fmt, argv );
400 else if( (msgbuf = (char *)(malloc( newlen ))) != NULL )
402 /* The handler is operating in "one shot" mode; record
403 * the severity of the incoming message and dispatch it
404 * immediately, in its own separate MessageBox.
406 mode = code | (mode & ~DMH_SEVERITY_MASK);
407 return dispatch_message( vsprintf( msgbuf, fmt, argv ) );
411 static inline HWND last_active_popup( HWND owner )
413 /* Local helper function to ensure that diagnostic MessageBox
414 * dialogues are assigned to the most recently active pop-up,
415 * if any, invoked by the primary owner application window.
417 HWND popup = GetLastActivePopup( owner );
418 if( IsWindow( popup ) )
423 int dmhTypeGUI::dispatch_message( int len )
425 /* Helper used by both the control() and notify() methods,
426 * for dispatching messages, either individually or as a
427 * "digest mode" collection, to a MessageBox.
431 /* The MessageBox will exhibit only an "OK" button...
434 switch( mode & DMH_SEVERITY_MASK )
436 /* ...and will be adorned, as appropriate to the
437 * recorded message severity, with...
440 /* ...an icon which is indicative of an
441 * informational message...
443 status |= MB_ICONINFORMATION;
447 /* ...a warning message...
449 status |= MB_ICONWARNING;
453 /* ...or, if neither of the above is
454 * appropriate, an error message.
456 status |= MB_ICONERROR;
460 * The owner window has yet to be identified; try to match
461 * it, on the basis of the window class name, to the top
462 * level application window.
464 owner = FindWindow( progname, NULL );
466 /* Dispatch the message...
468 MessageBox( last_active_popup( owner ), msgbuf, progname, status );
470 /* ...then release the heap memory used to format it.
472 free( (void *)(msgbuf) );
473 msgbuf = NULL; msglen = 0;
475 /* Always return the number of characters in the message,
476 * as notified by the calling method.
481 int dmhTypeGUI::printf( const char *fmt, va_list argv )
483 /* Display arbitrary text messages via the diagnostic message handler.
485 if( console_hook != NULL )
487 * When the DMH stream has an associated PTY, then we simply
488 * emit the message via that.
490 return console_hook->printf( fmt, argv );
492 /* Conversely, when there is no associated PTY, we redirect the
493 * message for display in a windows message box, in the guise of
494 * a DMH_INFO notification.
496 return notify( DMH_INFO, fmt, argv );
499 /* $RCSfile$: end of file */