From 4e697fee2c0df42bb2247377d11171cf790d05e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herm=C3=A8s=20B=C3=A9lusca-Ma=C3=AFto?= Date: Mon, 16 Oct 2017 23:58:23 +0200 Subject: [PATCH] [CONUTILS] Split stream.c into input and output stream modules. As code grows, this will allow for better maintenance of the console stream code. In particular the input stream module will contain special code for handling TTYs, and this is something not all console programs will need. Having this code in a separate module will allow for the linker to possibly remove this code when it is unused. --- sdk/lib/conutils/CMakeLists.txt | 2 + sdk/lib/conutils/README.txt | 27 +- sdk/lib/conutils/conutils.h | 22 +- sdk/lib/conutils/instream.c | 49 ++ sdk/lib/conutils/instream.h | 46 ++ sdk/lib/conutils/outstream.c | 762 +++++++++++++++++++++++++++++ sdk/lib/conutils/outstream.h | 162 +++++++ sdk/lib/conutils/pager.c | 16 +- sdk/lib/conutils/pager.h | 18 +- sdk/lib/conutils/screen.c | 16 +- sdk/lib/conutils/screen.h | 17 +- sdk/lib/conutils/stream.c | 772 +----------------------------- sdk/lib/conutils/stream.h | 138 +----- sdk/lib/conutils/stream_private.h | 55 +++ sdk/lib/conutils/utils.c | 16 +- sdk/lib/conutils/utils.h | 19 +- 16 files changed, 1179 insertions(+), 958 deletions(-) create mode 100644 sdk/lib/conutils/instream.c create mode 100644 sdk/lib/conutils/instream.h create mode 100644 sdk/lib/conutils/outstream.c create mode 100644 sdk/lib/conutils/outstream.h create mode 100644 sdk/lib/conutils/stream_private.h diff --git a/sdk/lib/conutils/CMakeLists.txt b/sdk/lib/conutils/CMakeLists.txt index 8b978d8e3b8..90d8dfb98dc 100644 --- a/sdk/lib/conutils/CMakeLists.txt +++ b/sdk/lib/conutils/CMakeLists.txt @@ -1,5 +1,7 @@ list(APPEND SOURCE + # instream.c + outstream.c pager.c screen.c stream.c diff --git a/sdk/lib/conutils/README.txt b/sdk/lib/conutils/README.txt index 680512c8204..4a4a7a73a8a 100644 --- a/sdk/lib/conutils/README.txt +++ b/sdk/lib/conutils/README.txt @@ -1,7 +1,9 @@ -The ReactOS Console Utilities Library v0.1 +The ReactOS Console Utilities Library v0.2 ========================================== -COPYRIGHT: Under GPLv2, see COPYING in the top level directory. +LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) +COPYRIGHT: Copyright 2017-2018 ReactOS Team + Copyright 2017-2018 Hermes Belusca-Maito CREDITS: Thanks to the many people who originally wrote the code that finally ended up inside this library, with more or less refactoring, or whose code served as a basis for some functions of the library. @@ -21,23 +23,22 @@ together with printf-like functionality. CONTENTS ~-~-~-~- -0. 'conutils_base' (utils.c and utils.h): Base set of functions for loading +-- Main ConUtils Library -- + +0. "BASE" (utils.c and utils.h): Base set of functions for loading string resources and message strings, and handle type identification. -1. 'conutils_stream' (stream.c and stream.h): Console Stream API (CON_STREAM): +1. "STREAM" (stream.c and stream.h, instream.c and instream.h, outstream.c + and outstream.h): Console Stream API (CON_STREAM): Stream initialization, basic ConStreamRead/Write. Stream utility functions: - ConPuts/Printf, ConResPuts/Printf, ConMsgPuts/Printf. - Depends on 'conutils_base'. + ConPuts/Printf, ConResPuts/Printf, ConMsgPuts/Printf. Depends on "BASE". -2. 'conutils_screen' (screen.c and screen.h): Console Screen API (CON_SCREEN): +2. "SCREEN" (screen.c and screen.h): Console Screen API (CON_SCREEN): Introduces the notion of console/terminal screen around the streams. Manages console/terminal screen metrics for Win32 consoles and TTYs (serial...). Additional Screen utility functions. - Depends on 'conutils_stream', and indirectly on 'conutils_base'. + Depends on "STREAM", and indirectly on "BASE". -3. 'conutils_pager' (pager.c and pager.h): Console Pager API (CON_PAGER): +3. "PAGER" (pager.c and pager.h): Console Pager API (CON_PAGER): Implements core console/terminal paging functionality around console screens. - Depends on 'conutils_screen' and indirectly on 'conutils_stream' and - 'conutils_base'. - -4. More to come! + Depends on "SCREEN", and indirectly on "STREAM" and "BASE". diff --git a/sdk/lib/conutils/conutils.h b/sdk/lib/conutils/conutils.h index 2857bd450b0..93d79e8848a 100644 --- a/sdk/lib/conutils/conutils.h +++ b/sdk/lib/conutils/conutils.h @@ -1,25 +1,29 @@ /* - * COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS Console Utilities Library - * FILE: sdk/lib/conutils/conutils.h - * PURPOSE: Provides simple abstraction wrappers around CRT streams or - * Win32 console API I/O functions, to deal with i18n + Unicode - * related problems. - * PROGRAMMERS: - Hermes Belusca-Maito (for the library); - * - All programmers who wrote the different console applications - * from which I took those functions and improved them. + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Provides simple abstraction wrappers around CRT streams or + * Win32 console API I/O functions, to deal with i18n + Unicode + * related problems. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito */ #ifndef __CONUTILS_H__ #define __CONUTILS_H__ +#pragma once + #ifndef _UNICODE #error The ConUtils library only supports compilation with _UNICODE defined, at the moment! #endif #include "utils.h" #include "stream.h" +// #include "instream.h" +#include "outstream.h" #include "screen.h" #include "pager.h" #endif /* __CONUTILS_H__ */ + +/* EOF */ diff --git a/sdk/lib/conutils/instream.c b/sdk/lib/conutils/instream.c new file mode 100644 index 00000000000..a9fa105766c --- /dev/null +++ b/sdk/lib/conutils/instream.c @@ -0,0 +1,49 @@ +/* + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Provides basic abstraction wrappers around CRT streams or + * Win32 console API I/O functions, to deal with i18n + Unicode + * related problems. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito + */ + +/* + * Enable this define if you want to only use CRT functions to output + * UNICODE stream to the console, as in the way explained by + * http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html + */ +/** NOTE: Experimental! Don't use USE_CRT yet because output to console is a bit broken **/ +// #define USE_CRT + +/* FIXME: Temporary HACK before we cleanly support UNICODE functions */ +#define UNICODE +#define _UNICODE + +#ifdef USE_CRT +#include +#include +#endif /* USE_CRT */ + +#include // limits.h // For MB_LEN_MAX + +#include +#include +#include +#include // MAKEINTRESOURCEW, RT_STRING +#include // Console APIs (only if kernel32 support included) +#include + +/* PSEH for SEH Support */ +#include + +#include "conutils.h" +#include "stream.h" +#include "stream_private.h" + + +/* + * Console I/O utility API -- Input + */ + +/* EOF */ diff --git a/sdk/lib/conutils/instream.h b/sdk/lib/conutils/instream.h new file mode 100644 index 00000000000..d16a9c30de1 --- /dev/null +++ b/sdk/lib/conutils/instream.h @@ -0,0 +1,46 @@ +/* + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Provides basic abstraction wrappers around CRT streams or + * Win32 console API I/O functions, to deal with i18n + Unicode + * related problems. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito + */ + +#ifndef __INSTREAM_H__ +#define __INSTREAM_H__ + +#pragma once + +/* + * Enable this define if you want to only use CRT functions to output + * UNICODE stream to the console, as in the way explained by + * http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html + */ +/** NOTE: Experimental! Don't use USE_CRT yet because output to console is a bit broken **/ +// #define USE_CRT + +#ifndef _UNICODE +#error The ConUtils library at the moment only supports compilation with _UNICODE defined! +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Shadow type, implementation-specific +typedef struct _CON_STREAM CON_STREAM, *PCON_STREAM; + +/* + * Console I/O utility API -- Input + */ + + +#ifdef __cplusplus +} +#endif + +#endif /* __INSTREAM_H__ */ + +/* EOF */ diff --git a/sdk/lib/conutils/outstream.c b/sdk/lib/conutils/outstream.c new file mode 100644 index 00000000000..63adaf31841 --- /dev/null +++ b/sdk/lib/conutils/outstream.c @@ -0,0 +1,762 @@ +/* + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Provides basic abstraction wrappers around CRT streams or + * Win32 console API I/O functions, to deal with i18n + Unicode + * related problems. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito + */ + +/* + * Enable this define if you want to only use CRT functions to output + * UNICODE stream to the console, as in the way explained by + * http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html + */ +/** NOTE: Experimental! Don't use USE_CRT yet because output to console is a bit broken **/ +// #define USE_CRT + +/* FIXME: Temporary HACK before we cleanly support UNICODE functions */ +#define UNICODE +#define _UNICODE + +#ifdef USE_CRT +#include +#include +#endif /* USE_CRT */ + +#include // limits.h // For MB_LEN_MAX + +#include +#include +#include +#include // MAKEINTRESOURCEW, RT_STRING +#include // Console APIs (only if kernel32 support included) +#include + +/* PSEH for SEH Support */ +#include + +#include "conutils.h" +#include "stream.h" +#include "stream_private.h" + + +// #define RC_STRING_MAX_SIZE 4096 +#define CON_RC_STRING_MAX_SIZE 4096 +// #define MAX_BUFFER_SIZE 4096 // Some programs (wlanconf, shutdown) set it to 5024 +// #define OUTPUT_BUFFER_SIZE 4096 // Name given in cmd/console.c +// MAX_STRING_SIZE // Name given in diskpart + +// #define MAX_MESSAGE_SIZE 512 // See shutdown... + + +/* + * Console I/O utility API -- Output + */ + +// NOTE: Should be called with the stream locked. +INT +__stdcall +ConWrite( + IN PCON_STREAM Stream, + IN PTCHAR szStr, + IN DWORD len) +{ +#ifndef USE_CRT + DWORD TotalLen = len, dwNumBytes = 0; + PVOID p; + + // CHAR strOem[CON_RC_STRING_MAX_SIZE]; // Some static buffer... + + /* If we do not write anything, just return */ + if (!szStr || len == 0) + return 0; + + /* Check whether we are writing to a console */ + // if (IsConsoleHandle(Stream->hHandle)) + if (Stream->IsConsole) + { + // TODO: Check if (ConStream->Mode == WideText or UTF16Text) ?? + + /* + * This code is inspired from _cputws, in particular from the fact that, + * according to MSDN: https://msdn.microsoft.com/en-us/library/ms687401(v=vs.85).aspx + * the buffer size must be less than 64 KB. + * + * A similar code can be used for implementing _cputs too. + */ + + DWORD cchWrite; + TotalLen = len, dwNumBytes = 0; + + while (len > 0) + { + cchWrite = min(len, 65535 / sizeof(WCHAR)); + + // FIXME: Check return value! + WriteConsole(Stream->hHandle, szStr, cchWrite, &dwNumBytes, NULL); + + szStr += cchWrite; + len -= cchWrite; + } + + return (INT)TotalLen; // FIXME: Really return the number of chars written! + } + + /* + * We are redirected and writing to a file or pipe instead of the console. + * Convert the string from TCHARs to the desired output format, if the two differ. + * + * Implementation NOTE: + * MultiByteToWideChar (resp. WideCharToMultiByte) are equivalent to + * OemToCharBuffW (resp. CharToOemBuffW), but the latters uselessly + * depend on user32.dll, while MultiByteToWideChar and WideCharToMultiByte + * only need kernel32.dll. + */ + if ((Stream->Mode == WideText) || (Stream->Mode == UTF16Text)) + { +#ifndef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16 + /* Convert from the current process/thread's codepage to UTF-16 */ + WCHAR *buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR)); + if (!buffer) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return 0; + } + len = (DWORD)MultiByteToWideChar(CP_THREAD_ACP, // CP_ACP, CP_OEMCP + 0, szStr, (INT)len, buffer, (INT)len); + szStr = (PVOID)buffer; +#else + /* + * Do not perform any conversion since we are already in UTF-16, + * that is the same encoding as the stream. + */ +#endif + + /* + * Find any newline character in the buffer, + * write the part BEFORE the newline, then write + * a carriage-return + newline, and then write + * the remaining part of the buffer. + * + * This fixes output in files and serial console. + */ + while (len > 0) + { + /* Loop until we find a \r or \n character */ + // FIXME: What about the pair \r\n ? + p = szStr; + while (len > 0 && *(PWCHAR)p != L'\r' && *(PWCHAR)p != L'\n') + { + /* Advance one character */ + p = (PVOID)((PWCHAR)p + 1); + len--; + } + + /* Write everything up to \r or \n */ + dwNumBytes = ((PWCHAR)p - (PWCHAR)szStr) * sizeof(WCHAR); + WriteFile(Stream->hHandle, szStr, dwNumBytes, &dwNumBytes, NULL); + + /* If we hit \r or \n ... */ + if (len > 0 && (*(PWCHAR)p == L'\r' || *(PWCHAR)p == L'\n')) + { + /* ... send a carriage-return + newline sequence and skip \r or \n */ + WriteFile(Stream->hHandle, L"\r\n", 2 * sizeof(WCHAR), &dwNumBytes, NULL); + szStr = (PVOID)((PWCHAR)p + 1); + len--; + } + } + +#ifndef _UNICODE + HeapFree(GetProcessHeap(), 0, buffer); +#endif + } + else if ((Stream->Mode == UTF8Text) || (Stream->Mode == AnsiText)) + { + CHAR *buffer; + + /* + * Resolve the codepage cache if it was not assigned yet + * (only if the stream is in ANSI mode; in UTF8 mode the + * codepage was already set to CP_UTF8). + */ + if (/*(Stream->Mode == AnsiText) &&*/ (Stream->CodePage == INVALID_CP)) + Stream->CodePage = GetConsoleOutputCP(); // CP_ACP, CP_OEMCP + +#ifdef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16 + /* Convert from UTF-16 to either UTF-8 or ANSI, using stream codepage */ + // NOTE: MB_LEN_MAX defined either in limits.h or in stdlib.h . + buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * MB_LEN_MAX); + if (!buffer) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return 0; + } + len = WideCharToMultiByte(Stream->CodePage, 0, + szStr, len, buffer, len * MB_LEN_MAX, + NULL, NULL); + szStr = (PVOID)buffer; +#else + /* + * Convert from the current process/thread's codepage to either + * UTF-8 or ANSI, using stream codepage. + * We need to perform a double conversion, by going through UTF-16. + */ + // TODO! + #error "Need to implement double conversion!" +#endif + + /* + * Find any newline character in the buffer, + * write the part BEFORE the newline, then write + * a carriage-return + newline, and then write + * the remaining part of the buffer. + * + * This fixes output in files and serial console. + */ + while (len > 0) + { + /* Loop until we find a \r or \n character */ + // FIXME: What about the pair \r\n ? + p = szStr; + while (len > 0 && *(PCHAR)p != '\r' && *(PCHAR)p != '\n') + { + /* Advance one character */ + p = (PVOID)((PCHAR)p + 1); + len--; + } + + /* Write everything up to \r or \n */ + dwNumBytes = ((PCHAR)p - (PCHAR)szStr) * sizeof(CHAR); + WriteFile(Stream->hHandle, szStr, dwNumBytes, &dwNumBytes, NULL); + + /* If we hit \r or \n ... */ + if (len > 0 && (*(PCHAR)p == '\r' || *(PCHAR)p == '\n')) + { + /* ... send a carriage-return + newline sequence and skip \r or \n */ + WriteFile(Stream->hHandle, "\r\n", 2, &dwNumBytes, NULL); + szStr = (PVOID)((PCHAR)p + 1); + len--; + } + } + +#ifdef _UNICODE + HeapFree(GetProcessHeap(), 0, buffer); +#else + // TODO! +#endif + } + else // if (Stream->Mode == Binary) + { + /* Directly output the string */ + WriteFile(Stream->hHandle, szStr, len, &dwNumBytes, NULL); + } + + // FIXME! + return (INT)TotalLen; + +#else /* defined(USE_CRT) */ + + DWORD total = len; + DWORD written = 0; + + /* If we do not write anything, just return */ + if (!szStr || len == 0) + return 0; + +#if 1 + /* + * There is no "counted" printf-to-stream or puts-like function, therefore + * we use this trick to output the counted string to the stream. + */ + while (1) + { + written = fwprintf(Stream->fStream, L"%.*s", total, szStr); + if (written < total) + { + /* + * Some embedded NULL or special character + * was encountered, print it apart. + */ + if (written == 0) + { + fputwc(*szStr, Stream->fStream); + written++; + } + + szStr += written; + total -= written; + } + else + { + break; + } + } + return (INT)len; +#else + /* ANSI text or Binary output only */ + _setmode(_fileno(Stream->fStream), _O_TEXT); // _O_BINARY + return fwrite(szStr, sizeof(*szStr), len, Stream->fStream); +#endif + +#endif /* defined(USE_CRT) */ +} + + +#define CON_STREAM_WRITE_CALL(Stream, Str, Len) \ + (Stream)->WriteFunc((Stream), (Str), (Len)); + +/* Lock the stream only in non-USE_CRT mode (otherwise use the CRT stream lock) */ +#ifndef USE_CRT + +#define CON_STREAM_WRITE2(Stream, Str, Len, RetLen) \ +do { \ + EnterCriticalSection(&(Stream)->Lock); \ + (RetLen) = CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ + LeaveCriticalSection(&(Stream)->Lock); \ +} while(0) + +#define CON_STREAM_WRITE(Stream, Str, Len) \ +do { \ + EnterCriticalSection(&(Stream)->Lock); \ + CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ + LeaveCriticalSection(&(Stream)->Lock); \ +} while(0) + +#else + +#define CON_STREAM_WRITE2(Stream, Str, Len, RetLen) \ +do { \ + (RetLen) = CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ +} while(0) + +#define CON_STREAM_WRITE(Stream, Str, Len) \ +do { \ + CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ +} while(0) + +#endif + + +INT +ConStreamWrite( + IN PCON_STREAM Stream, + IN PTCHAR szStr, + IN DWORD len) +{ + INT Len; + CON_STREAM_WRITE2(Stream, szStr, len, Len); + return Len; +} + +INT +ConPuts( + IN PCON_STREAM Stream, + IN LPWSTR szStr) +{ + INT Len; + + Len = wcslen(szStr); + CON_STREAM_WRITE2(Stream, szStr, Len, Len); + + /* Fixup returned length in case of errors */ + if (Len < 0) + Len = 0; + + return Len; +} + +INT +ConPrintfV( + IN PCON_STREAM Stream, + IN LPWSTR szStr, + IN va_list args) // arg_ptr +{ + INT Len; + WCHAR bufSrc[CON_RC_STRING_MAX_SIZE]; + + // Len = vfwprintf(Stream->fStream, szStr, args); // vfprintf for direct ANSI + + /* + * Reuse szStr as the pointer to end-of-string, to compute + * the string length instead of calling wcslen(). + */ + // StringCchVPrintfW(bufSrc, ARRAYSIZE(bufSrc), szStr, args); + // Len = wcslen(bufSrc); + StringCchVPrintfExW(bufSrc, ARRAYSIZE(bufSrc), &szStr, NULL, 0, szStr, args); + Len = szStr - bufSrc; + + CON_STREAM_WRITE2(Stream, bufSrc, Len, Len); + + /* Fixup returned length in case of errors */ + if (Len < 0) + Len = 0; + + return Len; +} + +INT +__cdecl +ConPrintf( + IN PCON_STREAM Stream, + IN LPWSTR szStr, + ...) +{ + INT Len; + va_list args; + + // Len = vfwprintf(Stream->fStream, szMsgBuf, args); // vfprintf for direct ANSI + + // StringCchPrintfW + va_start(args, szStr); + Len = ConPrintfV(Stream, szStr, args); + va_end(args); + + return Len; +} + +INT +ConResPutsEx( + IN PCON_STREAM Stream, + IN HINSTANCE hInstance OPTIONAL, + IN UINT uID) +{ + INT Len; + PWCHAR szStr = NULL; + + Len = K32LoadStringW(hInstance, uID, (PWSTR)&szStr, 0); + if (szStr && Len) + // Len = ConPuts(Stream, szStr); + CON_STREAM_WRITE2(Stream, szStr, Len, Len); + + /* Fixup returned length in case of errors */ + if (Len < 0) + Len = 0; + + return Len; +} + +INT +ConResPuts( + IN PCON_STREAM Stream, + IN UINT uID) +{ + return ConResPutsEx(Stream, NULL /*GetModuleHandleW(NULL)*/, uID); +} + +INT +ConResPrintfExV( + IN PCON_STREAM Stream, + IN HINSTANCE hInstance OPTIONAL, + IN UINT uID, + IN va_list args) // arg_ptr +{ + INT Len; + WCHAR bufSrc[CON_RC_STRING_MAX_SIZE]; + + // NOTE: We may use the special behaviour where nBufMaxSize == 0 + Len = K32LoadStringW(hInstance, uID, bufSrc, ARRAYSIZE(bufSrc)); + if (Len) + Len = ConPrintfV(Stream, bufSrc, args); + + return Len; +} + +INT +ConResPrintfV( + IN PCON_STREAM Stream, + IN UINT uID, + IN va_list args) // arg_ptr +{ + return ConResPrintfExV(Stream, NULL /*GetModuleHandleW(NULL)*/, uID, args); +} + +INT +__cdecl +ConResPrintfEx( + IN PCON_STREAM Stream, + IN HINSTANCE hInstance OPTIONAL, + IN UINT uID, + ...) +{ + INT Len; + va_list args; + + va_start(args, uID); + Len = ConResPrintfExV(Stream, hInstance, uID, args); + va_end(args); + + return Len; +} + +INT +__cdecl +ConResPrintf( + IN PCON_STREAM Stream, + IN UINT uID, + ...) +{ + INT Len; + va_list args; + + va_start(args, uID); + Len = ConResPrintfV(Stream, uID, args); + va_end(args); + + return Len; +} + +INT +ConMsgPuts( + IN PCON_STREAM Stream, + IN DWORD dwFlags, + IN LPCVOID lpSource OPTIONAL, + IN DWORD dwMessageId, + IN DWORD dwLanguageId) +{ + INT Len; + DWORD dwLength = 0; + LPWSTR lpMsgBuf = NULL; + + /* + * Sanitize dwFlags. This version always ignore explicitely the inserts + * as we emulate the behaviour of the *puts function. + */ + dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer. + dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS; // Ignore inserts for FormatMessage. + dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; + + dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK; + + /* + * Retrieve the message string without appending extra newlines. + * Wrap in SEH to protect from invalid string parameters. + */ + _SEH2_TRY + { + dwLength = FormatMessageW(dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + (LPWSTR)&lpMsgBuf, + 0, NULL); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + } + _SEH2_END; + + Len = (INT)dwLength; + + if (!lpMsgBuf) + { + // ASSERT(dwLength == 0); + } + else + { + // ASSERT(dwLength != 0); + + /* lpMsgBuf is NULL-terminated by FormatMessage */ + // Len = ConPuts(Stream, lpMsgBuf); + CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len); + + /* Fixup returned length in case of errors */ + if (Len < 0) + Len = 0; + + /* Free the buffer allocated by FormatMessage */ + LocalFree(lpMsgBuf); + } + + return Len; +} + +INT +ConMsgPrintf2V( + IN PCON_STREAM Stream, + IN DWORD dwFlags, + IN LPCVOID lpSource OPTIONAL, + IN DWORD dwMessageId, + IN DWORD dwLanguageId, + IN va_list args) // arg_ptr +{ + INT Len; + DWORD dwLength = 0; + LPWSTR lpMsgBuf = NULL; + + /* + * Sanitize dwFlags. This version always ignore explicitely the inserts. + * The string that we will return to the user will not be pre-formatted. + */ + dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer. + dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS; // Ignore inserts for FormatMessage. + dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; + + dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK; + + /* + * Retrieve the message string without appending extra newlines. + * Wrap in SEH to protect from invalid string parameters. + */ + _SEH2_TRY + { + dwLength = FormatMessageW(dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + (LPWSTR)&lpMsgBuf, + 0, NULL); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + } + _SEH2_END; + + Len = (INT)dwLength; + + if (!lpMsgBuf) + { + // ASSERT(dwLength == 0); + } + else + { + // ASSERT(dwLength != 0); + + /* lpMsgBuf is NULL-terminated by FormatMessage */ + Len = ConPrintfV(Stream, lpMsgBuf, args); + // CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len); + + /* Fixup returned length in case of errors */ + if (Len < 0) + Len = 0; + + /* Free the buffer allocated by FormatMessage */ + LocalFree(lpMsgBuf); + } + + return Len; +} + +INT +ConMsgPrintfV( + IN PCON_STREAM Stream, + IN DWORD dwFlags, + IN LPCVOID lpSource OPTIONAL, + IN DWORD dwMessageId, + IN DWORD dwLanguageId, + IN va_list args) // arg_ptr +{ + INT Len; + DWORD dwLength = 0; + LPWSTR lpMsgBuf = NULL; + + /* Sanitize dwFlags */ + dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer. +// dwFlags &= ~FORMAT_MESSAGE_IGNORE_INSERTS; // We always use arguments. + dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; // We always use arguments of type 'va_list'. + + // + // NOTE: Technique taken from eventvwr.c!GetMessageStringFromDll() + // + + dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK; + + /* + * Retrieve the message string without appending extra newlines. + * Use the "safe" FormatMessage version (SEH-protected) to protect + * from invalid string parameters. + */ + dwLength = FormatMessageSafeW(dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + (LPWSTR)&lpMsgBuf, + 0, &args); + + Len = (INT)dwLength; + + if (!lpMsgBuf) + { + // ASSERT(dwLength == 0); + } + else + { + // ASSERT(dwLength != 0); + + // Len = ConPrintfV(Stream, lpMsgBuf, args); + CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len); + + /* Fixup returned length in case of errors */ + if (Len < 0) + Len = 0; + + /* Free the buffer allocated by FormatMessage */ + LocalFree(lpMsgBuf); + } + + return Len; +} + +INT +__cdecl +ConMsgPrintf( + IN PCON_STREAM Stream, + IN DWORD dwFlags, + IN LPCVOID lpSource OPTIONAL, + IN DWORD dwMessageId, + IN DWORD dwLanguageId, + ...) +{ + INT Len; + va_list args; + + va_start(args, dwLanguageId); + // ConMsgPrintf2V + Len = ConMsgPrintfV(Stream, + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + args); + va_end(args); + + return Len; +} + + + +VOID +ConClearLine(IN PCON_STREAM Stream) +{ + HANDLE hOutput = ConStreamGetOSHandle(Stream); + + /* + * Erase the full line where the cursor is, and move + * the cursor back to the beginning of the line. + */ + + if (IsConsoleHandle(hOutput)) + { + CONSOLE_SCREEN_BUFFER_INFO csbi; + DWORD dwWritten; + + GetConsoleScreenBufferInfo(hOutput, &csbi); + + csbi.dwCursorPosition.X = 0; + // csbi.dwCursorPosition.Y; + + FillConsoleOutputCharacterW(hOutput, L' ', + csbi.dwSize.X, + csbi.dwCursorPosition, + &dwWritten); + SetConsoleCursorPosition(hOutput, csbi.dwCursorPosition); + } + else if (IsTTYHandle(hOutput)) + { + ConPuts(Stream, L"\x1B[2K\x1B[1G"); // FIXME: Just use WriteFile + } + // else, do nothing for files +} + +/* EOF */ diff --git a/sdk/lib/conutils/outstream.h b/sdk/lib/conutils/outstream.h new file mode 100644 index 00000000000..a4099e2e10b --- /dev/null +++ b/sdk/lib/conutils/outstream.h @@ -0,0 +1,162 @@ +/* + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Provides basic abstraction wrappers around CRT streams or + * Win32 console API I/O functions, to deal with i18n + Unicode + * related problems. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito + */ + +#ifndef __OUTSTREAM_H__ +#define __OUTSTREAM_H__ + +#pragma once + +/* + * Enable this define if you want to only use CRT functions to output + * UNICODE stream to the console, as in the way explained by + * http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html + */ +/** NOTE: Experimental! Don't use USE_CRT yet because output to console is a bit broken **/ +// #define USE_CRT + +#ifndef _UNICODE +#error The ConUtils library at the moment only supports compilation with _UNICODE defined! +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Shadow type, implementation-specific +typedef struct _CON_STREAM CON_STREAM, *PCON_STREAM; + +// typedef INT (__stdcall *CON_READ_FUNC)(IN PCON_STREAM, IN PTCHAR, IN DWORD); + // Stream, szStr, len +typedef INT (__stdcall *CON_WRITE_FUNC)(IN PCON_STREAM, IN PTCHAR, IN DWORD); + + +/* + * Console I/O utility API -- Output + */ + +INT +__stdcall +ConWrite( + IN PCON_STREAM Stream, + IN PTCHAR szStr, + IN DWORD len); + +INT +ConStreamWrite( + IN PCON_STREAM Stream, + IN PTCHAR szStr, + IN DWORD len); + +INT +ConPuts( + IN PCON_STREAM Stream, + IN LPWSTR szStr); + +INT +ConPrintfV( + IN PCON_STREAM Stream, + IN LPWSTR szStr, + IN va_list args); // arg_ptr + +INT +__cdecl +ConPrintf( + IN PCON_STREAM Stream, + IN LPWSTR szStr, + ...); + +INT +ConResPutsEx( + IN PCON_STREAM Stream, + IN HINSTANCE hInstance OPTIONAL, + IN UINT uID); + +INT +ConResPuts( + IN PCON_STREAM Stream, + IN UINT uID); + +INT +ConResPrintfExV( + IN PCON_STREAM Stream, + IN HINSTANCE hInstance OPTIONAL, + IN UINT uID, + IN va_list args); // arg_ptr + +INT +ConResPrintfV( + IN PCON_STREAM Stream, + IN UINT uID, + IN va_list args); // arg_ptr + +INT +__cdecl +ConResPrintfEx( + IN PCON_STREAM Stream, + IN HINSTANCE hInstance OPTIONAL, + IN UINT uID, + ...); + +INT +__cdecl +ConResPrintf( + IN PCON_STREAM Stream, + IN UINT uID, + ...); + +INT +ConMsgPuts( + IN PCON_STREAM Stream, + IN DWORD dwFlags, + IN LPCVOID lpSource OPTIONAL, + IN DWORD dwMessageId, + IN DWORD dwLanguageId); + +INT +ConMsgPrintf2V( + IN PCON_STREAM Stream, + IN DWORD dwFlags, + IN LPCVOID lpSource OPTIONAL, + IN DWORD dwMessageId, + IN DWORD dwLanguageId, + IN va_list args); // arg_ptr + +INT +ConMsgPrintfV( + IN PCON_STREAM Stream, + IN DWORD dwFlags, + IN LPCVOID lpSource OPTIONAL, + IN DWORD dwMessageId, + IN DWORD dwLanguageId, + IN va_list args); // arg_ptr + +INT +__cdecl +ConMsgPrintf( + IN PCON_STREAM Stream, + IN DWORD dwFlags, + IN LPCVOID lpSource OPTIONAL, + IN DWORD dwMessageId, + IN DWORD dwLanguageId, + ...); + + + +VOID +ConClearLine(IN PCON_STREAM Stream); + + +#ifdef __cplusplus +} +#endif + +#endif /* __OUTSTREAM_H__ */ + +/* EOF */ diff --git a/sdk/lib/conutils/pager.c b/sdk/lib/conutils/pager.c index cc2c2c4181b..ac6bd3f0b51 100644 --- a/sdk/lib/conutils/pager.c +++ b/sdk/lib/conutils/pager.c @@ -1,19 +1,15 @@ /* - * COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS Console Utilities Library - * FILE: sdk/lib/conutils/pager.c - * PURPOSE: Console/terminal paging functionality. - * PROGRAMMERS: - Hermes Belusca-Maito (for the library); - * - All programmers who wrote the different console applications - * from which I took those functions and improved them. + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Console/terminal paging functionality. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito */ /* FIXME: Temporary HACK before we cleanly support UNICODE functions */ #define UNICODE #define _UNICODE -#include // limits.h // For MB_LEN_MAX - #include #include // #include @@ -158,3 +154,5 @@ ConResPaging( return ConResPagingEx(Pager, PagePrompt, StartPaging, NULL /*GetModuleHandleW(NULL)*/, uID); } + +/* EOF */ diff --git a/sdk/lib/conutils/pager.h b/sdk/lib/conutils/pager.h index 6c62ab955d5..486b520d124 100644 --- a/sdk/lib/conutils/pager.h +++ b/sdk/lib/conutils/pager.h @@ -1,16 +1,16 @@ /* - * COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS Console Utilities Library - * FILE: sdk/lib/conutils/pager.h - * PURPOSE: Console/terminal paging functionality. - * PROGRAMMERS: - Hermes Belusca-Maito (for the library); - * - All programmers who wrote the different console applications - * from which I took those functions and improved them. + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Console/terminal paging functionality. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito */ #ifndef __PAGER_H__ #define __PAGER_H__ +#pragma once + #ifndef _UNICODE #error The ConUtils library at the moment only supports compilation with _UNICODE defined! #endif @@ -42,6 +42,7 @@ do { \ (pPager)->LineCount = 0; \ } while (0) + // Pager, Done, Total typedef BOOL (__stdcall *PAGE_PROMPT)(IN PCON_PAGER, IN DWORD, IN DWORD); @@ -78,4 +79,7 @@ ConResPaging( #ifdef __cplusplus } #endif + #endif /* __PAGER_H__ */ + +/* EOF */ diff --git a/sdk/lib/conutils/screen.c b/sdk/lib/conutils/screen.c index b6bc8681593..04f0b6e91d4 100644 --- a/sdk/lib/conutils/screen.c +++ b/sdk/lib/conutils/screen.c @@ -1,19 +1,15 @@ /* - * COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS Console Utilities Library - * FILE: sdk/lib/conutils/screen.c - * PURPOSE: Console/terminal screen management. - * PROGRAMMERS: - Hermes Belusca-Maito (for the library); - * - All programmers who wrote the different console applications - * from which I took those functions and improved them. + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Console/terminal screen management. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito */ /* FIXME: Temporary HACK before we cleanly support UNICODE functions */ #define UNICODE #define _UNICODE -#include // limits.h // For MB_LEN_MAX - #include #include // #include @@ -173,3 +169,5 @@ ConClearScreen(IN PCON_SCREEN Screen) CON_STREAM_WRITE(Screen->Stream, &ch, 1); } } + +/* EOF */ diff --git a/sdk/lib/conutils/screen.h b/sdk/lib/conutils/screen.h index bc2cce9d14a..2ab11a7a659 100644 --- a/sdk/lib/conutils/screen.h +++ b/sdk/lib/conutils/screen.h @@ -1,16 +1,16 @@ /* - * COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS Console Utilities Library - * FILE: sdk/lib/conutils/screen.h - * PURPOSE: Console/terminal screen management. - * PROGRAMMERS: - Hermes Belusca-Maito (for the library); - * - All programmers who wrote the different console applications - * from which I took those functions and improved them. + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Console/terminal screen management. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito */ #ifndef __SCREEN_H__ #define __SCREEN_H__ +#pragma once + #ifndef _UNICODE #error The ConUtils library at the moment only supports compilation with _UNICODE defined! #endif @@ -60,4 +60,7 @@ ConClearScreen(IN PCON_SCREEN Screen); #ifdef __cplusplus } #endif + #endif /* __SCREEN_H__ */ + +/* EOF */ diff --git a/sdk/lib/conutils/stream.c b/sdk/lib/conutils/stream.c index f31d397c27c..5b843c183e8 100644 --- a/sdk/lib/conutils/stream.c +++ b/sdk/lib/conutils/stream.c @@ -1,13 +1,11 @@ /* - * COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS Console Utilities Library - * FILE: sdk/lib/conutils/stream.c - * PURPOSE: Provides basic abstraction wrappers around CRT streams or - * Win32 console API I/O functions, to deal with i18n + Unicode - * related problems. - * PROGRAMMERS: - Hermes Belusca-Maito (for the library); - * - All programmers who wrote the different console applications - * from which I took those functions and improved them. + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Provides basic abstraction wrappers around CRT streams or + * Win32 console API I/O functions, to deal with i18n + Unicode + * related problems. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito */ /* @@ -27,63 +25,22 @@ #include #endif /* USE_CRT */ -#include // limits.h // For MB_LEN_MAX - #include #include #include -#include // MAKEINTRESOURCEW, RT_STRING +// #include // MAKEINTRESOURCEW, RT_STRING #include // Console APIs (only if kernel32 support included) #include -/* PSEH for SEH Support */ -#include - #include "conutils.h" #include "stream.h" - - -// #define RC_STRING_MAX_SIZE 4096 -#define CON_RC_STRING_MAX_SIZE 4096 -// #define MAX_BUFFER_SIZE 4096 // Some programs (wlanconf, shutdown) set it to 5024 -// #define OUTPUT_BUFFER_SIZE 4096 // Name given in cmd/console.c -// MAX_STRING_SIZE // Name given in diskpart - -// #define MAX_MESSAGE_SIZE 512 // See shutdown... +#include "stream_private.h" /* * Console I/O streams */ -typedef struct _CON_STREAM -{ - CON_WRITE_FUNC WriteFunc; - -#ifdef USE_CRT - FILE* fStream; -#else - BOOL IsInitialized; - CRITICAL_SECTION Lock; - - HANDLE hHandle; - - /* - * TRUE if 'hHandle' refers to a console, in which case I/O UTF-16 - * is directly used. If 'hHandle' refers to a file or a pipe, the - * 'Mode' flag is used. - */ - BOOL IsConsole; - - /* - * The 'Mode' flag is used to know the translation mode - * when 'hHandle' refers to a file or a pipe. - */ - CON_STREAM_MODE Mode; - UINT CodePage; // Used to convert UTF-16 text to some ANSI codepage. -#endif /* defined(USE_CRT) */ -} CON_STREAM, *PCON_STREAM; - /* * Standard console streams, initialized by * calls to ConStreamInit/ConInitStdStreams. @@ -339,713 +296,4 @@ ConStreamSetOSHandle( #endif } - -/* - * Console I/O utility API - * (for the moment, only Output) - */ - -// NOTE: Should be called with the stream locked. -INT -__stdcall -ConWrite( - IN PCON_STREAM Stream, - IN PTCHAR szStr, - IN DWORD len) -{ -#ifndef USE_CRT - DWORD TotalLen = len, dwNumBytes = 0; - PVOID p; - - // CHAR strOem[CON_RC_STRING_MAX_SIZE]; // Some static buffer... - - /* If we do not write anything, just return */ - if (!szStr || len == 0) - return 0; - - /* Check whether we are writing to a console */ - // if (IsConsoleHandle(Stream->hHandle)) - if (Stream->IsConsole) - { - // TODO: Check if (ConStream->Mode == WideText or UTF16Text) ?? - - /* - * This code is inspired from _cputws, in particular from the fact that, - * according to MSDN: https://msdn.microsoft.com/en-us/library/ms687401(v=vs.85).aspx - * the buffer size must be less than 64 KB. - * - * A similar code can be used for implementing _cputs too. - */ - - DWORD cchWrite; - TotalLen = len, dwNumBytes = 0; - - while (len > 0) - { - cchWrite = min(len, 65535 / sizeof(WCHAR)); - - // FIXME: Check return value! - WriteConsole(Stream->hHandle, szStr, cchWrite, &dwNumBytes, NULL); - - szStr += cchWrite; - len -= cchWrite; - } - - return (INT)TotalLen; // FIXME: Really return the number of chars written! - } - - /* - * We are redirected and writing to a file or pipe instead of the console. - * Convert the string from TCHARs to the desired output format, if the two differ. - * - * Implementation NOTE: - * MultiByteToWideChar (resp. WideCharToMultiByte) are equivalent to - * OemToCharBuffW (resp. CharToOemBuffW), but the latters uselessly - * depend on user32.dll, while MultiByteToWideChar and WideCharToMultiByte - * only need kernel32.dll. - */ - if ((Stream->Mode == WideText) || (Stream->Mode == UTF16Text)) - { -#ifndef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16 - /* Convert from the current process/thread's codepage to UTF-16 */ - WCHAR *buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR)); - if (!buffer) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return 0; - } - len = (DWORD)MultiByteToWideChar(CP_THREAD_ACP, // CP_ACP, CP_OEMCP - 0, szStr, (INT)len, buffer, (INT)len); - szStr = (PVOID)buffer; -#else - /* - * Do not perform any conversion since we are already in UTF-16, - * that is the same encoding as the stream. - */ -#endif - - /* - * Find any newline character in the buffer, - * write the part BEFORE the newline, then write - * a carriage-return + newline, and then write - * the remaining part of the buffer. - * - * This fixes output in files and serial console. - */ - while (len > 0) - { - /* Loop until we find a \r or \n character */ - // FIXME: What about the pair \r\n ? - p = szStr; - while (len > 0 && *(PWCHAR)p != L'\r' && *(PWCHAR)p != L'\n') - { - /* Advance one character */ - p = (PVOID)((PWCHAR)p + 1); - len--; - } - - /* Write everything up to \r or \n */ - dwNumBytes = ((PWCHAR)p - (PWCHAR)szStr) * sizeof(WCHAR); - WriteFile(Stream->hHandle, szStr, dwNumBytes, &dwNumBytes, NULL); - - /* If we hit \r or \n ... */ - if (len > 0 && (*(PWCHAR)p == L'\r' || *(PWCHAR)p == L'\n')) - { - /* ... send a carriage-return + newline sequence and skip \r or \n */ - WriteFile(Stream->hHandle, L"\r\n", 2 * sizeof(WCHAR), &dwNumBytes, NULL); - szStr = (PVOID)((PWCHAR)p + 1); - len--; - } - } - -#ifndef _UNICODE - HeapFree(GetProcessHeap(), 0, buffer); -#endif - } - else if ((Stream->Mode == UTF8Text) || (Stream->Mode == AnsiText)) - { - CHAR *buffer; - - /* - * Resolve the codepage cache if it was not assigned yet - * (only if the stream is in ANSI mode; in UTF8 mode the - * codepage was already set to CP_UTF8). - */ - if (/*(Stream->Mode == AnsiText) &&*/ (Stream->CodePage == INVALID_CP)) - Stream->CodePage = GetConsoleOutputCP(); // CP_ACP, CP_OEMCP - -#ifdef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16 - /* Convert from UTF-16 to either UTF-8 or ANSI, using stream codepage */ - // NOTE: MB_LEN_MAX defined either in limits.h or in stdlib.h . - buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * MB_LEN_MAX); - if (!buffer) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return 0; - } - len = WideCharToMultiByte(Stream->CodePage, 0, - szStr, len, buffer, len * MB_LEN_MAX, - NULL, NULL); - szStr = (PVOID)buffer; -#else - /* - * Convert from the current process/thread's codepage to either - * UTF-8 or ANSI, using stream codepage. - * We need to perform a double conversion, by going through UTF-16. - */ - // TODO! - #error "Need to implement double conversion!" -#endif - - /* - * Find any newline character in the buffer, - * write the part BEFORE the newline, then write - * a carriage-return + newline, and then write - * the remaining part of the buffer. - * - * This fixes output in files and serial console. - */ - while (len > 0) - { - /* Loop until we find a \r or \n character */ - // FIXME: What about the pair \r\n ? - p = szStr; - while (len > 0 && *(PCHAR)p != '\r' && *(PCHAR)p != '\n') - { - /* Advance one character */ - p = (PVOID)((PCHAR)p + 1); - len--; - } - - /* Write everything up to \r or \n */ - dwNumBytes = ((PCHAR)p - (PCHAR)szStr) * sizeof(CHAR); - WriteFile(Stream->hHandle, szStr, dwNumBytes, &dwNumBytes, NULL); - - /* If we hit \r or \n ... */ - if (len > 0 && (*(PCHAR)p == '\r' || *(PCHAR)p == '\n')) - { - /* ... send a carriage-return + newline sequence and skip \r or \n */ - WriteFile(Stream->hHandle, "\r\n", 2, &dwNumBytes, NULL); - szStr = (PVOID)((PCHAR)p + 1); - len--; - } - } - -#ifdef _UNICODE - HeapFree(GetProcessHeap(), 0, buffer); -#else - // TODO! -#endif - } - else // if (Stream->Mode == Binary) - { - /* Directly output the string */ - WriteFile(Stream->hHandle, szStr, len, &dwNumBytes, NULL); - } - - // FIXME! - return (INT)TotalLen; - -#else /* defined(USE_CRT) */ - - DWORD total = len; - DWORD written = 0; - - /* If we do not write anything, just return */ - if (!szStr || len == 0) - return 0; - -#if 1 - /* - * There is no "counted" printf-to-stream or puts-like function, therefore - * we use this trick to output the counted string to the stream. - */ - while (1) - { - written = fwprintf(Stream->fStream, L"%.*s", total, szStr); - if (written < total) - { - /* - * Some embedded NULL or special character - * was encountered, print it apart. - */ - if (written == 0) - { - fputwc(*szStr, Stream->fStream); - written++; - } - - szStr += written; - total -= written; - } - else - { - break; - } - } - return (INT)len; -#else - /* ANSI text or Binary output only */ - _setmode(_fileno(Stream->fStream), _O_TEXT); // _O_BINARY - return fwrite(szStr, sizeof(*szStr), len, Stream->fStream); -#endif - -#endif /* defined(USE_CRT) */ -} - - -#define CON_STREAM_WRITE_CALL(Stream, Str, Len) \ - (Stream)->WriteFunc((Stream), (Str), (Len)); - -/* Lock the stream only in non-USE_CRT mode (otherwise use the CRT stream lock) */ -#ifndef USE_CRT - -#define CON_STREAM_WRITE2(Stream, Str, Len, RetLen) \ -do { \ - EnterCriticalSection(&(Stream)->Lock); \ - (RetLen) = CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ - LeaveCriticalSection(&(Stream)->Lock); \ -} while(0) - -#define CON_STREAM_WRITE(Stream, Str, Len) \ -do { \ - EnterCriticalSection(&(Stream)->Lock); \ - CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ - LeaveCriticalSection(&(Stream)->Lock); \ -} while(0) - -#else - -#define CON_STREAM_WRITE2(Stream, Str, Len, RetLen) \ -do { \ - (RetLen) = CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ -} while(0) - -#define CON_STREAM_WRITE(Stream, Str, Len) \ -do { \ - CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ -} while(0) - -#endif - - -INT -ConStreamWrite( - IN PCON_STREAM Stream, - IN PTCHAR szStr, - IN DWORD len) -{ - INT Len; - CON_STREAM_WRITE2(Stream, szStr, len, Len); - return Len; -} - -INT -ConPuts( - IN PCON_STREAM Stream, - IN LPWSTR szStr) -{ - INT Len; - - Len = wcslen(szStr); - CON_STREAM_WRITE2(Stream, szStr, Len, Len); - - /* Fixup returned length in case of errors */ - if (Len < 0) - Len = 0; - - return Len; -} - -INT -ConPrintfV( - IN PCON_STREAM Stream, - IN LPWSTR szStr, - IN va_list args) // arg_ptr -{ - INT Len; - WCHAR bufSrc[CON_RC_STRING_MAX_SIZE]; - - // Len = vfwprintf(Stream->fStream, szStr, args); // vfprintf for direct ANSI - - /* - * Reuse szStr as the pointer to end-of-string, to compute - * the string length instead of calling wcslen(). - */ - // StringCchVPrintfW(bufSrc, ARRAYSIZE(bufSrc), szStr, args); - // Len = wcslen(bufSrc); - StringCchVPrintfExW(bufSrc, ARRAYSIZE(bufSrc), &szStr, NULL, 0, szStr, args); - Len = szStr - bufSrc; - - CON_STREAM_WRITE2(Stream, bufSrc, Len, Len); - - /* Fixup returned length in case of errors */ - if (Len < 0) - Len = 0; - - return Len; -} - -INT -__cdecl -ConPrintf( - IN PCON_STREAM Stream, - IN LPWSTR szStr, - ...) -{ - INT Len; - va_list args; - - // Len = vfwprintf(Stream->fStream, szMsgBuf, args); // vfprintf for direct ANSI - - // StringCchPrintfW - va_start(args, szStr); - Len = ConPrintfV(Stream, szStr, args); - va_end(args); - - return Len; -} - -INT -ConResPutsEx( - IN PCON_STREAM Stream, - IN HINSTANCE hInstance OPTIONAL, - IN UINT uID) -{ - INT Len; - PWCHAR szStr = NULL; - - Len = K32LoadStringW(hInstance, uID, (PWSTR)&szStr, 0); - if (szStr && Len) - // Len = ConPuts(Stream, szStr); - CON_STREAM_WRITE2(Stream, szStr, Len, Len); - - /* Fixup returned length in case of errors */ - if (Len < 0) - Len = 0; - - return Len; -} - -INT -ConResPuts( - IN PCON_STREAM Stream, - IN UINT uID) -{ - return ConResPutsEx(Stream, NULL /*GetModuleHandleW(NULL)*/, uID); -} - -INT -ConResPrintfExV( - IN PCON_STREAM Stream, - IN HINSTANCE hInstance OPTIONAL, - IN UINT uID, - IN va_list args) // arg_ptr -{ - INT Len; - WCHAR bufSrc[CON_RC_STRING_MAX_SIZE]; - - // NOTE: We may use the special behaviour where nBufMaxSize == 0 - Len = K32LoadStringW(hInstance, uID, bufSrc, ARRAYSIZE(bufSrc)); - if (Len) - Len = ConPrintfV(Stream, bufSrc, args); - - return Len; -} - -INT -ConResPrintfV( - IN PCON_STREAM Stream, - IN UINT uID, - IN va_list args) // arg_ptr -{ - return ConResPrintfExV(Stream, NULL /*GetModuleHandleW(NULL)*/, uID, args); -} - -INT -__cdecl -ConResPrintfEx( - IN PCON_STREAM Stream, - IN HINSTANCE hInstance OPTIONAL, - IN UINT uID, - ...) -{ - INT Len; - va_list args; - - va_start(args, uID); - Len = ConResPrintfExV(Stream, hInstance, uID, args); - va_end(args); - - return Len; -} - -INT -__cdecl -ConResPrintf( - IN PCON_STREAM Stream, - IN UINT uID, - ...) -{ - INT Len; - va_list args; - - va_start(args, uID); - Len = ConResPrintfV(Stream, uID, args); - va_end(args); - - return Len; -} - -INT -ConMsgPuts( - IN PCON_STREAM Stream, - IN DWORD dwFlags, - IN LPCVOID lpSource OPTIONAL, - IN DWORD dwMessageId, - IN DWORD dwLanguageId) -{ - INT Len; - DWORD dwLength = 0; - LPWSTR lpMsgBuf = NULL; - - /* - * Sanitize dwFlags. This version always ignore explicitely the inserts - * as we emulate the behaviour of the *puts function. - */ - dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer. - dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS; // Ignore inserts for FormatMessage. - dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; - - dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK; - - /* - * Retrieve the message string without appending extra newlines. - * Wrap in SEH to protect from invalid string parameters. - */ - _SEH2_TRY - { - dwLength = FormatMessageW(dwFlags, - lpSource, - dwMessageId, - dwLanguageId, - (LPWSTR)&lpMsgBuf, - 0, NULL); - } - _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) - { - } - _SEH2_END; - - Len = (INT)dwLength; - - if (!lpMsgBuf) - { - // ASSERT(dwLength == 0); - } - else - { - // ASSERT(dwLength != 0); - - /* lpMsgBuf is NULL-terminated by FormatMessage */ - // Len = ConPuts(Stream, lpMsgBuf); - CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len); - - /* Fixup returned length in case of errors */ - if (Len < 0) - Len = 0; - - /* Free the buffer allocated by FormatMessage */ - LocalFree(lpMsgBuf); - } - - return Len; -} - -INT -ConMsgPrintf2V( - IN PCON_STREAM Stream, - IN DWORD dwFlags, - IN LPCVOID lpSource OPTIONAL, - IN DWORD dwMessageId, - IN DWORD dwLanguageId, - IN va_list args) // arg_ptr -{ - INT Len; - DWORD dwLength = 0; - LPWSTR lpMsgBuf = NULL; - - /* - * Sanitize dwFlags. This version always ignore explicitely the inserts. - * The string that we will return to the user will not be pre-formatted. - */ - dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer. - dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS; // Ignore inserts for FormatMessage. - dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; - - dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK; - - /* - * Retrieve the message string without appending extra newlines. - * Wrap in SEH to protect from invalid string parameters. - */ - _SEH2_TRY - { - dwLength = FormatMessageW(dwFlags, - lpSource, - dwMessageId, - dwLanguageId, - (LPWSTR)&lpMsgBuf, - 0, NULL); - } - _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) - { - } - _SEH2_END; - - Len = (INT)dwLength; - - if (!lpMsgBuf) - { - // ASSERT(dwLength == 0); - } - else - { - // ASSERT(dwLength != 0); - - /* lpMsgBuf is NULL-terminated by FormatMessage */ - Len = ConPrintfV(Stream, lpMsgBuf, args); - // CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len); - - /* Fixup returned length in case of errors */ - if (Len < 0) - Len = 0; - - /* Free the buffer allocated by FormatMessage */ - LocalFree(lpMsgBuf); - } - - return Len; -} - -INT -ConMsgPrintfV( - IN PCON_STREAM Stream, - IN DWORD dwFlags, - IN LPCVOID lpSource OPTIONAL, - IN DWORD dwMessageId, - IN DWORD dwLanguageId, - IN va_list args) // arg_ptr -{ - INT Len; - DWORD dwLength = 0; - LPWSTR lpMsgBuf = NULL; - - /* Sanitize dwFlags */ - dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer. -// dwFlags &= ~FORMAT_MESSAGE_IGNORE_INSERTS; // We always use arguments. - dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; // We always use arguments of type 'va_list'. - - // - // NOTE: Technique taken from eventvwr.c!GetMessageStringFromDll() - // - - dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK; - - /* - * Retrieve the message string without appending extra newlines. - * Use the "safe" FormatMessage version (SEH-protected) to protect - * from invalid string parameters. - */ - dwLength = FormatMessageSafeW(dwFlags, - lpSource, - dwMessageId, - dwLanguageId, - (LPWSTR)&lpMsgBuf, - 0, &args); - - Len = (INT)dwLength; - - if (!lpMsgBuf) - { - // ASSERT(dwLength == 0); - } - else - { - // ASSERT(dwLength != 0); - - // Len = ConPrintfV(Stream, lpMsgBuf, args); - CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len); - - /* Fixup returned length in case of errors */ - if (Len < 0) - Len = 0; - - /* Free the buffer allocated by FormatMessage */ - LocalFree(lpMsgBuf); - } - - return Len; -} - -INT -__cdecl -ConMsgPrintf( - IN PCON_STREAM Stream, - IN DWORD dwFlags, - IN LPCVOID lpSource OPTIONAL, - IN DWORD dwMessageId, - IN DWORD dwLanguageId, - ...) -{ - INT Len; - va_list args; - - va_start(args, dwLanguageId); - // ConMsgPrintf2V - Len = ConMsgPrintfV(Stream, - dwFlags, - lpSource, - dwMessageId, - dwLanguageId, - args); - va_end(args); - - return Len; -} - - - -VOID -ConClearLine(IN PCON_STREAM Stream) -{ - HANDLE hOutput = ConStreamGetOSHandle(Stream); - - /* - * Erase the full line where the cursor is, and move - * the cursor back to the beginning of the line. - */ - - if (IsConsoleHandle(hOutput)) - { - CONSOLE_SCREEN_BUFFER_INFO csbi; - DWORD dwWritten; - - GetConsoleScreenBufferInfo(hOutput, &csbi); - - csbi.dwCursorPosition.X = 0; - // csbi.dwCursorPosition.Y; - - FillConsoleOutputCharacterW(hOutput, L' ', - csbi.dwSize.X, - csbi.dwCursorPosition, - &dwWritten); - SetConsoleCursorPosition(hOutput, csbi.dwCursorPosition); - } - else if (IsTTYHandle(hOutput)) - { - ConPuts(Stream, L"\x1B[2K\x1B[1G"); // FIXME: Just use WriteFile - } - // else, do nothing for files -} - +/* EOF */ diff --git a/sdk/lib/conutils/stream.h b/sdk/lib/conutils/stream.h index 7b6038705b2..f3804b61034 100644 --- a/sdk/lib/conutils/stream.h +++ b/sdk/lib/conutils/stream.h @@ -1,18 +1,18 @@ /* - * COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS Console Utilities Library - * FILE: sdk/lib/conutils/stream.h - * PURPOSE: Provides basic abstraction wrappers around CRT streams or - * Win32 console API I/O functions, to deal with i18n + Unicode - * related problems. - * PROGRAMMERS: - Hermes Belusca-Maito (for the library); - * - All programmers who wrote the different console applications - * from which I took those functions and improved them. + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Provides basic abstraction wrappers around CRT streams or + * Win32 console API I/O functions, to deal with i18n + Unicode + * related problems. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito */ #ifndef __STREAM_H__ #define __STREAM_H__ +#pragma once + /* * Enable this define if you want to only use CRT functions to output * UNICODE stream to the console, as in the way explained by @@ -152,124 +152,10 @@ ConStreamSetOSHandle( IN HANDLE Handle); -/* - * Console I/O utility API - * (for the moment, only Output) - */ - -INT -__stdcall -ConWrite( - IN PCON_STREAM Stream, - IN PTCHAR szStr, - IN DWORD len); - -INT -ConStreamWrite( - IN PCON_STREAM Stream, - IN PTCHAR szStr, - IN DWORD len); - -INT -ConPuts( - IN PCON_STREAM Stream, - IN LPWSTR szStr); - -INT -ConPrintfV( - IN PCON_STREAM Stream, - IN LPWSTR szStr, - IN va_list args); // arg_ptr - -INT -__cdecl -ConPrintf( - IN PCON_STREAM Stream, - IN LPWSTR szStr, - ...); - -INT -ConResPutsEx( - IN PCON_STREAM Stream, - IN HINSTANCE hInstance OPTIONAL, - IN UINT uID); - -INT -ConResPuts( - IN PCON_STREAM Stream, - IN UINT uID); - -INT -ConResPrintfExV( - IN PCON_STREAM Stream, - IN HINSTANCE hInstance OPTIONAL, - IN UINT uID, - IN va_list args); // arg_ptr - -INT -ConResPrintfV( - IN PCON_STREAM Stream, - IN UINT uID, - IN va_list args); // arg_ptr - -INT -__cdecl -ConResPrintfEx( - IN PCON_STREAM Stream, - IN HINSTANCE hInstance OPTIONAL, - IN UINT uID, - ...); - -INT -__cdecl -ConResPrintf( - IN PCON_STREAM Stream, - IN UINT uID, - ...); - -INT -ConMsgPuts( - IN PCON_STREAM Stream, - IN DWORD dwFlags, - IN LPCVOID lpSource OPTIONAL, - IN DWORD dwMessageId, - IN DWORD dwLanguageId); - -INT -ConMsgPrintf2V( - IN PCON_STREAM Stream, - IN DWORD dwFlags, - IN LPCVOID lpSource OPTIONAL, - IN DWORD dwMessageId, - IN DWORD dwLanguageId, - IN va_list args); // arg_ptr - -INT -ConMsgPrintfV( - IN PCON_STREAM Stream, - IN DWORD dwFlags, - IN LPCVOID lpSource OPTIONAL, - IN DWORD dwMessageId, - IN DWORD dwLanguageId, - IN va_list args); // arg_ptr - -INT -__cdecl -ConMsgPrintf( - IN PCON_STREAM Stream, - IN DWORD dwFlags, - IN LPCVOID lpSource OPTIONAL, - IN DWORD dwMessageId, - IN DWORD dwLanguageId, - ...); - - - -VOID -ConClearLine(IN PCON_STREAM Stream); - - #ifdef __cplusplus } #endif + #endif /* __STREAM_H__ */ + +/* EOF */ diff --git a/sdk/lib/conutils/stream_private.h b/sdk/lib/conutils/stream_private.h new file mode 100644 index 00000000000..6b801dfb234 --- /dev/null +++ b/sdk/lib/conutils/stream_private.h @@ -0,0 +1,55 @@ +/* + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Provides basic abstraction wrappers around CRT streams or + * Win32 console API I/O functions, to deal with i18n + Unicode + * related problems. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito + */ + +#ifndef __STREAM_PRIVATE_H__ +#define __STREAM_PRIVATE_H__ + +#pragma once + +/* + * Console I/O streams + */ + +#if 0 +// Shadow type, implementation-specific +typedef struct _CON_STREAM CON_STREAM, *PCON_STREAM; +#endif + +typedef struct _CON_STREAM +{ + CON_WRITE_FUNC WriteFunc; + +#ifdef USE_CRT + FILE* fStream; +#else + BOOL IsInitialized; + CRITICAL_SECTION Lock; + + HANDLE hHandle; + + /* + * TRUE if 'hHandle' refers to a console, in which case I/O UTF-16 + * is directly used. If 'hHandle' refers to a file or a pipe, the + * 'Mode' flag is used. + */ + BOOL IsConsole; + + /* + * The 'Mode' flag is used to know the translation mode + * when 'hHandle' refers to a file or a pipe. + */ + CON_STREAM_MODE Mode; + UINT CodePage; // Used to convert UTF-16 text to some ANSI codepage. +#endif /* defined(USE_CRT) */ +} CON_STREAM, *PCON_STREAM; + +#endif /* __STREAM_PRIVATE_H__ */ + +/* EOF */ diff --git a/sdk/lib/conutils/utils.c b/sdk/lib/conutils/utils.c index b46ee911688..b5dfdeefd14 100644 --- a/sdk/lib/conutils/utils.c +++ b/sdk/lib/conutils/utils.c @@ -1,12 +1,10 @@ /* - * COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS Console Utilities Library - * FILE: sdk/lib/conutils/utils.c - * PURPOSE: Base set of functions for loading string resources - * and message strings, and handle type identification. - * PROGRAMMERS: - Hermes Belusca-Maito (for the library); - * - All programmers who wrote the different console applications - * from which I took those functions and improved them. + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Base set of functions for loading string resources + * and message strings, and handle type identification. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito */ /* FIXME: Temporary HACK before we cleanly support UNICODE functions */ @@ -232,3 +230,5 @@ IsConsoleHandle(IN HANDLE hHandle) */ return GetConsoleMode(hHandle, &dwMode); } + +/* EOF */ diff --git a/sdk/lib/conutils/utils.h b/sdk/lib/conutils/utils.h index 52063c25bf4..9f414ab0a84 100644 --- a/sdk/lib/conutils/utils.h +++ b/sdk/lib/conutils/utils.h @@ -1,17 +1,17 @@ /* - * COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS Console Utilities Library - * FILE: sdk/lib/conutils/utils.h - * PURPOSE: Base set of functions for loading string resources - * and message strings, and handle type identification. - * PROGRAMMERS: - Hermes Belusca-Maito (for the library); - * - All programmers who wrote the different console applications - * from which I took those functions and improved them. + * PROJECT: ReactOS Console Utilities Library + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Base set of functions for loading string resources + * and message strings, and handle type identification. + * COPYRIGHT: Copyright 2017-2018 ReactOS Team + * Copyright 2017-2018 Hermes Belusca-Maito */ #ifndef __UTILS_H__ #define __UTILS_H__ +#pragma once + #ifndef _UNICODE #error The ConUtils library at the moment only supports compilation with _UNICODE defined! #endif @@ -57,4 +57,7 @@ IsConsoleHandle(IN HANDLE hHandle); #ifdef __cplusplus } #endif + #endif /* __UTILS_H__ */ + +/* EOF */