3 * Fake console for Windows applications where a console is not available.
4 * (C) 2011-2012 Jonathan Campbell.
5 * Hackipedia DOS library.
7 * This code is licensed under the LGPL.
8 * <insert LGPL legal text here>
10 * This code allows the DOS/CPU test code to print to a console despite the
11 * fact that Windows 3.0/3.1 and Win32s do not provide a console. For this
12 * code to work, the program must include specific headers and #define a
13 * macro. The header will then redefine various standard C functions to
14 * redirect their use into this program. This code is not used for targets
15 * that provide a console.
20 # include <windows/apihelp.h>
39 #include <hw/cpu/cpu.h>
40 #include <hw/dos/dos.h>
41 #include <hw/dos/dosbox.h>
42 #include <hw/dos/doswin.h>
43 #include <hw/dos/winfcon.h>
45 #ifdef WIN_STDOUT_CONSOLE
47 /* _export is not valid for Win32. this silences a Watcom linker warning */
48 #if TARGET_MSDOS == 32
49 # define winproc_export
51 # define winproc_export _export
61 static char _win_WindowProcClass[128];
63 /* If we stick all these variables in the data segment and reference
64 * them directly, then we'll work from a single instance, but run into
65 * problems with who's data segment to use once we run in multiple
66 * instances. The problem, is that when an application creates a
67 * window of our class, the Window Proc is not guaranteed to be called
68 * with our DS segment/selector. In fact, it often seems to be the
69 * data segment of the first instance by which the window class was
70 * created. And under Windows 3.0, unless you used __loadds and
71 * MakeProcInstance, the DS segment could be any random segment
72 * that happened to be there when you were called.
74 * Our Window Proc below absolves itself of these problems by storing
75 * the pointer to the context in the Window data associated with the
76 * window (GetWindowLong/SetWindowLong), then using only that context
77 * pointer for maintaining itself.
79 * This DS segment limitation only affects the Window procedure and
80 * any functions called by the Window procedure. Other parts, like
81 * WinMain, do not have to worry about whether DS is correct and the
82 * data segment will always be the current instance running.
84 * Note that the above limitations are only an issue for the Win16
85 * world. The Win32 world is free from this hell and so we only
86 * have to maintain one context structure. */
87 typedef struct _win_console_ctx {
90 int conHeight,conWidth;
91 int _win_kb_i,_win_kb_o;
92 int monoSpaceFontHeight;
93 #if TARGET_MSDOS == 32 && defined(WIN386)
94 short int monoSpaceFontWidth;
96 int monoSpaceFontWidth;
106 #if TARGET_MSDOS == 16 || (TARGET_MSDOS == 32 && defined(WIN386))
111 HINSTANCE _win_hInstance;
112 static struct _win_console_ctx _this_console;
113 static char temprintf[1024];
115 #if TARGET_MSDOS == 32 && defined(WIN386)
116 # define USER_GWW_CTX 0
117 # define USER_GWW_MAX 6
118 #elif TARGET_MSDOS == 16
119 # define USER_GWW_CTX 0
120 # define USER_GWW_MAX 4
122 # define USER_GWW_MAX 0
124 #define USER_GCW_MAX 0
127 return _this_console.hwndMain;
130 int _win_kb_insert(struct _win_console_ctx FAR *ctx,char c) {
131 if ((ctx->_win_kb_i+1)%KBSIZE == ctx->_win_kb_o) {
136 ctx->_win_kb[ctx->_win_kb_i] = c;
137 if (++ctx->_win_kb_i >= KBSIZE) ctx->_win_kb_i = 0;
142 void (*sig)(int x) = signal(SIGINT,SIG_DFL);
143 if (sig != SIG_IGN && sig != SIG_DFL) sig(SIGINT);
145 if (sig == SIG_DFL) longjmp(_this_console.exit_jmp,1);
148 void _win_sigint_post(struct _win_console_ctx FAR *ctx) {
149 /* because doing a longjmp() out of a Window proc is very foolish */
150 ctx->pendingSigInt = 1;
153 #if ((TARGET_MSDOS == 16 && TARGET_WINDOWS < 31) || (TARGET_MSDOS == 32 && defined(WIN386)))
154 FARPROC _win_WindowProc_MPI;
156 /* NTS: Win16 only: DS (data segment) is NOT necessarily the data segment of the instance
157 * that spawned the window! Any attempt to access local variables will likely refer
158 * to the copy in the first instance */
159 /* NTS: All code in this routine deliberately does not refer to the local data segment, unless it
160 * has to (which it does through the segment value in the context). This reduces problems with
161 * the screwy callback design in Windows 3.0/3.1. */
162 /* NTS: Do NOT use __loadds on this function prototype. It will seem to work, but because __loadds
163 * reloads the (cached) instance data segment it will cause all instances to crash when you
164 * spawn multiple instances and then close the first one you spawned. NOT using __loadds
165 * removes that crash. */
166 WindowProcType_NoLoadDS winproc_export _win_WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam) {
167 #if TARGET_MSDOS == 32 && defined(WIN386)
168 struct _win_console_ctx FAR *_ctx_console;
170 unsigned short s = GetWindowWord(hwnd,USER_GWW_CTX);
171 unsigned int o = GetWindowLong(hwnd,USER_GWW_CTX+2);
172 _ctx_console = (void far *)MK_FP(s,o);
174 if (_ctx_console == NULL) return DefWindowProc(hwnd,message,wparam,lparam);
175 #elif TARGET_MSDOS == 16
176 struct _win_console_ctx FAR *_ctx_console;
177 _ctx_console = (void far *)GetWindowLong(hwnd,USER_GWW_CTX);
178 if (_ctx_console == NULL) return DefWindowProc(hwnd,message,wparam,lparam);
180 # define _ctx_console (&_this_console)
183 if (message == WM_GETMINMAXINFO) {
184 #if TARGET_MSDOS == 32 && defined(WIN386) /* Watcom Win386 does NOT translate LPARAM for us */
185 MINMAXINFO FAR *mm = (MINMAXINFO FAR*)win386_help_MapAliasToFlat(lparam);
186 if (mm == NULL) return DefWindowProc(hwnd,message,wparam,lparam);
188 MINMAXINFO FAR *mm = (MINMAXINFO FAR*)(lparam);
190 mm->ptMaxSize.x = (_ctx_console->monoSpaceFontWidth * _ctx_console->conWidth) +
191 (2 * GetSystemMetrics(SM_CXFRAME));
192 mm->ptMaxSize.y = (_ctx_console->monoSpaceFontHeight * _ctx_console->conHeight) +
193 (2 * GetSystemMetrics(SM_CYFRAME)) + GetSystemMetrics(SM_CYCAPTION);
194 mm->ptMinTrackSize.x = mm->ptMaxSize.x;
195 mm->ptMinTrackSize.y = mm->ptMaxSize.y;
196 mm->ptMaxTrackSize.x = mm->ptMaxSize.x;
197 mm->ptMaxTrackSize.y = mm->ptMaxSize.y;
200 else if (message == WM_CLOSE) {
201 if (_ctx_console->allowClose) DestroyWindow(hwnd);
202 else _win_sigint_post(_ctx_console);
203 _ctx_console->userReqClose = 1;
205 else if (message == WM_DESTROY) {
206 _ctx_console->allowClose = 1;
207 _ctx_console->userReqClose = 1;
208 if (_ctx_console->myCaret) {
211 _ctx_console->myCaret = 0;
215 _ctx_console->hwndMain = NULL;
218 else if (message == WM_SETCURSOR) {
219 if (LOWORD(lparam) == HTCLIENT) {
220 SetCursor(LoadCursor(NULL,IDC_ARROW));
224 return DefWindowProc(hwnd,message,wparam,lparam);
227 else if (message == WM_ACTIVATE) {
229 if (!_ctx_console->myCaret) {
230 CreateCaret(hwnd,NULL,_ctx_console->monoSpaceFontWidth,_ctx_console->monoSpaceFontHeight);
231 SetCaretPos(_ctx_console->conX * _ctx_console->monoSpaceFontWidth,
232 _ctx_console->conY * _ctx_console->monoSpaceFontHeight);
234 _ctx_console->myCaret = 1;
238 if (_ctx_console->myCaret) {
241 _ctx_console->myCaret = 0;
245 /* BUGFIX: Microsoft Windows 3.1 SDK says "return 0 if we processed the message".
246 * Yet if we actually do, we get funny behavior. Like if I minimize another
247 * application's window and then activate this app again, every keypress
248 * causes Windows to send WM_SYSKEYDOWN/WM_SYSKEYUP. Somehow passing it
249 * down to DefWindowProc() solves this. */
250 return DefWindowProc(hwnd,message,wparam,lparam);
252 else if (message == WM_CHAR) {
253 if (wparam > 0 && wparam <= 126) {
256 if (_ctx_console->allowClose) DestroyWindow(hwnd);
257 else _win_sigint_post(_ctx_console);
260 _win_kb_insert(_ctx_console,(char)wparam);
264 else if (message == WM_ERASEBKGND) {
267 if (GetUpdateRect(hwnd,&um,FALSE)) {
268 HBRUSH oldBrush,newBrush;
271 newPen = (HPEN)GetStockObject(NULL_PEN);
272 newBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);
274 oldPen = SelectObject((HDC)wparam,newPen);
275 oldBrush = SelectObject((HDC)wparam,newBrush);
277 Rectangle((HDC)wparam,um.left,um.top,um.right+1,um.bottom+1);
279 SelectObject((HDC)wparam,oldBrush);
280 SelectObject((HDC)wparam,oldPen);
283 return 1; /* Important: Returning 1 signals to Windows that we processed the message. Windows 3.0 gets really screwed up if we don't! */
285 else if (message == WM_PAINT) {
288 if (GetUpdateRect(hwnd,&um,TRUE)) {
293 BeginPaint(hwnd,&ps);
294 SetBkMode(ps.hdc,OPAQUE);
295 SetBkColor(ps.hdc,RGB(255,255,255));
296 SetTextColor(ps.hdc,RGB(0,0,0));
297 of = (HFONT)SelectObject(ps.hdc,_ctx_console->monoSpaceFont);
298 for (y=0;y < _ctx_console->conHeight;y++) {
299 TextOut(ps.hdc,0,y * _ctx_console->monoSpaceFontHeight,
300 _ctx_console->console + (_ctx_console->conWidth * y),
301 _ctx_console->conWidth);
303 SelectObject(ps.hdc,of);
307 return 0; /* Return 0 to signal we processed the message */
310 return DefWindowProc(hwnd,message,wparam,lparam);
318 return _this_console._win_kb_i != _this_console._win_kb_o;
324 int c = (int)((unsigned char)_this_console._win_kb[_this_console._win_kb_o]);
325 if (++_this_console._win_kb_o >= KBSIZE) _this_console._win_kb_o = 0;
335 int _win_kb_read(char *p,int sz) {
344 int _win_kb_write(const char *p,int sz) {
353 int _win_read(int fd,void *buf,int sz) {
354 if (fd == 0) return _win_kb_read((char*)buf,sz);
355 else if (fd == 1 || fd == 2) return -1;
356 else return read(fd,buf,sz);
359 int _win_write(int fd,const void *buf,int sz) {
360 if (fd == 0) return -1;
361 else if (fd == 1 || fd == 2) return _win_kb_write(buf,sz);
362 else return write(fd,buf,sz);
365 int _win_isatty(int fd) {
366 if (fd == 0 || fd == 1 || fd == 2) return 1; /* we're emulating one, so, yeah */
370 void _win_pump_wait() {
373 if (GetMessage(&msg,NULL,0,0)) {
374 TranslateMessage(&msg);
375 DispatchMessage(&msg);
376 while (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
377 TranslateMessage(&msg);
378 DispatchMessage(&msg);
382 if (_this_console.pendingSigInt) {
383 _this_console.pendingSigInt = 0;
391 #if TARGET_MSDOS == 16 || (TARGET_MSDOS == 32 && defined(WIN386))
392 /* Hack: Windows has this nice "GetTickCount()" function that has serious problems
393 * maintaining a count if we don't process the message pump! Doing this
394 * prevents portions of this code from getting stuck in infinite loops
395 * waiting for the damn timer to advance. Note that this is a serious
396 * problem that only plagues Windows 3.1 and earlier. Windows 95 doesn't
397 * have this problem. */
398 PostMessage(_this_console.hwndMain,WM_USER,0,0);
400 if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
402 TranslateMessage(&msg);
403 DispatchMessage(&msg);
404 } while (PeekMessage(&msg,NULL,0,0,PM_REMOVE));
407 if (_this_console.pendingSigInt) {
408 _this_console.pendingSigInt = 0;
413 void _win_update_cursor() {
414 if (_this_console.myCaret)
415 SetCaretPos(_this_console.conX * _this_console.monoSpaceFontWidth,
416 _this_console.conY * _this_console.monoSpaceFontHeight);
419 void _win_redraw_line_row() {
420 if (_this_console.conY >= 0 && _this_console.conY < _this_console.conHeight) {
421 HDC hdc = GetDC(_this_console.hwndMain);
424 SetBkMode(hdc,OPAQUE);
425 SetBkColor(hdc,RGB(255,255,255));
426 SetTextColor(hdc,RGB(0,0,0));
427 of = (HFONT)SelectObject(hdc,_this_console.monoSpaceFont);
428 if (_this_console.myCaret) HideCaret(_this_console.hwndMain);
429 TextOut(hdc,0,_this_console.conY * _this_console.monoSpaceFontHeight,
430 _this_console.console + (_this_console.conWidth * _this_console.conY),_this_console.conWidth);
431 if (_this_console.myCaret) ShowCaret(_this_console.hwndMain);
432 SelectObject(hdc,of);
433 ReleaseDC(_this_console.hwndMain,hdc);
437 void _win_redraw_line_row_partial(int x1,int x2) {
438 if (x1 >= x2) return;
440 if (_this_console.conY >= 0 && _this_console.conY < _this_console.conHeight) {
441 HDC hdc = GetDC(_this_console.hwndMain);
444 SetBkMode(hdc,OPAQUE);
445 SetBkColor(hdc,RGB(255,255,255));
446 SetTextColor(hdc,RGB(0,0,0));
447 of = (HFONT)SelectObject(hdc,_this_console.monoSpaceFont);
448 if (_this_console.myCaret) HideCaret(_this_console.hwndMain);
449 TextOut(hdc,x1 * _this_console.monoSpaceFontWidth,_this_console.conY * _this_console.monoSpaceFontHeight,
450 _this_console.console + (_this_console.conWidth * _this_console.conY) + x1,x2 - x1);
451 if (_this_console.myCaret) ShowCaret(_this_console.hwndMain);
452 SelectObject(hdc,of);
453 ReleaseDC(_this_console.hwndMain,hdc);
457 void _win_scrollup() {
458 HDC hdc = GetDC(_this_console.hwndMain);
459 if (_this_console.myCaret) HideCaret(_this_console.hwndMain);
460 BitBlt(hdc,0,0,_this_console.conWidth * _this_console.monoSpaceFontWidth,
461 _this_console.conHeight * _this_console.monoSpaceFontHeight,hdc,
462 0,_this_console.monoSpaceFontHeight,SRCCOPY);
463 if (_this_console.myCaret) ShowCaret(_this_console.hwndMain);
464 ReleaseDC(_this_console.hwndMain,hdc);
466 memmove(_this_console.console,_this_console.console+_this_console.conWidth,
467 (_this_console.conHeight-1)*_this_console.conWidth);
468 memset(_this_console.console+((_this_console.conHeight-1)*_this_console.conWidth),
469 ' ',_this_console.conWidth);
472 void _win_newline() {
473 _this_console.conX = 0;
474 if ((_this_console.conY+1) == _this_console.conHeight) {
475 _win_redraw_line_row();
477 _win_redraw_line_row();
478 _win_update_cursor();
482 _win_redraw_line_row();
483 _this_console.conY++;
487 /* write to the console. does NOT redraw the screen unless we get a newline or we need to scroll up */
488 void _win_putc(char c) {
493 _this_console.conX = 0;
494 _win_redraw_line_row();
495 _win_update_cursor();
499 if (_this_console.conX < _this_console.conWidth)
500 _this_console.console[(_this_console.conY*_this_console.conWidth)+_this_console.conX] = c;
501 if (++_this_console.conX == _this_console.conWidth)
506 size_t _win_printf(const char *fmt,...) {
507 int fX = _this_console.conX;
512 vsnprintf(temprintf,sizeof(temprintf)-1,fmt,va);
518 if (*t == 13 || *t == 10) fX = 0;
521 if (fX <= _this_console.conX) _win_redraw_line_row_partial(fX,_this_console.conX);
522 _win_update_cursor();
529 /* HACK: I don't know if real systems do this or QEMU is doing something wrong, but apparently if a program
530 * rapidly prints a lot of text under Windows 3.1 (like the RDTSC test program) it can cause the GDI
531 * to become 100% focused on TextOut() to the point not even the cursor updates when you move it, and
532 * keyboard events to become severely stalled. Our solution to this problem is to see if we're running
533 * under Windows 3.1 or earlier, and if so, purposely slow down our output with a software delay */
535 #if defined(TARGET_WINDOWS)
536 # if TARGET_MSDOS == 32 && defined(WIN386)
538 const DWORD ConDelay = 16UL; /* 16ms */
542 #if defined(TARGET_WINDOWS)
543 if (windows_mode != WINDOWS_NT) {
544 # if TARGET_MSDOS == 32 && defined(WIN386)
547 # if TARGET_MSDOS == 16
548 if (ToolHelpInit()) {
551 ti.dwSize = sizeof(ti);
552 if (__TimerCount(&ti)) {
553 base = ti.dwmsSinceStart;
558 if (!__TimerCount(&ti)) break;
559 m = ti.dwmsSinceStart;
560 } while ((m - base) < ConDelay);
569 base = GetTickCount();
574 } while ((m - base) < ConDelay);
582 * For Win16, programmers generally use WINAPI WinMain(...) but WINAPI is defined in such a way
583 * that it always makes the function prolog return FAR. Unfortunately, when Watcom C's runtime
584 * calls this function in a memory model that's compact or small, the call is made as if NEAR,
585 * not FAR. To avoid a GPF or crash on return, we must simply declare it PASCAL instead. */
586 int PASCAL _win_main_con_entry(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow,int (_cdecl *_main_f)(int argc,char**,char**)) {
590 _win_hInstance = hInstance;
591 snprintf(_win_WindowProcClass,sizeof(_win_WindowProcClass),"_HW_DOS_WINFCON_%lX",(DWORD)hInstance);
592 #if ((TARGET_MSDOS == 16 && TARGET_WINDOWS < 31) || (TARGET_MSDOS == 32 && defined(WIN386)))
593 _win_WindowProc_MPI = MakeProcInstance((FARPROC)_win_WindowProc,hInstance);
596 /* clear the console */
597 memset(&_this_console,0,sizeof(_this_console));
598 memset(_this_console.console,' ',sizeof(_this_console.console));
599 _this_console._win_kb_i = _this_console._win_kb_o = 0;
600 _this_console.conHeight = 25;
601 _this_console.conWidth = 80;
602 _this_console.conX = 0;
603 _this_console.conY = 0;
604 #if TARGET_MSDOS == 16 || (TARGET_MSDOS == 32 && defined(WIN386))
612 _this_console.my_ds = s;
616 /* we need to know at this point what DOS/Windows combination we're running under */
620 /* we want each instance to have it's own WNDCLASS, even though Windows (Win16) considers them all instances
621 * coming from the same HMODULE. In Win32, there is no such thing as a "previous instance" anyway */
622 wnd.style = CS_HREDRAW|CS_VREDRAW;
623 #if ((TARGET_MSDOS == 16 && TARGET_WINDOWS < 31) || (TARGET_MSDOS == 32 && defined(WIN386)))
624 wnd.lpfnWndProc = (WNDPROC)_win_WindowProc_MPI;
626 wnd.lpfnWndProc = _win_WindowProc;
628 wnd.cbClsExtra = USER_GCW_MAX;
629 wnd.cbWndExtra = USER_GWW_MAX;
630 wnd.hInstance = hInstance;
633 wnd.hbrBackground = NULL;
634 wnd.lpszMenuName = NULL;
635 wnd.lpszClassName = _win_WindowProcClass;
637 if (!RegisterClass(&wnd)) {
638 MessageBox(NULL,"Unable to register Window class","Oops!",MB_OK);
642 /* Use the full path of our EXE image by default */
646 if (!GetModuleFileName(hInstance,title,sizeof(title)-1))
647 strcpy(title,"<unknown>");
649 _this_console.hwndMain = CreateWindow(_win_WindowProcClass,title,
651 CW_USEDEFAULT,CW_USEDEFAULT,
657 if (!_this_console.hwndMain) {
658 MessageBox(NULL,"Unable to create window","Oops!",MB_OK);
662 #if TARGET_MSDOS == 32 && defined(WIN386)
663 /* our Win386 hack needs the address of our console context */
664 SetWindowWord(_this_console.hwndMain,USER_GWW_CTX,(WORD)FP_SEG(&_this_console));
665 SetWindowLong(_this_console.hwndMain,USER_GWW_CTX+2,(DWORD)FP_OFF(&_this_console));
666 #elif TARGET_MSDOS == 16
667 /* our Win16 hack needs the address of our console context */
668 SetWindowLong(_this_console.hwndMain,USER_GWW_CTX,(DWORD)(&_this_console));
671 /* Create the monospace font we use for terminal display */
673 _this_console.monoSpaceFont = CreateFont(-12,0,0,0,FW_NORMAL,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FIXED_PITCH | FF_DONTCARE,"Terminal");
674 if (!_this_console.monoSpaceFont) {
675 MessageBox(NULL,"Unable to create Font","Oops!",MB_OK);
680 HWND hwnd = GetDesktopWindow();
681 HDC hdc = GetDC(hwnd);
682 _this_console.monoSpaceFontHeight = 12;
683 if (!GetCharWidth(hdc,'A','A',&_this_console.monoSpaceFontWidth)) _this_console.monoSpaceFontWidth = 9;
688 ShowWindow(_this_console.hwndMain,nCmdShow);
689 UpdateWindow(_this_console.hwndMain);
690 SetWindowPos(_this_console.hwndMain,HWND_TOP,0,0,
691 (_this_console.monoSpaceFontWidth * _this_console.conWidth) +
692 (2 * GetSystemMetrics(SM_CXFRAME)),
693 (_this_console.monoSpaceFontHeight * _this_console.conHeight) +
694 (2 * GetSystemMetrics(SM_CYFRAME)) + GetSystemMetrics(SM_CYCAPTION),
697 if (setjmp(_this_console.exit_jmp) == 0)
698 _main_f(0,NULL,NULL); /* <- FIXME: We need to take the command line and generate argv[]. Also generate envp[] */
700 if (!_this_console.userReqClose) {
701 _win_printf("\n<program terminated>");
702 _this_console.allowClose = 1;
703 while (GetMessage(&msg,NULL,0,0)) {
704 TranslateMessage(&msg);
705 DispatchMessage(&msg);
709 if (IsWindow(_this_console.hwndMain)) {
710 DestroyWindow(_this_console.hwndMain);
711 while (GetMessage(&msg,NULL,0,0)) {
712 TranslateMessage(&msg);
713 DispatchMessage(&msg);
718 DeleteObject(_this_console.monoSpaceFont);
719 _this_console.monoSpaceFont = NULL;