/* $Id: console.c,v 1.1 2008-03-27 00:41:33 vsc Exp $ Part of SWI-Prolog Author: Jan Wielemaker E-mail: jan@swi.psy.uva.nl WWW: http://www.swi-prolog.org Copyright (C): 1985-2002, University of Amsterdam This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This file defines a console for porting (unix) stream-based applications to MS-Windows. It has been developed for SWI-Prolog. The main source is part of SWI-Prolog. The SWI-Prolog source is at http://www.swi-prolog.org - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Thread design: There are two threads. The Prolog engine runs in the main thread. The other thread deals with the window. Basically, it processes events and if anything is typed it puts it into a queue. The main thread at some stage forks the display thread, running window_loop(). This thread initialises the input and then sends WM_RLC_READY to the main thread to indicate it is ready to accept data. If data is to be written, Prolog calls rlc_write(), which posts the WM_RLC_WRITE to the display thread, waiting on the termination. If data is to be read, rlc_read() posts a WM_RLC_FLUSH, and then waits while dispatching events, for the display-thread to fill the buffer and send WM_RLC_INPUT (which is just sent to make GetMessage() in rlc_read() return). Towards an MT version on Windows -------------------------------- If we want to move towards a multi-threaded version for MS-Windows, the console code needs to be changed significantly, as we need to be able to create multiple consoles to support thread_attach_console/0. The most logical solution seems to be to reverse the thread-structure, Prolog starting and running in the main-thread and creating a console creates a new thread for this console. There are two ways to keep track of the console to use. Cleanest might be to add an argument denoting the allocated console and alternatively we could use thread-local data. We can also combine the two: add an additional argument, but allow passing NULL to use the default console for this thread. Menus ----- The current console provides a menu that can be extended from Prolog. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #ifdef O_DEBUG_HEAP static void initHeapDebug(void); #include #else #define initHeapDebug() #endif #include #include #ifndef WM_MOUSEWHEEL /* sometimes not defined */ #define WM_MOUSEWHEEL 0x020A #endif #ifndef WM_UNICHAR #define WM_UNICHAR 0x109 #define UNICODE_NOCHAR 0xFFFF #endif #if (_MSC_VER < 1400) && !_WIN64 typedef DWORD DWORD_PTR; #endif #include #include #include #include #define _MAKE_DLL 1 #undef _export #include "console.h" #include "menu.h" #include "common.h" #include #include #include #ifndef isletter #define isletter(c) (_istalpha(c) || (c) == '_') #endif #ifndef MAXPATHLEN #define MAXPATHLEN 256 #endif #ifndef CHAR_MAX #define CHAR_MAX 256 #endif #define MAXLINE 1024 /* max chars per line */ #define CMD_INITIAL 0 #define CMD_ESC 1 #define CMD_ANSI 2 #define GWL_DATA 0 /* offset for client data */ #define CHG_RESET 0 /* unchenged */ #define CHG_CHANGED 1 /* changed, but no clear */ #define CHG_CLEAR 2 /* clear */ #define CHG_CARET 4 /* caret has moved */ #define SEL_CHAR 0 /* character-unit selection */ #define SEL_WORD 1 /* word-unit selection */ #define SEL_LINE 2 /* line-unit selection */ #ifndef EOS #define EOS 0 #endif #define ESC 27 /* the escape character */ #define WM_RLC_INPUT WM_USER+10 /* Just somewhere ... */ #define WM_RLC_WRITE WM_USER+11 /* write data */ #define WM_RLC_FLUSH WM_USER+12 /* flush buffered data */ #define WM_RLC_READY WM_USER+13 /* Window thread is ready */ #define WM_RLC_CLOSEWIN WM_USER+14 /* Close the window */ /*#define WM_RLC_MENU WM_USER+15 Insert a menu (defined in menu.h) */ #define IMODE_RAW 1 /* char-by-char */ #define IMODE_COOKED 2 /* line-by-line */ #define NextLine(b, i) ((i) < (b)->height-1 ? (i)+1 : 0) #define PrevLine(b, i) ((i) > 0 ? (i)-1 : (b)->height-1) #define Bounds(v, mn, mx) ((v) < (mn) ? (mn) : (v) > (mx) ? (mx) : (v)) #define Control(x) ((x) - '@') #define streq(s, q) (_tcscmp((s), (q)) == 0) #include "console_i.h" /* internal package stuff */ #define OPT_SIZE 0x01 #define OPT_POSITION 0x02 /******************************* * DATA * *******************************/ RlcData _rlc_stdio = NULL; /* the main buffer */ static int _rlc_show; /* initial show */ static char _rlc_word_chars[CHAR_MAX]; /* word-characters (selection) */ static const TCHAR * _rlc_program; /* name of the program */ static HANDLE _rlc_hinstance; /* Global instance */ static HICON _rlc_hicon; /* Global icon */ /******************************* * FUNCTIONS * *******************************/ static LRESULT WINAPI rlc_wnd_proc(HWND win, UINT msg, WPARAM wP, LPARAM lP); static void rlc_place_caret(RlcData b); static void rlc_resize_pixel_units(RlcData b, int w, int h); static RlcData rlc_make_buffer(int w, int h); static int rlc_count_lines(RlcData b, int from, int to); static void rlc_add_line(RlcData b); static void rlc_open_line(RlcData b); static void rlc_update_scrollbar(RlcData b); static void rlc_paste(RlcData b); static void rlc_init_text_dimensions(RlcData b, HFONT f); static void rlc_save_font_options(HFONT f, rlc_console_attr *attr); static void rlc_get_options(rlc_console_attr *attr); static HKEY rlc_option_key(rlc_console_attr *attr, int create); static void rlc_progbase(TCHAR *path, TCHAR *base); static int rlc_add_queue(RlcData b, RlcQueue q, int chr); static int rlc_add_lines(RlcData b, int here, int add); static void rlc_start_selection(RlcData b, int x, int y); static void rlc_extend_selection(RlcData b, int x, int y); static void rlc_word_selection(RlcData b, int x, int y); static void rlc_copy(RlcData b); static void rlc_destroy(RlcData b); static void rlc_request_redraw(RlcData b); static void rlc_redraw(RlcData b); static int rlc_breakargs(TCHAR *line, TCHAR **argv); static void rlc_resize(RlcData b, int w, int h); static void rlc_adjust_line(RlcData b, int line); static int text_width(RlcData b, HDC hdc, const TCHAR *text, int len); static void rlc_queryfont(RlcData b); static void rlc_do_write(RlcData b, TCHAR *buf, int count); static void rlc_reinit_line(RlcData b, int line); static void rlc_free_line(RlcData b, int line); static int rlc_between(RlcData b, int f, int t, int v); static void free_user_data(RlcData b); static RlcQueue rlc_make_queue(int size); static void rlc_free_queue(RlcQueue q); static int rlc_from_queue(RlcQueue q); static int rlc_is_empty_queue(RlcQueue q); static void rlc_empty_queue(RlcQueue q); extern int main(); static RlcUpdateHook _rlc_update_hook; static RlcTimerHook _rlc_timer_hook; static RlcRenderHook _rlc_render_hook; static RlcRenderAllHook _rlc_render_all_hook; static RlcInterruptHook _rlc_interrupt_hook; static RlcResizeHook _rlc_resize_hook; static RlcMenuHook _rlc_menu_hook; static RlcMessageHook _rlc_message_hook; static int _rlc_copy_output_to_debug_output=0; /* != 0: copy to debugger */ static int emulate_three_buttons; static HWND emu_hwnd; /* Emulating for this window */ static void _rlc_create_kill_window(RlcData b); static DWORD WINAPI window_loop(LPVOID arg); /* console window proc */ #ifdef _DEBUG #include static void Dprintf(const TCHAR *fmt, ...); static void Dprint_lines(RlcData b, int from, int to); #define DEBUG(Code) Code /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - It might look a bit weird not to use , but for some reason it looks as if the application thread continues if the asserting is trapped using the normal assert()!? Just but a debugger breakpoint on rlc_assert() and all functions normally. rlc_check_assertions() is a (very) incomplete check that everything we expect to be true about the data is indeed the case. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ void rlc_assert(const TCHAR *msg) { MessageBox(NULL, msg, _T("Console assertion failed"), MB_OK|MB_TASKMODAL); } void rlc_check_assertions(RlcData b) { int window_last = rlc_add_lines(b, b->window_start, b->window_size-1); int y; assert(b->last != b->first || b->first == 0); assert(b->caret_x >= 0 && b->caret_x < b->width); /* TBD: debug properly */ /*assert(rlc_between(b, b->window_start, window_last, b->caret_y));*/ for(y=0; yheight; y++) { TextLine tl = &b->lines[y]; assert(tl->size >= 0 && tl->size <= b->width); } } #else #define DEBUG(Code) ((void)0) #define rlc_check_assertions(b) #endif /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - rlc_long_name(TCHAR *buffer) Translate a filename, possibly holding 8+3 abbreviated parts into the `real' filename. I couldn't find a direct call for this. If you have it, I'd be glad to receive a better implementation. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static void rlc_long_name(TCHAR *file) { TCHAR buf[MAXPATHLEN]; TCHAR *i = file; TCHAR *o = buf; TCHAR *ok = buf; int changed = 0; while(*i) { int dirty = FALSE; while(*i && *i != '\\') { if ( *i == '~' ) dirty++; *o++ = *i++; } if ( dirty ) { WIN32_FIND_DATA data; HANDLE h; *o = '\0'; if ( (h=FindFirstFile(buf, &data)) != INVALID_HANDLE_VALUE ) { _tcscpy(ok, data.cFileName); FindClose(h); o = ok + _tcslen(ok); changed++; } } if ( *i ) *o++ = *i++; ok = o; } if ( changed ) { *o = '\0'; _tcscpy(file, buf); } } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - If %PLTERM_CLASS% is in the environment, this value is used as Windows class identifier for the console window. This allows external programs to start PLWIN.EXE and find the window it has started in order to embed it. In old versions this was fixed to "RlcConsole" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static TCHAR * rlc_window_class(HICON icon) { static TCHAR winclassname[32]; static WNDCLASS wndClass; HINSTANCE instance = _rlc_hinstance; if ( !winclassname[0] ) { if ( !GetEnvironmentVariable(TEXT("PLTERM_CLASS"), winclassname, sizeof(winclassname)) ) _stprintf(winclassname, TEXT("PlTerm-%d"), instance); wndClass.lpszClassName = winclassname; wndClass.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS; wndClass.lpfnWndProc = (LPVOID) rlc_wnd_proc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = sizeof(intptr_t); wndClass.hInstance = instance; if ( icon ) wndClass.hIcon = icon; else wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndClass.hCursor = LoadCursor(NULL, IDC_IBEAM); wndClass.hbrBackground = (HBRUSH) NULL; wndClass.lpszMenuName = NULL; RegisterClass(&wndClass); } return winclassname; } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - There are two ways to get the commandline. It is passed to WinMain as 8-bit string. This version does *not* include the command itself. It is also available through GetCommandLine(), which does include the command itself and returns LPTSTR (Unicode/ANSI). We assume the latter. Nevertheless, for backward compatibility as well as easy to extract the full pathname of the executable, we replace argv[0] with the intptr_t filename version of the current module, so argv[0] is guaranteed to be a full path refering to the .exe file. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ int rlc_main(HANDLE hInstance, HANDLE hPrevInstance, LPTSTR lpszCmdLine, int nCmdShow, RlcMain mainfunc, HICON icon) { TCHAR * argv[100]; int argc; TCHAR program[MAXPATHLEN]; TCHAR progbase[100]; RlcData b; rlc_console_attr attr; initHeapDebug(); _rlc_hinstance = hInstance; _rlc_show = nCmdShow; _rlc_hicon = icon; GetModuleFileName(hInstance, program, sizeof(program)); rlc_long_name(program); argc = rlc_breakargs(lpszCmdLine, argv); argv[0] = program; rlc_progbase(argv[0], progbase); memset(&attr, 0, sizeof(attr)); _rlc_program = attr.title = progbase; _rlc_stdio = b = rlc_create_console(&attr); if ( mainfunc ) return (*mainfunc)(b, argc, argv); else return 0; } rlc_console rlc_create_console(rlc_console_attr *attr) { RlcData b; MSG msg; const TCHAR *title; rlc_get_options(attr); if ( attr->title ) title = attr->title; else title = _T("Untitled"); b = rlc_make_buffer(attr->width, attr->savelines); b->create_attributes = attr; _tcscpy(b->current_title, title); if ( attr->key ) { b->regkey_name = _tcsdup(attr->key); } rlc_init_text_dimensions(b, NULL); _rlc_create_kill_window(b); DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &b->application_thread, 0, FALSE, DUPLICATE_SAME_ACCESS); b->application_thread_id = GetCurrentThreadId(); b->console_thread = CreateThread(NULL, /* security */ 2048, /* stack */ window_loop, b, /* proc+arg */ 0, /* flags */ &b->console_thread_id); /* id */ /* wait till the window is created */ GetMessage(&msg, NULL, WM_RLC_READY, WM_RLC_READY); b->create_attributes = NULL; /* release this data */ return b; } static void rlc_create_window(RlcData b) { HWND hwnd; rlc_console_attr *a = b->create_attributes; RECT rect; DWORD style = (WS_OVERLAPPEDWINDOW|WS_VSCROLL); /* One would assume AdjustWindowRect() uses WS_VSCROLL to add the width of the scrollbar. I think this isn't true, but maybe there is another reason for getting 2 characters shorter each invocation ... */ rect.left = a->x; rect.top = a->y; rect.right = a->x + (a->width+2) * b->cw + GetSystemMetrics(SM_CXVSCROLL); rect.bottom = a->y + a->height * b->ch; AdjustWindowRect(&rect, style, TRUE); hwnd = CreateWindow(rlc_window_class(_rlc_hicon), b->current_title, style, a->x, a->y, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, _rlc_hinstance, NULL); b->window = hwnd; SetWindowLong(hwnd, GWL_DATA, (LONG) b); SetScrollRange(hwnd, SB_VERT, 0, b->sb_lines, FALSE); SetScrollPos(hwnd, SB_VERT, b->sb_start, TRUE); b->queue = rlc_make_queue(256); b->sb_lines = rlc_count_lines(b, b->first, b->last); b->sb_start = rlc_count_lines(b, b->first, b->window_start); b->foreground = GetSysColor(COLOR_WINDOWTEXT); b->background = GetSysColor(COLOR_WINDOW); b->sel_foreground = GetSysColor(COLOR_HIGHLIGHTTEXT); b->sel_background = GetSysColor(COLOR_HIGHLIGHT); if ( GetSystemMetrics(SM_CMOUSEBUTTONS) == 2 ) emulate_three_buttons = 120; rlc_add_menu_bar(b->window); ShowWindow(hwnd, _rlc_show); UpdateWindow(hwnd); } int rlc_iswin32s() { if( GetVersion() & 0x80000000 && (GetVersion() & 0xFF) ==3) return TRUE; else return FALSE; } static void rlc_progbase(TCHAR *path, TCHAR *base) { TCHAR *s; TCHAR *e; if ( !(s=_tcsrchr(path, '\\')) ) s = path; /* takes the filename part */ else s++; if ( !(e = _tcschr(s, '.')) ) _tcscpy(base, s); else { _tcsncpy(base, s, e-s); base[e-s] = '\0'; } } /******************************* * HIDDEN WINDOW * *******************************/ static LRESULT WINAPI rlc_kill_wnd_proc(HWND hwnd, UINT message, UINT wParam, LONG lParam) { switch(message) { case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } static TCHAR * rlc_kill_window_class() { static TCHAR winclassname[32]; static WNDCLASS wndClass; HINSTANCE instance = _rlc_hinstance; if ( !winclassname[0] ) { _stprintf(winclassname, _T("Console-hidden-win%d"), instance); wndClass.style = 0; wndClass.lpfnWndProc = (LPVOID) rlc_kill_wnd_proc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = instance; wndClass.hIcon = NULL; wndClass.hCursor = NULL; wndClass.hbrBackground = GetStockObject(WHITE_BRUSH); wndClass.lpszMenuName = NULL; wndClass.lpszClassName = winclassname; RegisterClass(&wndClass); } return winclassname; } static void _rlc_create_kill_window(RlcData b) { b->kill_window = CreateWindow(rlc_kill_window_class(), _T("Console hidden window"), 0, 0, 0, 32, 32, NULL, NULL, _rlc_hinstance, NULL); } /******************************* * REGISTRY COMMUNICATION * *******************************/ #define MAXREGSTRLEN 1024 static void reg_save_int(HKEY key, const TCHAR *name, int value) { DWORD val = value; if ( RegSetValueEx(key, name, 0, REG_DWORD_LITTLE_ENDIAN, (LPBYTE)&val, sizeof(val)) != ERROR_SUCCESS ) DEBUG(MessageBox(NULL, _T("Failed to save int setting"), _T("Error"), MB_OK)); } static void reg_save_str(HKEY key, const TCHAR *name, TCHAR *value) { if ( RegSetValueEx(key, name, 0, REG_SZ, (LPBYTE)value, (DWORD)(_tcslen(value)+1)*sizeof(TCHAR)) != ERROR_SUCCESS ) DEBUG(MessageBox(NULL, _T("Failed to save string setting"), _T("Error"), MB_OK)); } static void rlc_save_options(RlcData b) { HKEY key; rlc_console_attr attr; memset(&attr, 0, sizeof(attr)); attr.key = b->regkey_name; if ( !(key = rlc_option_key(&attr, TRUE)) ) return; reg_save_int(key, _T("SaveLines"), b->height); if ( b->modified_options & OPT_SIZE ) { reg_save_int(key, _T("Width"), b->width); reg_save_int(key, _T("Height"), b->window_size); } if ( b->modified_options & OPT_POSITION ) { reg_save_int(key, _T("X"), b->win_x); reg_save_int(key, _T("Y"), b->win_y); } rlc_save_font_options(b->hfont, &attr); if ( attr.face_name[0] ) { reg_save_str(key, _T("FaceName"), attr.face_name); reg_save_int(key, _T("FontFamily"), attr.font_family); reg_save_int(key, _T("FontSize"), attr.font_size); reg_save_int(key, _T("FontWeight"), attr.font_weight); reg_save_int(key, _T("FontCharSet"), attr.font_char_set); } RegCloseKey(key); } static void reg_get_int(HKEY key, const TCHAR *name, int mn, int def, int mx, int *value) { DWORD type; BYTE data[8]; DWORD len = sizeof(data); if ( *value ) return; /* use default */ if ( RegQueryValueEx(key, name, NULL, &type, data, &len) == ERROR_SUCCESS ) { switch(type) { /*case REG_DWORD:*/ /* Same case !? */ case REG_DWORD_LITTLE_ENDIAN: { DWORD *valp = (DWORD *)data; int v = *valp; if ( mn < mx ) { if ( v < mn ) v = mn; else if ( v > mx ) v = mx; } *value = v; } } } else *value = def; } static void reg_get_str(HKEY key, const TCHAR *name, TCHAR *value, int length) { DWORD type; BYTE data[MAXREGSTRLEN*sizeof(TCHAR)]; DWORD len = sizeof(data); if ( *value ) return; /* use default */ if ( RegQueryValueEx(key, name, NULL, &type, data, &len) == ERROR_SUCCESS ) { switch(type) { case REG_SZ: { TCHAR *val = (TCHAR*)data; _tcsncpy(value, val, length-1); value[length-1] = '\0'; } } } } HKEY reg_open_key(TCHAR **which, int create) { HKEY key = HKEY_CURRENT_USER; DWORD disp; LONG rval; for( ; *which; which++) { HKEY tmp; if ( which[1] ) { if ( RegOpenKeyEx(key, which[0], 0L, KEY_READ, &tmp) == ERROR_SUCCESS ) { key = tmp; continue; } if ( !create ) return NULL; } rval = RegCreateKeyEx(key, which[0], 0, _T(""), 0, KEY_ALL_ACCESS, NULL, &tmp, &disp); RegCloseKey(key); if ( rval == ERROR_SUCCESS ) key = tmp; else return NULL; } return key; } static HKEY rlc_option_key(rlc_console_attr *attr, int create) { TCHAR Prog[256]; TCHAR *address[] = { _T("Software"), RLC_VENDOR, Prog, _T("Console"), (TCHAR *)attr->key, /* possible secondary key */ NULL }; const TCHAR *s; TCHAR *q; for(s=_rlc_program, q=Prog; *s; s++, q++) /* capitalise the key */ { *q = (s==_rlc_program ? _totupper(*s) : _totlower(*s)); } *q = EOS; return reg_open_key(address, create); } static void rlc_get_options(rlc_console_attr *attr) { HKEY key; if ( !(key = rlc_option_key(attr, FALSE)) ) { if ( !attr->width ) attr->width = 80; if ( !attr->height ) attr->height = 24; if ( !attr->savelines ) attr->savelines = 200; return; } { int minx, miny, maxx, maxy; RECT rect; SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0); minx = rect.top; miny = rect.left; maxx = rect.right - 40; maxy = rect.bottom - 40; reg_get_int(key, _T("SaveLines"), 200, 200, 100000, &attr->savelines); reg_get_int(key, _T("Width"), 20, 80, 300, &attr->width); reg_get_int(key, _T("Height"), 5, 24, 100, &attr->height); reg_get_int(key, _T("X"), minx, minx, maxx, &attr->x); reg_get_int(key, _T("Y"), miny, miny, maxy, &attr->y); } reg_get_str(key, _T("FaceName"), attr->face_name, sizeof(attr->face_name)/sizeof(TCHAR)); reg_get_int(key, _T("FontFamily"), 0, 0, 0, &attr->font_family); reg_get_int(key, _T("FontSize"), 0, 0, 0, &attr->font_size); reg_get_int(key, _T("FontWeight"), 0, 0, 0, &attr->font_weight); reg_get_int(key, _T("FontCharSet"), 0, 0, 0, &attr->font_char_set); RegCloseKey(key); } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Windows-'95 appears to quote names of files because files may hold spaces. rlc_breakargs() will pass a quoted strings as one argument. If it can't find the closing quote, it will tread the quote as a normal character. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static int rlc_breakargs(TCHAR *line, TCHAR **argv) { int argc = 0; while(*line) { int q; while(*line && _istspace(*line)) line++; if ( (q = *line) == '"' || q == '\'' ) /* quoted arguments */ { TCHAR *start = line+1; TCHAR *end = start; while( *end && *end != q ) end++; if ( *end == q ) { *end = '\0'; argv[argc++] = start; line = end+1; continue; } } if ( *line ) { argv[argc++] = line; while(*line && !_istspace(*line)) line++; if ( *line ) *line++ = '\0'; } } argv[argc] = NULL; /* add trailing NULL pointer to argv */ return argc; } /******************************* * ATTRIBUTES * *******************************/ COLORREF rlc_color(rlc_console con, int which, COLORREF c) { HDC hdc; COLORREF old; RlcData b = rlc_get_data(con); hdc = GetDC(NULL); c = GetNearestColor(hdc, c); ReleaseDC(NULL, hdc); switch(which) { case RLC_WINDOW: old = b->background; b->background = c; break; case RLC_TEXT: old = b->foreground; b->foreground = c; break; case RLC_HIGHLIGHT: old = b->sel_background; b->sel_background = c; break; case RLC_HIGHLIGHTTEXT: old = b->sel_foreground; b->sel_foreground = c; break; default: return (COLORREF)-1; } if ( b->window ) InvalidateRect(b->window, NULL, TRUE); return old; } static int rlc_kill(RlcData b) { DWORD_PTR result; switch(b->closing++) { case 0: b->queue->flags |= RLC_EOF; PostThreadMessage(b->application_thread_id, WM_RLC_INPUT, 0, 0); return TRUE; case 1: if ( _rlc_interrupt_hook ) { (*_rlc_interrupt_hook)(b, SIGINT); return TRUE; } default: if ( !SendMessageTimeout(b->kill_window, WM_DESTROY, 0, 0, SMTO_ABORTIFHUNG, 5000, &result) ) { if ( b->window ) { switch( MessageBox(b->window, _T("Main task is not responding.") _T("Click \"OK\" to terminate it"), _T("Error"), MB_OKCANCEL|MB_ICONEXCLAMATION|MB_APPLMODAL) ) { case IDCANCEL: return FALSE; } TerminateThread(b->application_thread, 1); return TRUE; } } } return FALSE; } static void rlc_interrupt(RlcData b) { if ( _rlc_interrupt_hook ) (*_rlc_interrupt_hook)((rlc_console)b, SIGINT); else raise(SIGINT); } static void typed_char(RlcData b, int chr) { if ( chr == Control('C') ) rlc_interrupt(b); else if ( chr == Control('V') || chr == Control('Y') ) rlc_paste(b); else if ( b->queue ) rlc_add_queue(b, b->queue, chr); } /******************************* * WINDOW PROCEDURE * *******************************/ static void rlc_destroy(RlcData b) { if ( b && b->window ) { DestroyWindow(b->window); b->window = NULL; b->closing = 3; } } static int IsDownKey(code) { short mask = GetKeyState(code); return mask & 0x8000; } static LRESULT WINAPI rlc_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { RlcData b = (RlcData) GetWindowLong(hwnd, GWL_DATA); switch(message) { case WM_CREATE: return 0; case WM_SIZE: if ( wParam != SIZE_MINIMIZED ) { rlc_resize_pixel_units(b, LOWORD(lParam), HIWORD(lParam)); b->modified_options |= OPT_SIZE; } return 0; case WM_MOVE: { WINDOWPLACEMENT placement; placement.length = sizeof(placement); GetWindowPlacement(hwnd, &placement); if ( placement.showCmd == SW_SHOWNORMAL ) { b->win_x = placement.rcNormalPosition.left; b->win_y = placement.rcNormalPosition.top; b->modified_options |= OPT_POSITION; } return 0; } case WM_SETFOCUS: b->has_focus = TRUE; CreateCaret(hwnd, NULL, b->fixedfont ? b->cw : 3, b->ch-1); rlc_place_caret(b); return 0; case WM_KILLFOCUS: b->has_focus = FALSE; b->caret_is_shown = FALSE; HideCaret(hwnd); DestroyCaret(); return 0; case WM_PAINT: rlc_redraw(b); return 0; case WM_COMMAND: { UINT item = (UINT) LOWORD(wParam); const TCHAR *name; switch( item ) { case IDM_PASTE: rlc_paste(b); return 0; case IDM_COPY: return 0; /* no op: already done */ case IDM_CUT: break; /* TBD: cut */ case IDM_BREAK: rlc_interrupt(b); break; case IDM_FONT: rlc_queryfont(b); return 0; case IDM_EXIT: if ( rlc_kill(b) ) return 0; break; } if ( (name = lookupMenuId(item)) ) { if ( _rlc_menu_hook ) { (*_rlc_menu_hook)(b, name); } return 0; } break; } { int chr; case WM_KEYDOWN: /* up is sent only once */ { switch((int) wParam) { case VK_DELETE: chr = 127; break; case VK_LEFT: chr = Control('B'); break; case VK_RIGHT: chr = Control('F'); break; case VK_UP: chr = Control('P'); break; case VK_DOWN: chr = Control('N'); break; case VK_HOME: chr = Control('A'); break; case VK_END: chr = Control('E'); break; case VK_PRIOR: /* page up */ { int maxdo = rlc_count_lines(b, b->first, b->window_start); int pagdo = b->window_size - 1; b->window_start = rlc_add_lines(b, b->window_start, -min(maxdo, pagdo)); scrolledbykey: rlc_update_scrollbar(b); InvalidateRect(hwnd, NULL, FALSE); return 0; } case VK_NEXT: /* page down */ { int maxup = rlc_count_lines(b, b->window_start, b->last); int pagup = b->window_size - 1; b->window_start = rlc_add_lines(b, b->window_start, min(maxup, pagup)); goto scrolledbykey; } default: goto break2; } if ( chr > 0 ) { if ( IsDownKey(VK_CONTROL) ) typed_char(b, ESC); typed_char(b, chr); return 0; } break2: break; } case WM_UNICHAR: chr = (int)wParam; typed_char(b, chr); return 0; case WM_SYSCHAR: typed_char(b, ESC); /* Play escape-something */ case WM_CHAR: chr = (int)wParam; typed_char(b, chr); return 0; } /* selection handling */ case WM_MBUTTONDOWN: middle_down: return 0; case WM_MBUTTONUP: middle_up: rlc_paste(b); return 0; case WM_LBUTTONDOWN: { POINTS pt; if ( emulate_three_buttons ) { MSG msg; Sleep(emulate_three_buttons); if ( PeekMessage(&msg, hwnd, WM_RBUTTONDOWN, WM_RBUTTONDOWN, PM_REMOVE) ) { emu_hwnd = hwnd; goto middle_down; } } pt = MAKEPOINTS(lParam); rlc_start_selection(b, pt.x, pt.y); return 0; } case WM_LBUTTONUP: case WM_RBUTTONUP: if ( emu_hwnd == hwnd ) { if ( wParam & (MK_RBUTTON|MK_LBUTTON) ) goto middle_up; else { emu_hwnd = 0; return 0; } } else { rlc_copy(b); return 0; } case WM_LBUTTONDBLCLK: { POINTS pt = MAKEPOINTS(lParam); rlc_word_selection(b, pt.x, pt.y); return 0; } case WM_RBUTTONDOWN: { POINTS pt; if ( emulate_three_buttons ) { MSG msg; Sleep(emulate_three_buttons); if ( PeekMessage(&msg, hwnd, WM_LBUTTONDOWN, WM_LBUTTONDOWN, PM_REMOVE) ) { emu_hwnd = hwnd; goto middle_down; } } pt = MAKEPOINTS(lParam); rlc_extend_selection(b, pt.x, pt.y); return 0; } case WM_MOUSEMOVE: { POINTS pt = MAKEPOINTS(lParam); if ( (wParam & (MK_LBUTTON|MK_RBUTTON)) && (wParam & (MK_LBUTTON|MK_RBUTTON)) != (MK_LBUTTON|MK_RBUTTON) ) { rlc_extend_selection(b, pt.x, pt.y); return 0; } break; } case WM_MOUSEWHEEL: { short angle = (short)HIWORD(wParam); if ( angle < 0 ) { if ( b->window_start != b->last ) b->window_start = NextLine(b, b->window_start); } else { if ( b->window_start != b->first ) b->window_start = PrevLine(b, b->window_start); } rlc_update_scrollbar(b); InvalidateRect(hwnd, NULL, FALSE); return 0; } /* scrolling */ case WM_VSCROLL: { switch( LOWORD(wParam) ) { case SB_LINEUP: if ( b->window_start != b->first ) b->window_start = PrevLine(b, b->window_start); break; case SB_LINEDOWN: if ( b->window_start != b->last ) b->window_start = NextLine(b, b->window_start); break; case SB_PAGEUP: { int maxdo = rlc_count_lines(b, b->first, b->window_start); int pagdo = b->window_size - 1; b->window_start = rlc_add_lines(b, b->window_start, -min(maxdo, pagdo)); break; } case SB_PAGEDOWN: { int maxup = rlc_count_lines(b, b->window_start, b->last); int pagup = b->window_size - 1; b->window_start = rlc_add_lines(b, b->window_start, min(maxup, pagup)); break; } case SB_THUMBTRACK: b->window_start = rlc_add_lines(b, b->first, HIWORD(wParam)); break; } rlc_update_scrollbar(b); InvalidateRect(hwnd, NULL, FALSE); return 0; } case WM_TIMER: if ( _rlc_timer_hook && wParam >= RLC_APPTIMER_ID ) { (*_rlc_timer_hook)((int) wParam); return 0; } break; case WM_RENDERALLFORMATS: if ( _rlc_render_all_hook ) { (*_rlc_render_all_hook)(); return 0; } break; case WM_RENDERFORMAT: if ( _rlc_render_hook && (*_rlc_render_hook)(wParam) ) return 0; break; case WM_ERASEBKGND: { HDC hdc = (HDC) wParam; RECT rect; HBRUSH hbrush; COLORREF rgb = b->background; hbrush = CreateSolidBrush(rgb); GetClipBox(hdc, &rect); FillRect(hdc, &rect, hbrush); DeleteObject(hbrush); return 1; /* non-zero: I've erased it */ } case WM_SYSCOLORCHANGE: b->foreground = GetSysColor(COLOR_WINDOWTEXT); b->background = GetSysColor(COLOR_WINDOW); b->sel_foreground = GetSysColor(COLOR_HIGHLIGHTTEXT); b->sel_background = GetSysColor(COLOR_HIGHLIGHT); return 0; case WM_RLC_WRITE: { int count = (int)wParam; TCHAR *buf = (TCHAR *)lParam; if ( OQSIZE - b->output_queued > count ) { _tcsncpy(&b->output_queue[b->output_queued], buf, count); b->output_queued += count; } else { if ( b->output_queued > 0 ) rlc_flush_output(b); if ( count <= OQSIZE ) { _tcsncpy(b->output_queue, buf, count); b->output_queued = count; } else rlc_do_write(b, buf, count); } return 0; } case WM_RLC_FLUSH: { rlc_flush_output(b); return 0; } case WM_RLC_MENU: { rlc_menu_action((rlc_console) b, (struct menu_data*)lParam); return 0; } case WM_RLC_CLOSEWIN: return 0; case WM_CLOSE: if ( rlc_kill(b) ) return 0; break; case WM_DESTROY: b->window = NULL; PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } static int rlc_get_message(MSG *msg, HWND hwnd, UINT low, UINT high) { int rc; again: if ( (rc=GetMessage(msg, hwnd, low, high)) ) { if ( _rlc_message_hook && (*_rlc_message_hook)(msg->hwnd, msg->message, msg->wParam, msg->lParam) ) goto again; } return rc; } static void rlc_dispatch(RlcData b) { MSG msg; if ( rlc_get_message(&msg, NULL, 0, 0) && msg.message != WM_RLC_CLOSEWIN ) { /* DEBUG(Dprintf("Thread %x got message 0x%04x\n", GetCurrentThreadId(), msg.message)); */ TranslateMessage(&msg); DispatchMessage(&msg); rlc_flush_output(b); return; } else { DEBUG(Dprintf(_T("Thread %x got WM_RLC_CLOSEWIN\n"), GetCurrentThreadId())); b->queue->flags |= RLC_EOF; } } void rlc_yield() { MSG msg; while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) { TranslateMessage(&msg); DispatchMessage(&msg); } } /******************************* * CHARACTER TYPES * *******************************/ static void rlc_init_word_chars() { int i; for(i=0; i 0 && chr < CHAR_MAX ) _rlc_word_chars[chr] = isword; } int rlc_is_word_char(int chr) { if ( chr > 0 && chr < CHAR_MAX ) return _rlc_word_chars[chr]; return _istalnum((wint_t)chr); /* only UNICODE version */ } /******************************* * SELECTION * *******************************/ #define SelLT(l1, c1, l2, c2) ((l1) < (l2) || (l1) == (l2) && (c1) < (c2)) #define SelEQ(l1, c1, l2, c2) ((l1) == (l2) && (c1) == (c2)) static int rlc_min(RlcData b, int x, int y) { if ( rlc_count_lines(b, b->first, x) < rlc_count_lines(b, b->first, y) ) return x; return y; } static int rlc_max(RlcData b, int x, int y) { if ( rlc_count_lines(b, b->first, x) > rlc_count_lines(b, b->first, y) ) return x; return y; } static void rlc_changed_line(RlcData b, int i, int mask) { b->lines[i].changed |= mask; } static void rlc_set_selection(RlcData b, int sl, int sc, int el, int ec) { int sch = rlc_min(b, sl, b->sel_start_line); int ech = rlc_max(b, el, b->sel_end_line); int nel = NextLine(b, el); int nsel= NextLine(b, b->sel_end_line); int i; int innow = FALSE; int insoon = FALSE; /* find the lines that changed */ for(i=sch; ; i = NextLine(b, i)) { if ( i == sl ) { insoon = TRUE; if ( i == b->sel_start_line ) { innow = TRUE; if ( sc != b->sel_start_char || (i == el && i != b->sel_end_line) || (i == b->sel_end_line && i != el) ) rlc_changed_line(b, i, CHG_CHANGED); } else rlc_changed_line(b, i, CHG_CHANGED); } else if ( i == b->sel_start_line ) { innow = TRUE; rlc_changed_line(b, i, CHG_CHANGED); } if ( i == b->sel_end_line ) { if ( (i == el && ec != b->sel_end_char) || el != i ) rlc_changed_line(b, i, CHG_CHANGED); } if ( innow != insoon ) rlc_changed_line(b, i, CHG_CHANGED); if ( i == nel ) { insoon = FALSE; if ( i == nsel ) innow = FALSE; else rlc_changed_line(b, i, CHG_CHANGED); } else if ( i == nsel ) { innow = FALSE; rlc_changed_line(b, i, CHG_CHANGED); } if ( i == ech ) break; } /* update the attributes */ b->sel_start_line = sl; b->sel_start_char = sc; b->sel_end_line = el; b->sel_end_char = ec; /* ... and request a repaint */ rlc_request_redraw(b); } void rlc_translate_mouse(RlcData b, int x, int y, int *line, int *chr) { int ln = b->window_start; int n = b->window_size; /* # lines */ TextLine tl; x-= b->cw; /* margin */ if ( !b->window ) return; while( y > b->ch && ln != b->last && n-- > 0 ) { ln = NextLine(b, ln); y -= b->ch; } *line = ln; tl = &b->lines[ln]; if ( b->fixedfont ) { *chr = min(x/b->cw, tl->size); } else if ( tl->size == 0 ) { *chr = 0; } else { TCHAR *s = tl->text; HDC hdc = GetDC(b->window); int f = 0; int t = tl->size; int m = (f+t)/2; int i; SelectObject(hdc, b->hfont); for(i=10; --i > 0; m=(f+t)/2) { int w; w = text_width(b, hdc, s, m); if ( x > w ) { int cw; GetCharWidth32(hdc, s[m], s[m], &cw); if ( x < w+cw ) { *chr = m; return; } f = m+1; } else { t = m; } } *chr = m; } } static void rlc_start_selection(RlcData b, int x, int y) { int l, c; rlc_translate_mouse(b, x, y, &l, &c); b->sel_unit = SEL_CHAR; b->sel_org_line = l; b->sel_org_char = c; rlc_set_selection(b, l, c, l, c); } static void rlc_end_selection(RlcData b, int x, int y) { int l, c; rlc_translate_mouse(b, x, y, &l, &c); if ( SelLT(l, c, b->sel_org_line, b->sel_org_char) ) rlc_set_selection(b, l, c, b->sel_org_line, b->sel_org_char); else if ( SelLT(b->sel_org_line, b->sel_org_char, l, c) ) rlc_set_selection(b, b->sel_org_line, b->sel_org_char, l, c); rlc_set_selection(b, l, c, l, c); } static int /* v >= f && v <= t */ rlc_between(RlcData b, int f, int t, int v) { int h = rlc_count_lines(b, b->first, v); if ( h >= rlc_count_lines(b, b->first, f) && h <= rlc_count_lines(b, b->first, t) ) return TRUE; return FALSE; } static void rlc_word_selection(RlcData b, int x, int y) { int l, c; rlc_translate_mouse(b, x, y, &l, &c); if ( rlc_between(b, b->first, b->last, l) ) { TextLine tl = &b->lines[l]; if ( c < tl->size && rlc_is_word_char(tl->text[c]) ) { int f, t; for(f=c; f>0 && rlc_is_word_char(tl->text[f-1]); f--) ; for(t=c; tsize && rlc_is_word_char(tl->text[t]); t++) ; rlc_set_selection(b, l, f, l, t); } } b->sel_unit = SEL_WORD; } static void rlc_extend_selection(RlcData b, int x, int y) { int l, c; rlc_translate_mouse(b, x, y, &l, &c); if ( SelLT(l, c, b->sel_org_line, b->sel_org_char) ) { if ( b->sel_unit == SEL_WORD ) { if ( rlc_between(b, b->first, b->last, l) ) { TextLine tl = &b->lines[l]; if ( c < tl->size && rlc_is_word_char(tl->text[c]) ) for(; c > 0 && rlc_is_word_char(tl->text[c-1]); c--) ; } } else if ( b->sel_unit == SEL_LINE ) c = 0; rlc_set_selection(b, l, c, b->sel_end_line, b->sel_end_char); } else if ( SelLT(b->sel_org_line, b->sel_org_char, l, c) ) { if ( b->sel_unit == SEL_WORD ) { if ( rlc_between(b, b->first, b->last, l) ) { TextLine tl = &b->lines[l]; if ( c < tl->size && rlc_is_word_char(tl->text[c]) ) for(; c < tl->size && rlc_is_word_char(tl->text[c]); c++) ; } } else if ( b->sel_unit == SEL_LINE ) c = b->width; rlc_set_selection(b, b->sel_start_line, b->sel_start_char, l, c); } } static TCHAR * rlc_read_from_window(RlcData b, int sl, int sc, int el, int ec) { int bufsize = 256; TCHAR *buf; int i = 0; if ( el < sl || el == sl && ec < sc ) return NULL; /* invalid region */ if ( !(buf = rlc_malloc(bufsize * sizeof(TCHAR))) ) return NULL; /* not enough memory */ for( ; ; sc = 0, sl = NextLine(b, sl)) { TextLine tl = &b->lines[sl]; if ( tl ) { int e = (sl == el ? ec : tl->size); if ( e > tl->size ) e = tl->size; while(sc < e) { if ( i >= bufsize ) { bufsize *= 2; if ( !(buf = rlc_realloc(buf, bufsize * sizeof(TCHAR))) ) return NULL; /* not enough memory */ } buf[i++] = tl->text[sc++]; } } if ( sl == el || sl == b->last ) { buf[i++] = '\0'; return buf; } if ( tl && !tl->softreturn ) { if ( i+1 >= bufsize ) { bufsize *= 2; if ( !(buf = rlc_realloc(buf, bufsize * sizeof(TCHAR))) ) return NULL; /* not enough memory */ } buf[i++] = '\r'; /* Bill ... */ buf[i++] = '\n'; } } } static TCHAR * rlc_selection(RlcData b) { if ( SelEQ(b->sel_start_line, b->sel_start_char, b->sel_end_line, b->sel_end_char) ) return NULL; return rlc_read_from_window(b, b->sel_start_line, b->sel_start_char, b->sel_end_line, b->sel_end_char); } static void rlc_copy(RlcData b) { TCHAR *sel = rlc_selection(b); if ( sel && b->window ) { size_t size = _tcslen(sel); HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (size + 1)*sizeof(TCHAR)); TCHAR far *data; size_t i; if ( !mem ) { MessageBox(NULL, _T("Not enough memory to copy"), _T("Error"), MB_OK); return; } data = GlobalLock(mem); for(i=0; iwindow); EmptyClipboard(); #ifdef UNICODE SetClipboardData(CF_UNICODETEXT, mem); #else SetClipboardData(CF_TEXT, mem); #endif CloseClipboard(); rlc_free(sel); } } /******************************* * REPAINT * *******************************/ static void rlc_place_caret(RlcData b) { if ( b->has_focus && b->window ) { int line = rlc_count_lines(b, b->window_start, b->caret_y); if ( line < b->window_size ) { if ( b->fixedfont ) { SetCaretPos((b->caret_x + 1) * b->cw, line * b->ch); } else { HDC hdc = GetDC(b->window); SIZE tsize; TextLine tl = &b->lines[b->caret_y]; HFONT old; old = SelectObject(hdc, b->hfont); GetTextExtentPoint32(hdc, tl->text, b->caret_x, &tsize); SelectObject(hdc, old); ReleaseDC(b->window, hdc); SetCaretPos(b->cw + tsize.cx, line * b->ch); } if ( !b->caret_is_shown ) { ShowCaret(b->window); b->caret_is_shown = TRUE; return; } } else { if ( b->caret_is_shown == TRUE ) { HideCaret(b->window); b->caret_is_shown = FALSE; } } } b->caret_is_shown = FALSE; } static void rlc_update_scrollbar(RlcData b) { if ( b->window ) { int nsb_lines = rlc_count_lines(b, b->first, b->last); int nsb_start = rlc_count_lines(b, b->first, b->window_start); if ( nsb_lines != b->sb_lines || nsb_start != b->sb_start ) { SetScrollRange(b->window, SB_VERT, 0, nsb_lines, FALSE); SetScrollPos( b->window, SB_VERT, nsb_start, TRUE); b->sb_lines = nsb_lines; b->sb_start = nsb_start; } } } static void rlc_redraw(RlcData b) { PAINTSTRUCT ps; HDC hdc = BeginPaint(b->window, &ps); int sl = max(0, ps.rcPaint.top/b->ch); int el = min(b->window_size, ps.rcPaint.bottom/b->ch); int l = rlc_add_lines(b, b->window_start, sl); int pl = sl; /* physical line */ RECT rect; HBRUSH bg; int stockbg; int insel = FALSE; /* selected lines? */ SelectObject(hdc, b->hfont); SetTextColor(hdc, b->foreground); SetBkColor(hdc, b->background); if ( b->background == RGB(255, 255, 255) ) { bg = GetStockObject(WHITE_BRUSH); stockbg = TRUE; } else { bg = CreateSolidBrush(b->background); stockbg = FALSE; } if ( b->has_focus && b->caret_is_shown ) { HideCaret(b->window); b->caret_is_shown = FALSE; } if ( rlc_count_lines(b, b->first, b->sel_start_line) < rlc_count_lines(b, b->first, l) && rlc_count_lines(b, b->first, b->sel_end_line) >= rlc_count_lines(b, b->first, l) ) insel = TRUE; if ( insel ) { SetBkColor(hdc, b->sel_background); SetTextColor(hdc, b->sel_foreground); } for(; pl <= el; l = NextLine(b, l), pl++) { TextLine tl = &b->lines[l]; TCHAR text[MAXLINE]; int ty = b->ch * pl; int cx = b->cw; if ( !tl->text ) { int i; TCHAR *t; tl->size = 0; for(i=0, t=text; iwidth; i++) *t++ = ' '; } else { int i; TCHAR *t, *s; for(i=0, t=text, s=tl->text; isize; i++) *t++ = *s++; for(; iwidth; i++) *t++ = ' '; } rect.top = ty; rect.bottom = rect.top + b->ch; /* compute selection */ if ( l == b->sel_start_line ) { int cf = b->sel_start_char; int ce = (b->sel_end_line != b->sel_start_line ? b->width : b->sel_end_char); if ( cf > 0 ) { TextOut(hdc, cx, ty, text, cf); cx += text_width(b, hdc, text, cf); } SetBkColor(hdc, b->sel_background); SetTextColor(hdc, b->sel_foreground); TextOut(hdc, cx, ty, &text[cf], ce-cf); cx += text_width(b, hdc, &text[cf], ce-cf); if ( l == b->sel_end_line ) { SetBkColor(hdc, b->background); SetTextColor(hdc, b->foreground); TextOut(hdc, cx, ty, &text[ce], b->width - ce); cx += text_width(b, hdc, &text[ce], b->width - ce); } else insel = TRUE; } else if ( l == b->sel_end_line ) /* end of selection */ { int ce = b->sel_end_char; insel = FALSE; TextOut(hdc, cx, ty, text, ce); cx += text_width(b, hdc, text, ce); SetBkColor(hdc, b->background); SetTextColor(hdc, b->foreground); TextOut(hdc, cx, ty, &text[ce], b->width - ce); cx += text_width(b, hdc, &text[ce], b->width - ce); } else /* entire line in/out selection */ { TextOut(hdc, cx, ty, text, b->width); cx += text_width(b, hdc, text, b->width); } /* clear remainder of line */ if ( cx < b->width * (b->cw+1) ) { rect.left = cx; rect.right = b->width * (b->cw+1); rect.top = b->ch * pl; rect.bottom = rect.top + b->ch; FillRect(hdc, &rect, bg); } tl->changed = CHG_RESET; if ( l == b->last ) /* clear to end of window */ { rect.left = b->cw; rect.right = b->width * (b->cw+1); rect.top = b->ch * (pl+1); rect.bottom = b->ch * (el+1); FillRect(hdc, &rect, bg); break; } } rlc_place_caret(b); b->changed = CHG_RESET; if ( !stockbg ) DeleteObject(bg); EndPaint(b->window, &ps); rlc_update_scrollbar(b); } static void rlc_request_redraw(RlcData b) { if ( b->changed & CHG_CHANGED ) { if ( b->window ) InvalidateRect(b->window, NULL, FALSE); } else { int i = b->window_start; int y = 0; RECT rect; int first = TRUE; int clear = FALSE; rect.left = b->cw; rect.right = (b->width+1) * b->cw; for(; y < b->window_size; y++, i = NextLine(b, i)) { TextLine l = &b->lines[i]; if ( l->changed & CHG_CHANGED ) { if ( first ) { rect.top = y * b->ch; rect.bottom = rect.top + b->ch; first = FALSE; } else rect.bottom = (y+1) * b->ch; if ( l->changed & CHG_CLEAR ) clear = TRUE; } if ( i == b->last ) break; } if ( !first && b->window ) InvalidateRect(b->window, &rect, FALSE); /*clear);*/ else if ( b->changed & CHG_CARET ) rlc_place_caret(b); } } static void rlc_normalise(RlcData b) { if ( rlc_count_lines(b, b->window_start, b->caret_y) >= b->window_size ) { b->window_start = rlc_add_lines(b, b->caret_y, -(b->window_size-1)); b->changed |= CHG_CARET|CHG_CLEAR|CHG_CHANGED; rlc_request_redraw(b); } } static void rlc_resize_pixel_units(RlcData b, int w, int h) { int nw = max(20, w/b->cw)-2; /* 1 character space for margins */ int nh = max(1, h/b->ch); DEBUG(Dprintf(_T("rlc_resize_pixel_units(%p, %d, %d) (%dx%d)\n"), b, w, h, nw, nh)); if ( b->width == nw && b->window_size == nh ) return; /* no real change */ rlc_resize(b, nw, nh); if ( _rlc_resize_hook ) (*_rlc_resize_hook)(b->width, b->window_size); else { #ifdef SIGWINCH raise(SIGWINCH); #endif } rlc_request_redraw(b); } /******************************* * FONT * *******************************/ static void rlc_init_text_dimensions(RlcData b, HFONT font) { HDC hdc; TEXTMETRIC tm; if ( font ) { b->hfont = font; } else if ( b->create_attributes ) { rlc_console_attr *a = b->create_attributes; if ( !a->face_name[0] ) b->hfont = GetStockObject(ANSI_FIXED_FONT); else { LOGFONT lfont; memset(&lfont, 0, sizeof(lfont)); lfont.lfHeight = a->font_size; lfont.lfWeight = a->font_weight; lfont.lfPitchAndFamily = a->font_family; lfont.lfCharSet = a->font_char_set; _tcsncpy(lfont.lfFaceName, a->face_name, 31); if ( !(b->hfont = CreateFontIndirect(&lfont)) ) b->hfont = GetStockObject(ANSI_FIXED_FONT); } } else b->hfont = GetStockObject(ANSI_FIXED_FONT); /* test for fixed?*/ hdc = GetDC(NULL); SelectObject(hdc, b->hfont); GetTextMetrics(hdc, &tm); b->cw = tm.tmAveCharWidth; b->cb = tm.tmHeight; b->ch = tm.tmHeight + tm.tmExternalLeading; b->fixedfont = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH ? FALSE : TRUE); ReleaseDC(NULL, hdc); if ( b->window ) { RECT rect; if ( b->has_focus == TRUE ) { CreateCaret(b->window, NULL, b->fixedfont ? b->cw : 3, b->ch-1); rlc_place_caret(b); } GetClientRect(b->window, &rect); rlc_resize_pixel_units(b, rect.right - rect.left, rect.bottom - rect.top); } } static int text_width(RlcData b, HDC hdc, const TCHAR *text, int len) { if ( b->fixedfont ) { return len * b->cw; } else { SIZE size; GetTextExtentPoint32(hdc, text, len, &size); return size.cx; } } static void rlc_save_font_options(HFONT font, rlc_console_attr *attr) { if ( font == GetStockObject(ANSI_FIXED_FONT) ) { attr->face_name[0] = '\0'; } else { LOGFONT lf; if ( GetObject(font, sizeof(lf), &lf) ) { memcpy(attr->face_name, lf.lfFaceName, sizeof(attr->face_name)-1); attr->font_family = lf.lfPitchAndFamily; attr->font_size = lf.lfHeight; attr->font_weight = lf.lfWeight; attr->font_char_set = lf.lfCharSet; } } } /******************************* * FONT SELECTION * *******************************/ static void rlc_queryfont(RlcData b) { CHOOSEFONT cf; LOGFONT lf; memset(&cf, 0, sizeof(cf)); memset(&lf, 0, sizeof(lf)); lf.lfHeight = 16; lf.lfWeight = FW_NORMAL; lf.lfPitchAndFamily = FIXED_PITCH|FF_MODERN; cf.lStructSize = sizeof(cf); cf.hwndOwner = b->window; cf.lpLogFont = &lf; cf.Flags = CF_SCREENFONTS| CF_NOVERTFONTS| CF_NOSIMULATIONS| CF_FORCEFONTEXIST| CF_INITTOLOGFONTSTRUCT; cf.nFontType = SCREEN_FONTTYPE; if ( ChooseFont(&cf) ) { HFONT f; if ( (f = CreateFontIndirect(&lf)) ) { rlc_init_text_dimensions(b, f); InvalidateRect(b->window, NULL, TRUE); } } } /******************************* * BUFFER INITIALISATION * *******************************/ static RlcData rlc_make_buffer(int w, int h) { RlcData b = rlc_malloc(sizeof(rlc_data)); int i; memset(b, 0, sizeof(*b)); b->magic = RLC_MAGIC; b->height = h; b->width = w; b->window_size = 25; b->lines = rlc_malloc(sizeof(text_line) * h); b->cmdstat = CMD_INITIAL; b->changed = CHG_CARET|CHG_CHANGED|CHG_CLEAR; b->imode = IMODE_COOKED; /* switch on first rlc_read() call */ b->imodeswitch = FALSE; b->lhead = NULL; b->ltail = NULL; memset(b->lines, 0, sizeof(text_line) * h); for(i=0; ilines[i].adjusted = TRUE; rlc_init_word_chars(); return b; } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Copy all lines one `back' (i.e. towards older lines). If the oldest (first) line is adjacent to the last, throw it away. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static void rlc_shift_lines_down(RlcData b, int line) { int i = b->first; int p = PrevLine(b, i); if ( p != b->last ) /* move first (oldest line) */ { b->first = p; b->lines[p] = b->lines[i]; } else /* delete first (oldest) line */ rlc_free_line(b, b->first); /* copy the lines */ for(p=i, i = NextLine(b, i); p != line; p=i, i = NextLine(b, i)) b->lines[p] = b->lines[i]; b->lines[line].text = NULL; /* make this one `free' */ b->lines[line].size = 0; b->lines[line].adjusted = TRUE; b->lines[line].softreturn = FALSE; } static void rlc_shift_lines_up(RlcData b, int line) { int prev = PrevLine(b, line); while(line != b->first) { b->lines[line] = b->lines[prev]; line = prev; prev = PrevLine(b, prev); } rlc_reinit_line(b, b->first); b->first = NextLine(b, b->first); } static void rlc_resize(RlcData b, int w, int h) { int i; if ( b->width == w && b->window_size == h ) return; /* no real change */ DEBUG(Dprintf(_T("Resizing %dx%d --> %dx%d\n"), b->width, b->window_size, w, h)); b->window_size = h; b->width = w; for(i = b->first; /*i != b->last*/; i = NextLine(b, i)) { TextLine tl = &b->lines[i]; if ( tl->text && tl->adjusted == FALSE ) rlc_adjust_line(b, i); if ( tl->size > w ) { if ( !tl->softreturn ) /* hard --> soft */ { TextLine pl; rlc_shift_lines_down(b, i); DEBUG(Dprint_lines(b, b->first, b->first)); DEBUG(Dprintf(_T("b->first = %d, b->last = %d\n"), b->first, b->last)); pl = &b->lines[PrevLine(b, i)]; /* this is the moved line */ tl->text = rlc_malloc((pl->size - w)*sizeof(TCHAR)); memmove(tl->text, &pl->text[w], (pl->size - w)*sizeof(TCHAR)); DEBUG(Dprintf(_T("Copied %d chars from line %d to %d\n"), pl->size - w, pl - b->lines, i)); tl->size = pl->size - w; tl->adjusted = TRUE; tl->softreturn = FALSE; pl->softreturn = TRUE; pl->text = rlc_realloc(pl->text, w * sizeof(TCHAR)); pl->size = w; pl->adjusted = TRUE; i = (int)(pl - b->lines); DEBUG(Dprint_lines(b, b->first, b->last)); } else /* put in next line */ { TextLine nl; int move = tl->size - w; if ( i == b->last ) rlc_add_line(b); nl = &b->lines[NextLine(b, i)]; nl->text = rlc_realloc(nl->text, (nl->size + move)*sizeof(TCHAR)); memmove(&nl->text[move], nl->text, nl->size*sizeof(TCHAR)); memmove(nl->text, &tl->text[w], move*sizeof(TCHAR)); nl->size += move; tl->size = w; } } else if ( tl->text && tl->softreturn && tl->size < w ) { TextLine nl; if ( i == b->last ) rlc_add_line(b); nl = &b->lines[NextLine(b, i)]; nl->text = rlc_realloc(nl->text, (nl->size + tl->size)*sizeof(TCHAR)); memmove(&nl->text[tl->size], nl->text, nl->size*sizeof(TCHAR)); memmove(nl->text, tl->text, tl->size*sizeof(TCHAR)); nl->size += tl->size; nl->adjusted = TRUE; rlc_shift_lines_up(b, i); } if ( i == b->last ) break; } for(i = NextLine(b, i); i != b->first; i = NextLine(b, i)) rlc_free_line(b, i); if ( rlc_count_lines(b, b->first, b->last) < h ) b->window_start = b->first; else b->window_start = rlc_add_lines(b, b->last, -(h-1)); b->caret_y = b->last; b->caret_x = b->lines[b->last].size; b->changed |= CHG_CARET|CHG_CHANGED|CHG_CLEAR; rlc_check_assertions(b); } static void rlc_reinit_line(RlcData b, int line) { TextLine tl = &b->lines[line]; tl->text = NULL; tl->adjusted = FALSE; tl->size = 0; tl->softreturn = FALSE; } static void rlc_free_line(RlcData b, int line) { TextLine tl = &b->lines[line]; if ( tl->text ) { rlc_free(tl->text); rlc_reinit_line(b, line); } } static void rlc_adjust_line(RlcData b, int line) { TextLine tl = &b->lines[line]; if ( tl->text && !tl->adjusted ) { tl->text = rlc_realloc(tl->text, tl->size == 0 ? sizeof(TCHAR) : tl->size * sizeof(TCHAR)); tl->adjusted = TRUE; } } static void rlc_unadjust_line(RlcData b, int line) { TextLine tl = &b->lines[line]; if ( tl->text ) { if ( tl->adjusted ) { tl->text = rlc_realloc(tl->text, (b->width + 1)*sizeof(TCHAR)); tl->adjusted = FALSE; } } else { tl->text = rlc_malloc((b->width + 1)*sizeof(TCHAR)); tl->adjusted = FALSE; tl->size = 0; } } static void rlc_open_line(RlcData b) { int i = b->last; if ( i == b->sel_start_line ) rlc_set_selection(b, 0, 0, 0, 0); /* clear the selection */ if ( i == b->first ) { rlc_free_line(b, b->first); b->first = NextLine(b, b->first); } b->lines[i].text = rlc_malloc((b->width + 1)*sizeof(TCHAR)); b->lines[i].adjusted = FALSE; b->lines[i].size = 0; b->lines[i].softreturn = FALSE; } static void rlc_add_line(RlcData b) { b->last = NextLine(b, b->last); rlc_open_line(b); } /******************************* * CALCULATIONS * *******************************/ static int rlc_count_lines(RlcData b, int from, int to) { if ( to >= from ) return to-from; return to + b->height - from; } static int rlc_add_lines(RlcData b, int here, int add) { here += add; while ( here < 0 ) here += b->height; while ( here >= b->height ) here -= b->height; return here; } /******************************* * ANSI SEQUENCE HANDLING * *******************************/ static void rlc_need_arg(RlcData b, int arg, int def) { if ( b->argc < arg ) { b->argv[arg-1] = def; b->argc = arg; } } static void rlc_caret_up(RlcData b, int arg) { while(arg-- > 0 && b->caret_y != b->first) b->caret_y = PrevLine(b, b->caret_y); b->changed |= CHG_CARET; } static void rlc_caret_down(RlcData b, int arg) { while ( arg-- > 0 ) { if ( b->caret_y == b->last ) rlc_add_line(b); b->caret_y = NextLine(b, b->caret_y); b->lines[b->caret_y].softreturn = FALSE; /* ? why not only on open? */ } b->changed |= CHG_CARET; /* scroll? */ if ( rlc_count_lines(b, b->window_start, b->caret_y) >= b->window_size ) { b->window_start = rlc_add_lines(b, b->caret_y, -(b->window_size-1)); b->changed |= CHG_CHANGED|CHG_CLEAR; } rlc_check_assertions(b); } static void rlc_caret_forward(RlcData b, int arg) { while(arg-- > 0) { if ( ++b->caret_x >= b->width ) { b->lines[b->caret_y].softreturn = TRUE; b->caret_x = 0; rlc_caret_down(b, 1); } } b->changed |= CHG_CARET; } static void rlc_caret_backward(RlcData b, int arg) { while(arg-- > 0) { if ( b->caret_x-- == 0 ) { rlc_caret_up(b, 1); b->caret_x = b->width-1; } } b->changed |= CHG_CARET; } static void rlc_cariage_return(RlcData b) { b->caret_x = 0; b->changed |= CHG_CARET; } static void rlc_tab(RlcData b) { TextLine tl = &b->lines[b->caret_y]; do { rlc_caret_forward(b, 1); } while( (b->caret_x % 8) != 0 ); if ( tl->size < b->caret_x ) { rlc_unadjust_line(b, b->caret_y); while ( tl->size < b->caret_x ) tl->text[tl->size++] = ' '; } b->changed |= CHG_CARET; } static void rlc_set_caret(RlcData b, int x, int y) { int cy = rlc_count_lines(b, b->window_start, b->caret_y); y = Bounds(y, 0, b->window_size); if ( y < cy ) b->caret_y = rlc_add_lines(b, b->window_start, y); else rlc_caret_down(b, y-cy); b->caret_x = Bounds(x, 0, b->width-1); b->changed |= CHG_CARET; } static void rlc_save_caret_position(RlcData b) { b->scaret_y = rlc_count_lines(b, b->window_start, b->caret_y); b->scaret_x = b->caret_x; } static void rlc_restore_caret_position(RlcData b) { rlc_set_caret(b, b->scaret_x, b->scaret_y); } static void rlc_erase_display(RlcData b) { int i = b->window_start; int last = rlc_add_lines(b, b->window_start, b->window_size); do { b->lines[i].size = 0; i = NextLine(b, i); } while ( i != last ); b->changed |= CHG_CHANGED|CHG_CLEAR|CHG_CARET; rlc_set_caret(b, 0, 0); } static void rlc_erase_line(RlcData b) { TextLine tl = &b->lines[b->caret_y]; tl->size = b->caret_x; tl->changed |= CHG_CHANGED|CHG_CLEAR; } static void rlc_put(RlcData b, int chr) { TextLine tl = &b->lines[b->caret_y]; rlc_unadjust_line(b, b->caret_y); while( tl->size < b->caret_x ) tl->text[tl->size++] = ' '; tl->text[b->caret_x] = chr; if ( tl->size <= b->caret_x ) tl->size = b->caret_x + 1; tl->changed |= CHG_CHANGED; rlc_caret_forward(b, 1); } #ifdef _DEBUG #define CMD(c) {cmd = _T(#c); c;} #else #define CMD(c) {c;} #endif static void rlc_putansi(RlcData b, int chr) { #ifdef _DEBUG TCHAR *cmd; #endif switch(b->cmdstat) { case CMD_INITIAL: switch(chr) { case '\b': CMD(rlc_caret_backward(b, 1)); break; case Control('G'): MessageBeep(MB_ICONEXCLAMATION); break; case '\r': CMD(rlc_cariage_return(b)); break; case '\n': CMD(rlc_caret_down(b, 1)); break; case '\t': CMD(rlc_tab(b)); break; case 27: /* ESC */ b->cmdstat = CMD_ESC; break; default: CMD(rlc_put(b, chr)); break; } break; case CMD_ESC: switch(chr) { case '[': b->cmdstat = CMD_ANSI; b->argc = 0; b->argstat = 0; /* no arg */ break; default: b->cmdstat = CMD_INITIAL; break; } break; case CMD_ANSI: /* ESC [ */ if ( _istdigit((wint_t)chr) ) { if ( !b->argstat ) { b->argv[b->argc] = (chr - '0'); b->argstat = 1; /* positive */ } else { b->argv[b->argc] = b->argv[b->argc] * 10 + (chr - '0'); } break; } if ( !b->argstat && chr == '-' ) { b->argstat = -1; /* negative */ break; } if ( b->argstat ) { b->argv[b->argc] *= b->argstat; if ( b->argc < (ANSI_MAX_ARGC-1) ) b->argc++; /* silently discard more of them */ b->argstat = 0; } switch(chr) { case ';': break; /* wait for more args */ case 'H': case 'f': rlc_need_arg(b, 1, 0); rlc_need_arg(b, 2, 0); CMD(rlc_set_caret(b, b->argv[0], b->argv[1])); break; case 'A': rlc_need_arg(b, 1, 1); CMD(rlc_caret_up(b, b->argv[0])); break; case 'B': rlc_need_arg(b, 1, 1); CMD(rlc_caret_down(b, b->argv[0])); break; case 'C': rlc_need_arg(b, 1, 1); CMD(rlc_caret_forward(b, b->argv[0])); break; case 'D': rlc_need_arg(b, 1, 1); CMD(rlc_caret_backward(b, b->argv[0])); break; case 's': CMD(rlc_save_caret_position(b)); break; case 'u': CMD(rlc_restore_caret_position(b)); break; case 'J': if ( b->argv[0] == 2 ) CMD(rlc_erase_display(b)); break; case 'K': CMD(rlc_erase_line(b)); break; } b->cmdstat = CMD_INITIAL; } rlc_check_assertions(b); } /******************************* * CUT/PASTE * *******************************/ static void rlc_paste(RlcData b) { HGLOBAL mem; if ( b->window ) { OpenClipboard(b->window); if ( (mem = GetClipboardData(CF_UNICODETEXT)) ) { wchar_t *data = GlobalLock(mem); int i; RlcQueue q = b->queue; if ( q ) { for(i=0; data[i]; i++) { rlc_add_queue(b, q, data[i]); if ( data[i] == '\r' && data[i+1] == '\n' ) i++; } } GlobalUnlock(mem); } else if ( (mem = GetClipboardData(CF_TEXT)) ) { char far *data = GlobalLock(mem); int i; RlcQueue q = b->queue; if ( q ) { for(i=0; data[i]; i++) { rlc_add_queue(b, q, data[i]); if ( data[i] == '\r' && data[i+1] == '\n' ) i++; } } GlobalUnlock(mem); } CloseClipboard(); } } /******************************* * LINE-READ SUPPORT * *******************************/ void rlc_get_mark(rlc_console c, RlcMark m) { RlcData b = rlc_get_data(c); m->mark_x = b->caret_x; m->mark_y = b->caret_y; } void rlc_goto_mark(rlc_console c, RlcMark m, const TCHAR *data, size_t offset) { RlcData b = rlc_get_data(c); b->caret_x = m->mark_x; b->caret_y = m->mark_y; for( ; offset-- > 0; data++ ) { switch(*data) { case '\t': rlc_tab(b); break; case '\n': b->caret_x = 0; rlc_caret_down(b, 1); break; default: rlc_caret_forward(b, 1); } } } void rlc_erase_from_caret(rlc_console c) { RlcData b = rlc_get_data(c); int i = b->caret_y; int x = b->caret_x; int last = rlc_add_lines(b, b->window_start, b->window_size); do { TextLine tl = &b->lines[i]; if ( tl->size != x ) { tl->size = x; tl->changed |= CHG_CHANGED|CHG_CLEAR; } i = NextLine(b, i); x = 0; } while ( i != last ); } void rlc_putchar(rlc_console c, int chr) { RlcData b = rlc_get_data(c); rlc_putansi(b, chr); } TCHAR * rlc_read_screen(rlc_console c, RlcMark f, RlcMark t) { RlcData b = rlc_get_data(c); TCHAR *buf; buf = rlc_read_from_window(b, f->mark_y, f->mark_x, t->mark_y, t->mark_x); return buf; } void rlc_update(rlc_console c) { RlcData b = rlc_get_data(c); if ( b->window ) { rlc_normalise(b); rlc_request_redraw(b); UpdateWindow(b->window); } } /******************************* * UPDATE THREAD * *******************************/ DWORD WINAPI window_loop(LPVOID arg) { RlcData b = (RlcData) arg; rlc_create_window(b); /* if we do not do this, all windows */ /* created by Prolog (XPCE) will be */ /* in the background and inactive! */ if ( !AttachThreadInput(b->application_thread_id, b->console_thread_id, TRUE) ) rlc_putansi(b, '!'); PostThreadMessage(b->application_thread_id, WM_RLC_READY, 0, 0); while(!b->closing) { switch( b->imode ) { case IMODE_COOKED: { TCHAR *line = read_line(b); if ( line != RL_CANCELED_CHARP ) { LQueued lq = rlc_malloc(sizeof(lqueued)); lq->next = NULL; lq->line = line; if ( b->ltail ) { b->ltail->next = lq; b->ltail = lq; } else { b->lhead = b->ltail = lq; /* awake main thread */ PostThreadMessage(b->application_thread_id, WM_RLC_INPUT, 0, 0); } } break; } case IMODE_RAW: { MSG msg; if ( rlc_get_message(&msg, NULL, 0, 0) ) { TranslateMessage(&msg); DispatchMessage(&msg); rlc_flush_output(b); } else goto out; if ( b->imodeswitch ) { b->imodeswitch = FALSE; } } } } if ( b->closing <= 2 ) { MSG msg; TCHAR *waiting = _T("\r\nWaiting for Prolog. ") _T("Close again to force termination .."); rlc_write(b, waiting, _tcslen(waiting)); while ( b->closing <= 2 && rlc_get_message(&msg, NULL, 0, 0) ) { TranslateMessage(&msg); DispatchMessage(&msg); rlc_flush_output(b); } } out: { DWORD appthread = b->application_thread_id; rlc_destroy(b); PostThreadMessage(appthread, WM_RLC_READY, 0, 0); } return 0; } /******************************* * WATCOM/DOS I/O * *******************************/ int getch(rlc_console c) { RlcData b = rlc_get_data(c); RlcQueue q = b->queue; int fromcon = (GetCurrentThreadId() == b->console_thread_id); while( rlc_is_empty_queue(q) ) { if ( q->flags & RLC_EOF ) return EOF; if ( !fromcon ) { MSG msg; if ( rlc_get_message(&msg, NULL, 0, 0) ) { TranslateMessage(&msg); DispatchMessage(&msg); } else return EOF; } else { rlc_dispatch(b); if ( b->imodeswitch ) { b->imodeswitch = FALSE; return IMODE_SWITCH_CHAR; } } } return rlc_from_queue(q); } int getche(rlc_console c) { RlcData b = rlc_get_data(c); int chr = getch(b); rlc_putansi(b, chr); return chr; } /******************************* * GO32 FUNCTIONS * *******************************/ int getkey(rlc_console con) { int c; RlcData b = rlc_get_data(con); int fromcon = (GetCurrentThreadId() == b->console_thread_id); if ( !fromcon && b->imode != IMODE_RAW ) { int old = b->imode; b->imode = IMODE_RAW; b->imodeswitch = TRUE; c = getch(b); b->imode = old; b->imodeswitch = TRUE; } else c = getch(b); return c; } int kbhit(rlc_console c) { RlcData b = rlc_get_data(c); return !rlc_is_empty_queue(b->queue); } void ScreenGetCursor(rlc_console c, int *row, int *col) { RlcData b = rlc_get_data(c); *row = rlc_count_lines(b, b->window_start, b->caret_y) + 1; *col = b->caret_x + 1; } void ScreenSetCursor(rlc_console c, int row, int col) { RlcData b = rlc_get_data(c); rlc_set_caret(b, col-1, row-1); } int ScreenCols(rlc_console c) { RlcData b = rlc_get_data(c); return b->width; } int ScreenRows(rlc_console c) { RlcData b = rlc_get_data(c); return b->window_size; } /******************************* * QUEUE * *******************************/ #define QN(q, i) ((i)+1 >= (q)->size ? 0 : (i)+1) RlcQueue rlc_make_queue(int size) { RlcQueue q; if ( (q = rlc_malloc(sizeof(rlc_queue))) ) { q->first = q->last = 0; q->size = size; q->flags = 0; if ( (q->buffer = rlc_malloc(sizeof(TCHAR) * size)) ) return q; } return NULL; /* not enough memory */ } void rlc_free_queue(RlcQueue q) { if ( q ) { if ( q->buffer ) rlc_free(q->buffer); rlc_free(q); } } static int rlc_resize_queue(RlcQueue q, int size) { TCHAR *newbuf; if ( (newbuf = rlc_malloc(size*sizeof(TCHAR))) ) { TCHAR *o = newbuf; int c; while( (c=rlc_from_queue(q)) != -1 ) *o++ = c; if ( q->buffer ) rlc_free(q->buffer); q->buffer = newbuf; q->first = 0; q->last = (int)(o-newbuf); q->size = size; return TRUE; } return FALSE; } static int rlc_add_queue(RlcData b, RlcQueue q, int chr) { int empty = (q->first == q->last); while(q->size < 50000) { if ( QN(q, q->last) != q->first ) { q->buffer[q->last] = chr; q->last = QN(q, q->last); if ( empty ) PostThreadMessage(b->application_thread_id, WM_RLC_INPUT, 0, 0); return TRUE; } rlc_resize_queue(q, q->size*2); } return FALSE; } int rlc_is_empty_queue(RlcQueue q) { if ( q->first == q->last ) return TRUE; return FALSE; } void rlc_empty_queue(RlcQueue q) { q->first = q->last = 0; } static int rlc_from_queue(RlcQueue q) { if ( q->first != q->last ) { int chr = q->buffer[q->first]; q->first = QN(q, q->first); return chr; } return -1; } /******************************* * BUFFERED I/O * *******************************/ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - When using UNICODE, count is in bytes! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ size_t rlc_read(rlc_console c, TCHAR *buf, size_t count) { RlcData d = rlc_get_data(c); size_t give; MSG msg; if ( d->closing ) return 0; /* signal EOF when closing */ PostThreadMessage(d->console_thread_id, WM_RLC_FLUSH, 0, 0); if ( _rlc_update_hook ) (*_rlc_update_hook)(); d->promptbuf[d->promptlen] = EOS; _tcscpy(d->prompt, d->promptbuf); if ( d->read_buffer.given >= d->read_buffer.length ) { if ( d->read_buffer.line ) { rlc_free(d->read_buffer.line); d->read_buffer.line = NULL; } if ( d->imode != IMODE_COOKED ) { d->imode = IMODE_COOKED; d->imodeswitch = TRUE; } while(!d->lhead) { if ( rlc_get_message(&msg, NULL, 0, 0) ) { TranslateMessage(&msg); DispatchMessage(&msg); } else return -1; } { LQueued lq = d->lhead; d->read_buffer.line = lq->line; if ( lq->next ) d->lhead = lq->next; else d->lhead = d->ltail = NULL; rlc_free(lq); } d->read_buffer.length = _tcslen(d->read_buffer.line); d->read_buffer.given = 0; } if ( d->read_buffer.length - d->read_buffer.given > count ) give = count; else give = d->read_buffer.length - d->read_buffer.given; _tcsncpy(buf, d->read_buffer.line+d->read_buffer.given, give); d->read_buffer.given += give; return give; } static void rlc_do_write(RlcData b, TCHAR *buf, int count) { if ( count > 0 ) { int n = 0; TCHAR *s = buf; while(n++ < count) { int chr = *s++; if ( chr == '\n' ) rlc_putansi(b, '\r'); rlc_putansi(b, chr); } rlc_normalise(b); if ( b->window ) { rlc_request_redraw(b); UpdateWindow(b->window); } } } int rlc_flush_output(rlc_console c) { RlcData b = rlc_get_data(c); if ( !b ) return -1; if ( b->output_queued ) { rlc_do_write(b, b->output_queue, b->output_queued); b->output_queued = 0; } return 0; } size_t rlc_write(rlc_console c, TCHAR *buf, size_t count) { DWORD_PTR result; TCHAR *e, *s; RlcData b = rlc_get_data(c); if ( !b ) return -1; for(s=buf, e=&buf[count]; spromptlen = 0; else if ( b->promptlen < MAXPROMPT-1 ) b->promptbuf[b->promptlen++] = *s; } if ( b->window ) { if ( SendMessageTimeout(b->window, WM_RLC_WRITE, (WPARAM)count, (LPARAM)buf, SMTO_NORMAL, 10000, &result) ) { PostMessage(b->window, WM_RLC_FLUSH, 0, 0); return count; } } return -1; /* I/O error */ } static void free_rlc_data(RlcData b) { b->magic = 42; /* so next gets errors */ if ( b->lines ) { int i; for(i=0; iheight; i++) { if ( b->lines[i].text ) free(b->lines[i].text); } free(b->lines); } if ( b->read_buffer.line ) free(b->read_buffer.line); free(b); } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - rlc_close() tries to gracefully get rid of the console thread. It does so by posting WM_RLC_CLOSEWIN and then waiting for a WM_RLC_READY reply. It waits for a maximum of 1.5 second, which should be fine as the console thread should not have intptr_t-lasting activities. If the timeout expires it hopes for the best. This was the old situation and proved to be sound on Windows-NT, but not on 95 and '98. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ int rlc_close(rlc_console c) { RlcData b = (RlcData)c; MSG msg; int i; if ( b->magic != RLC_MAGIC ) return -1; rlc_save_options(b); b->closing = 3; PostMessage(b->window, WM_RLC_CLOSEWIN, 0, 0); /* wait for termination */ for(i=0; i<30; i++) { if ( PeekMessage(&msg, NULL, WM_RLC_READY, WM_RLC_READY, PM_REMOVE) ) break; Sleep(50); } b->magic = 0; free_user_data(c); free_rlc_data(b); return 0; } const TCHAR * rlc_prompt(rlc_console c, const TCHAR *new) { RlcData b = rlc_get_data(c); if ( b ) { if ( new ) { _tcsncpy(b->prompt, new, MAXPROMPT); b->prompt[MAXPROMPT-1] = EOS; } return b->prompt; } return _T(""); } void rlc_clearprompt(rlc_console c) { RlcData b = rlc_get_data(c); if ( b ) { b->promptlen = 0; b->prompt[0] = EOS; } } /******************************* * MISC STUFF * *******************************/ static TCHAR current_title[RLC_TITLE_MAX]; void rlc_title(rlc_console c, TCHAR *title, TCHAR *old, int size) { RlcData b = rlc_get_data(c); if ( old ) memmove(old, b->current_title, size*sizeof(TCHAR)); if ( title ) { if ( b->window ) SetWindowText(b->window, title); memmove(b->current_title, title, RLC_TITLE_MAX*sizeof(TCHAR)); } } void rlc_icon(rlc_console c, HICON icon) { #ifdef WIN64 SetClassLong(rlc_hwnd(c), GCLP_HICON, (LONG) icon); #else SetClassLong(rlc_hwnd(c), GCL_HICON, (LONG) icon); #endif } int rlc_window_pos(rlc_console c, HWND hWndInsertAfter, int x, int y, int w, int h, UINT flags) { RlcData b = rlc_get_data(c); if ( b ) { w *= b->cw; h *= b->ch; SetWindowPos(b->window, hWndInsertAfter, x, y, w, h, flags); return TRUE; } return FALSE; } HANDLE rlc_hinstance() { return _rlc_hinstance; } HWND rlc_hwnd(rlc_console c) { RlcData b = rlc_get_data(c); return b ? b->window : (HWND)NULL; } /******************************* * SETTING OPTIONS * *******************************/ int rlc_copy_output_to_debug_output(int new) { int old = _rlc_copy_output_to_debug_output; _rlc_copy_output_to_debug_output = new; return old; } RlcUpdateHook rlc_update_hook(RlcUpdateHook new) { RlcUpdateHook old = _rlc_update_hook; _rlc_update_hook = new; return old; } RlcTimerHook rlc_timer_hook(RlcTimerHook new) { RlcTimerHook old = _rlc_timer_hook; _rlc_timer_hook = new; return old; } RlcRenderHook rlc_render_hook(RlcRenderHook new) { RlcRenderHook old = _rlc_render_hook; _rlc_render_hook = new; return old; } RlcRenderAllHook rlc_render_all_hook(RlcRenderAllHook new) { RlcRenderAllHook old = _rlc_render_all_hook; _rlc_render_all_hook = new; return old; } RlcInterruptHook rlc_interrupt_hook(RlcInterruptHook new) { RlcInterruptHook old = _rlc_interrupt_hook; _rlc_interrupt_hook = new; return old; } RlcResizeHook rlc_resize_hook(RlcResizeHook new) { RlcResizeHook old = _rlc_resize_hook; _rlc_resize_hook = new; return old; } RlcMenuHook rlc_menu_hook(RlcMenuHook new) { RlcMenuHook old = _rlc_menu_hook; _rlc_menu_hook = new; return old; } RlcMessageHook rlc_message_hook(RlcMessageHook new) { RlcMessageHook old = _rlc_message_hook; _rlc_message_hook = new; return old; } int rlc_set(rlc_console c, int what, uintptr_t data, RlcFreeDataHook hook) { RlcData b = rlc_get_data(c); switch(what) { default: if ( what >= RLC_VALUE(0) && what <= RLC_VALUE(MAX_USER_VALUES) ) { b->values[what-RLC_VALUE(0)].data = data; b->values[what-RLC_VALUE(0)].hook = hook; return TRUE; } return FALSE; } } int rlc_get(rlc_console c, int what, uintptr_t *data) { RlcData b = (RlcData)c; if ( !b ) return FALSE; switch(what) { case RLC_APPLICATION_THREAD: *data = (uintptr_t)b->application_thread; return TRUE; case RLC_APPLICATION_THREAD_ID: *data = (uintptr_t)b->application_thread_id; return TRUE; default: if ( what >= RLC_VALUE(0) && what <= RLC_VALUE(MAX_USER_VALUES) ) { *data = b->values[what-RLC_VALUE(0)].data; return TRUE; } return FALSE; } } static void free_user_data(RlcData b) { user_data *d = b->values; int i; for(i=0; ihook) ) { uintptr_t data = d->data; d->hook = NULL; d->data = 0L; (*hook)(data); } } } /******************************* * UTIL * *******************************/ static void noMemory() { MessageBox(NULL, _T("Not enough memory"), _T("Console"), MB_OK|MB_TASKMODAL); ExitProcess(1); } void * rlc_malloc(size_t size) { void *ptr = malloc(size); if ( !ptr && size > 0 ) noMemory(); #ifdef _DEBUG memset(ptr, 0xbf, size); #endif return ptr; } void * rlc_realloc(void *ptr, size_t size) { void *ptr2 = realloc(ptr, size); if ( !ptr2 && size > 0 ) noMemory(); return ptr2; } void rlc_free(void *ptr) { free(ptr); } #ifndef initHeapDebug /******************************* * DEBUG * *******************************/ static void initHeapDebug(void) { int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG ); if ( !(tmpFlag & _CRTDBG_CHECK_ALWAYS_DF) ) { /*MessageBox(NULL, "setting malloc() debugging", "SWI-Prolog console", MB_OK|MB_TASKMODAL);*/ tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF; _CrtSetDbgFlag(tmpFlag); } else { /*MessageBox(NULL, "malloc() debugging lready set", "SWI-Prolog console", MB_OK|MB_TASKMODAL);*/ } } #endif /*initHeapDebug*/ #ifdef _DEBUG static void Dprintf(const TCHAR *fmt, ...) { TCHAR buf[1024]; va_list args; va_start(args, fmt); _vstprintf(buf, fmt, args); va_end(args); OutputDebugString(buf); } static void Dprint_lines(RlcData b, int from, int to) { TCHAR buf[1024]; for( ; ; from = NextLine(b, from)) { TextLine tl = &b->lines[from]; memcpy(buf, tl->text, tl->size); buf[tl->size] = EOS; Dprintf(_T("%03d: (0x%08x) \"%s\"\n"), from, tl->text, buf); if ( from == to ) break; } } #endif