The MinGW.org Installation Manager Tool
リビジョン | 2fe2646c69cf31bcd03721ac35f7f56816712fdf (tree) |
---|---|
日時 | 2012-12-05 22:50:11 |
作者 | Keith Marshall <keithmarshall@user...> |
コミッター | Keith Marshall |
Implement PTY display emulation for GUI mode DMH.
@@ -1,3 +1,26 @@ | ||
1 | +2012-12-05 Keith Marshall <keithmarshall@users.sourceforge.net> | |
2 | + | |
3 | + Implement PTY display emulation for GUI mode DMH. | |
4 | + | |
5 | + * src/dmhcore.h (dmhTypeGeneric::set_console_hook): New public virtual | |
6 | + method; declare it, providing inline default do-nothing implementation. | |
7 | + (dmhTypeGeneric::severity_tag): New protected static method; declare it. | |
8 | + (dmhTypeGeneric::notification_format): New protected static property; | |
9 | + declare it. | |
10 | + | |
11 | + * src/dmh.cpp (dmhTypeGeneric::severity_tag): Implement it. | |
12 | + (dmhTypeGeneric::notification_format): Define and initialise it. | |
13 | + (dmhTypeTTY::notify): Use them. | |
14 | + (dmh_setpty): New function; declare prototype and implement it. | |
15 | + | |
16 | + * src/guidmh.cpp (dmhTypePTY): New locally implemented class. | |
17 | + (dmhTypeGUI::set_console_hook): New virtual method; declare and | |
18 | + implement it; it stores a dmhTypePTY reference pointer into... | |
19 | + (dmhTypeGUI::console_hook): ...this new private member variable. | |
20 | + (dmhTypeGUI::dmhTypeGUI): Initialise it. | |
21 | + (dmhTypeGUI::notify) [console_hook != NULL]: Delegate to it. | |
22 | + (dmhTypeGUI::printf) [console_hook != NULL]: Likewise. | |
23 | + | |
1 | 24 | 2012-12-03 Keith Marshall <keithmarshall@users.sourceforge.net> |
2 | 25 | |
3 | 26 | Implement generalised API for dialogue boxes with worker threads. |
@@ -34,6 +34,12 @@ | ||
34 | 34 | #define WIN32_LEAN_AND_MEAN |
35 | 35 | #include <windows.h> |
36 | 36 | |
37 | +/* Prototype this at point of use, rather than in dmhcore.h, | |
38 | + * so that we don't need to arbitrarily include windows.h in | |
39 | + * any DMH client. | |
40 | + */ | |
41 | +EXTERN_C void dmh_setpty( HWND ); | |
42 | + | |
37 | 43 | /* Implementation of the dmh_exception class. |
38 | 44 | */ |
39 | 45 | static const char *unspecified_error = "Unspecified error"; |
@@ -161,9 +167,9 @@ inline int dmhTypeTTY::emit_and_flush( int status ) | ||
161 | 167 | return status; |
162 | 168 | } |
163 | 169 | |
164 | -int dmhTypeTTY::notify( const dmh_severity code, const char *fmt, va_list argv ) | |
170 | +const char *dmhTypeGeneric::severity_tag( dmh_severity code ) | |
165 | 171 | { |
166 | - /* Message dispatcher for console class applications. | |
172 | + /* Helper function to assign labels to the known severity codes. | |
167 | 173 | */ |
168 | 174 | static const char *severity[] = |
169 | 175 | { |
@@ -174,13 +180,24 @@ int dmhTypeTTY::notify( const dmh_severity code, const char *fmt, va_list argv ) | ||
174 | 180 | "ERROR", /* DMH_ERROR */ |
175 | 181 | "FATAL" /* DMH_FATAL */ |
176 | 182 | }; |
183 | + return severity[code]; | |
184 | +} | |
185 | + | |
186 | +/* Establish the format to be used for the prefix string, which is | |
187 | + * attached to TTY and PTY notifications. | |
188 | + */ | |
189 | +const char *dmhTypeGeneric::notification_format = "%s: *** %s *** "; | |
177 | 190 | |
191 | +int dmhTypeTTY::notify( const dmh_severity code, const char *fmt, va_list argv ) | |
192 | +{ | |
193 | + /* Message dispatcher for console class applications. | |
194 | + */ | |
178 | 195 | /* Dispatch the message to standard error, terminate application |
179 | 196 | * if DMH_FATAL, else continue, returning the message length. |
180 | 197 | */ |
181 | 198 | return abort_if_fatal( code, |
182 | 199 | emit_and_flush( |
183 | - fprintf( stderr, "%s: *** %s *** ", progname, severity[code] ) | |
200 | + fprintf( stderr, notification_format, progname, severity_tag( code ) ) | |
184 | 201 | + vfprintf( stderr, fmt, argv ) |
185 | 202 | ) |
186 | 203 | ); |
@@ -196,9 +213,21 @@ int dmhTypeTTY::printf( const char *fmt, va_list argv ) | ||
196 | 213 | |
197 | 214 | EXTERN_C uint16_t dmh_control( const uint16_t request, const uint16_t mask ) |
198 | 215 | { |
216 | + /* Public helper to access and manipulate the control channel for | |
217 | + * the diagnostic message handler. | |
218 | + */ | |
199 | 219 | return dmh->control( request, mask ); |
200 | 220 | } |
201 | 221 | |
222 | +EXTERN_C void dmh_setpty( HWND console ) | |
223 | +{ | |
224 | + /* Public API for assignment of a GUI window as a pseudo-console | |
225 | + * for streaming of diagnostic message handler output; ("console" | |
226 | + * is assumed to refer to a Windows EDITTEXT control). | |
227 | + */ | |
228 | + dmh->set_console_hook( (void *)(console) ); | |
229 | +} | |
230 | + | |
202 | 231 | EXTERN_C int dmh_notify( const dmh_severity code, const char *fmt, ... ) |
203 | 232 | { |
204 | 233 | /* Public entry point for diagnostic message dispatcher. |
@@ -36,12 +36,15 @@ class dmhTypeGeneric | ||
36 | 36 | */ |
37 | 37 | public: |
38 | 38 | dmhTypeGeneric( const char* ); |
39 | + virtual void set_console_hook( void * ){} | |
39 | 40 | virtual uint16_t control( const uint16_t, const uint16_t ) = 0; |
40 | 41 | virtual int notify( const dmh_severity, const char*, va_list ) = 0; |
41 | 42 | virtual int printf( const char*, va_list ) = 0; |
42 | 43 | |
43 | 44 | protected: |
44 | 45 | const char *progname; |
46 | + static const char *severity_tag( dmh_severity ); | |
47 | + static const char *notification_format; | |
45 | 48 | }; |
46 | 49 | |
47 | 50 | #endif /* DMHCORE_H: $RCSfile$: end of file */ |
@@ -34,6 +34,217 @@ | ||
34 | 34 | #define WIN32_LEAN_AND_MEAN |
35 | 35 | #include <windows.h> |
36 | 36 | |
37 | +/* Establish limits on the buffer size for any EDITTEXT control which is | |
38 | + * used to emulate the VDU device of a pseudo-TTY diagnostic console. | |
39 | + */ | |
40 | +#define DMH_PTY_MIN_BUFSIZ 4096 | |
41 | +#define DMH_PTY_MAX_BUFSIZ 32768 | |
42 | + | |
43 | +class dmhTypePTY | |
44 | +{ | |
45 | + /* An auxiliary class, which may be associated with a DMH controller, | |
46 | + * to control the emulated VDU device of a pseudo-TTY console. | |
47 | + */ | |
48 | + public: | |
49 | + dmhTypePTY( HWND ); | |
50 | + int printf( const char *, va_list ); | |
51 | + ~dmhTypePTY(); | |
52 | + | |
53 | + private: | |
54 | + HWND console_hook; | |
55 | + static HFONT console_font; | |
56 | + char *console_buffer, *caret; size_t max; | |
57 | + int putchar( int ); | |
58 | +}; | |
59 | + | |
60 | +/* When we assign an emulated PTY display to a DMH data stream, in a | |
61 | + * GUI context, we will wish to also assign a monospaced font object, | |
62 | + * (Lucida Console, for preference). We will create this font object | |
63 | + * once, on making the first PTY assignment, and will maintain this | |
64 | + * static reference to locate it; we initialise the reference to NULL, | |
65 | + * indicating the requirement to create the font object. | |
66 | + */ | |
67 | +HFONT dmhTypePTY::console_font = NULL; | |
68 | + | |
69 | +static int CALLBACK dmhWordBreak( const char *buf, int cur, int max, int op ) | |
70 | +{ | |
71 | + /* This trivial word-break call-back is assigned in place of the default | |
72 | + * handler for any EDITTEXT control used as a pseudo-TTY; it emulates the | |
73 | + * behaviour of a dumb VT-100 display, wrapping text at the right extent | |
74 | + * of the "screen", regardless of proximity to any natural word-break. | |
75 | + * | |
76 | + * Note that this call-back function must conform to the prototype which | |
77 | + * has been specified by Microsoft, even though it uses neither the "buf" | |
78 | + * nor the "max" arguments; we simply confirm, on request, that each and | |
79 | + * every character in "buf" may be considered as a word-break delimiter, | |
80 | + * and that the "cur" position in "buf" is a potential word-break point. | |
81 | + */ | |
82 | + return (op == WB_ISDELIMITER) ? TRUE : cur; | |
83 | +} | |
84 | + | |
85 | +dmhTypePTY::dmhTypePTY( HWND console ): console_hook( console ) | |
86 | +{ | |
87 | + /* Construct a PTY controller and assign it to the EDITTEXT control which | |
88 | + * we assume, without checking, has been passed as "console". | |
89 | + */ | |
90 | + if( console_font == NULL ) | |
91 | + { | |
92 | + /* The font object for use with PTY diagnostic streams has yet to be | |
93 | + * created; create it now. | |
94 | + */ | |
95 | + LOGFONT font_info; | |
96 | + console_font = (HFONT)(SendMessage( console, WM_GETFONT, 0, 0 )); | |
97 | + GetObject( console_font, sizeof( LOGFONT ), &font_info ); | |
98 | + strcpy( (char *)(&(font_info.lfFaceName)), "Lucida Console" ); | |
99 | + font_info.lfHeight = (font_info.lfHeight * 9) / 10; | |
100 | + font_info.lfWidth = (font_info.lfWidth * 9) / 10; | |
101 | + console_font = CreateFontIndirect( &font_info ); | |
102 | + } | |
103 | + if( console_font != NULL ) | |
104 | + /* | |
105 | + * When we have a valid font object, we instruct the EDITTEXT control | |
106 | + * to use it, within its device context. | |
107 | + */ | |
108 | + SendMessage( console, WM_SETFONT, (WPARAM)(console_font), TRUE ); | |
109 | + | |
110 | + /* Override the default EDITTEXT word-wrapping behaviour, so that we | |
111 | + * more accurately emulate the display behaviour of a VT-100 device. | |
112 | + */ | |
113 | + SendMessage( console, EM_SETWORDBREAKPROC, 0, (LPARAM)(dmhWordBreak) ); | |
114 | + | |
115 | + /* Finally, establish a working buffer for output to the PTY, and set | |
116 | + * the initial caret position for the output text stream. | |
117 | + */ | |
118 | + caret = console_buffer = (char *)(malloc( max = DMH_PTY_MIN_BUFSIZ )); | |
119 | +} | |
120 | + | |
121 | +int dmhTypePTY::printf( const char *fmt, va_list argv ) | |
122 | +{ | |
123 | + /* Handler for printf() style output to a DMH pseudo-TTY stream. | |
124 | + * | |
125 | + * We begin by formatting the specified arguments, while saving | |
126 | + * the result into a local transfer buffer; (as a side effect, | |
127 | + * this also sets the output character count)... | |
128 | + */ | |
129 | + char buf[1 + vsnprintf( NULL, 0, fmt, argv )]; | |
130 | + int retval = vsnprintf( buf, sizeof( buf ), fmt, argv ); | |
131 | + for( char *bufptr = buf; *bufptr; ++bufptr ) | |
132 | + { | |
133 | + /* We transfer the content of the local buffer to the "device" | |
134 | + * buffer; (we do this character by character, because the DMH | |
135 | + * output stream encodes line breaks as God intended -- using | |
136 | + * '\n' only -- but the EDITTEXT control which emulates the PTY | |
137 | + * "device" requires Lucifer's "\r\n" encoding; thus '\r' and | |
138 | + * '\n' require special handling). | |
139 | + */ | |
140 | + if( *bufptr == '\r' ) | |
141 | + { | |
142 | + /* An explicit '\r' in the data stream should return the | |
143 | + * caret to the start of the current PHYSICAL line in the | |
144 | + * EDITTEXT display | |
145 | + * | |
146 | + * FIXME: must implement this; ignore, (and discount), it | |
147 | + * for now. | |
148 | + */ | |
149 | + --retval; | |
150 | + } | |
151 | + else | |
152 | + { /* Any other character is transferred to the "device" buffer... | |
153 | + */ | |
154 | + if( *bufptr == '\n' ) | |
155 | + { | |
156 | + /* ...inserting, (and counting), an implied '\r' before each | |
157 | + * '\n', to comply with Lucifer's encoding standard. | |
158 | + */ | |
159 | + putchar( '\r' ); | |
160 | + ++retval; | |
161 | + } | |
162 | + putchar( *bufptr ); | |
163 | + } | |
164 | + } | |
165 | + /* When all output has been transferred to the "device" buffer, we | |
166 | + * terminate and flush it to the EDITTEXT control. | |
167 | + */ | |
168 | + *caret = '\0'; | |
169 | + SendMessage( console_hook, EM_SETSEL, 0, (LPARAM)(-1) ); | |
170 | + SendMessage( console_hook, EM_REPLACESEL, FALSE, (LPARAM)(console_buffer) ); | |
171 | + | |
172 | + /* Finally, we repaint the display window, and return the output | |
173 | + * character count. | |
174 | + */ | |
175 | + InvalidateRect( console_hook, NULL, FALSE ); | |
176 | + UpdateWindow( console_hook ); | |
177 | + return retval; | |
178 | +} | |
179 | + | |
180 | +int dmhTypePTY::putchar( int charval ) | |
181 | +{ | |
182 | + /* Helper method for appending a single character to the PTY "device" | |
183 | + * buffer; this will allocate an expanding buffer, (up to the maximum | |
184 | + * specified size limit), as may be required. | |
185 | + */ | |
186 | + size_t offset; | |
187 | + if( (offset = caret - console_buffer) >= max ) | |
188 | + { | |
189 | + /* The current "device" buffer is full; compute a new size, for | |
190 | + * possible buffer expansion. | |
191 | + */ | |
192 | + size_t newsize; | |
193 | + if( (newsize = max + DMH_PTY_MIN_BUFSIZ) > DMH_PTY_MAX_BUFSIZ ) | |
194 | + newsize = DMH_PTY_MAX_BUFSIZ; | |
195 | + | |
196 | + if( newsize > max ) | |
197 | + { | |
198 | + /* The buffer has not yet grown to its maximum allowed size; | |
199 | + * attempt to allocate additional memory, to expand it. | |
200 | + */ | |
201 | + char *newbuf; | |
202 | + if( (newbuf = (char *)(realloc( console_buffer, newsize ))) != NULL ) | |
203 | + { | |
204 | + /* Allocation was successful; complete assignment of the new | |
205 | + * buffer, and adjust the caret to preserve its position, at | |
206 | + * its original offset within the new buffer. | |
207 | + */ | |
208 | + max = newsize; | |
209 | + caret = (console_buffer = newbuf) + offset; | |
210 | + } | |
211 | + } | |
212 | + if( offset >= max ) | |
213 | + { | |
214 | + /* The buffer has reached its maximum permitted size, (or there | |
215 | + * was insufficient free memory to expand it), and there still | |
216 | + * isn't room to accommodate another character; locate the start | |
217 | + * of the SECOND logical line within it... | |
218 | + */ | |
219 | + char *mark = console_buffer, *endptr = caret; | |
220 | + while( *mark && (mark < caret) && (*mark++ != '\n') ) | |
221 | + ; | |
222 | + /* ...then copy it, and all following lines, to the start of the | |
223 | + * buffer, so deleting the FIRST logical line, and thus free up | |
224 | + * an equivalent amount of space at the end. | |
225 | + */ | |
226 | + for( caret = console_buffer; mark < endptr; ) | |
227 | + *caret++ = *mark++; | |
228 | + } | |
229 | + } | |
230 | + /* Finally, store the current character into the "device" buffer, and | |
231 | + * adjust the caret position in readiness for the next. | |
232 | + */ | |
233 | + return *caret++ = charval; | |
234 | +} | |
235 | + | |
236 | +dmhTypePTY::~dmhTypePTY() | |
237 | +{ | |
238 | + /* When closing a PTY controller handle, we may free the memory which | |
239 | + * we allocated for its "device" buffer. (Note that we DO NOT delete | |
240 | + * the assocaiated font object, because the associated EDITTEXT control | |
241 | + * may outlive its controller, and will continue to need it; however, | |
242 | + * it does not need the buffer, because it maintains its own private | |
243 | + * copy of the content). | |
244 | + */ | |
245 | + free( (void *)(console_buffer) ); | |
246 | +} | |
247 | + | |
37 | 248 | class dmhTypeGUI: public dmhTypeGeneric |
38 | 249 | { |
39 | 250 | /* Diagnostic message handler for use in GUI applications. |
@@ -43,11 +254,13 @@ class dmhTypeGUI: public dmhTypeGeneric | ||
43 | 254 | virtual uint16_t control( const uint16_t, const uint16_t ); |
44 | 255 | virtual int notify( const dmh_severity, const char*, va_list ); |
45 | 256 | virtual int printf( const char*, va_list ); |
257 | + virtual void set_console_hook( void * ); | |
46 | 258 | |
47 | 259 | private: |
48 | 260 | HWND owner; uint16_t mode; |
49 | 261 | char *msgbuf; size_t msglen; |
50 | 262 | int dispatch_message( int ); |
263 | + dmhTypePTY *console_hook; | |
51 | 264 | }; |
52 | 265 | |
53 | 266 | EXTERN_C |
@@ -67,9 +280,14 @@ dmhTypeGeneric *dmh_init_gui( const char *progname ) | ||
67 | 280 | dmhTypeGUI::dmhTypeGUI( const char* name ):dmhTypeGeneric( name ), |
68 | 281 | /* |
69 | 282 | * This handler also requires initialisation of the |
70 | - * message box content collector. | |
283 | + * message box content collector... | |
284 | + */ | |
285 | + owner( NULL ), mode( 0 ), msgbuf( NULL ), msglen( 0 ), | |
286 | + /* | |
287 | + * ...and must set the default state for pseudo-console | |
288 | + * association to "none". | |
71 | 289 | */ |
72 | - owner( NULL ), mode( 0 ), msgbuf( NULL ), msglen( 0 ){} | |
290 | + console_hook( NULL ){} | |
73 | 291 | |
74 | 292 | uint16_t dmhTypeGUI::control( const uint16_t request, const uint16_t mask ) |
75 | 293 | { |
@@ -84,11 +302,32 @@ uint16_t dmhTypeGUI::control( const uint16_t request, const uint16_t mask ) | ||
84 | 302 | return mode = request | (mode & mask); |
85 | 303 | } |
86 | 304 | |
305 | +void dmhTypeGUI::set_console_hook( void *console ) | |
306 | +{ | |
307 | + /* Helper method to assign an emulated PTY "device" to the DMH stream; | |
308 | + * (note that any prior PTY assignment is first discarded). | |
309 | + */ | |
310 | + delete console_hook; | |
311 | + console_hook = (console == NULL) ? NULL : new dmhTypePTY( (HWND)(console) ); | |
312 | +} | |
313 | + | |
87 | 314 | int dmhTypeGUI::notify( const dmh_severity code, const char *fmt, va_list argv ) |
88 | 315 | { |
89 | 316 | /* Message dispatcher for GUI applications; this formats messages |
90 | 317 | * for display within a conventional MessageBox dialogue. |
91 | 318 | */ |
319 | + if( console_hook != NULL ) | |
320 | + { | |
321 | + /* When the DMH stream has an associated PTY, then we direct this | |
322 | + * notification to it, analogously to the CLI use of a TTY. | |
323 | + */ | |
324 | + return dmh_printf( notification_format, progname, severity_tag( code ) ) | |
325 | + + console_hook->printf( fmt, argv ); | |
326 | + } | |
327 | + | |
328 | + /* Conversely, when there is no associated PTY, then we lay out the | |
329 | + * notification for display in a windows message box. | |
330 | + */ | |
92 | 331 | int newlen = 1 + vsnprintf( NULL, 0, fmt, argv ); |
93 | 332 | if( mode & DMH_COMPILE_DIGEST ) |
94 | 333 | { |
@@ -200,11 +439,17 @@ int dmhTypeGUI::dispatch_message( int len ) | ||
200 | 439 | int dmhTypeGUI::printf( const char *fmt, va_list argv ) |
201 | 440 | { |
202 | 441 | /* Display arbitrary text messages via the diagnostic message handler. |
203 | - * | |
204 | - * FIXME: this is a stub; ideally, we would like to emulate | |
205 | - * console behaviour for displaying the message stream, but | |
206 | - * until a formal implementation can be provided, we simply | |
207 | - * emit each message in its own informational MessageBox. | |
442 | + */ | |
443 | + if( console_hook != NULL ) | |
444 | + /* | |
445 | + * When the DMH stream has an associated PTY, then we simply | |
446 | + * emit the message via that. | |
447 | + */ | |
448 | + return console_hook->printf( fmt, argv ); | |
449 | + | |
450 | + /* Conversely, when there is no associated PTY, we redirect the | |
451 | + * message for display in a windows message box, in the guise of | |
452 | + * a DMH_INFO notification. | |
208 | 453 | */ |
209 | 454 | return notify( DMH_INFO, fmt, argv ); |
210 | 455 | } |