From 533d9d5f59eada8c4984095c97422b14f4362edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herm=C3=A8s=20B=C3=A9lusca-Ma=C3=AFto?= Date: Mon, 17 Oct 2016 22:19:22 +0000 Subject: [PATCH] [MODE]: Numerous fixes for the MODE utility: - Parse the command line the same peculiar way as Windows' mode.com tool. This is certainly done such as to be backwards-compatible with MS-DOS' tool. - Fix serial port string parsing, based on Wine's dll/win32/kernel32/wine/comm.c code. Note that the BuildCommDCB kernel32 API has the same syntax :) - Implement support for MODE CON codepage, as well as screen size and typematic settings. Screen size code is based on NTVDM's one and will be subject to fixes later on. MISSING stuff: - LPT/COM port redirection. - Localization :) CORE-12176 #resolve CORE-7998 CORE-8001 svn path=/trunk/; revision=72980 --- .../applications/cmdutils/mode/CMakeLists.txt | 2 +- .../base/applications/cmdutils/mode/mode.c | 1272 +++++++++++++---- 2 files changed, 967 insertions(+), 307 deletions(-) diff --git a/reactos/base/applications/cmdutils/mode/CMakeLists.txt b/reactos/base/applications/cmdutils/mode/CMakeLists.txt index bf53b6af767..f6466a13e38 100644 --- a/reactos/base/applications/cmdutils/mode/CMakeLists.txt +++ b/reactos/base/applications/cmdutils/mode/CMakeLists.txt @@ -1,6 +1,6 @@ add_executable(mode mode.c mode.rc) set_module_type(mode win32cui UNICODE) -set_target_properties(mode PROPERTIES SUFFIX ".com") add_importlibs(mode user32 msvcrt kernel32) +set_target_properties(mode PROPERTIES SUFFIX ".com") add_cd_file(TARGET mode DESTINATION reactos/system32 FOR all) diff --git a/reactos/base/applications/cmdutils/mode/mode.c b/reactos/base/applications/cmdutils/mode/mode.c index 56de0b81075..a60729ada2f 100644 --- a/reactos/base/applications/cmdutils/mode/mode.c +++ b/reactos/base/applications/cmdutils/mode/mode.c @@ -19,16 +19,24 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* + * COPYRIGHT: See COPYING in the top level directory + * PROJECT: ReactOS Mode Utility + * FILE: base/applications/cmdutils/mode/mode.c + * PURPOSE: Provides fast mode setup for DOS devices. + * PROGRAMMERS: Robert Dickenson + * Hermes Belusca-Maito + */ + +#include #include #include #include #include -#include #define MAX_PORTNAME_LEN 20 #define MAX_COMPORT_NUM 10 -#define MAX_COMPARAM_LEN 20 #define ASSERT(a) @@ -46,23 +54,9 @@ const WCHAR* const usage_strings[] = L" [rts=on|off|hs|tg] [idsr=on|off]", }; -const WCHAR* const parity_strings[] = +void Usage(void) { - L"None", // default - L"Odd", // only symbol in this set to have a 'd' in it - L"Even", // ... 'v' in it - L"Mark", // ... 'm' in it - L"Space" // ... 's' and/or a 'c' in it -}; - -const WCHAR* const control_strings[] = { L"OFF", L"ON", L"HANDSHAKE", L"TOGGLE" }; - -const WCHAR* const stopbit_strings[] = { L"1", L"1.5", L"2" }; - - -int Usage() -{ - int i; + UINT i; wprintf(L"\nConfigures system devices.\n\n"); for (i = 0; i < ARRAYSIZE(usage_strings); i++) @@ -70,21 +64,19 @@ int Usage() wprintf(L"%s\n", usage_strings[i]); } wprintf(L"\n"); - return 0; } -int QueryDevices() +int QueryDevices(VOID) { WCHAR buffer[20240]; - int len; WCHAR* ptr = buffer; *ptr = L'\0'; + // FIXME: Dynamically allocate 'buffer' in a loop. if (QueryDosDeviceW(NULL, buffer, ARRAYSIZE(buffer))) { while (*ptr != L'\0') { - len = wcslen(ptr); if (wcsstr(ptr, L"COM")) { wprintf(L" Found serial device - %s\n", ptr); @@ -101,7 +93,7 @@ int QueryDevices() { // wprintf(L" Found other device - %s\n", ptr); } - ptr += (len+1); + ptr += (wcslen(ptr) + 1); } } else @@ -111,7 +103,7 @@ int QueryDevices() return 1; } -int ShowParallelStatus(int nPortNum) +int ShowParallelStatus(INT nPortNum) { WCHAR buffer[250]; WCHAR szPortName[MAX_PORTNAME_LEN]; @@ -124,7 +116,7 @@ int ShowParallelStatus(int nPortNum) WCHAR* ptr = wcsrchr(buffer, L'\\'); if (ptr != NULL) { - if (0 == wcscmp(szPortName, ++ptr)) + if (_wcsicmp(szPortName, ++ptr) == 0) { wprintf(L" Printer output is not being rerouted.\n"); } @@ -146,12 +138,60 @@ int ShowParallelStatus(int nPortNum) return 1; } -int ShowConsoleStatus() +int SetParallelState(INT nPortNum) +{ + WCHAR szPortName[MAX_PORTNAME_LEN]; + WCHAR szTargetPath[MAX_PORTNAME_LEN]; + + swprintf(szPortName, L"LPT%d", nPortNum); + swprintf(szTargetPath, L"COM%d", nPortNum); + if (!DefineDosDeviceW(DDD_REMOVE_DEFINITION, szPortName, szTargetPath)) + { + wprintf(L"SetParallelState(%d) - DefineDosDevice(%s) failed: 0x%lx\n", nPortNum, szPortName, GetLastError()); + } + + ShowParallelStatus(nPortNum); + return 0; +} + + +static PCWSTR +ParseNumber(PCWSTR argStr, PDWORD Number) +{ + INT value, skip = 0; + + value = swscanf(argStr, L"%lu%n", Number, &skip); + if (!value) return NULL; + argStr += skip; + return argStr; +} + + +/* + \??\COM1 + \Device\NamedPipe\Spooler\LPT1 +BOOL DefineDosDevice( + DWORD dwFlags, // options + LPCTSTR lpDeviceName, // device name + LPCTSTR lpTargetPath // path string +); +DWORD QueryDosDevice( + LPCTSTR lpDeviceName, // MS-DOS device name string + LPTSTR lpTargetPath, // query results buffer + DWORD ucchMax // maximum size of buffer +); + */ + + +/*****************************************************************************\ + ** C O N S O L E H E L P E R S ** +\*****************************************************************************/ + +int ShowConsoleStatus(VOID) { - DWORD dwKbdDelay; - DWORD dwKbdSpeed; - CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo; HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo; + DWORD dwKbdDelay, dwKbdSpeed; wprintf(L"\nStatus for device CON:\n"); wprintf(L"-----------------------\n"); @@ -160,11 +200,11 @@ int ShowConsoleStatus() wprintf(L" Lines: %d\n", ConsoleScreenBufferInfo.dwSize.Y); wprintf(L" Columns: %d\n", ConsoleScreenBufferInfo.dwSize.X); } - if (SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &dwKbdDelay, 0)) + if (SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &dwKbdDelay, 0)) { wprintf(L" Keyboard delay: %ld\n", dwKbdDelay); } - if (SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &dwKbdSpeed, 0)) + if (SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &dwKbdSpeed, 0)) { wprintf(L" Keyboard rate: %ld\n", dwKbdSpeed); } @@ -172,10 +212,292 @@ int ShowConsoleStatus() return 0; } -static -BOOL SerialPortQuery(int nPortNum, LPDCB pDCB, LPCOMMTIMEOUTS pCommTimeouts, BOOL bWrite) +int ShowConsoleCPStatus(VOID) { - BOOL result; + wprintf(L"\nStatus for device CON:\n"); + wprintf(L"-----------------------\n"); + wprintf(L" Code page: %d\n", GetConsoleOutputCP()); + return 0; +} + +/* + * See, or adjust if needed, subsystems/mvdm/ntvdm/console/video.c!ResizeTextConsole() + * for more information. + */ +static VOID +ResizeTextConsole( + HANDLE hConsoleOutput, + PCONSOLE_SCREEN_BUFFER_INFO pcsbi, // FIXME + PCOORD Resolution, + PSMALL_RECT WindowSize OPTIONAL) +{ + BOOL Success; + SMALL_RECT ConRect; + SHORT oldWidth, oldHeight; + + /* + * Use this trick to effectively resize the console buffer and window, + * because: + * - SetConsoleScreenBufferSize fails if the new console screen buffer size + * is smaller than the current console window size, and: + * - SetConsoleWindowInfo fails if the new console window size is larger + * than the current console screen buffer size. + */ + + + // /* Retrieve the latest console information */ + // GetConsoleScreenBufferInfo(hConsoleOutput, &ConsoleInfo); + + oldWidth = pcsbi->srWindow.Right - pcsbi->srWindow.Left + 1; + oldHeight = pcsbi->srWindow.Bottom - pcsbi->srWindow.Top + 1; + + /* + * If the current console window is too large to hold the full contents + * of the new screen buffer, resize it first. + */ + if (oldWidth > Resolution->X || oldHeight > Resolution->Y) + { + // + // NOTE: This is not a problem if we move the window back to (0,0) + // because when we resize the screen buffer, the window will move back + // to where the cursor is. Or, if the screen buffer is not resized, + // when we readjust again the window, we will move back to a correct + // position. This is what we wanted after all... + // + + ConRect.Left = ConRect.Top = 0; + ConRect.Right = ConRect.Left + min(oldWidth , Resolution->X) - 1; + ConRect.Bottom = ConRect.Top + min(oldHeight, Resolution->Y) - 1; + + Success = SetConsoleWindowInfo(hConsoleOutput, TRUE, &ConRect); + if (!Success) wprintf(L"(resize) SetConsoleWindowInfo(1) failed with error %d\n", GetLastError()); + } + + /* Resize the screen buffer if needed */ + if (Resolution->X != pcsbi->dwSize.X || Resolution->Y != pcsbi->dwSize.Y) + { + /* + * SetConsoleScreenBufferSize automatically takes into account the current + * cursor position when it computes starting which row it should copy text + * when resizing the sceenbuffer, and scrolls the console window such that + * the cursor is placed in it again. We therefore do not need to care about + * the cursor position and do the maths ourselves. + */ + Success = SetConsoleScreenBufferSize(hConsoleOutput, *Resolution); + if (!Success) wprintf(L"(resize) SetConsoleScreenBufferSize failed with error %d\n", GetLastError()); + + /* + * Setting a new screen buffer size can change other information, + * so update the saved console information. + */ + GetConsoleScreenBufferInfo(hConsoleOutput, pcsbi); + } + + if (!WindowSize) + { + ConRect.Left = 0; + ConRect.Right = ConRect.Left + Resolution->X - 1; + ConRect.Bottom = max(pcsbi->dwCursorPosition.Y, Resolution->Y - 1); + ConRect.Top = ConRect.Bottom - Resolution->Y + 1; + + // NOTE: We may take pcsbi->dwMaximumWindowSize into account + } + else + { + ConRect.Left = ConRect.Top = 0; + ConRect.Right = ConRect.Left + WindowSize->Right - WindowSize->Left; + ConRect.Bottom = ConRect.Top + WindowSize->Bottom - WindowSize->Top ; + } + + Success = SetConsoleWindowInfo(hConsoleOutput, TRUE, &ConRect); + if (!Success) wprintf(L"(resize) SetConsoleWindowInfo(2) failed with error %d\n", GetLastError()); +} + +int SetConsoleStateOld(IN PCWSTR ArgStr) +{ + PCWSTR argStr = ArgStr; + + HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo; + COORD dwSize; + DWORD value; + + if (!GetConsoleScreenBufferInfo(hConsoleOutput, &ConsoleScreenBufferInfo)) + { + // TODO: Error message? + return 0; + } + + dwSize = ConsoleScreenBufferInfo.dwSize; + + /* Parse the column number (only MANDATORY argument) */ + value = 0; + argStr = ParseNumber(argStr, &value); + if (!argStr) goto invalid_parameter; + dwSize.X = (SHORT)value; + + /* Parse the line number (OPTIONAL argument) */ + while (*argStr == L' ') argStr++; + if (!*argStr) goto Quit; + if (*argStr++ != L',') goto invalid_parameter; + while (*argStr == L' ') argStr++; + + value = 0; + argStr = ParseNumber(argStr, &value); + if (!argStr) goto invalid_parameter; + dwSize.Y = (SHORT)value; + + /* This should be the end of the string */ + if (*argStr) argStr++; + while (*argStr == L' ') argStr++; + if (*argStr) goto invalid_parameter; + +Quit: + /* + * See, or adjust if needed, subsystems/mvdm/ntvdm/console/video.c!ResizeTextConsole() + * for more information. + */ + ResizeTextConsole(hConsoleOutput, &ConsoleScreenBufferInfo, + &dwSize, NULL); + return 0; + +invalid_parameter: + wprintf(L"Invalid parameter - %s\n", ArgStr); + return 1; +} + +int SetConsoleState(IN PCWSTR ArgStr) +{ + PCWSTR argStr = ArgStr; + BOOL dispMode = FALSE, kbdMode = FALSE; + + HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo; + COORD dwSize; + DWORD dwKbdDelay, dwKbdSpeed; + DWORD value; + + if (!GetConsoleScreenBufferInfo(hConsoleOutput, &ConsoleScreenBufferInfo)) + { + // TODO: Error message? + return 0; + } + if (!SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &dwKbdDelay, 0)) + { + // TODO: Error message? + return 0; + } + if (!SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &dwKbdSpeed, 0)) + { + // TODO: Error message? + return 0; + } + + dwSize = ConsoleScreenBufferInfo.dwSize; + + while (argStr && *argStr) + { + while (*argStr == L' ') argStr++; + if (!*argStr) break; + + if (!kbdMode && _wcsnicmp(argStr, L"COLS=", 5) == 0) + { + dispMode = TRUE; + + value = 0; + argStr = ParseNumber(argStr+5, &value); + if (!argStr) goto invalid_parameter; + dwSize.X = (SHORT)value; + } + else if (!kbdMode && _wcsnicmp(argStr, L"LINES=", 6) == 0) + { + dispMode = TRUE; + + value = 0; + argStr = ParseNumber(argStr+6, &value); + if (!argStr) goto invalid_parameter; + dwSize.Y = (SHORT)value; + } + else if (!dispMode && _wcsnicmp(argStr, L"RATE=", 5) == 0) + { + kbdMode = TRUE; + + argStr = ParseNumber(argStr+5, &dwKbdSpeed); + if (!argStr) goto invalid_parameter; + } + else if (!dispMode && _wcsnicmp(argStr, L"DELAY=", 6) == 0) + { + kbdMode = TRUE; + + argStr = ParseNumber(argStr+6, &dwKbdDelay); + if (!argStr) goto invalid_parameter; + } + else + { +invalid_parameter: + wprintf(L"Invalid parameter - %s\n", ArgStr); + return 1; + } + } + + if (dispMode) + { + /* + * See, or adjust if needed, subsystems/mvdm/ntvdm/console/video.c!ResizeTextConsole() + * for more information. + */ + ResizeTextConsole(hConsoleOutput, &ConsoleScreenBufferInfo, + &dwSize, NULL); + } + else if (kbdMode) + { + /* + * Set the new keyboard settings. If those values are greater than + * their allowed range, they are automatically corrected as follows: + * dwKbdSpeed = min(dwKbdSpeed, 31); + * dwKbdDelay = (dwKbdDelay % 4); + */ + SystemParametersInfoW(SPI_SETKEYBOARDDELAY, dwKbdDelay, NULL, 0); + SystemParametersInfoW(SPI_SETKEYBOARDSPEED, dwKbdSpeed, NULL, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE); + } + + return 0; +} + +int SetConsoleCPState(IN PCWSTR ArgStr) +{ + PCWSTR argStr = ArgStr; + DWORD CodePage = 0; + + if ( (_wcsnicmp(argStr, L"SELECT=", 7) == 0 && (argStr += 7)) || + (_wcsnicmp(argStr, L"SEL=", 4) == 0 && (argStr += 4)) ) + { + argStr = ParseNumber(argStr, &CodePage); + if (!argStr) goto invalid_parameter; + + SetConsoleCP(CodePage); + SetConsoleOutputCP(CodePage); + ShowConsoleCPStatus(); + } + else + { +invalid_parameter: + wprintf(L"Invalid parameter - %s\n", ArgStr); + return 1; + } + + return 0; +} + + +/*****************************************************************************\ + ** S E R I A L P O R T H E L P E R S ** +\*****************************************************************************/ + +static BOOL +SerialPortQuery(INT nPortNum, LPDCB pDCB, LPCOMMTIMEOUTS pCommTimeouts, BOOL bWrite) +{ + BOOL Success; HANDLE hPort; WCHAR szPortName[MAX_PORTNAME_LEN]; @@ -184,7 +506,7 @@ BOOL SerialPortQuery(int nPortNum, LPDCB pDCB, LPCOMMTIMEOUTS pCommTimeouts, BOO swprintf(szPortName, L"COM%d", nPortNum); hPort = CreateFileW(szPortName, - GENERIC_READ | GENERIC_WRITE, + bWrite ? GENERIC_WRITE : GENERIC_READ, 0, // exclusive NULL, // sec attr OPEN_EXISTING, @@ -198,29 +520,40 @@ BOOL SerialPortQuery(int nPortNum, LPDCB pDCB, LPCOMMTIMEOUTS pCommTimeouts, BOO return FALSE; } - result = bWrite ? SetCommState(hPort, pDCB) - : GetCommState(hPort, pDCB); - if (!result) + Success = bWrite ? SetCommState(hPort, pDCB) + : GetCommState(hPort, pDCB); + if (!Success) { wprintf(L"Failed to %s the status for device COM%d:\n", bWrite ? L"set" : L"get", nPortNum); - CloseHandle(hPort); - return FALSE; + goto Quit; } - result = bWrite ? SetCommTimeouts(hPort, pCommTimeouts) - : GetCommTimeouts(hPort, pCommTimeouts); - if (!result) + Success = bWrite ? SetCommTimeouts(hPort, pCommTimeouts) + : GetCommTimeouts(hPort, pCommTimeouts); + if (!Success) { - wprintf(L"Failed to %s Timeout status for device COM%d:\n", bWrite ? L"set" : L"get", nPortNum); - CloseHandle(hPort); - return FALSE; + wprintf(L"Failed to %s timeout status for device COM%d:\n", bWrite ? L"set" : L"get", nPortNum); + goto Quit; } + +Quit: CloseHandle(hPort); - return TRUE; + return Success; } -int ShowSerialStatus(int nPortNum) +int ShowSerialStatus(INT nPortNum) { + static const LPCWSTR parity_strings[] = + { + L"None", // NOPARITY + L"Odd", // ODDPARITY + L"Even", // EVENPARITY + L"Mark", // MARKPARITY + L"Space" // SPACEPARITY + }; + static const LPCWSTR control_strings[] = { L"OFF", L"ON", L"HANDSHAKE", L"TOGGLE" }; + static const LPCWSTR stopbit_strings[] = { L"1", L"1.5", L"2" }; + DCB dcb; COMMTIMEOUTS CommTimeouts; @@ -244,7 +577,9 @@ int ShowSerialStatus(int nPortNum) wprintf(L" Parity: %s\n", parity_strings[dcb.Parity]); wprintf(L" Data Bits: %d\n", dcb.ByteSize); wprintf(L" Stop Bits: %s\n", stopbit_strings[dcb.StopBits]); - wprintf(L" Timeout: %s\n", CommTimeouts.ReadIntervalTimeout ? L"ON" : L"OFF"); + wprintf(L" Timeout: %s\n", + (CommTimeouts.ReadTotalTimeoutConstant != 0) || + (CommTimeouts.WriteTotalTimeoutConstant != 0) ? L"ON" : L"OFF"); wprintf(L" XON/XOFF: %s\n", dcb.fOutX ? L"ON" : L"OFF"); wprintf(L" CTS handshaking: %s\n", dcb.fOutxCtsFlow ? L"ON" : L"OFF"); wprintf(L" DSR handshaking: %s\n", dcb.fOutxDsrFlow ? L"ON" : L"OFF"); @@ -254,300 +589,625 @@ int ShowSerialStatus(int nPortNum) return 0; } -int SetParallelState(int nPortNum) -{ - WCHAR szPortName[MAX_PORTNAME_LEN]; - WCHAR szTargetPath[MAX_PORTNAME_LEN]; - swprintf(szPortName, L"LPT%d", nPortNum); - swprintf(szTargetPath, L"COM%d", nPortNum); - if (!DefineDosDeviceW(DDD_REMOVE_DEFINITION, szPortName, szTargetPath)) +/* + * Those procedures are inspired from Wine's dll/win32/kernel32/wine/comm.c + * Copyright 1996 Erik Bos and Marcus Meissner. + */ + +static PCWSTR +ParseModes(PCWSTR argStr, PBYTE Mode) +{ + if (_wcsnicmp(argStr, L"OFF", 3) == 0) { - wprintf(L"SetParallelState(%d) - DefineDosDevice(%s) failed: 0x%lx\n", nPortNum, szPortName, GetLastError()); + argStr += 3; + *Mode = 0; } - return 0; -} - -/* - \??\COM1 - \Device\NamedPipe\Spooler\LPT1 -BOOL DefineDosDevice( - DWORD dwFlags, // options - LPCTSTR lpDeviceName, // device name - LPCTSTR lpTargetPath // path string -); -DWORD QueryDosDevice( - LPCTSTR lpDeviceName, // MS-DOS device name string - LPTSTR lpTargetPath, // query results buffer - DWORD ucchMax // maximum size of buffer -); - */ - -int SetConsoleState() -{ -/* - "Select code page: MODE CON[:] CP SELECT=yyy", - "Code page status: MODE CON[:] CP [/STATUS]", - "Display mode: MODE CON[:] [COLS=c] [LINES=n]", - "Typematic rate: MODE CON[:] [RATE=r DELAY=d]", - */ - return 0; -} - -static -int ExtractModeSerialParams(const WCHAR* param) -{ - if (wcsstr(param, L"OFF")) - return 0; - else if (wcsstr(param, L"ON")) - return 1; - else if (wcsstr(param, L"HS")) - return 2; - else if (wcsstr(param, L"TG")) - return 3; - - return -1; -} - -int SetSerialState(int nPortNum, int args, WCHAR *argv[]) -{ - int arg; - int value; - DCB dcb; - COMMTIMEOUTS CommTimeouts; - WCHAR buf[MAX_COMPARAM_LEN+1]; - - if (SerialPortQuery(nPortNum, &dcb, &CommTimeouts, FALSE)) + else if (_wcsnicmp(argStr, L"ON", 2) == 0) { - for (arg = 2; arg < args; arg++) + argStr += 2; + *Mode = 1; + } + else if (_wcsnicmp(argStr, L"HS", 2) == 0) + { + argStr += 2; + *Mode = 2; + } + else if (_wcsnicmp(argStr, L"TG", 2) == 0) + { + argStr += 2; + *Mode = 3; + } + + return NULL; +} + +static PCWSTR +ParseBaudRate(PCWSTR argStr, PDWORD BaudRate) +{ + argStr = ParseNumber(argStr, BaudRate); + if (!argStr) return NULL; + + /* + * Check for Baud Rate abbreviations. This means that using + * those values as real baud rates is impossible using MODE. + */ + switch (*BaudRate) + { + /* BaudRate = 110, 150, 300, 600 */ + case 11: case 15: case 30: case 60: + *BaudRate *= 10; + break; + + /* BaudRate = 1200, 2400, 4800, 9600 */ + case 12: case 24: case 48: case 96: + *BaudRate *= 100; + break; + + case 19: + *BaudRate = 19200; + break; + } + + return argStr; +} + +static PCWSTR +ParseParity(PCWSTR argStr, PBYTE Parity) +{ + switch (towupper(*argStr++)) + { + case L'N': + *Parity = NOPARITY; + break; + + case L'O': + *Parity = ODDPARITY; + break; + + case L'E': + *Parity = EVENPARITY; + break; + + case L'M': + *Parity = MARKPARITY; + break; + + case L'S': + *Parity = SPACEPARITY; + break; + + default: + return NULL; + } + + return argStr; +} + +static PCWSTR +ParseByteSize(PCWSTR argStr, PBYTE ByteSize) +{ + DWORD value = 0; + + argStr = ParseNumber(argStr, &value); + if (!argStr) return NULL; + + *ByteSize = (BYTE)value; + if (*ByteSize < 5 || *ByteSize > 8) + return NULL; + + return argStr; +} + +static PCWSTR +ParseStopBits(PCWSTR argStr, PBYTE StopBits) +{ + if (_wcsnicmp(argStr, L"1.5", 3) == 0) + { + argStr += 3; + *StopBits = ONE5STOPBITS; + } + else + { + if (*argStr == L'1') + *StopBits = ONESTOPBIT; + else if (*argStr == L'2') + *StopBits = TWOSTOPBITS; + else + return NULL; + + argStr++; + } + + return argStr; +} + +/* + * Build a DCB using the old style settings string eg: "96,n,8,1" + * + * See dll/win32/kernel32/wine/comm.c!COMM_BuildOldCommDCB() + * for more information. + */ +static BOOL +BuildOldCommDCB( + OUT LPDCB pDCB, + IN PCWSTR ArgStr) +{ + PCWSTR argStr = ArgStr; + BOOL stop = FALSE; + + /* + * Parse the baud rate (only MANDATORY argument) + */ + argStr = ParseBaudRate(argStr, &pDCB->BaudRate); + if (!argStr) return FALSE; + + + /* + * Now parse the rest (OPTIONAL arguments) + */ + + while (*argStr == L' ') argStr++; + if (!*argStr) goto Quit; + if (*argStr++ != L',') return FALSE; + while (*argStr == L' ') argStr++; + if (!*argStr) goto Quit; + + /* Parse the parity */ + // Default: EVENPARITY + pDCB->Parity = EVENPARITY; + if (*argStr != L',') + { + argStr = ParseParity(argStr, &pDCB->Parity); + if (!argStr) return FALSE; + } + + while (*argStr == L' ') argStr++; + if (!*argStr) goto Quit; + if (*argStr++ != L',') return FALSE; + while (*argStr == L' ') argStr++; + if (!*argStr) goto Quit; + + /* Parse the data bits */ + // Default: 7 + pDCB->ByteSize = 7; + if (*argStr != L',') + { + argStr = ParseByteSize(argStr, &pDCB->ByteSize); + if (!argStr) return FALSE; + } + + while (*argStr == L' ') argStr++; + if (!*argStr) goto Quit; + if (*argStr++ != L',') return FALSE; + while (*argStr == L' ') argStr++; + if (!*argStr) goto Quit; + + /* Parse the stop bits */ + // Default: 1, or 2 for BAUD=110 + // pDCB->StopBits = ONESTOPBIT; + if (*argStr != L',') + { + stop = TRUE; + argStr = ParseStopBits(argStr, &pDCB->StopBits); + if (!argStr) return FALSE; + } + + /* The last parameter (flow control "retry") is really optional */ + while (*argStr == L' ') argStr++; + if (!*argStr) goto Quit; + if (*argStr++ != L',') return FALSE; + while (*argStr == L' ') argStr++; + if (!*argStr) goto Quit; + +Quit: + switch (towupper(*argStr)) + { + case L'\0': + pDCB->fInX = FALSE; + pDCB->fOutX = FALSE; + pDCB->fOutxCtsFlow = FALSE; + pDCB->fOutxDsrFlow = FALSE; + pDCB->fDtrControl = DTR_CONTROL_ENABLE; + pDCB->fRtsControl = RTS_CONTROL_ENABLE; + break; + + case L'X': + pDCB->fInX = TRUE; + pDCB->fOutX = TRUE; + pDCB->fOutxCtsFlow = FALSE; + pDCB->fOutxDsrFlow = FALSE; + pDCB->fDtrControl = DTR_CONTROL_ENABLE; + pDCB->fRtsControl = RTS_CONTROL_ENABLE; + break; + + case L'P': + pDCB->fInX = FALSE; + pDCB->fOutX = FALSE; + pDCB->fOutxCtsFlow = TRUE; + pDCB->fOutxDsrFlow = TRUE; + pDCB->fDtrControl = DTR_CONTROL_HANDSHAKE; + pDCB->fRtsControl = RTS_CONTROL_HANDSHAKE; + break; + + default: + /* Unsupported */ + return FALSE; + } + + /* This should be the end of the string */ + if (*argStr) argStr++; + while (*argStr == L' ') argStr++; + if (*argStr) return FALSE; + + /* If stop bits were not specified, a default is always supplied */ + if (!stop) + { + if (pDCB->BaudRate == 110) + pDCB->StopBits = TWOSTOPBITS; + else + pDCB->StopBits = ONESTOPBIT; + } + return TRUE; +} + +/* + * Build a DCB using the new style settings string. + * eg: "baud=9600 parity=n data=8 stop=1 xon=on to=on" + * + * See dll/win32/kernel32/wine/comm.c!COMM_BuildNewCommDCB() + * for more information. + */ +static BOOL +BuildNewCommDCB( + OUT LPDCB pDCB, + OUT LPCOMMTIMEOUTS pCommTimeouts, + IN PCWSTR ArgStr) +{ + PCWSTR argStr = ArgStr; + BOOL baud = FALSE, stop = FALSE; + BYTE value; + + while (argStr && *argStr) + { + while (*argStr == L' ') argStr++; + if (!*argStr) break; + + if (_wcsnicmp(argStr, L"BAUD=", 5) == 0) { - if (wcslen(argv[arg]) > MAX_COMPARAM_LEN) + baud = TRUE; + argStr = ParseBaudRate(argStr+5, &pDCB->BaudRate); + if (!argStr) return FALSE; + } + else if (_wcsnicmp(argStr, L"PARITY=", 7) == 0) + { + // Default: EVENPARITY + argStr = ParseParity(argStr+7, &pDCB->Parity); + if (!argStr) return FALSE; + } + else if (_wcsnicmp(argStr, L"DATA=", 5) == 0) + { + // Default: 7 + argStr = ParseByteSize(argStr+5, &pDCB->ByteSize); + if (!argStr) return FALSE; + } + else if (_wcsnicmp(argStr, L"STOP=", 5) == 0) + { + // Default: 1, or 2 for BAUD=110 + stop = TRUE; + argStr = ParseStopBits(argStr+5, &pDCB->StopBits); + if (!argStr) return FALSE; + } + else if (_wcsnicmp(argStr, L"TO=", 3) == 0) // TO=ON|OFF + { + /* Only total time-outs are get/set by Windows' MODE.COM */ + argStr = ParseModes(argStr+3, &value); + if (!argStr) return FALSE; + if (value == 0) // OFF { - wprintf(L"Invalid parameter (too long) - %s\n", argv[arg]); - return 1; + pCommTimeouts->ReadTotalTimeoutConstant = 0; + pCommTimeouts->WriteTotalTimeoutConstant = 0; } - wcscpy(buf, argv[arg]); - _wcslwr(buf); - if (wcsstr(buf, L"baud=")) + else if (value == 1) // ON { - wscanf(buf+5, L"%lu", &dcb.BaudRate); - } - else if (wcsstr(buf, L"parity=")) - { - if (wcschr(buf, L'D')) - dcb.Parity = 1; - else if (wcschr(buf, L'V')) - dcb.Parity = 2; - else if (wcschr(buf, L'M')) - dcb.Parity = 3; - else if (wcschr(buf, L'S')) - dcb.Parity = 4; - else - dcb.Parity = 0; - } - else if (wcsstr(buf, L"data=")) - { - wscanf(buf+5, L"%lu", &dcb.ByteSize); - } - else if (wcsstr(buf, L"stop=")) - { - if (wcschr(buf, L'5')) - dcb.StopBits = 1; - else if (wcschr(buf, L'2')) - dcb.StopBits = 2; - else - dcb.StopBits = 0; - } - else if (wcsstr(buf, L"to=")) // to=on|off - { - value = ExtractModeSerialParams(buf); - if (value != -1) - { - } - else - { - goto invalid_serial_parameter; - } - } - else if (wcsstr(buf, L"xon=")) // xon=on|off - { - value = ExtractModeSerialParams(buf); - if (value != -1) - { - dcb.fOutX = value; - dcb.fInX = value; - } - else - { - goto invalid_serial_parameter; - } - } - else if (wcsstr(buf, L"odsr=")) // odsr=on|off - { - value = ExtractModeSerialParams(buf); - if (value != -1) - { - dcb.fOutxDsrFlow = value; - } - else - { - goto invalid_serial_parameter; - } - } - else if (wcsstr(buf, L"octs=")) // octs=on|off - { - value = ExtractModeSerialParams(buf); - if (value != -1) - { - dcb.fOutxCtsFlow = value; - } - else - { - goto invalid_serial_parameter; - } - } - else if (wcsstr(buf, L"dtr=")) // dtr=on|off|hs - { - value = ExtractModeSerialParams(buf); - if (value != -1) - { - dcb.fDtrControl = value; - } - else - { - goto invalid_serial_parameter; - } - } - else if (wcsstr(buf, L"rts=")) // rts=on|off|hs|tg - { - value = ExtractModeSerialParams(buf); - if (value != -1) - { - dcb.fRtsControl = value; - } - else - { - goto invalid_serial_parameter; - } - } - else if (wcsstr(buf, L"idsr=")) // idsr=on|off - { - value = ExtractModeSerialParams(buf); - if (value != -1) - { - dcb.fDsrSensitivity = value; - } - else - { - goto invalid_serial_parameter; - } + pCommTimeouts->ReadTotalTimeoutConstant = 60000; + pCommTimeouts->WriteTotalTimeoutConstant = 60000; } else { -invalid_serial_parameter:; - wprintf(L"Invalid parameter - %s\n", buf); - return 1; + return FALSE; } } - SerialPortQuery(nPortNum, &dcb, &CommTimeouts, TRUE); + else if (_wcsnicmp(argStr, L"XON=", 4) == 0) // XON=ON|OFF + { + argStr = ParseModes(argStr+4, &value); + if (!argStr) return FALSE; + if ((value == 0) || (value == 1)) + { + pDCB->fOutX = value; + pDCB->fInX = value; + } + else + { + return FALSE; + } + } + else if (_wcsnicmp(argStr, L"ODSR=", 5) == 0) // ODSR=ON|OFF + { + value = 0; + argStr = ParseModes(argStr+5, &value); + if (!argStr) return FALSE; + if ((value == 0) || (value == 1)) + pDCB->fOutxDsrFlow = value; + else + return FALSE; + } + else if (_wcsnicmp(argStr, L"OCTS=", 5) == 0) // OCTS=ON|OFF + { + value = 0; + argStr = ParseModes(argStr+5, &value); + if (!argStr) return FALSE; + if ((value == 0) || (value == 1)) + pDCB->fOutxCtsFlow = value; + else + return FALSE; + } + else if (_wcsnicmp(argStr, L"DTR=", 4) == 0) // DTR=ON|OFF|HS + { + value = 0; + argStr = ParseModes(argStr+4, &value); + if (!argStr) return FALSE; + if ((value == 0) || (value == 1) || (value == 2)) + pDCB->fDtrControl = value; + else + return FALSE; + } + else if (_wcsnicmp(argStr, L"RTS=", 4) == 0) // RTS=ON|OFF|HS|TG + { + value = 0; + argStr = ParseModes(argStr+4, &value); + if (!argStr) return FALSE; + if ((value == 0) || (value == 1) || (value == 2) || (value == 3)) + pDCB->fRtsControl = value; + else + return FALSE; + } + else if (_wcsnicmp(argStr, L"IDSR=", 5) == 0) // IDSR=ON|OFF + { + value = 0; + argStr = ParseModes(argStr+5, &value); + if (!argStr) return FALSE; + if ((value == 0) || (value == 1)) + pDCB->fDsrSensitivity = value; + else + return FALSE; + } + else + { + return FALSE; + } } + + /* If stop bits were not specified, a default is always supplied */ + if (!stop) + { + if (baud && pDCB->BaudRate == 110) + pDCB->StopBits = TWOSTOPBITS; + else + pDCB->StopBits = ONESTOPBIT; + } + return TRUE; +} + +int SetSerialState(INT nPortNum, IN PCWSTR ArgStr) +{ + BOOL Success; + DCB dcb; + COMMTIMEOUTS CommTimeouts; + + if (!SerialPortQuery(nPortNum, &dcb, &CommTimeouts, FALSE)) + { + // TODO: Error message? + return 0; + } + + /* + * Check whether we should use the old or the new MODE syntax: + * in the old syntax, the separators are both spaces and commas. + */ + if (wcschr(ArgStr, L',')) + Success = BuildOldCommDCB(&dcb, ArgStr); + else + Success = BuildNewCommDCB(&dcb, &CommTimeouts, ArgStr); + + if (!Success) + { + wprintf(L"Invalid parameter - %s\n", ArgStr); + return 1; + } + + SerialPortQuery(nPortNum, &dcb, &CommTimeouts, TRUE); + ShowSerialStatus(nPortNum); + return 0; } -int find_portnum(const WCHAR* cmdverb) -{ - int portnum = -1; - if (cmdverb[3] >= L'0' && cmdverb[3] <= L'9') +/*****************************************************************************\ + ** E N T R Y P O I N T ** +\*****************************************************************************/ + +static PCWSTR +FindPortNum(PCWSTR argStr, PINT PortNum) +{ + *PortNum = -1; + + if (*argStr >= L'0' && *argStr <= L'9') { - portnum = cmdverb[3] - L'0'; - if (cmdverb[4] >= L'0' && cmdverb[4] <= L'9') + *PortNum = *argStr - L'0'; + argStr++; + if (*argStr >= L'0' && *argStr <= L'9') { - portnum *= 10; - portnum += cmdverb[4] - L'0'; + *PortNum *= 10; + *PortNum += *argStr - L'0'; } } - return portnum; + else + { + return NULL; + } + + return argStr; } int wmain(int argc, WCHAR* argv[]) { - int nPortNum; - WCHAR param1[MAX_COMPARAM_LEN+1]; - WCHAR param2[MAX_COMPARAM_LEN+1]; + int ret = 0; + int arg; + SIZE_T ArgStrSize; + PCWSTR ArgStr, argStr; - if (argc > 1) + INT nPortNum; + + /* + * MODE.COM has a very peculiar way of parsing its arguments, + * as they can be even not separated by any space. This extreme + * behaviour certainly is present for backwards compatibility + * with the oldest versions of the utility present on MS-DOS. + * + * For example, such a command: + * "MODE.COM COM1baud=9600parity=ndata=8stop=1xon=onto=on" + * will be correctly understood as: + * "MODE.COM COM1 baud=9600 parity=n data=8 stop=1 xon=on to=on" + * + * Note also that the "/STATUS" switch is actually really "/STA". + * + * However we will not use GetCommandLine() because we do not want + * to deal with the prepended application path and try to find + * where the arguments start. Our approach here will consist in + * flattening the arguments vector. + */ + ArgStrSize = 0; + + /* Compute space needed for the new string, and allocate it */ + for (arg = 1; arg < argc; arg++) { - if (wcslen(argv[1]) > MAX_COMPARAM_LEN) + ArgStrSize += wcslen(argv[arg]) + 1; // 1 for space + } + ArgStr = HeapAlloc(GetProcessHeap(), 0, (ArgStrSize + 1) * sizeof(WCHAR)); + if (ArgStr == NULL) + { + wprintf(L"ERROR: Not enough memory\n"); + return 1; + } + + /* Copy the contents and NULL-terminate the string */ + argStr = ArgStr; + for (arg = 1; arg < argc; arg++) + { + wcscpy((PWSTR)argStr, argv[arg]); + argStr += wcslen(argv[arg]); + *(PWSTR)argStr++ = L' '; + } + *(PWSTR)argStr = L'\0'; + + /* Parse the command line */ + argStr = ArgStr; + + while (*argStr == L' ') argStr++; + if (!*argStr) goto show_status; + + if (wcsstr(argStr, L"/?") || wcsstr(argStr, L"-?")) + { + Usage(); + goto Quit; + } + else if (_wcsnicmp(argStr, L"/STA", 4) == 0) + { + // FIXME: Check if there are other "parameters" after the status, + // in which case this is invalid. + goto show_status; + } + else if (_wcsnicmp(argStr, L"LPT", 3) == 0) + { + argStr = FindPortNum(argStr+3, &nPortNum); + if (!argStr || nPortNum == -1) + goto invalid_parameter; + + if (*argStr == L':') argStr++; + while (*argStr == L' ') argStr++; + + ret = ShowParallelStatus(nPortNum); + goto Quit; + } + else if (_wcsnicmp(argStr, L"COM", 3) == 0) + { + argStr = FindPortNum(argStr+3, &nPortNum); + if (!argStr || nPortNum == -1) + goto invalid_parameter; + + if (*argStr == L':') argStr++; + while (*argStr == L' ') argStr++; + + if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0) + ret = ShowSerialStatus(nPortNum); + else + ret = SetSerialState(nPortNum, argStr); + goto Quit; + } + else if (_wcsnicmp(argStr, L"CON", 3) == 0) + { + argStr += 3; + + if (*argStr == L':') argStr++; + while (*argStr == L' ') argStr++; + + if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0) { - wprintf(L"Invalid parameter (too long) - %s\n", argv[1]); - return 1; + ret = ShowConsoleStatus(); } - wcscpy(param1, argv[1]); - _wcslwr(param1); - if (argc > 2) + else if ( (_wcsnicmp(argStr, L"CP", 2) == 0 && (argStr += 2)) || + (_wcsnicmp(argStr, L"CODEPAGE", 8) == 0 && (argStr += 8)) ) { - if (wcslen(argv[2]) > MAX_COMPARAM_LEN) - { - wprintf(L"Invalid parameter (too long) - %s\n", argv[2]); - return 1; - } - wcscpy(param2, argv[2]); - _wcslwr(param2); + while (*argStr == L' ') argStr++; + + if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0) + ret = ShowConsoleCPStatus(); + else + ret = SetConsoleCPState(argStr); } else { - param2[0] = L'\0'; + ret = SetConsoleState(argStr); } - if (wcsstr(param1, L"/?") || wcsstr(param1, L"-?")) - { - return Usage(); - } - else if (wcsstr(param1, L"/status")) - { - goto show_status; - } - else if (wcsstr(param1, L"lpt")) - { - nPortNum = find_portnum(param1); - if (nPortNum != -1) - return ShowParallelStatus(nPortNum); - } - else if (wcsstr(param1, L"con")) - { - return ShowConsoleStatus(); - } - else if (wcsstr(param1, L"com")) - { - nPortNum = find_portnum(param1); - if (nPortNum != -1) - { - if (param2[0] == L'\0' || wcsstr(param2, L"/status")) - { - return ShowSerialStatus(nPortNum); - } - else - { - return SetSerialState(nPortNum, argc, argv); - } - } - } - wprintf(L"Invalid parameter - %s\n", param1); - return 1; + goto Quit; } + // else if (wcschr(argStr, L',')) else { -show_status:; - - QueryDevices(); -/* - ShowParallelStatus(1); - for (nPortNum = 0; nPortNum < MAX_COMPORT_NUM; nPortNum++) - { - ShowSerialStatus(nPortNum + 1); - } - ShowConsoleStatus(); - */ + /* Old syntax: MODE [COLS],[LINES] */ + ret = SetConsoleStateOld(argStr); + goto Quit; } - return 0; + +show_status: + QueryDevices(); +/* + ShowParallelStatus(1); + for (nPortNum = 0; nPortNum < MAX_COMPORT_NUM; nPortNum++) + { + ShowSerialStatus(nPortNum + 1); + } + ShowConsoleStatus(); +*/ + goto Quit; + +invalid_parameter: + wprintf(L"Invalid parameter - %s\n", ArgStr); + goto Quit; + +Quit: + /* Free the string and quit */ + HeapFree(GetProcessHeap(), 0, (PWSTR)ArgStr); + return ret; }