the final move the files win32/console.c and win32/editline.c have to removed too
Name: con_move ChangeLog: moved all client-side console code to kernel DLL and directory License: X11 GenDate: 2002/07/30 19:19:22 UTC ModifiedFiles: dlls/kernel/Makefile.in dlls/ntdll/Makefile.in dlls/kernel/console.c AddedFiles: dlls/kernel/editline.c =================================================================== RCS file: /home/cvs/cvsroot/wine/wine/dlls/kernel/Makefile.in,v retrieving revision 1.37 diff -u -u -r1.37 Makefile.in --- dlls/kernel/Makefile.in 14 Jun 2002 02:09:08 -0000 1.37 +++ dlls/kernel/Makefile.in 30 Jul 2002 12:33:42 -0000 @@ -11,6 +11,7 @@ comm.c \ console.c \ debugger.c \ + editline.c \ format_msg.c \ kernel_main.c \ stress.c \ Index: dlls/kernel/console.c =================================================================== RCS file: /home/cvs/cvsroot/wine/wine/dlls/kernel/console.c,v retrieving revision 1.5 diff -u -u -r1.5 console.c --- dlls/kernel/console.c 31 May 2002 23:25:48 -0000 1.5 +++ dlls/kernel/console.c 30 Jul 2002 18:55:49 -0000 @@ -5,7 +5,7 @@ * Copyright 1997 Karl Garrison * Copyright 1998 John Richardson * Copyright 1998 Marcus Meissner - * Copyright 2001 Eric Pouech + * Copyright 2001,2002 Eric Pouech * Copyright 2001 Alexandre Julliard * * This library is free software; you can redistribute it and/or @@ -29,6 +29,7 @@ */ #include "config.h" +#include "wine/port.h" #include <stdio.h> #include <string.h> @@ -43,6 +44,7 @@ #include "wine/exception.h" #include "wine/unicode.h" #include "wine/debug.h" +#include "msvcrt/excpt.h" WINE_DEFAULT_DEBUG_CHANNEL(console); @@ -818,3 +820,1410 @@ return c; } #endif /* defined(__i386__) */ + +/* editline.c */ +extern WCHAR* CONSOLE_Readline(HANDLE, int); + +static WCHAR* S_EditString /* = NULL */; +static unsigned S_EditStrPos /* = 0 */; + +/*********************************************************************** + * FreeConsole (KERNEL32.@) + */ +BOOL WINAPI FreeConsole(VOID) +{ + BOOL ret; + + SERVER_START_REQ(free_console) + { + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + return ret; +} + +/****************************************************************** + * start_console_renderer + * + * helper for AllocConsole + * starts the renderer process + */ +static BOOL start_console_renderer(void) +{ + char buffer[256]; + int ret; + STARTUPINFOA si; + PROCESS_INFORMATION pi; + HANDLE hEvent = 0; + LPSTR p; + OBJECT_ATTRIBUTES attr; + + attr.Length = sizeof(attr); + attr.RootDirectory = 0; + attr.Attributes = OBJ_INHERIT; + attr.ObjectName = NULL; + attr.SecurityDescriptor = NULL; + attr.SecurityQualityOfService = NULL; + + NtCreateEvent(&hEvent, EVENT_ALL_ACCESS, &attr, TRUE, FALSE); + if (!hEvent) return FALSE; + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + + /* FIXME: use dynamic allocation for most of the buffers below */ + /* first try environment variable */ + if ((p = getenv("WINECONSOLE")) != NULL) + { + ret = snprintf(buffer, sizeof(buffer), "%s --use-event=%d", p, hEvent); + if ((ret > -1) && (ret < sizeof(buffer)) && + CreateProcessA(NULL, buffer, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi)) + goto succeed; + ERR("Couldn't launch Wine console from WINECONSOLE env var... trying default access\n"); + } + + /* then try the regular PATH */ + sprintf(buffer, "wineconsole --use-event=%d", hEvent); + if (CreateProcessA(NULL, buffer, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi)) + goto succeed; + + goto the_end; + + succeed: + if (WaitForSingleObject(hEvent, INFINITE) != WAIT_OBJECT_0) goto the_end; + CloseHandle(hEvent); + + TRACE("Started wineconsole pid=%08lx tid=%08lx\n", pi.dwProcessId, pi.dwThreadId); + + return TRUE; + + the_end: + ERR("Can't allocate console\n"); + CloseHandle(hEvent); + return FALSE; +} + +/*********************************************************************** + * AllocConsole (KERNEL32.@) + * + * creates an xterm with a pty to our program + */ +BOOL WINAPI AllocConsole(void) +{ + HANDLE handle_in = INVALID_HANDLE_VALUE; + HANDLE handle_out = INVALID_HANDLE_VALUE; + HANDLE handle_err = INVALID_HANDLE_VALUE; + STARTUPINFOW si; + + TRACE("()\n"); + + handle_in = CreateFileA( "CONIN$", GENERIC_READ|GENERIC_WRITE|SYNCHRONIZE, + 0, NULL, OPEN_EXISTING, 0, 0 ); + + if (handle_in != INVALID_HANDLE_VALUE) + { + /* we already have a console opened on this process, don't create a new one */ + CloseHandle(handle_in); + return FALSE; + } + + if (!start_console_renderer()) + goto the_end; + + handle_in = CreateFileA( "CONIN$", GENERIC_READ|GENERIC_WRITE|SYNCHRONIZE, + 0, NULL, OPEN_EXISTING, 0, 0 ); + if (handle_in == INVALID_HANDLE_VALUE) goto the_end; + + handle_out = CreateFileA( "CONOUT$", GENERIC_READ|GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, 0, 0 ); + if (handle_out == INVALID_HANDLE_VALUE) goto the_end; + + if (!DuplicateHandle(GetCurrentProcess(), handle_out, GetCurrentProcess(), &handle_err, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + goto the_end; + + /* NT resets the STD_*_HANDLEs on console alloc */ + SetStdHandle(STD_INPUT_HANDLE, handle_in); + SetStdHandle(STD_OUTPUT_HANDLE, handle_out); + SetStdHandle(STD_ERROR_HANDLE, handle_err); + + GetStartupInfoW(&si); + if (si.dwFlags & STARTF_USECOUNTCHARS) + { + COORD c; + c.X = si.dwXCountChars; + c.Y = si.dwYCountChars; + SetConsoleScreenBufferSize(handle_out, c); + } + if (si.dwFlags & STARTF_USEFILLATTRIBUTE) + SetConsoleTextAttribute(handle_out, si.dwFillAttribute); + if (si.lpTitle) + SetConsoleTitleW(si.lpTitle); + + SetLastError(ERROR_SUCCESS); + + return TRUE; + + the_end: + ERR("Can't allocate console\n"); + if (handle_in != INVALID_HANDLE_VALUE) CloseHandle(handle_in); + if (handle_out != INVALID_HANDLE_VALUE) CloseHandle(handle_out); + if (handle_err != INVALID_HANDLE_VALUE) CloseHandle(handle_err); + FreeConsole(); + return FALSE; +} + + +/****************************************************************************** + * read_console_input + * + * Helper function for ReadConsole, ReadConsoleInput and PeekConsoleInput + */ +static BOOL read_console_input(HANDLE handle, LPINPUT_RECORD buffer, DWORD count, + LPDWORD pRead, BOOL flush) +{ + BOOL ret; + unsigned read = 0; + + SERVER_START_REQ( read_console_input ) + { + req->handle = handle; + req->flush = flush; + wine_server_set_reply( req, buffer, count * sizeof(INPUT_RECORD) ); + if ((ret = !wine_server_call_err( req ))) read = reply->read; + } + SERVER_END_REQ; + if (pRead) *pRead = read; + return ret; +} + + +/*********************************************************************** + * ReadConsoleA (KERNEL32.@) + */ +BOOL WINAPI ReadConsoleA(HANDLE hConsoleInput, LPVOID lpBuffer, DWORD nNumberOfCharsToRead, + LPDWORD lpNumberOfCharsRead, LPVOID lpReserved) +{ + LPWSTR ptr = HeapAlloc(GetProcessHeap(), 0, nNumberOfCharsToRead * sizeof(WCHAR)); + DWORD ncr = 0; + BOOL ret; + + if ((ret = ReadConsoleW(hConsoleInput, ptr, nNumberOfCharsToRead, &ncr, NULL))) + ncr = WideCharToMultiByte(CP_ACP, 0, ptr, ncr, lpBuffer, nNumberOfCharsToRead, NULL, NULL); + + if (lpNumberOfCharsRead) *lpNumberOfCharsRead = ncr; + HeapFree(GetProcessHeap(), 0, ptr); + + return ret; +} + +/*********************************************************************** + * ReadConsoleW (KERNEL32.@) + */ +BOOL WINAPI ReadConsoleW(HANDLE hConsoleInput, LPVOID lpBuffer, + DWORD nNumberOfCharsToRead, LPDWORD lpNumberOfCharsRead, LPVOID lpReserved) +{ + DWORD charsread; + LPWSTR xbuf = (LPWSTR)lpBuffer; + DWORD mode; + + TRACE("(%d,%p,%ld,%p,%p)\n", + hConsoleInput, lpBuffer, nNumberOfCharsToRead, lpNumberOfCharsRead, lpReserved); + + if (!GetConsoleMode(hConsoleInput, &mode)) + return FALSE; + + if (mode & ENABLE_LINE_INPUT) + { + if (!S_EditString || S_EditString[S_EditStrPos] == 0) + { + if (S_EditString) HeapFree(GetProcessHeap(), 0, S_EditString); + if (!(S_EditString = CONSOLE_Readline(hConsoleInput, mode & WINE_ENABLE_LINE_INPUT_EMACS))) + return FALSE; + S_EditStrPos = 0; + } + charsread = lstrlenW(&S_EditString[S_EditStrPos]); + if (charsread > nNumberOfCharsToRead) charsread = nNumberOfCharsToRead; + memcpy(xbuf, &S_EditString[S_EditStrPos], charsread * sizeof(WCHAR)); + S_EditStrPos += charsread; + } + else + { + INPUT_RECORD ir; + DWORD count; + + /* FIXME: should we read at least 1 char? The SDK does not say */ + /* wait for at least one available input record (it doesn't mean we'll have + * chars stored in xbuf... + */ + WaitForSingleObject(hConsoleInput, INFINITE); + for (charsread = 0; charsread < nNumberOfCharsToRead;) + { + if (!read_console_input(hConsoleInput, &ir, 1, &count, TRUE)) return FALSE; + if (count && ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown && + ir.Event.KeyEvent.uChar.UnicodeChar && + !(ir.Event.KeyEvent.dwControlKeyState & ENHANCED_KEY)) + { + xbuf[charsread++] = ir.Event.KeyEvent.uChar.UnicodeChar; + } + } + } + + if (lpNumberOfCharsRead) *lpNumberOfCharsRead = charsread; + + return TRUE; +} + + +/*********************************************************************** + * ReadConsoleInputW (KERNEL32.@) + */ +BOOL WINAPI ReadConsoleInputW(HANDLE hConsoleInput, LPINPUT_RECORD lpBuffer, + DWORD nLength, LPDWORD lpNumberOfEventsRead) +{ + DWORD count; + + if (!nLength) + { + if (lpNumberOfEventsRead) *lpNumberOfEventsRead = 0; + return TRUE; + } + + /* loop until we get at least one event */ + for (;;) + { + WaitForSingleObject(hConsoleInput, INFINITE); + if (!read_console_input(hConsoleInput, lpBuffer, nLength, &count, TRUE)) + return FALSE; + if (count) + { + if (lpNumberOfEventsRead) *lpNumberOfEventsRead = count; + return TRUE; + } + } +} + + +/****************************************************************************** + * WriteConsoleOutputCharacterW [KERNEL32.@] Copies character to consecutive + * cells in the console screen buffer + * + * PARAMS + * hConsoleOutput [I] Handle to screen buffer + * str [I] Pointer to buffer with chars to write + * length [I] Number of cells to write to + * coord [I] Coords of first cell + * lpNumCharsWritten [O] Pointer to number of cells written + * + * RETURNS + * Success: TRUE + * Failure: FALSE + * + */ +BOOL WINAPI WriteConsoleOutputCharacterW( HANDLE hConsoleOutput, LPCWSTR str, DWORD length, + COORD coord, LPDWORD lpNumCharsWritten ) +{ + BOOL ret; + + TRACE("(%d,%s,%ld,%dx%d,%p)\n", hConsoleOutput, + debugstr_wn(str, length), length, coord.X, coord.Y, lpNumCharsWritten); + + SERVER_START_REQ( write_console_output ) + { + req->handle = hConsoleOutput; + req->x = coord.X; + req->y = coord.Y; + req->mode = CHAR_INFO_MODE_TEXT; + req->wrap = TRUE; + wine_server_add_data( req, str, length * sizeof(WCHAR) ); + if ((ret = !wine_server_call_err( req ))) + { + if (lpNumCharsWritten) *lpNumCharsWritten = reply->written; + } + } + SERVER_END_REQ; + return ret; +} + + +/****************************************************************************** + * SetConsoleTitleW [KERNEL32.@] Sets title bar string for console + * + * PARAMS + * title [I] Address of new title + * + * RETURNS + * Success: TRUE + * Failure: FALSE + */ +BOOL WINAPI SetConsoleTitleW(LPCWSTR title) +{ + BOOL ret; + + SERVER_START_REQ( set_console_input_info ) + { + req->handle = 0; + req->mask = SET_CONSOLE_INPUT_INFO_TITLE; + wine_server_add_data( req, title, strlenW(title) * sizeof(WCHAR) ); + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + return ret; +} + + +/*********************************************************************** + * GetNumberOfConsoleMouseButtons (KERNEL32.@) + */ +BOOL WINAPI GetNumberOfConsoleMouseButtons(LPDWORD nrofbuttons) +{ + FIXME("(%p): stub\n", nrofbuttons); + *nrofbuttons = 2; + return TRUE; +} + +/****************************************************************************** + * SetConsoleInputExeNameW [KERNEL32.@] + * + * BUGS + * Unimplemented + */ +BOOL WINAPI SetConsoleInputExeNameW(LPCWSTR name) +{ + FIXME("(%s): stub!\n", debugstr_w(name)); + + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return TRUE; +} + +/****************************************************************************** + * SetConsoleInputExeNameA [KERNEL32.@] + * + * BUGS + * Unimplemented + */ +BOOL WINAPI SetConsoleInputExeNameA(LPCSTR name) +{ + int len = MultiByteToWideChar(CP_ACP, 0, name, -1, NULL, 0); + LPWSTR xptr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); + BOOL ret; + + if (!xptr) return FALSE; + + MultiByteToWideChar(CP_ACP, 0, name, -1, xptr, len); + ret = SetConsoleInputExeNameW(xptr); + HeapFree(GetProcessHeap(), 0, xptr); + + return ret; +} + +/****************************************************************** + * CONSOLE_DefaultHandler + * + * Final control event handler + */ +static BOOL WINAPI CONSOLE_DefaultHandler(DWORD dwCtrlType) +{ + FIXME("Terminating process %lx on event %lx\n", GetCurrentProcessId(), dwCtrlType); + ExitProcess(0); + /* should never go here */ + return TRUE; +} + +/****************************************************************************** + * SetConsoleCtrlHandler [KERNEL32.@] Adds function to calling process list + * + * PARAMS + * func [I] Address of handler function + * add [I] Handler to add or remove + * + * RETURNS + * Success: TRUE + * Failure: FALSE + * + * CHANGED + * James Sutherland (JamesSutherland@gmx.de) + * Added global variables console_ignore_ctrl_c and handlers[] + * Does not yet do any error checking, or set LastError if failed. + * This doesn't yet matter, since these handlers are not yet called...! + */ + +struct ConsoleHandler { + PHANDLER_ROUTINE handler; + struct ConsoleHandler* next; +}; + +static unsigned int CONSOLE_IgnoreCtrlC = 0; /* FIXME: this should be inherited somehow */ +static struct ConsoleHandler CONSOLE_DefaultConsoleHandler = {CONSOLE_DefaultHandler, NULL}; +static struct ConsoleHandler* CONSOLE_Handlers = &CONSOLE_DefaultConsoleHandler; +static CRITICAL_SECTION CONSOLE_CritSect = CRITICAL_SECTION_INIT("console_ctrl_section"); + +/*****************************************************************************/ + +BOOL WINAPI SetConsoleCtrlHandler(PHANDLER_ROUTINE func, BOOL add) +{ + BOOL ret = TRUE; + + FIXME("(%p,%i) - no error checking or testing yet\n", func, add); + + if (!func) + { + CONSOLE_IgnoreCtrlC = add; + } + else if (add) + { + struct ConsoleHandler* ch = HeapAlloc(GetProcessHeap(), 0, sizeof(struct ConsoleHandler)); + + if (!ch) return FALSE; + ch->handler = func; + EnterCriticalSection(&CONSOLE_CritSect); + ch->next = CONSOLE_Handlers; + CONSOLE_Handlers = ch; + LeaveCriticalSection(&CONSOLE_CritSect); + } + else + { + struct ConsoleHandler** ch; + EnterCriticalSection(&CONSOLE_CritSect); + for (ch = &CONSOLE_Handlers; *ch; *ch = (*ch)->next) + { + if ((*ch)->handler == func) break; + } + if (*ch) + { + struct ConsoleHandler* rch = *ch; + + /* sanity check */ + if (rch == &CONSOLE_DefaultConsoleHandler) + { + ERR("Who's trying to remove default handler???\n"); + ret = FALSE; + } + else + { + rch = *ch; + *ch = (*ch)->next; + HeapFree(GetProcessHeap(), 0, rch); + } + } + else + { + WARN("Attempt to remove non-installed CtrlHandler %p\n", func); + ret = FALSE; + } + LeaveCriticalSection(&CONSOLE_CritSect); + } + return ret; +} + +static WINE_EXCEPTION_FILTER(CONSOLE_CtrlEventHandler) +{ + TRACE("(%lx)\n", GetExceptionCode()); + return EXCEPTION_EXECUTE_HANDLER; +} + +static DWORD WINAPI CONSOLE_HandleCtrlCEntry(void* pmt) +{ + struct ConsoleHandler* ch; + + EnterCriticalSection(&CONSOLE_CritSect); + /* the debugger didn't continue... so, pass to ctrl handlers */ + for (ch = CONSOLE_Handlers; ch; ch = ch->next) + { + if (ch->handler((DWORD)pmt)) break; + } + LeaveCriticalSection(&CONSOLE_CritSect); + return 0; +} + +/****************************************************************** + * CONSOLE_HandleCtrlC + * + * Check whether the shall manipulate CtrlC events + */ +int CONSOLE_HandleCtrlC(unsigned sig) +{ + /* FIXME: better test whether a console is attached to this process ??? */ + extern unsigned CONSOLE_GetNumHistoryEntries(void); + if (CONSOLE_GetNumHistoryEntries() == (unsigned)-1) return 0; + if (CONSOLE_IgnoreCtrlC) return 1; + + /* try to pass the exception to the debugger + * if it continues, there's nothing more to do + * otherwise, we need to send the ctrl-event to the handlers + */ + __TRY + { + RaiseException( DBG_CONTROL_C, 0, 0, NULL ); + } + __EXCEPT(CONSOLE_CtrlEventHandler) + { + /* Create a separate thread to signal all the events. This would allow to + * synchronize between setting the handlers and actually calling them + */ + CreateThread(NULL, 0, CONSOLE_HandleCtrlCEntry, (void*)CTRL_C_EVENT, 0, NULL); + } + __ENDTRY; + return 1; +} + +/****************************************************************************** + * GenerateConsoleCtrlEvent [KERNEL32.@] Simulate a CTRL-C or CTRL-BREAK + * + * PARAMS + * dwCtrlEvent [I] Type of event + * dwProcessGroupID [I] Process group ID to send event to + * + * RETURNS + * Success: True + * Failure: False (and *should* [but doesn't] set LastError) + */ +BOOL WINAPI GenerateConsoleCtrlEvent(DWORD dwCtrlEvent, + DWORD dwProcessGroupID) +{ + BOOL ret; + + TRACE("(%ld, %ld)\n", dwCtrlEvent, dwProcessGroupID); + + if (dwCtrlEvent != CTRL_C_EVENT && dwCtrlEvent != CTRL_BREAK_EVENT) + { + ERR("Invalid event %ld for PGID %ld\n", dwCtrlEvent, dwProcessGroupID); + return FALSE; + } + + SERVER_START_REQ( send_console_signal ) + { + req->signal = dwCtrlEvent; + req->group_id = (void*)dwProcessGroupID; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + + return ret; +} + + +/****************************************************************************** + * CreateConsoleScreenBuffer [KERNEL32.@] Creates a console screen buffer + * + * PARAMS + * dwDesiredAccess [I] Access flag + * dwShareMode [I] Buffer share mode + * sa [I] Security attributes + * dwFlags [I] Type of buffer to create + * lpScreenBufferData [I] Reserved + * + * NOTES + * Should call SetLastError + * + * RETURNS + * Success: Handle to new console screen buffer + * Failure: INVALID_HANDLE_VALUE + */ +HANDLE WINAPI CreateConsoleScreenBuffer(DWORD dwDesiredAccess, DWORD dwShareMode, + LPSECURITY_ATTRIBUTES sa, DWORD dwFlags, + LPVOID lpScreenBufferData) +{ + HANDLE ret = INVALID_HANDLE_VALUE; + + TRACE("(%ld,%ld,%p,%ld,%p)\n", + dwDesiredAccess, dwShareMode, sa, dwFlags, lpScreenBufferData); + + if (dwFlags != CONSOLE_TEXTMODE_BUFFER || lpScreenBufferData != NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return INVALID_HANDLE_VALUE; + } + + SERVER_START_REQ(create_console_output) + { + req->handle_in = 0; + req->access = dwDesiredAccess; + req->share = dwShareMode; + req->inherit = (sa && sa->bInheritHandle); + if (!wine_server_call_err( req )) ret = reply->handle_out; + } + SERVER_END_REQ; + + return ret; +} + + +/*********************************************************************** + * GetConsoleScreenBufferInfo (KERNEL32.@) + */ +BOOL WINAPI GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, LPCONSOLE_SCREEN_BUFFER_INFO csbi) +{ + BOOL ret; + + SERVER_START_REQ(get_console_output_info) + { + req->handle = hConsoleOutput; + if ((ret = !wine_server_call_err( req ))) + { + csbi->dwSize.X = reply->width; + csbi->dwSize.Y = reply->height; + csbi->dwCursorPosition.X = reply->cursor_x; + csbi->dwCursorPosition.Y = reply->cursor_y; + csbi->wAttributes = reply->attr; + csbi->srWindow.Left = reply->win_left; + csbi->srWindow.Right = reply->win_right; + csbi->srWindow.Top = reply->win_top; + csbi->srWindow.Bottom = reply->win_bottom; + csbi->dwMaximumWindowSize.X = reply->max_width; + csbi->dwMaximumWindowSize.Y = reply->max_height; + } + } + SERVER_END_REQ; + + return ret; +} + + +/****************************************************************************** + * SetConsoleActiveScreenBuffer [KERNEL32.@] Sets buffer to current console + * + * RETURNS + * Success: TRUE + * Failure: FALSE + */ +BOOL WINAPI SetConsoleActiveScreenBuffer(HANDLE hConsoleOutput) +{ + BOOL ret; + + TRACE("(%x)\n", hConsoleOutput); + + SERVER_START_REQ( set_console_input_info ) + { + req->handle = 0; + req->mask = SET_CONSOLE_INPUT_INFO_ACTIVE_SB; + req->active_sb = hConsoleOutput; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + return ret; +} + + +/*********************************************************************** + * GetConsoleMode (KERNEL32.@) + */ +BOOL WINAPI GetConsoleMode(HANDLE hcon, LPDWORD mode) +{ + BOOL ret; + + SERVER_START_REQ(get_console_mode) + { + req->handle = hcon; + ret = !wine_server_call_err( req ); + if (ret && mode) *mode = reply->mode; + } + SERVER_END_REQ; + return ret; +} + + +/****************************************************************************** + * SetConsoleMode [KERNEL32.@] Sets input mode of console's input buffer + * + * PARAMS + * hcon [I] Handle to console input or screen buffer + * mode [I] Input or output mode to set + * + * RETURNS + * Success: TRUE + * Failure: FALSE + * + * mode: + * ENABLE_PROCESSED_INPUT 0x01 + * ENABLE_LINE_INPUT 0x02 + * ENABLE_ECHO_INPUT 0x04 + * ENABLE_WINDOW_INPUT 0x08 + * ENABLE_MOUSE_INPUT 0x10 + */ +BOOL WINAPI SetConsoleMode(HANDLE hcon, DWORD mode) +{ + BOOL ret; + + SERVER_START_REQ(set_console_mode) + { + req->handle = hcon; + req->mode = mode; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + /* FIXME: when resetting a console input to editline mode, I think we should + * empty the S_EditString buffer + */ + + TRACE("(%x,%lx) retval == %d\n", hcon, mode, ret); + + return ret; +} + + +/****************************************************************** + * write_char + * + * WriteConsoleOutput helper: hides server call semantics + */ +static int write_char(HANDLE hCon, LPCWSTR lpBuffer, int nc, COORD* pos) +{ + int written = -1; + + if (!nc) return 0; + + SERVER_START_REQ( write_console_output ) + { + req->handle = hCon; + req->x = pos->X; + req->y = pos->Y; + req->mode = CHAR_INFO_MODE_TEXTSTDATTR; + req->wrap = FALSE; + wine_server_add_data( req, lpBuffer, nc * sizeof(WCHAR) ); + if (!wine_server_call_err( req )) written = reply->written; + } + SERVER_END_REQ; + + if (written > 0) pos->X += written; + return written; +} + +/****************************************************************** + * next_line + * + * WriteConsoleOutput helper: handles passing to next line (+scrolling if necessary) + * + */ +static int next_line(HANDLE hCon, CONSOLE_SCREEN_BUFFER_INFO* csbi) +{ + SMALL_RECT src; + CHAR_INFO ci; + COORD dst; + + csbi->dwCursorPosition.X = 0; + csbi->dwCursorPosition.Y++; + + if (csbi->dwCursorPosition.Y < csbi->dwSize.Y) return 1; + + src.Top = 1; + src.Bottom = csbi->dwSize.Y - 1; + src.Left = 0; + src.Right = csbi->dwSize.X - 1; + + dst.X = 0; + dst.Y = 0; + + ci.Attributes = csbi->wAttributes; + ci.Char.UnicodeChar = ' '; + + csbi->dwCursorPosition.Y--; + if (!ScrollConsoleScreenBufferW(hCon, &src, NULL, dst, &ci)) + return 0; + return 1; +} + +/****************************************************************** + * write_block + * + * WriteConsoleOutput helper: writes a block of non special characters + * Block can spread on several lines, and wrapping, if needed, is + * handled + * + */ +static int write_block(HANDLE hCon, CONSOLE_SCREEN_BUFFER_INFO* csbi, + DWORD mode, LPWSTR ptr, int len) +{ + int blk; /* number of chars to write on current line */ + + if (len <= 0) return 1; + + if (mode & ENABLE_WRAP_AT_EOL_OUTPUT) /* writes remaining on next line */ + { + int done; + + for (done = 0; done < len; done += blk) + { + blk = min(len - done, csbi->dwSize.X - csbi->dwCursorPosition.X); + + if (write_char(hCon, ptr + done, blk, &csbi->dwCursorPosition) != blk) + return 0; + if (csbi->dwCursorPosition.X == csbi->dwSize.X && !next_line(hCon, csbi)) + return 0; + } + } + else + { + blk = min(len, csbi->dwSize.X - csbi->dwCursorPosition.X); + + if (write_char(hCon, ptr, blk, &csbi->dwCursorPosition) != blk) + return 0; + if (blk < len) + { + csbi->dwCursorPosition.X = csbi->dwSize.X - 1; + /* all remaining chars should be written on last column, + * so only overwrite the last column with last char in block + */ + if (write_char(hCon, ptr + len - 1, 1, &csbi->dwCursorPosition) != 1) + return 0; + csbi->dwCursorPosition.X = csbi->dwSize.X - 1; + } + } + + return 1; +} + +/*********************************************************************** + * WriteConsoleW (KERNEL32.@) + */ +BOOL WINAPI WriteConsoleW(HANDLE hConsoleOutput, LPCVOID lpBuffer, DWORD nNumberOfCharsToWrite, + LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved) +{ + DWORD mode; + DWORD nw = 0; + WCHAR* psz = (WCHAR*)lpBuffer; + CONSOLE_SCREEN_BUFFER_INFO csbi; + int k, first = 0; + + TRACE("%d %s %ld %p %p\n", + hConsoleOutput, debugstr_wn(lpBuffer, nNumberOfCharsToWrite), + nNumberOfCharsToWrite, lpNumberOfCharsWritten, lpReserved); + + if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = 0; + + if (!GetConsoleMode(hConsoleOutput, &mode) || + !GetConsoleScreenBufferInfo(hConsoleOutput, &csbi)) + return FALSE; + + if (mode & ENABLE_PROCESSED_OUTPUT) + { + int i; + + for (i = 0; i < nNumberOfCharsToWrite; i++) + { + switch (psz[i]) + { + case '\b': case '\t': case '\n': case '\a': case '\r': + /* don't handle here the i-th char... done below */ + if ((k = i - first) > 0) + { + if (!write_block(hConsoleOutput, &csbi, mode, &psz[first], k)) + goto the_end; + nw += k; + } + first = i + 1; + nw++; + } + switch (psz[i]) + { + case '\b': + if (csbi.dwCursorPosition.X > 0) csbi.dwCursorPosition.X--; + break; + case '\t': + { + WCHAR tmp[8] = {' ',' ',' ',' ',' ',' ',' ',' '}; + + if (!write_block(hConsoleOutput, &csbi, mode, tmp, + ((csbi.dwCursorPosition.X + 8) & ~7) - csbi.dwCursorPosition.X)) + goto the_end; + } + break; + case '\n': + next_line(hConsoleOutput, &csbi); + break; + case '\a': + Beep(400, 300); + break; + case '\r': + csbi.dwCursorPosition.X = 0; + break; + default: + break; + } + } + } + + /* write the remaining block (if any) if processed output is enabled, or the + * entire buffer otherwise + */ + if ((k = nNumberOfCharsToWrite - first) > 0) + { + if (!write_block(hConsoleOutput, &csbi, mode, &psz[first], k)) + goto the_end; + nw += k; + } + + the_end: + SetConsoleCursorPosition(hConsoleOutput, csbi.dwCursorPosition); + if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = nw; + return nw != 0; +} + + +/*********************************************************************** + * WriteConsoleA (KERNEL32.@) + */ +BOOL WINAPI WriteConsoleA(HANDLE hConsoleOutput, LPCVOID lpBuffer, DWORD nNumberOfCharsToWrite, + LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved) +{ + BOOL ret; + LPWSTR xstring; + DWORD n; + + n = MultiByteToWideChar(CP_ACP, 0, lpBuffer, nNumberOfCharsToWrite, NULL, 0); + + if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = 0; + xstring = HeapAlloc(GetProcessHeap(), 0, n * sizeof(WCHAR)); + if (!xstring) return 0; + + MultiByteToWideChar(CP_ACP, 0, lpBuffer, nNumberOfCharsToWrite, xstring, n); + + ret = WriteConsoleW(hConsoleOutput, xstring, n, lpNumberOfCharsWritten, 0); + + HeapFree(GetProcessHeap(), 0, xstring); + + return ret; +} + +/****************************************************************************** + * SetConsoleCursorPosition [KERNEL32.@] + * Sets the cursor position in console + * + * PARAMS + * hConsoleOutput [I] Handle of console screen buffer + * dwCursorPosition [I] New cursor position coordinates + * + * RETURNS STD + */ +BOOL WINAPI SetConsoleCursorPosition(HANDLE hcon, COORD pos) +{ + BOOL ret; + CONSOLE_SCREEN_BUFFER_INFO csbi; + int do_move = 0; + int w, h; + + TRACE("%x %d %d\n", hcon, pos.X, pos.Y); + + SERVER_START_REQ(set_console_output_info) + { + req->handle = hcon; + req->cursor_x = pos.X; + req->cursor_y = pos.Y; + req->mask = SET_CONSOLE_OUTPUT_INFO_CURSOR_POS; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + + if (!ret || !GetConsoleScreenBufferInfo(hcon, &csbi)) + return FALSE; + + /* if cursor is no longer visible, scroll the visible window... */ + w = csbi.srWindow.Right - csbi.srWindow.Left + 1; + h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + if (pos.X < csbi.srWindow.Left) + { + csbi.srWindow.Left = min(pos.X, csbi.dwSize.X - w); + do_move++; + } + else if (pos.X > csbi.srWindow.Right) + { + csbi.srWindow.Left = max(pos.X, w) - w + 1; + do_move++; + } + csbi.srWindow.Right = csbi.srWindow.Left + w - 1; + + if (pos.Y < csbi.srWindow.Top) + { + csbi.srWindow.Top = min(pos.Y, csbi.dwSize.Y - h); + do_move++; + } + else if (pos.Y > csbi.srWindow.Bottom) + { + csbi.srWindow.Top = max(pos.Y, h) - h + 1; + do_move++; + } + csbi.srWindow.Bottom = csbi.srWindow.Top + h - 1; + + ret = (do_move) ? SetConsoleWindowInfo(hcon, TRUE, &csbi.srWindow) : TRUE; + + return ret; +} + +/****************************************************************************** + * GetConsoleCursorInfo [KERNEL32.@] Gets size and visibility of console + * + * PARAMS + * hcon [I] Handle to console screen buffer + * cinfo [O] Address of cursor information + * + * RETURNS + * Success: TRUE + * Failure: FALSE + */ +BOOL WINAPI GetConsoleCursorInfo(HANDLE hcon, LPCONSOLE_CURSOR_INFO cinfo) +{ + BOOL ret; + + SERVER_START_REQ(get_console_output_info) + { + req->handle = hcon; + ret = !wine_server_call_err( req ); + if (ret && cinfo) + { + cinfo->dwSize = reply->cursor_size; + cinfo->bVisible = reply->cursor_visible; + } + } + SERVER_END_REQ; + return ret; +} + + +/****************************************************************************** + * SetConsoleCursorInfo [KERNEL32.@] Sets size and visibility of cursor + * + * PARAMS + * hcon [I] Handle to console screen buffer + * cinfo [I] Address of cursor information + * RETURNS + * Success: TRUE + * Failure: FALSE + */ +BOOL WINAPI SetConsoleCursorInfo(HANDLE hCon, LPCONSOLE_CURSOR_INFO cinfo) +{ + BOOL ret; + + SERVER_START_REQ(set_console_output_info) + { + req->handle = hCon; + req->cursor_size = cinfo->dwSize; + req->cursor_visible = cinfo->bVisible; + req->mask = SET_CONSOLE_OUTPUT_INFO_CURSOR_GEOM; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + return ret; +} + + +/****************************************************************************** + * SetConsoleWindowInfo [KERNEL32.@] Sets size and position of console + * + * PARAMS + * hcon [I] Handle to console screen buffer + * bAbsolute [I] Coordinate type flag + * window [I] Address of new window rectangle + * RETURNS + * Success: TRUE + * Failure: FALSE + */ +BOOL WINAPI SetConsoleWindowInfo(HANDLE hCon, BOOL bAbsolute, LPSMALL_RECT window) +{ + SMALL_RECT p = *window; + BOOL ret; + + if (!bAbsolute) + { + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (!GetConsoleScreenBufferInfo(hCon, &csbi)) + return FALSE; + p.Left += csbi.srWindow.Left; + p.Top += csbi.srWindow.Top; + p.Right += csbi.srWindow.Left; + p.Bottom += csbi.srWindow.Top; + } + SERVER_START_REQ(set_console_output_info) + { + req->handle = hCon; + req->win_left = p.Left; + req->win_top = p.Top; + req->win_right = p.Right; + req->win_bottom = p.Bottom; + req->mask = SET_CONSOLE_OUTPUT_INFO_DISPLAY_WINDOW; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + + return ret; +} + + +/****************************************************************************** + * SetConsoleTextAttribute [KERNEL32.@] Sets colors for text + * + * Sets the foreground and background color attributes of characters + * written to the screen buffer. + * + * RETURNS + * Success: TRUE + * Failure: FALSE + */ +BOOL WINAPI SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttr) +{ + BOOL ret; + + SERVER_START_REQ(set_console_output_info) + { + req->handle = hConsoleOutput; + req->attr = wAttr; + req->mask = SET_CONSOLE_OUTPUT_INFO_ATTR; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + return ret; +} + + +/****************************************************************************** + * SetConsoleScreenBufferSize [KERNEL32.@] Changes size of console + * + * PARAMS + * hConsoleOutput [I] Handle to console screen buffer + * dwSize [I] New size in character rows and cols + * + * RETURNS + * Success: TRUE + * Failure: FALSE + */ +BOOL WINAPI SetConsoleScreenBufferSize(HANDLE hConsoleOutput, COORD dwSize) +{ + BOOL ret; + + SERVER_START_REQ(set_console_output_info) + { + req->handle = hConsoleOutput; + req->width = dwSize.X; + req->height = dwSize.Y; + req->mask = SET_CONSOLE_OUTPUT_INFO_SIZE; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + return ret; +} + + +/****************************************************************************** + * ScrollConsoleScreenBufferA [KERNEL32.@] + * + */ +BOOL WINAPI ScrollConsoleScreenBufferA(HANDLE hConsoleOutput, LPSMALL_RECT lpScrollRect, + LPSMALL_RECT lpClipRect, COORD dwDestOrigin, + LPCHAR_INFO lpFill) +{ + CHAR_INFO ciw; + + ciw.Attributes = lpFill->Attributes; + MultiByteToWideChar(CP_ACP, 0, &lpFill->Char.AsciiChar, 1, &ciw.Char.UnicodeChar, 1); + + return ScrollConsoleScreenBufferW(hConsoleOutput, lpScrollRect, lpClipRect, + dwDestOrigin, &ciw); +} + +/****************************************************************** + * CONSOLE_FillLineUniform + * + * Helper function for ScrollConsoleScreenBufferW + * Fills a part of a line with a constant character info + */ +void CONSOLE_FillLineUniform(HANDLE hConsoleOutput, int i, int j, int len, LPCHAR_INFO lpFill) +{ + SERVER_START_REQ( fill_console_output ) + { + req->handle = hConsoleOutput; + req->mode = CHAR_INFO_MODE_TEXTATTR; + req->x = i; + req->y = j; + req->count = len; + req->wrap = FALSE; + req->data.ch = lpFill->Char.UnicodeChar; + req->data.attr = lpFill->Attributes; + wine_server_call_err( req ); + } + SERVER_END_REQ; +} + +/****************************************************************************** + * ScrollConsoleScreenBufferW [KERNEL32.@] + * + */ + +BOOL WINAPI ScrollConsoleScreenBufferW(HANDLE hConsoleOutput, LPSMALL_RECT lpScrollRect, + LPSMALL_RECT lpClipRect, COORD dwDestOrigin, + LPCHAR_INFO lpFill) +{ + SMALL_RECT dst; + DWORD ret; + int i, j; + int start = -1; + SMALL_RECT clip; + CONSOLE_SCREEN_BUFFER_INFO csbi; + BOOL inside; + + if (lpClipRect) + TRACE("(%d,(%d,%d-%d,%d),(%d,%d-%d,%d),%d-%d,%p)\n", hConsoleOutput, + lpScrollRect->Left, lpScrollRect->Top, + lpScrollRect->Right, lpScrollRect->Bottom, + lpClipRect->Left, lpClipRect->Top, + lpClipRect->Right, lpClipRect->Bottom, + dwDestOrigin.X, dwDestOrigin.Y, lpFill); + else + TRACE("(%d,(%d,%d-%d,%d),(nil),%d-%d,%p)\n", hConsoleOutput, + lpScrollRect->Left, lpScrollRect->Top, + lpScrollRect->Right, lpScrollRect->Bottom, + dwDestOrigin.X, dwDestOrigin.Y, lpFill); + + if (!GetConsoleScreenBufferInfo(hConsoleOutput, &csbi)) + return FALSE; + + /* step 1: get dst rect */ + dst.Left = dwDestOrigin.X; + dst.Top = dwDestOrigin.Y; + dst.Right = dst.Left + (lpScrollRect->Right - lpScrollRect->Left); + dst.Bottom = dst.Top + (lpScrollRect->Bottom - lpScrollRect->Top); + + /* step 2a: compute the final clip rect (optional passed clip and screen buffer limits */ + if (lpClipRect) + { + clip.Left = max(0, lpClipRect->Left); + clip.Right = min(csbi.dwSize.X - 1, lpClipRect->Right); + clip.Top = max(0, lpClipRect->Top); + clip.Bottom = min(csbi.dwSize.Y - 1, lpClipRect->Bottom); + } + else + { + clip.Left = 0; + clip.Right = csbi.dwSize.X - 1; + clip.Top = 0; + clip.Bottom = csbi.dwSize.Y - 1; + } + if (clip.Left > clip.Right || clip.Top > clip.Bottom) return FALSE; + + /* step 2b: clip dst rect */ + if (dst.Left < clip.Left ) dst.Left = clip.Left; + if (dst.Top < clip.Top ) dst.Top = clip.Top; + if (dst.Right > clip.Right ) dst.Right = clip.Right; + if (dst.Bottom > clip.Bottom) dst.Bottom = clip.Bottom; + + /* step 3: transfer the bits */ + SERVER_START_REQ(move_console_output) + { + req->handle = hConsoleOutput; + req->x_src = lpScrollRect->Left; + req->y_src = lpScrollRect->Top; + req->x_dst = dst.Left; + req->y_dst = dst.Top; + req->w = dst.Right - dst.Left + 1; + req->h = dst.Bottom - dst.Top + 1; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + + if (!ret) return FALSE; + + /* step 4: clean out the exposed part */ + + /* have to write cell [i,j] if it is not in dst rect (because it has already + * been written to by the scroll) and is in clip (we shall not write + * outside of clip) + */ + for (j = max(lpScrollRect->Top, clip.Top); j <= min(lpScrollRect->Bottom, clip.Bottom); j++) + { + inside = dst.Top <= j && j <= dst.Bottom; + start = -1; + for (i = max(lpScrollRect->Left, clip.Left); i <= min(lpScrollRect->Right, clip.Right); i++) + { + if (inside && dst.Left <= i && i <= dst.Right) + { + if (start != -1) + { + CONSOLE_FillLineUniform(hConsoleOutput, start, j, i - start, lpFill); + start = -1; + } + } + else + { + if (start == -1) start = i; + } + } + if (start != -1) + CONSOLE_FillLineUniform(hConsoleOutput, start, j, i - start, lpFill); + } + + return TRUE; +} + + +/* ==================================================================== + * + * Console manipulation functions + * + * ====================================================================*/ + +/* some missing functions... + * FIXME: those are likely to be defined as undocumented function in kernel32 (or part of them) + * should get the right API and implement them + * GetConsoleCommandHistory[AW] (dword dword dword) + * GetConsoleCommandHistoryLength[AW] + * SetConsoleCommandHistoryMode + * SetConsoleNumberOfCommands[AW] + */ +int CONSOLE_GetHistory(int idx, WCHAR* buf, int buf_len) +{ + int len = 0; + + SERVER_START_REQ( get_console_input_history ) + { + req->handle = 0; + req->index = idx; + if (buf && buf_len > 1) + { + wine_server_set_reply( req, buf, (buf_len - 1) * sizeof(WCHAR) ); + } + if (!wine_server_call_err( req )) + { + if (buf) buf[wine_server_reply_size(reply) / sizeof(WCHAR)] = 0; + len = reply->total / sizeof(WCHAR) + 1; + } + } + SERVER_END_REQ; + return len; +} + +/****************************************************************** + * CONSOLE_AppendHistory + * + * + */ +BOOL CONSOLE_AppendHistory(const WCHAR* ptr) +{ + size_t len = strlenW(ptr); + BOOL ret; + + while (len && (ptr[len - 1] == '\n' || ptr[len - 1] == '\r')) len--; + + SERVER_START_REQ( append_console_input_history ) + { + req->handle = 0; + wine_server_add_data( req, ptr, len * sizeof(WCHAR) ); + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + return ret; +} + +/****************************************************************** + * CONSOLE_GetNumHistoryEntries + * + * + */ +unsigned CONSOLE_GetNumHistoryEntries(void) +{ + unsigned ret = -1; + SERVER_START_REQ(get_console_input_info) + { + req->handle = 0; + if (!wine_server_call_err( req )) ret = reply->history_index; + } + SERVER_END_REQ; + return ret; +} Index: dlls/ntdll/Makefile.in =================================================================== RCS file: /home/cvs/cvsroot/wine/wine/dlls/ntdll/Makefile.in,v retrieving revision 1.29 diff -u -u -r1.29 Makefile.in --- dlls/ntdll/Makefile.in 10 Jul 2002 23:22:29 -0000 1.29 +++ dlls/ntdll/Makefile.in 30 Jul 2002 12:17:13 -0000 @@ -85,9 +85,7 @@ $(TOPOBJDIR)/scheduler/syslevel.c \ $(TOPOBJDIR)/scheduler/thread.c \ $(TOPOBJDIR)/scheduler/timer.c \ - $(TOPOBJDIR)/win32/console.c \ $(TOPOBJDIR)/win32/device.c \ - $(TOPOBJDIR)/win32/editline.c \ $(TOPOBJDIR)/win32/except.c \ $(TOPOBJDIR)/win32/init.c \ $(TOPOBJDIR)/win32/kernel32.c \ --- /dev/null Thu Jan 1 01:00:00 1970 +++ dlls/kernel/editline.c Tue Jul 30 14:30:27 2002 @@ -0,0 +1,879 @@ +/* + * line edition function for Win32 console + * + * Copyright 2001 Eric Pouech + * + * 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 + */ + +#include "config.h" +#include "wine/port.h" + +#include <string.h> + +#include "windef.h" +#include "winbase.h" +#include "wincon.h" +#include "wine/unicode.h" +#include "winnls.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(console); + +/* console.c */ +extern int CONSOLE_GetHistory(int idx, WCHAR* buf, int buf_len); +extern BOOL CONSOLE_AppendHistory(const WCHAR *p); +extern unsigned int CONSOLE_GetNumHistoryEntries(void); +extern void CONSOLE_FillLineUniform(HANDLE hConsoleOutput, int i, int j, int len, LPCHAR_INFO lpFill); + +struct WCEL_Context; + +typedef struct +{ + WCHAR val; /* vk or unicode char */ + void (*func)(struct WCEL_Context* ctx); +} KeyEntry; + +typedef struct +{ + DWORD keyState; /* keyState (from INPUT_RECORD) to match */ + BOOL chkChar; /* check vk or char */ + KeyEntry* entries; /* array of entries */ +} KeyMap; + +typedef struct WCEL_Context { + WCHAR* line; /* the line being edited */ + size_t alloc; /* number of WCHAR in line */ + unsigned len; /* number of chars in line */ + unsigned ofs; /* offset for cursor in current line */ + WCHAR* yanked; /* yanked line */ + unsigned mark; /* marked point (emacs mode only) */ + CONSOLE_SCREEN_BUFFER_INFO csbi; /* current state (initial cursor, window size, attribute) */ + HANDLE hConIn; + HANDLE hConOut; + unsigned done : 1, /* to 1 when we're done with editing */ + error : 1, /* to 1 when an error occurred in the editing */ + can_wrap : 1; /* to 1 when multi-line edition can take place */ + unsigned histSize; + unsigned histPos; + WCHAR* histCurr; +} WCEL_Context; + +#if 0 +static void WCEL_Dump(WCEL_Context* ctx, const char* pfx) +{ + MESSAGE("%s: [line=%s[alloc=%u] ofs=%u len=%u start=(%d,%d) mask=%c%c%c]\n" + "\t\thist=(size=%u pos=%u curr=%s)\n" + "\t\tyanked=%s\n", + pfx, debugstr_w(ctx->line), ctx->alloc, ctx->ofs, ctx->len, + ctx->csbi.dwCursorPosition.X, ctx->csbi.dwCursorPosition.Y, + ctx->done ? 'D' : 'd', ctx->error ? 'E' : 'e', ctx->can_wrap ? 'W' : 'w', + ctx->histSize, ctx->histPos, debugstr_w(ctx->histCurr), + debugstr_w(ctx->yanked)); +} +#endif + +/* ==================================================================== + * + * Console helper functions + * + * ====================================================================*/ + +static BOOL WCEL_Get(WCEL_Context* ctx, INPUT_RECORD* ir) +{ + DWORD retv; + + for (;;) + { + /* data available ? */ + if (ReadConsoleInputW(ctx->hConIn, ir, 1, &retv) && retv == 1) + return TRUE; + /* then wait... */ + switch (WaitForSingleObject(ctx->hConIn, INFINITE)) + { + case WAIT_OBJECT_0: + break; + default: + /* we have checked that hConIn was a console handle (could be sb) */ + ERR("Shouldn't happen\n"); + /* fall thru */ + case WAIT_ABANDONED: + case WAIT_TIMEOUT: + ctx->error = 1; + ERR("hmm bad situation\n"); + return FALSE; + } + } +} + +static inline void WCEL_Beep(WCEL_Context* ctx) +{ + Beep(400, 300); +} + +static inline BOOL WCEL_IsSingleLine(WCEL_Context* ctx, size_t len) +{ + return ctx->csbi.dwCursorPosition.X + ctx->len + len <= ctx->csbi.dwSize.X; +} + +static inline COORD WCEL_GetCoord(WCEL_Context* ctx, int ofs) +{ + COORD c; + unsigned len = ctx->csbi.dwSize.X - ctx->csbi.dwCursorPosition.X; + + c.Y = ctx->csbi.dwCursorPosition.Y; + if (ofs >= len) + { + ofs -= len; + c.X = ofs % ctx->csbi.dwSize.X; + c.Y += 1 + ofs / ctx->csbi.dwSize.X; + } + else c.X = ctx->csbi.dwCursorPosition.X + ofs; + return c; +} + +static inline void WCEL_Update(WCEL_Context* ctx, int beg, int len) +{ + WriteConsoleOutputCharacterW(ctx->hConOut, &ctx->line[beg], len, + WCEL_GetCoord(ctx, beg), NULL); +} + +/* ==================================================================== + * + * context manipulation functions + * + * ====================================================================*/ + +static BOOL WCEL_Grow(WCEL_Context* ctx, size_t len) +{ + if (!WCEL_IsSingleLine(ctx, len) && !ctx->can_wrap) + { + FIXME("Mode doesn't allow to wrap. However, we should allow to overwrite current string\n"); + return FALSE; + } + + if (ctx->len + len >= ctx->alloc) + { + WCHAR* newline; + size_t newsize; + + /* round up size to 32 byte-WCHAR boundary */ + newsize = (ctx->len + len + 1 + 31) & ~31; + newline = HeapReAlloc(GetProcessHeap(), 0, ctx->line, sizeof(WCHAR) * newsize); + if (!newline) return FALSE; + ctx->line = newline; + ctx->alloc = newsize; + } + return TRUE; +} + +static void WCEL_DeleteString(WCEL_Context* ctx, int beg, int end) +{ + unsigned str_len = end - beg; + COORD cbeg = WCEL_GetCoord(ctx, ctx->len - str_len); + COORD cend = WCEL_GetCoord(ctx, ctx->len); + CHAR_INFO ci; + + if (end < ctx->len) + memmove(&ctx->line[beg], &ctx->line[end], (ctx->len - end) * sizeof(WCHAR)); + /* we need to clean from ctx->len - str_len to ctx->len */ + + ci.Char.UnicodeChar = ' '; + ci.Attributes = ctx->csbi.wAttributes; + + if (cbeg.Y == cend.Y) + { + /* partial erase of sole line */ + CONSOLE_FillLineUniform(ctx->hConOut, cbeg.X, cbeg.Y, + cend.X - cbeg.X, &ci); + } + else + { + int i; + /* erase til eol on first line */ + CONSOLE_FillLineUniform(ctx->hConOut, cbeg.X, cbeg.Y, + ctx->csbi.dwSize.X - cbeg.X, &ci); + /* completly erase all the others (full lines) */ + for (i = cbeg.Y + 1; i < cend.Y; i++) + CONSOLE_FillLineUniform(ctx->hConOut, 0, i, ctx->csbi.dwSize.X, &ci); + /* erase from beg of line until last pos on last line */ + CONSOLE_FillLineUniform(ctx->hConOut, 0, cend.Y, cend.X, &ci); + } + ctx->len -= str_len; + WCEL_Update(ctx, 0, ctx->len); + ctx->line[ctx->len] = 0; +} + +static void WCEL_InsertString(WCEL_Context* ctx, const WCHAR* str) +{ + size_t len = lstrlenW(str); + + if (!len || !WCEL_Grow(ctx, len)) return; + if (ctx->len > ctx->ofs) + memmove(&ctx->line[ctx->ofs + len], &ctx->line[ctx->ofs], (ctx->len - ctx->ofs) * sizeof(WCHAR)); + memcpy(&ctx->line[ctx->ofs], str, len * sizeof(WCHAR)); + ctx->len += len; + ctx->line[ctx->len] = 0; + WCEL_Update(ctx, ctx->ofs, ctx->len - ctx->ofs); + + ctx->ofs += len; +} + +static void WCEL_InsertChar(WCEL_Context* ctx, WCHAR c) +{ + WCHAR buffer[2]; + + /* do not insert 0..31 control characters */ + if (c < ' ' && c != '\t') return; + + buffer[0] = c; + buffer[1] = 0; + WCEL_InsertString(ctx, buffer); +} + +static void WCEL_FreeYank(WCEL_Context* ctx) +{ + if (ctx->yanked) + { + HeapFree(GetProcessHeap(), 0, ctx->yanked); + ctx->yanked = NULL; + } +} + +static void WCEL_SaveYank(WCEL_Context* ctx, int beg, int end) +{ + int len = end - beg; + if (len <= 0) return; + + WCEL_FreeYank(ctx); + ctx->yanked = HeapReAlloc(GetProcessHeap(), 0, ctx->yanked, (len + 1) * sizeof(WCHAR)); + if (!ctx->yanked) return; + memcpy(ctx->yanked, &ctx->line[beg], len * sizeof(WCHAR)); + ctx->yanked[len] = 0; +} + +/* FIXME NTDLL doesn't export iswalnum, and I don't want to link in msvcrt when most + * of the data lay in unicode lib + */ +static inline BOOL WCEL_iswalnum(WCHAR wc) +{ + return get_char_typeW(wc) & (C1_ALPHA|C1_DIGIT|C1_LOWER|C1_UPPER); +} + +static int WCEL_GetLeftWordTransition(WCEL_Context* ctx, int ofs) +{ + ofs--; + while (ofs >= 0 && !WCEL_iswalnum(ctx->line[ofs])) ofs--; + while (ofs >= 0 && WCEL_iswalnum(ctx->line[ofs])) ofs--; + if (ofs >= 0) ofs++; + return max(ofs, 0); +} + +static int WCEL_GetRightWordTransition(WCEL_Context* ctx, int ofs) +{ + ofs++; + while (ofs <= ctx->len && WCEL_iswalnum(ctx->line[ofs])) ofs++; + while (ofs <= ctx->len && !WCEL_iswalnum(ctx->line[ofs])) ofs++; + return min(ofs, ctx->len); +} + +static WCHAR* WCEL_GetHistory(WCEL_Context* ctx, int idx) +{ + WCHAR* ptr; + + if (idx == ctx->histSize - 1) + { + ptr = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(ctx->histCurr) + 1) * sizeof(WCHAR)); + lstrcpyW(ptr, ctx->histCurr); + } + else + { + int len = CONSOLE_GetHistory(idx, NULL, 0); + + if ((ptr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)))) + { + CONSOLE_GetHistory(idx, ptr, len); + } + } + return ptr; +} + +static void WCEL_HistoryInit(WCEL_Context* ctx) +{ + ctx->histPos = CONSOLE_GetNumHistoryEntries(); + ctx->histSize = ctx->histPos + 1; + ctx->histCurr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WCHAR)); +} + +static void WCEL_MoveToHist(WCEL_Context* ctx, int idx) +{ + WCHAR* data = WCEL_GetHistory(ctx, idx); + int len = lstrlenW(data) + 1; + + /* save current line edition for recall when needed (FIXME seems broken to me) */ + if (ctx->histPos == ctx->histSize - 1) + { + if (ctx->histCurr) HeapFree(GetProcessHeap(), 0, ctx->histCurr); + ctx->histCurr = HeapAlloc(GetProcessHeap(), 0, (ctx->len + 1) * sizeof(WCHAR)); + memcpy(ctx->histCurr, ctx->line, (ctx->len + 1) * sizeof(WCHAR)); + } + /* need to clean also the screen if new string is shorter than old one */ + WCEL_DeleteString(ctx, 0, ctx->len); + ctx->ofs = 0; + /* insert new string */ + if (WCEL_Grow(ctx, len)) + { + WCEL_InsertString(ctx, data); + HeapFree(GetProcessHeap(), 0, data); + ctx->histPos = idx; + } +} + +static void WCEL_FindPrevInHist(WCEL_Context* ctx) +{ + int startPos = ctx->histPos; + WCHAR* data; + int len, oldofs; + + if (ctx->histPos && ctx->histPos == ctx->histSize) { + startPos--; + ctx->histPos--; + } + + do { + data = WCEL_GetHistory(ctx, ctx->histPos); + + if (ctx->histPos) ctx->histPos--; + else ctx->histPos = (ctx->histSize-1); + + len = lstrlenW(data) + 1; + if ((len >= ctx->ofs) && + (memcmp(ctx->line, data, ctx->ofs * sizeof(WCHAR)) == 0)) { + + /* need to clean also the screen if new string is shorter than old one */ + WCEL_DeleteString(ctx, 0, ctx->len); + + if (WCEL_Grow(ctx, len)) + { + oldofs = ctx->ofs; + ctx->ofs = 0; + WCEL_InsertString(ctx, data); + ctx->ofs = oldofs; + SetConsoleCursorPosition(ctx->hConOut, WCEL_GetCoord(ctx, ctx->ofs)); + HeapFree(GetProcessHeap(), 0, data); + return; + } + } + } while (ctx->histPos != startPos); + + return; +} + +/* ==================================================================== + * + * basic edition functions + * + * ====================================================================*/ + +static void WCEL_Done(WCEL_Context* ctx) +{ + WCHAR nl = '\n'; + if (!WCEL_Grow(ctx, 1)) return; + ctx->line[ctx->len++] = '\n'; + ctx->line[ctx->len] = 0; + WriteConsoleW(ctx->hConOut, &nl, 1, NULL, NULL); + ctx->done = 1; +} + +static void WCEL_MoveLeft(WCEL_Context* ctx) +{ + if (ctx->ofs > 0) ctx->ofs--; +} + +static void WCEL_MoveRight(WCEL_Context* ctx) +{ + if (ctx->ofs < ctx->len) ctx->ofs++; +} + +static void WCEL_MoveToLeftWord(WCEL_Context* ctx) +{ + int new_ofs = WCEL_GetLeftWordTransition(ctx, ctx->ofs); + if (new_ofs != ctx->ofs) ctx->ofs = new_ofs; +} + +static void WCEL_MoveToRightWord(WCEL_Context* ctx) +{ + int new_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); + if (new_ofs != ctx->ofs) ctx->ofs = new_ofs; +} + +static void WCEL_MoveToBeg(WCEL_Context* ctx) +{ + ctx->ofs = 0; +} + +static void WCEL_MoveToEnd(WCEL_Context* ctx) +{ + ctx->ofs = ctx->len; +} + +static void WCEL_SetMark(WCEL_Context* ctx) +{ + ctx->mark = ctx->ofs; +} + +static void WCEL_ExchangeMark(WCEL_Context* ctx) +{ + unsigned tmp; + + if (ctx->mark > ctx->len) return; + tmp = ctx->ofs; + ctx->ofs = ctx->mark; + ctx->mark = tmp; +} + +static void WCEL_CopyMarkedZone(WCEL_Context* ctx) +{ + unsigned beg, end; + + if (ctx->mark > ctx->len || ctx->mark == ctx->ofs) return; + if (ctx->mark > ctx->ofs) + { + beg = ctx->ofs; end = ctx->mark; + } + else + { + beg = ctx->mark; end = ctx->ofs; + } + WCEL_SaveYank(ctx, beg, end); +} + +static void WCEL_TransposeChar(WCEL_Context* ctx) +{ + WCHAR c; + + if (!ctx->ofs || ctx->ofs == ctx->len) return; + + c = ctx->line[ctx->ofs]; + ctx->line[ctx->ofs] = ctx->line[ctx->ofs - 1]; + ctx->line[ctx->ofs - 1] = c; + + WCEL_Update(ctx, ctx->ofs - 1, 2); + ctx->ofs++; +} + +static void WCEL_TransposeWords(WCEL_Context* ctx) +{ + int left_ofs = WCEL_GetLeftWordTransition(ctx, ctx->ofs), + right_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); + if (left_ofs < ctx->ofs && right_ofs > ctx->ofs) + { + unsigned len_r = right_ofs - ctx->ofs; + unsigned len_l = ctx->ofs - left_ofs; + + char* tmp = HeapAlloc(GetProcessHeap(), 0, len_r * sizeof(WCHAR)); + if (!tmp) return; + + memcpy(tmp, &ctx->line[ctx->ofs], len_r * sizeof(WCHAR)); + memmove(&ctx->line[left_ofs + len_r], &ctx->line[left_ofs], len_l * sizeof(WCHAR)); + memcpy(&ctx->line[left_ofs], tmp, len_r * sizeof(WCHAR)); + + HeapFree(GetProcessHeap(), 0, tmp); + WCEL_Update(ctx, left_ofs, len_l + len_r); + ctx->ofs = right_ofs; + } +} + +static void WCEL_LowerCaseWord(WCEL_Context* ctx) +{ + int new_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); + if (new_ofs != ctx->ofs) + { + int i; + for (i = ctx->ofs; i <= new_ofs; i++) + ctx->line[i] = tolowerW(ctx->line[i]); + WCEL_Update(ctx, ctx->ofs, new_ofs - ctx->ofs + 1); + ctx->ofs = new_ofs; + } +} + +static void WCEL_UpperCaseWord(WCEL_Context* ctx) +{ + int new_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); + if (new_ofs != ctx->ofs) + { + int i; + for (i = ctx->ofs; i <= new_ofs; i++) + ctx->line[i] = toupperW(ctx->line[i]); + WCEL_Update(ctx, ctx->ofs, new_ofs - ctx->ofs + 1); + ctx->ofs = new_ofs; + } +} + +static void WCEL_CapitalizeWord(WCEL_Context* ctx) +{ + int new_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); + if (new_ofs != ctx->ofs) + { + int i; + + ctx->line[ctx->ofs] = toupperW(ctx->line[ctx->ofs]); + for (i = ctx->ofs + 1; i <= new_ofs; i++) + ctx->line[i] = tolowerW(ctx->line[i]); + WCEL_Update(ctx, ctx->ofs, new_ofs - ctx->ofs + 1); + ctx->ofs = new_ofs; + } +} + +static void WCEL_Yank(WCEL_Context* ctx) +{ + WCEL_InsertString(ctx, ctx->yanked); +} + +static void WCEL_KillToEndOfLine(WCEL_Context* ctx) +{ + WCEL_SaveYank(ctx, ctx->ofs, ctx->len); + WCEL_DeleteString(ctx, ctx->ofs, ctx->len); +} + +static void WCEL_KillMarkedZone(WCEL_Context* ctx) +{ + unsigned beg, end; + + if (ctx->mark > ctx->len || ctx->mark == ctx->ofs) return; + if (ctx->mark > ctx->ofs) + { + beg = ctx->ofs; end = ctx->mark; + } + else + { + beg = ctx->mark; end = ctx->ofs; + } + WCEL_SaveYank(ctx, beg, end); + WCEL_DeleteString(ctx, beg, end); + ctx->ofs = beg; +} + +static void WCEL_DeletePrevChar(WCEL_Context* ctx) +{ + if (ctx->ofs) + { + WCEL_DeleteString(ctx, ctx->ofs - 1, ctx->ofs); + ctx->ofs--; + } +} + +static void WCEL_DeleteCurrChar(WCEL_Context* ctx) +{ + if (ctx->ofs < ctx->len) + WCEL_DeleteString(ctx, ctx->ofs, ctx->ofs + 1); +} + +static void WCEL_DeleteLeftWord(WCEL_Context* ctx) +{ + int new_ofs = WCEL_GetLeftWordTransition(ctx, ctx->ofs); + if (new_ofs != ctx->ofs) + { + WCEL_DeleteString(ctx, new_ofs, ctx->ofs); + ctx->ofs = new_ofs; + } +} + +static void WCEL_DeleteRightWord(WCEL_Context* ctx) +{ + int new_ofs = WCEL_GetRightWordTransition(ctx, ctx->ofs); + if (new_ofs != ctx->ofs) + { + WCEL_DeleteString(ctx, ctx->ofs, new_ofs); + } +} + +static void WCEL_MoveToPrevHist(WCEL_Context* ctx) +{ + if (ctx->histPos) WCEL_MoveToHist(ctx, ctx->histPos - 1); +} + +static void WCEL_MoveToNextHist(WCEL_Context* ctx) +{ + if (ctx->histPos < ctx->histSize - 1) WCEL_MoveToHist(ctx, ctx->histPos + 1); +} + +static void WCEL_MoveToFirstHist(WCEL_Context* ctx) +{ + if (ctx->histPos != 0) WCEL_MoveToHist(ctx, 0); +} + +static void WCEL_MoveToLastHist(WCEL_Context* ctx) +{ + if (ctx->histPos != ctx->histSize - 1) WCEL_MoveToHist(ctx, ctx->histSize - 1); +} + +static void WCEL_Redraw(WCEL_Context* ctx) +{ + COORD c = WCEL_GetCoord(ctx, ctx->len); + CHAR_INFO ci; + + WCEL_Update(ctx, 0, ctx->len); + + ci.Char.UnicodeChar = ' '; + ci.Attributes = ctx->csbi.wAttributes; + + CONSOLE_FillLineUniform(ctx->hConOut, c.X, c.Y, ctx->csbi.dwSize.X - c.X, &ci); +} + +static void WCEL_RepeatCount(WCEL_Context* ctx) +{ +#if 0 +/* FIXME: wait untill all console code is in kernel32 */ + INPUT_RECORD ir; + unsigned repeat = 0; + + while (WCEL_Get(ctx, &ir, FALSE)) + { + if (ir.EventType != KEY_EVENT) break; + if (ir.Event.KeyEvent.bKeyDown) + { + if ((ir.Event.KeyEvent.dwControlKeyState & ~(NUMLOCK_ON|SCROLLLOCK_ON|CAPSLOCK_ON)) != 0) + break; + if (ir.Event.KeyEvent.uChar.UnicodeChar < '0' || + ir.Event.KeyEvent.uChar.UnicodeChar > '9') + break; + repeat = repeat * 10 + ir.Event.KeyEvent.uChar.UnicodeChar - '0'; + } + WCEL_Get(ctx, &ir, TRUE); + } + FIXME("=> %u\n", repeat); +#endif +} + +/* ==================================================================== + * + * Key Maps + * + * ====================================================================*/ + +#define CTRL(x) ((x) - '@') +static KeyEntry StdKeyMap[] = +{ + {/*BACK*/0x08, WCEL_DeletePrevChar }, + {/*RETURN*/0x0d, WCEL_Done }, + {/*DEL*/127, WCEL_DeleteCurrChar }, + { 0, NULL } +}; + +static KeyEntry Win32ExtraStdKeyMap[] = +{ + {/*VK_F8*/ 0x77, WCEL_FindPrevInHist }, + { 0, NULL } +}; + + +static KeyEntry EmacsKeyMapCtrl[] = +{ + { CTRL('@'), WCEL_SetMark }, + { CTRL('A'), WCEL_MoveToBeg }, + { CTRL('B'), WCEL_MoveLeft }, + /* C: done in server */ + { CTRL('D'), WCEL_DeleteCurrChar }, + { CTRL('E'), WCEL_MoveToEnd }, + { CTRL('F'), WCEL_MoveRight }, + { CTRL('G'), WCEL_Beep }, + { CTRL('H'), WCEL_DeletePrevChar }, + /* I: meaningless (or tab ???) */ + { CTRL('J'), WCEL_Done }, + { CTRL('K'), WCEL_KillToEndOfLine }, + { CTRL('L'), WCEL_Redraw }, + { CTRL('M'), WCEL_Done }, + { CTRL('N'), WCEL_MoveToNextHist }, + /* O; insert line... meaningless */ + { CTRL('P'), WCEL_MoveToPrevHist }, + /* Q: [NIY] quoting... */ + /* R: [NIY] search backwards... */ + /* S: [NIY] search forwards... */ + { CTRL('T'), WCEL_TransposeChar }, + { CTRL('U'), WCEL_RepeatCount }, + /* V: paragraph down... meaningless */ + { CTRL('W'), WCEL_KillMarkedZone }, + { CTRL('X'), WCEL_ExchangeMark }, + { CTRL('Y'), WCEL_Yank }, + /* Z: meaningless */ + { 0, NULL } +}; + +static KeyEntry EmacsKeyMapAlt[] = +{ + {/*DEL*/127, WCEL_DeleteLeftWord }, + { '<', WCEL_MoveToFirstHist }, + { '>', WCEL_MoveToLastHist }, + { '?', WCEL_Beep }, + { 'b', WCEL_MoveToLeftWord }, + { 'c', WCEL_CapitalizeWord }, + { 'd', WCEL_DeleteRightWord }, + { 'f', WCEL_MoveToRightWord }, + { 'l', WCEL_LowerCaseWord }, + { 't', WCEL_TransposeWords }, + { 'u', WCEL_UpperCaseWord }, + { 'w', WCEL_CopyMarkedZone }, + { 0, NULL } +}; + +static KeyEntry EmacsKeyMapExtended[] = +{ + {/*RETURN*/ 0x0d, WCEL_Done }, + {/*VK_PRIOR*/0x21, WCEL_MoveToPrevHist }, + {/*VK_NEXT*/ 0x22, WCEL_MoveToNextHist }, + {/*VK_END*/ 0x23, WCEL_MoveToEnd }, + {/*VK_HOME*/ 0x24, WCEL_MoveToBeg }, + {/*VK_RIGHT*/0x27, WCEL_MoveRight }, + {/*VK_LEFT*/ 0x25, WCEL_MoveLeft }, + {/*VK_DEL*/ 0x2e, WCEL_DeleteCurrChar }, + { 0, NULL } +}; + +static KeyMap EmacsKeyMap[] = +{ + {0x00000000, 1, StdKeyMap}, + {0x00000001, 1, EmacsKeyMapAlt}, /* left alt */ + {0x00000002, 1, EmacsKeyMapAlt}, /* right alt */ + {0x00000004, 1, EmacsKeyMapCtrl}, /* left ctrl */ + {0x00000008, 1, EmacsKeyMapCtrl}, /* right ctrl */ + {0x00000100, 0, EmacsKeyMapExtended}, + {0, 0, 0} +}; + +static KeyEntry Win32KeyMapExtended[] = +{ + {/*VK_LEFT*/ 0x25, WCEL_MoveLeft }, + {/*VK_RIGHT*/0x27, WCEL_MoveRight }, + {/*VK_HOME*/ 0x24, WCEL_MoveToBeg }, + {/*VK_END*/ 0x23, WCEL_MoveToEnd }, + {/*VK_UP*/ 0x26, WCEL_MoveToPrevHist }, + {/*VK_DOWN*/ 0x28, WCEL_MoveToNextHist }, + {/*VK_DEL*/ 0x2e, WCEL_DeleteCurrChar }, + { 0, NULL } +}; + +static KeyEntry Win32KeyMapCtrlExtended[] = +{ + {/*VK_LEFT*/ 0x25, WCEL_MoveToLeftWord }, + {/*VK_RIGHT*/0x27, WCEL_MoveToRightWord }, + {/*VK_END*/ 0x23, WCEL_KillToEndOfLine }, + { 0, NULL } +}; + +KeyMap Win32KeyMap[] = +{ + {0x00000000, 1, StdKeyMap}, + {0x00000000, 0, Win32ExtraStdKeyMap}, + {0x00000100, 0, Win32KeyMapExtended}, + {0x00000104, 0, Win32KeyMapCtrlExtended}, + {0x00000108, 0, Win32KeyMapCtrlExtended}, + {0, 0, 0} +}; +#undef CTRL + +/* ==================================================================== + * + * Read line master function + * + * ====================================================================*/ + +WCHAR* CONSOLE_Readline(HANDLE hConsoleIn, int use_emacs) +{ + WCEL_Context ctx; + INPUT_RECORD ir; + KeyMap* km; + KeyEntry* ke; + unsigned ofs; + void (*func)(struct WCEL_Context* ctx); + DWORD ks; + + memset(&ctx, 0, sizeof(ctx)); + ctx.hConIn = hConsoleIn; + WCEL_HistoryInit(&ctx); + if ((ctx.hConOut = CreateFileA("CONOUT$", GENERIC_READ|GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, 0, 0 )) == INVALID_HANDLE_VALUE || + !GetConsoleScreenBufferInfo(ctx.hConOut, &ctx.csbi)) + return NULL; + ctx.can_wrap = (GetConsoleMode(ctx.hConOut, &ks) && (ks & ENABLE_WRAP_AT_EOL_OUTPUT)) ? 1 : 0; + + if (!WCEL_Grow(&ctx, 1)) + { + CloseHandle(ctx.hConOut); + return NULL; + } + ctx.line[0] = 0; + +/* EPP WCEL_Dump(&ctx, "init"); */ + + while (!ctx.done && !ctx.error && WCEL_Get(&ctx, &ir)) + { + if (ir.EventType != KEY_EVENT || !ir.Event.KeyEvent.bKeyDown) continue; + TRACE("key%s repeatCount=%u, keyCode=%02x scanCode=%02x char=%02x keyState=%08lx\n", + ir.Event.KeyEvent.bKeyDown ? "Down" : "Up ", ir.Event.KeyEvent.wRepeatCount, + ir.Event.KeyEvent.wVirtualKeyCode, ir.Event.KeyEvent.wVirtualScanCode, + ir.Event.KeyEvent.uChar.UnicodeChar, ir.Event.KeyEvent.dwControlKeyState); + +/* EPP WCEL_Dump(&ctx, "before func"); */ + ofs = ctx.ofs; + /* mask out some bits which don't interest us */ + ks = ir.Event.KeyEvent.dwControlKeyState & ~(NUMLOCK_ON|SCROLLLOCK_ON|CAPSLOCK_ON); + + func = NULL; + for (km = (use_emacs) ? EmacsKeyMap : Win32KeyMap; km->entries != NULL; km++) + { + if (km->keyState != ks) + continue; + if (km->chkChar) + { + for (ke = &km->entries[0]; ke->func != 0; ke++) + if (ke->val == ir.Event.KeyEvent.uChar.UnicodeChar) break; + } + else + { + for (ke = &km->entries[0]; ke->func != 0; ke++) + if (ke->val == ir.Event.KeyEvent.wVirtualKeyCode) break; + + } + if (ke->func) + { + func = ke->func; + break; + } + } + + if (func) + (func)(&ctx); + else if (!(ir.Event.KeyEvent.dwControlKeyState & (ENHANCED_KEY|LEFT_ALT_PRESSED))) + WCEL_InsertChar(&ctx, ir.Event.KeyEvent.uChar.UnicodeChar); + else TRACE("Dropped event\n"); + +/* EPP WCEL_Dump(&ctx, "after func"); */ + if (ctx.ofs != ofs) + SetConsoleCursorPosition(ctx.hConOut, WCEL_GetCoord(&ctx, ctx.ofs)); + } + if (ctx.error) + { + HeapFree(GetProcessHeap(), 0, ctx.line); + ctx.line = NULL; + } + WCEL_FreeYank(&ctx); + if (ctx.line) + CONSOLE_AppendHistory(ctx.line); + + CloseHandle(ctx.hConOut); + if (ctx.histCurr) HeapFree(GetProcessHeap(), 0, ctx.histCurr); + return ctx.line; +}