xref: /reactos/base/applications/cmdutils/mode/mode.c (revision 1734f297)
1 /*
2  * PROJECT:     ReactOS Mode Utility
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Provides fast mode setup for DOS devices.
5  * COPYRIGHT:   Copyright 2002 Robert Dickenson
6  *              Copyright 2016-2021 Hermes Belusca-Maito
7  */
8 /*
9  *  ReactOS mode console command
10  *
11  *  mode.c
12  *
13  *  Copyright (C) 2002  Robert Dickenson <robd@reactos.org>
14  *
15  *  This program is free software; you can redistribute it and/or modify
16  *  it under the terms of the GNU General Public License as published by
17  *  the Free Software Foundation; either version 2 of the License, or
18  *  (at your option) any later version.
19  *
20  *  This program is distributed in the hope that it will be useful,
21  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  *  GNU General Public License for more details.
24  *
25  *  You should have received a copy of the GNU General Public License
26  *  along with this program; if not, write to the Free Software
27  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28  */
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 
33 #include <windef.h>
34 #include <winbase.h>
35 #include <winuser.h>
36 #include <wincon.h>
37 
38 #include <conutils.h>
39 
40 #include "resource.h"
41 
42 #define MAX_PORTNAME_LEN 20
43 
44 #define ASSERT(a)
45 
46 /*** For fixes, see also network/net/main.c ***/
47 // VOID PrintPadding(...)
48 VOID
49 __cdecl
50 UnderlinedResPrintf(
51     IN PCON_STREAM Stream,
52     IN UINT uID,
53     ...)
54 {
55     INT Len;
56     va_list args;
57 
58 #define MAX_BUFFER_SIZE 4096
59     INT i;
60     WCHAR szMsgBuffer[MAX_BUFFER_SIZE];
61 
62     va_start(args, uID);
63     Len = ConResPrintfV(Stream, uID, args);
64     va_end(args);
65 
66     ConPuts(Stream, L"\n");
67     for (i = 0; i < Len; i++)
68          szMsgBuffer[i] = L'-';
69     szMsgBuffer[Len] = UNICODE_NULL;
70 
71     ConStreamWrite(Stream, szMsgBuffer, Len);
72 }
73 
74 int ShowParallelStatus(INT nPortNum)
75 {
76     WCHAR buffer[250];
77     WCHAR szPortName[MAX_PORTNAME_LEN];
78 
79     swprintf(szPortName, L"LPT%d", nPortNum);
80 
81     ConPuts(StdOut, L"\n");
82     UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, szPortName);
83     ConPuts(StdOut, L"\n");
84 
85     if (QueryDosDeviceW(szPortName, buffer, ARRAYSIZE(buffer)))
86     {
87         PWSTR ptr = wcsrchr(buffer, L'\\');
88         if (ptr != NULL)
89         {
90             if (_wcsicmp(szPortName, ++ptr) == 0)
91                 ConResPuts(StdOut, IDS_PRINTER_OUTPUT_NOT_REROUTED);
92             else
93                 ConResPrintf(StdOut, IDS_PRINTER_OUTPUT_REROUTED_SERIAL, ptr);
94 
95             return 0;
96         }
97         else
98         {
99             ConResPrintf(StdErr, IDS_ERROR_QUERY_DEVICES_FORM, szPortName, buffer);
100         }
101     }
102     else
103     {
104         ConPrintf(StdErr, L"ERROR: QueryDosDeviceW(%s) failed: 0x%lx\n", szPortName, GetLastError());
105     }
106     ConPuts(StdOut, L"\n");
107 
108     return 1;
109 }
110 
111 int SetParallelState(INT nPortNum)
112 {
113     WCHAR szPortName[MAX_PORTNAME_LEN];
114     WCHAR szTargetPath[MAX_PORTNAME_LEN];
115 
116     swprintf(szPortName, L"LPT%d", nPortNum);
117     swprintf(szTargetPath, L"COM%d", nPortNum);
118     if (!DefineDosDeviceW(DDD_REMOVE_DEFINITION, szPortName, szTargetPath))
119     {
120         ConPrintf(StdErr, L"ERROR: SetParallelState(%d) - DefineDosDevice(%s) failed: 0x%lx\n", nPortNum, szPortName, GetLastError());
121     }
122 
123     ShowParallelStatus(nPortNum);
124     return 0;
125 }
126 
127 
128 static PCWSTR
129 ParseNumber(PCWSTR argStr, PDWORD Number)
130 {
131     INT value, skip = 0;
132 
133     value = swscanf(argStr, L"%lu%n", Number, &skip);
134     if (!value) return NULL;
135     argStr += skip;
136     return argStr;
137 }
138 
139 
140 /*
141     \??\COM1
142     \Device\NamedPipe\Spooler\LPT1
143 BOOL DefineDosDevice(
144   DWORD dwFlags,         // options
145   LPCTSTR lpDeviceName,  // device name
146   LPCTSTR lpTargetPath   // path string
147 );
148 DWORD QueryDosDevice(
149   LPCTSTR lpDeviceName, // MS-DOS device name string
150   LPTSTR lpTargetPath,  // query results buffer
151   DWORD ucchMax         // maximum size of buffer
152 );
153  */
154 
155 
156 /*****************************************************************************\
157  **                      C O N S O L E   H E L P E R S                      **
158 \*****************************************************************************/
159 
160 int ShowConsoleStatus(VOID)
161 {
162     HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
163     CONSOLE_SCREEN_BUFFER_INFO csbi;
164     DWORD dwKbdDelay, dwKbdSpeed;
165 
166     ConPuts(StdOut, L"\n");
167     UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, L"CON");
168     ConPuts(StdOut, L"\n");
169 
170     if (GetConsoleScreenBufferInfo(hConOut, &csbi))
171     {
172         ConResPrintf(StdOut, IDS_CONSOLE_STATUS_LINES, csbi.dwSize.Y);
173         ConResPrintf(StdOut, IDS_CONSOLE_STATUS_COLS , csbi.dwSize.X);
174     }
175     if (SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &dwKbdSpeed, 0))
176     {
177         ConResPrintf(StdOut, IDS_CONSOLE_KBD_RATE, dwKbdSpeed);
178     }
179     if (SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &dwKbdDelay, 0))
180     {
181         ConResPrintf(StdOut, IDS_CONSOLE_KBD_DELAY, dwKbdDelay);
182     }
183     ConResPrintf(StdOut, IDS_CONSOLE_CODEPAGE, GetConsoleOutputCP());
184     ConPuts(StdOut, L"\n");
185 
186     return 0;
187 }
188 
189 int ShowConsoleCPStatus(VOID)
190 {
191     ConPuts(StdOut, L"\n");
192     UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, L"CON");
193     ConPuts(StdOut, L"\n");
194 
195     ConResPrintf(StdOut, IDS_CONSOLE_CODEPAGE, GetConsoleOutputCP());
196     ConPuts(StdOut, L"\n");
197 
198     return 0;
199 }
200 
201 static VOID
202 ClearScreen(
203     IN HANDLE hConOut,
204     IN PCONSOLE_SCREEN_BUFFER_INFO pcsbi)
205 {
206     COORD coPos;
207     DWORD dwWritten;
208 
209     coPos.X = 0;
210     coPos.Y = 0;
211     FillConsoleOutputAttribute(hConOut, pcsbi->wAttributes,
212                                pcsbi->dwSize.X * pcsbi->dwSize.Y,
213                                coPos, &dwWritten);
214     FillConsoleOutputCharacterW(hConOut, L' ',
215                                 pcsbi->dwSize.X * pcsbi->dwSize.Y,
216                                 coPos, &dwWritten);
217     SetConsoleCursorPosition(hConOut, coPos);
218 }
219 
220 /*
221  * See, or adjust if needed, subsystems/mvdm/ntvdm/console/video.c!ResizeTextConsole()
222  * for more information.
223  */
224 static BOOL
225 ResizeTextConsole(
226     IN HANDLE hConOut,
227     IN OUT PCONSOLE_SCREEN_BUFFER_INFO pcsbi,
228     IN COORD Resolution)
229 {
230     BOOL Success;
231     SHORT Width, Height;
232     SMALL_RECT ConRect;
233 
234     /*
235      * Use this trick to effectively resize the console buffer and window,
236      * because:
237      * - SetConsoleScreenBufferSize fails if the new console screen buffer size
238      *   is smaller than the current console window size, and:
239      * - SetConsoleWindowInfo fails if the new console window size is larger
240      *   than the current console screen buffer size.
241      */
242 
243     /* Resize the screen buffer only if needed */
244     if (Resolution.X != pcsbi->dwSize.X || Resolution.Y != pcsbi->dwSize.Y)
245     {
246         Width  = pcsbi->srWindow.Right  - pcsbi->srWindow.Left + 1;
247         Height = pcsbi->srWindow.Bottom - pcsbi->srWindow.Top  + 1;
248 
249         /*
250          * If the current console window is too large for
251          * the new screen buffer, resize it first.
252          */
253         if (Width > Resolution.X || Height > Resolution.Y)
254         {
255             /*
256              * NOTE: This is not a problem if we move the window back to (0,0)
257              * because when we resize the screen buffer, the window will move back
258              * to where the cursor is. Or, if the screen buffer is not resized,
259              * when we readjust again the window, we will move back to a correct
260              * position. This is what we wanted after all...
261              */
262             ConRect.Left   = ConRect.Top = 0;
263             ConRect.Right  = ConRect.Left + min(Width , Resolution.X) - 1;
264             ConRect.Bottom = ConRect.Top  + min(Height, Resolution.Y) - 1;
265 
266             Success = SetConsoleWindowInfo(hConOut, TRUE, &ConRect);
267             if (!Success) return FALSE;
268         }
269 
270         /*
271          * Now resize the screen buffer.
272          *
273          * SetConsoleScreenBufferSize automatically takes into account the current
274          * cursor position when it computes starting which row it should copy text
275          * when resizing the screen buffer, and scrolls the console window such that
276          * the cursor is placed in it again. We therefore do not need to care about
277          * the cursor position and do the maths ourselves.
278          */
279         Success = SetConsoleScreenBufferSize(hConOut, Resolution);
280         if (!Success) return FALSE;
281 
282         /*
283          * Setting a new screen buffer size can change other information,
284          * so update the console screen buffer information.
285          */
286         GetConsoleScreenBufferInfo(hConOut, pcsbi);
287     }
288 
289     /* Always resize the console window within the permitted maximum size */
290     Width  = min(Resolution.X, pcsbi->dwMaximumWindowSize.X);
291     Height = min(Resolution.Y, pcsbi->dwMaximumWindowSize.Y);
292     ConRect.Left   = 0;
293     ConRect.Right  = ConRect.Left + Width - 1;
294     ConRect.Bottom = max(pcsbi->dwCursorPosition.Y, Height - 1);
295     ConRect.Top    = ConRect.Bottom - Height + 1;
296 
297     SetConsoleWindowInfo(hConOut, TRUE, &ConRect);
298 
299     /* Update the console screen buffer information */
300     GetConsoleScreenBufferInfo(hConOut, pcsbi);
301 
302     return TRUE;
303 }
304 
305 int SetConsoleStateOld(IN PCWSTR ArgStr)
306 {
307     PCWSTR argStr = ArgStr;
308 
309     HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
310     CONSOLE_SCREEN_BUFFER_INFO csbi;
311     COORD Resolution;
312     DWORD value;
313 
314     if (!GetConsoleScreenBufferInfo(hConOut, &csbi))
315     {
316         // TODO: Error message?
317         return 0;
318     }
319 
320     Resolution = csbi.dwSize;
321 
322     /* Parse the column number (only MANDATORY argument) */
323     value = 0;
324     argStr = ParseNumber(argStr, &value);
325     if (!argStr) goto invalid_parameter;
326     Resolution.X = (SHORT)value;
327 
328     /* Parse the line number (OPTIONAL argument) */
329     while (*argStr == L' ') argStr++;
330     if (!*argStr) goto Quit;
331     if (*argStr++ != L',') goto invalid_parameter;
332     while (*argStr == L' ') argStr++;
333 
334     value = 0;
335     argStr = ParseNumber(argStr, &value);
336     if (!argStr) goto invalid_parameter;
337     Resolution.Y = (SHORT)value;
338 
339     /* This should be the end of the string */
340     while (*argStr == L' ') argStr++;
341     if (*argStr) goto invalid_parameter;
342 
343 Quit:
344     ClearScreen(hConOut, &csbi);
345     if (!ResizeTextConsole(hConOut, &csbi, Resolution))
346         ConResPuts(StdErr, IDS_ERROR_SCREEN_LINES_COL);
347 
348     return 0;
349 
350 invalid_parameter:
351     ConResPrintf(StdErr, IDS_ERROR_INVALID_PARAMETER, ArgStr);
352     return 1;
353 }
354 
355 int SetConsoleState(IN PCWSTR ArgStr)
356 {
357     PCWSTR argStr = ArgStr;
358     BOOL dispMode = FALSE, kbdMode = FALSE;
359 
360     HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
361     CONSOLE_SCREEN_BUFFER_INFO csbi;
362     COORD Resolution;
363     DWORD dwKbdDelay, dwKbdSpeed;
364     DWORD value;
365 
366     if (!GetConsoleScreenBufferInfo(hConOut, &csbi))
367     {
368         // TODO: Error message?
369         return 0;
370     }
371     if (!SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &dwKbdDelay, 0))
372     {
373         // TODO: Error message?
374         return 0;
375     }
376     if (!SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &dwKbdSpeed, 0))
377     {
378         // TODO: Error message?
379         return 0;
380     }
381 
382     Resolution = csbi.dwSize;
383 
384     while (argStr && *argStr)
385     {
386         while (*argStr == L' ') argStr++;
387         if (!*argStr) break;
388 
389         if (!kbdMode && _wcsnicmp(argStr, L"COLS=", 5) == 0)
390         {
391             dispMode = TRUE;
392 
393             value = 0;
394             argStr = ParseNumber(argStr+5, &value);
395             if (!argStr) goto invalid_parameter;
396             Resolution.X = (SHORT)value;
397         }
398         else if (!kbdMode && _wcsnicmp(argStr, L"LINES=", 6) == 0)
399         {
400             dispMode = TRUE;
401 
402             value = 0;
403             argStr = ParseNumber(argStr+6, &value);
404             if (!argStr) goto invalid_parameter;
405             Resolution.Y = (SHORT)value;
406         }
407         else if (!dispMode && _wcsnicmp(argStr, L"RATE=", 5) == 0)
408         {
409             kbdMode = TRUE;
410 
411             argStr = ParseNumber(argStr+5, &dwKbdSpeed);
412             if (!argStr) goto invalid_parameter;
413         }
414         else if (!dispMode && _wcsnicmp(argStr, L"DELAY=", 6) == 0)
415         {
416             kbdMode = TRUE;
417 
418             argStr = ParseNumber(argStr+6, &dwKbdDelay);
419             if (!argStr) goto invalid_parameter;
420         }
421         else
422         {
423 invalid_parameter:
424             ConResPrintf(StdErr, IDS_ERROR_INVALID_PARAMETER, ArgStr);
425             return 1;
426         }
427     }
428 
429     if (dispMode)
430     {
431         ClearScreen(hConOut, &csbi);
432         if (!ResizeTextConsole(hConOut, &csbi, Resolution))
433             ConResPuts(StdErr, IDS_ERROR_SCREEN_LINES_COL);
434     }
435     else if (kbdMode)
436     {
437         /*
438          * Set the new keyboard settings. If those values are greater than
439          * their allowed range, they are automatically corrected as follows:
440          *   dwKbdSpeed = min(dwKbdSpeed, 31);
441          *   dwKbdDelay = (dwKbdDelay % 4);
442          */
443         SystemParametersInfoW(SPI_SETKEYBOARDDELAY, dwKbdDelay, NULL, 0);
444         // "Invalid keyboard delay."
445         SystemParametersInfoW(SPI_SETKEYBOARDSPEED, dwKbdSpeed, NULL, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
446         // "Invalid keyboard rate."
447     }
448 
449     return 0;
450 }
451 
452 int SetConsoleCPState(IN PCWSTR ArgStr)
453 {
454     PCWSTR argStr = ArgStr;
455     DWORD value = 0;
456     UINT uOldCodePage, uNewCodePage;
457 
458     if ( (_wcsnicmp(argStr, L"SELECT=", 7) == 0 && (argStr += 7)) ||
459          (_wcsnicmp(argStr, L"SEL=", 4) == 0 && (argStr += 4)) )
460     {
461         argStr = ParseNumber(argStr, &value);
462         if (!argStr) goto invalid_parameter;
463 
464         /* This should be the end of the string */
465         while (*argStr == L' ') argStr++;
466         if (*argStr) goto invalid_parameter;
467     }
468     else
469     {
470 invalid_parameter:
471         ConResPrintf(StdErr, IDS_ERROR_INVALID_PARAMETER, ArgStr);
472         return 1;
473     }
474 
475     uNewCodePage = value;
476 
477 /**
478  ** IMPORTANT NOTE: This code must be kept synchronized with CHCP.COM
479  **/
480 
481     /*
482      * Save the original console code page to be restored
483      * in case SetConsoleCP() or SetConsoleOutputCP() fails.
484      */
485     uOldCodePage = GetConsoleCP();
486 
487     /*
488      * Try changing the console input and output code pages.
489      * If it succeeds, refresh the local code page information.
490      */
491     if (SetConsoleCP(uNewCodePage))
492     {
493         if (SetConsoleOutputCP(uNewCodePage))
494         {
495             /* Success, reset the current thread UI language
496              * and update the streams cached code page. */
497             ConSetThreadUILanguage(0);
498             ConStdStreamsSetCacheCodePage(uNewCodePage, uNewCodePage);
499 
500             /* Display the current console status */
501             ShowConsoleStatus();
502             return 0;
503         }
504         else
505         {
506             /* Failure, restore the original console code page */
507             SetConsoleCP(uOldCodePage);
508         }
509     }
510 
511     /* An error happened, display an error and bail out */
512     ConResPuts(StdErr, IDS_ERROR_INVALID_CODEPAGE);
513     return 1;
514 }
515 
516 
517 /*****************************************************************************\
518  **                  S E R I A L   P O R T   H E L P E R S                  **
519 \*****************************************************************************/
520 
521 static BOOL
522 SerialPortQuery(INT nPortNum, LPDCB pDCB, LPCOMMTIMEOUTS pCommTimeouts, BOOL bWrite)
523 {
524     BOOL Success;
525     HANDLE hPort;
526     WCHAR szPortName[MAX_PORTNAME_LEN];
527 
528     ASSERT(pDCB);
529     ASSERT(pCommTimeouts);
530 
531     swprintf(szPortName, L"COM%d", nPortNum);
532     hPort = CreateFileW(szPortName,
533                         bWrite ? GENERIC_WRITE : GENERIC_READ,
534                         0,     // exclusive
535                         NULL,  // sec attr
536                         OPEN_EXISTING,
537                         0,     // no attributes
538                         NULL); // no template
539 
540     if (hPort == INVALID_HANDLE_VALUE)
541     {
542         DWORD dwLastError = GetLastError();
543         if (dwLastError == ERROR_ACCESS_DENIED)
544             ConResPrintf(StdErr, IDS_ERROR_DEVICE_NOT_AVAILABLE, szPortName);
545         else
546             ConResPrintf(StdErr, IDS_ERROR_ILLEGAL_DEVICE_NAME, szPortName, dwLastError);
547         return FALSE;
548     }
549 
550     Success = bWrite ? SetCommState(hPort, pDCB)
551                      : GetCommState(hPort, pDCB);
552     if (!Success)
553     {
554         ConResPrintf(StdErr,
555                      bWrite ? IDS_ERROR_STATUS_SET_DEVICE : IDS_ERROR_STATUS_GET_DEVICE,
556                      szPortName);
557         goto Quit;
558     }
559 
560     Success = bWrite ? SetCommTimeouts(hPort, pCommTimeouts)
561                      : GetCommTimeouts(hPort, pCommTimeouts);
562     if (!Success)
563     {
564         ConResPrintf(StdErr,
565                      bWrite ? IDS_ERROR_TIMEOUT_SET_DEVICE : IDS_ERROR_TIMEOUT_GET_DEVICE,
566                      szPortName);
567         goto Quit;
568     }
569 
570 Quit:
571     CloseHandle(hPort);
572     return Success;
573 }
574 
575 int ShowSerialStatus(INT nPortNum)
576 {
577     static const LPCWSTR parity_strings[] =
578     {
579         L"None",    // NOPARITY
580         L"Odd",     // ODDPARITY
581         L"Even",    // EVENPARITY
582         L"Mark",    // MARKPARITY
583         L"Space"    // SPACEPARITY
584     };
585     static const LPCWSTR control_strings[] = { L"OFF", L"ON", L"HANDSHAKE", L"TOGGLE" };
586     static const LPCWSTR stopbit_strings[] = { L"1", L"1.5", L"2" };
587 
588     DCB dcb;
589     COMMTIMEOUTS CommTimeouts;
590     WCHAR szPortName[MAX_PORTNAME_LEN];
591 
592     if (!SerialPortQuery(nPortNum, &dcb, &CommTimeouts, FALSE))
593     {
594         return 1;
595     }
596     if (dcb.Parity >= ARRAYSIZE(parity_strings))
597     {
598         ConResPrintf(StdErr, IDS_ERROR_INVALID_PARITY_BITS, dcb.Parity);
599         dcb.Parity = 0;
600     }
601     if (dcb.StopBits >= ARRAYSIZE(stopbit_strings))
602     {
603         ConResPrintf(StdErr, IDS_ERROR_INVALID_STOP_BITS, dcb.StopBits);
604         dcb.StopBits = 0;
605     }
606 
607     swprintf(szPortName, L"COM%d", nPortNum);
608 
609     ConPuts(StdOut, L"\n");
610     UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, szPortName);
611     ConPuts(StdOut, L"\n");
612 
613     ConResPrintf(StdOut, IDS_COM_STATUS_BAUD, dcb.BaudRate);
614     ConResPrintf(StdOut, IDS_COM_STATUS_PARITY, parity_strings[dcb.Parity]);
615     ConResPrintf(StdOut, IDS_COM_STATUS_DATA_BITS, dcb.ByteSize);
616     ConResPrintf(StdOut, IDS_COM_STATUS_STOP_BITS, stopbit_strings[dcb.StopBits]);
617     ConResPrintf(StdOut, IDS_COM_STATUS_TIMEOUT,
618         control_strings[(CommTimeouts.ReadTotalTimeoutConstant  != 0) ||
619                         (CommTimeouts.WriteTotalTimeoutConstant != 0) ? 1 : 0]);
620     ConResPrintf(StdOut, IDS_COM_STATUS_XON_XOFF,
621         control_strings[dcb.fOutX ? 1 : 0]);
622     ConResPrintf(StdOut, IDS_COM_STATUS_CTS_HANDSHAKING,
623         control_strings[dcb.fOutxCtsFlow ? 1 : 0]);
624     ConResPrintf(StdOut, IDS_COM_STATUS_DSR_HANDSHAKING,
625         control_strings[dcb.fOutxDsrFlow ? 1 : 0]);
626     ConResPrintf(StdOut, IDS_COM_STATUS_DSR_SENSITIVITY,
627         control_strings[dcb.fDsrSensitivity ? 1 : 0]);
628     ConResPrintf(StdOut, IDS_COM_STATUS_DTR_CIRCUIT, control_strings[dcb.fDtrControl]);
629     ConResPrintf(StdOut, IDS_COM_STATUS_RTS_CIRCUIT, control_strings[dcb.fRtsControl]);
630     ConPuts(StdOut, L"\n");
631 
632     return 0;
633 }
634 
635 
636 /*
637  * Those procedures are inspired from Wine's dll/win32/kernel32/wine/comm.c
638  * Copyright 1996 Erik Bos and Marcus Meissner.
639  */
640 
641 static PCWSTR
642 ParseModes(PCWSTR argStr, PBYTE Mode)
643 {
644     if (_wcsnicmp(argStr, L"OFF", 3) == 0)
645     {
646         argStr += 3;
647         *Mode = 0;
648     }
649     else if (_wcsnicmp(argStr, L"ON", 2) == 0)
650     {
651         argStr += 2;
652         *Mode = 1;
653     }
654     else if (_wcsnicmp(argStr, L"HS", 2) == 0)
655     {
656         argStr += 2;
657         *Mode = 2;
658     }
659     else if (_wcsnicmp(argStr, L"TG", 2) == 0)
660     {
661         argStr += 2;
662         *Mode = 3;
663     }
664 
665     return NULL;
666 }
667 
668 static PCWSTR
669 ParseBaudRate(PCWSTR argStr, PDWORD BaudRate)
670 {
671     argStr = ParseNumber(argStr, BaudRate);
672     if (!argStr) return NULL;
673 
674     /*
675      * Check for Baud Rate abbreviations. This means that using
676      * those values as real baud rates is impossible using MODE.
677      */
678     switch (*BaudRate)
679     {
680         /* BaudRate = 110, 150, 300, 600 */
681         case 11: case 15: case 30: case 60:
682             *BaudRate *= 10;
683             break;
684 
685         /* BaudRate = 1200, 2400, 4800, 9600 */
686         case 12: case 24: case 48: case 96:
687             *BaudRate *= 100;
688             break;
689 
690         case 19:
691             *BaudRate = 19200;
692             break;
693     }
694 
695     return argStr;
696 }
697 
698 static PCWSTR
699 ParseParity(PCWSTR argStr, PBYTE Parity)
700 {
701     switch (towupper(*argStr++))
702     {
703         case L'N':
704             *Parity = NOPARITY;
705             break;
706 
707         case L'O':
708             *Parity = ODDPARITY;
709             break;
710 
711         case L'E':
712             *Parity = EVENPARITY;
713             break;
714 
715         case L'M':
716             *Parity = MARKPARITY;
717             break;
718 
719         case L'S':
720             *Parity = SPACEPARITY;
721             break;
722 
723         default:
724             return NULL;
725     }
726 
727     return argStr;
728 }
729 
730 static PCWSTR
731 ParseByteSize(PCWSTR argStr, PBYTE ByteSize)
732 {
733     DWORD value = 0;
734 
735     argStr = ParseNumber(argStr, &value);
736     if (!argStr) return NULL;
737 
738     *ByteSize = (BYTE)value;
739     if (*ByteSize < 5 || *ByteSize > 8)
740         return NULL;
741 
742     return argStr;
743 }
744 
745 static PCWSTR
746 ParseStopBits(PCWSTR argStr, PBYTE StopBits)
747 {
748     if (_wcsnicmp(argStr, L"1.5", 3) == 0)
749     {
750         argStr += 3;
751         *StopBits = ONE5STOPBITS;
752     }
753     else
754     {
755         if (*argStr == L'1')
756             *StopBits = ONESTOPBIT;
757         else if (*argStr == L'2')
758             *StopBits = TWOSTOPBITS;
759         else
760             return NULL;
761 
762         argStr++;
763     }
764 
765     return argStr;
766 }
767 
768 /*
769  * Build a DCB using the old style settings string eg: "96,n,8,1"
770  *
771  * See dll/win32/kernel32/wine/comm.c!COMM_BuildOldCommDCB()
772  * for more information.
773  */
774 static BOOL
775 BuildOldCommDCB(
776     OUT LPDCB pDCB,
777     IN PCWSTR ArgStr)
778 {
779     PCWSTR argStr = ArgStr;
780     BOOL stop = FALSE;
781 
782     /*
783      * Parse the baud rate (only MANDATORY argument)
784      */
785     argStr = ParseBaudRate(argStr, &pDCB->BaudRate);
786     if (!argStr) return FALSE;
787 
788 
789     /*
790      * Now parse the rest (OPTIONAL arguments)
791      */
792 
793     while (*argStr == L' ') argStr++;
794     if (!*argStr) goto Quit;
795     if (*argStr++ != L',') return FALSE;
796     while (*argStr == L' ') argStr++;
797     if (!*argStr) goto Quit;
798 
799     /* Parse the parity */
800     // Default: EVENPARITY
801     pDCB->Parity = EVENPARITY;
802     if (*argStr != L',')
803     {
804         argStr = ParseParity(argStr, &pDCB->Parity);
805         if (!argStr) return FALSE;
806     }
807 
808     while (*argStr == L' ') argStr++;
809     if (!*argStr) goto Quit;
810     if (*argStr++ != L',') return FALSE;
811     while (*argStr == L' ') argStr++;
812     if (!*argStr) goto Quit;
813 
814     /* Parse the data bits */
815     // Default: 7
816     pDCB->ByteSize = 7;
817     if (*argStr != L',')
818     {
819         argStr = ParseByteSize(argStr, &pDCB->ByteSize);
820         if (!argStr) return FALSE;
821     }
822 
823     while (*argStr == L' ') argStr++;
824     if (!*argStr) goto Quit;
825     if (*argStr++ != L',') return FALSE;
826     while (*argStr == L' ') argStr++;
827     if (!*argStr) goto Quit;
828 
829     /* Parse the stop bits */
830     // Default: 1, or 2 for BAUD=110
831     // pDCB->StopBits = ONESTOPBIT;
832     if (*argStr != L',')
833     {
834         stop = TRUE;
835         argStr = ParseStopBits(argStr, &pDCB->StopBits);
836         if (!argStr) return FALSE;
837     }
838 
839     /* The last parameter (flow control "retry") is really optional */
840     while (*argStr == L' ') argStr++;
841     if (!*argStr) goto Quit;
842     if (*argStr++ != L',') return FALSE;
843     while (*argStr == L' ') argStr++;
844     if (!*argStr) goto Quit;
845 
846 Quit:
847     switch (towupper(*argStr))
848     {
849         case L'\0':
850             pDCB->fInX  = FALSE;
851             pDCB->fOutX = FALSE;
852             pDCB->fOutxCtsFlow = FALSE;
853             pDCB->fOutxDsrFlow = FALSE;
854             pDCB->fDtrControl  = DTR_CONTROL_ENABLE;
855             pDCB->fRtsControl  = RTS_CONTROL_ENABLE;
856             break;
857 
858         case L'X':
859             pDCB->fInX  = TRUE;
860             pDCB->fOutX = TRUE;
861             pDCB->fOutxCtsFlow = FALSE;
862             pDCB->fOutxDsrFlow = FALSE;
863             pDCB->fDtrControl  = DTR_CONTROL_ENABLE;
864             pDCB->fRtsControl  = RTS_CONTROL_ENABLE;
865             break;
866 
867         case L'P':
868             pDCB->fInX  = FALSE;
869             pDCB->fOutX = FALSE;
870             pDCB->fOutxCtsFlow = TRUE;
871             pDCB->fOutxDsrFlow = TRUE;
872             pDCB->fDtrControl  = DTR_CONTROL_HANDSHAKE;
873             pDCB->fRtsControl  = RTS_CONTROL_HANDSHAKE;
874             break;
875 
876         default:
877             /* Unsupported */
878             return FALSE;
879     }
880     if (*argStr) argStr++;
881 
882     /* This should be the end of the string */
883     while (*argStr == L' ') argStr++;
884     if (*argStr) return FALSE;
885 
886     /* If stop bits were not specified, a default is always supplied */
887     if (!stop)
888     {
889         if (pDCB->BaudRate == 110)
890             pDCB->StopBits = TWOSTOPBITS;
891         else
892             pDCB->StopBits = ONESTOPBIT;
893     }
894     return TRUE;
895 }
896 
897 /*
898  * Build a DCB using the new style settings string.
899  * eg: "baud=9600 parity=n data=8 stop=1 xon=on to=on"
900  *
901  * See dll/win32/kernel32/wine/comm.c!COMM_BuildNewCommDCB()
902  * for more information.
903  */
904 static BOOL
905 BuildNewCommDCB(
906     OUT LPDCB pDCB,
907     OUT LPCOMMTIMEOUTS pCommTimeouts,
908     IN PCWSTR ArgStr)
909 {
910     PCWSTR argStr = ArgStr;
911     BOOL baud = FALSE, stop = FALSE;
912     BYTE value;
913 
914     while (argStr && *argStr)
915     {
916         while (*argStr == L' ') argStr++;
917         if (!*argStr) break;
918 
919         if (_wcsnicmp(argStr, L"BAUD=", 5) == 0)
920         {
921             baud = TRUE;
922             argStr = ParseBaudRate(argStr+5, &pDCB->BaudRate);
923             if (!argStr) return FALSE;
924         }
925         else if (_wcsnicmp(argStr, L"PARITY=", 7) == 0)
926         {
927             // Default: EVENPARITY
928             argStr = ParseParity(argStr+7, &pDCB->Parity);
929             if (!argStr) return FALSE;
930         }
931         else if (_wcsnicmp(argStr, L"DATA=", 5) == 0)
932         {
933             // Default: 7
934             argStr = ParseByteSize(argStr+5, &pDCB->ByteSize);
935             if (!argStr) return FALSE;
936         }
937         else if (_wcsnicmp(argStr, L"STOP=", 5) == 0)
938         {
939             // Default: 1, or 2 for BAUD=110
940             stop = TRUE;
941             argStr = ParseStopBits(argStr+5, &pDCB->StopBits);
942             if (!argStr) return FALSE;
943         }
944         else if (_wcsnicmp(argStr, L"TO=", 3) == 0) // TO=ON|OFF
945         {
946             /* Only total time-outs are get/set by Windows' MODE.COM */
947             argStr = ParseModes(argStr+3, &value);
948             if (!argStr) return FALSE;
949             if (value == 0) // OFF
950             {
951                 pCommTimeouts->ReadTotalTimeoutConstant  = 0;
952                 pCommTimeouts->WriteTotalTimeoutConstant = 0;
953             }
954             else if (value == 1) // ON
955             {
956                 pCommTimeouts->ReadTotalTimeoutConstant  = 60000;
957                 pCommTimeouts->WriteTotalTimeoutConstant = 60000;
958             }
959             else
960             {
961                 return FALSE;
962             }
963         }
964         else if (_wcsnicmp(argStr, L"XON=", 4) == 0) // XON=ON|OFF
965         {
966             argStr = ParseModes(argStr+4, &value);
967             if (!argStr) return FALSE;
968             if ((value == 0) || (value == 1))
969             {
970                 pDCB->fOutX = value;
971                 pDCB->fInX  = value;
972             }
973             else
974             {
975                 return FALSE;
976             }
977         }
978         else if (_wcsnicmp(argStr, L"ODSR=", 5) == 0) // ODSR=ON|OFF
979         {
980             value = 0;
981             argStr = ParseModes(argStr+5, &value);
982             if (!argStr) return FALSE;
983             if ((value == 0) || (value == 1))
984                 pDCB->fOutxDsrFlow = value;
985             else
986                 return FALSE;
987         }
988         else if (_wcsnicmp(argStr, L"OCTS=", 5) == 0) // OCTS=ON|OFF
989         {
990             value = 0;
991             argStr = ParseModes(argStr+5, &value);
992             if (!argStr) return FALSE;
993             if ((value == 0) || (value == 1))
994                 pDCB->fOutxCtsFlow = value;
995             else
996                 return FALSE;
997         }
998         else if (_wcsnicmp(argStr, L"DTR=", 4) == 0) // DTR=ON|OFF|HS
999         {
1000             value = 0;
1001             argStr = ParseModes(argStr+4, &value);
1002             if (!argStr) return FALSE;
1003             if ((value == 0) || (value == 1) || (value == 2))
1004                 pDCB->fDtrControl = value;
1005             else
1006                 return FALSE;
1007         }
1008         else if (_wcsnicmp(argStr, L"RTS=", 4) == 0) // RTS=ON|OFF|HS|TG
1009         {
1010             value = 0;
1011             argStr = ParseModes(argStr+4, &value);
1012             if (!argStr) return FALSE;
1013             if ((value == 0) || (value == 1) || (value == 2) || (value == 3))
1014                 pDCB->fRtsControl = value;
1015             else
1016                 return FALSE;
1017         }
1018         else if (_wcsnicmp(argStr, L"IDSR=", 5) == 0) // IDSR=ON|OFF
1019         {
1020             value = 0;
1021             argStr = ParseModes(argStr+5, &value);
1022             if (!argStr) return FALSE;
1023             if ((value == 0) || (value == 1))
1024                 pDCB->fDsrSensitivity = value;
1025             else
1026                 return FALSE;
1027         }
1028         else
1029         {
1030             return FALSE;
1031         }
1032     }
1033 
1034     /* If stop bits were not specified, a default is always supplied */
1035     if (!stop)
1036     {
1037         if (baud && pDCB->BaudRate == 110)
1038             pDCB->StopBits = TWOSTOPBITS;
1039         else
1040             pDCB->StopBits = ONESTOPBIT;
1041     }
1042     return TRUE;
1043 }
1044 
1045 int SetSerialState(INT nPortNum, IN PCWSTR ArgStr)
1046 {
1047     BOOL Success;
1048     DCB dcb;
1049     COMMTIMEOUTS CommTimeouts;
1050 
1051     if (!SerialPortQuery(nPortNum, &dcb, &CommTimeouts, FALSE))
1052     {
1053         // TODO: Error message?
1054         return 0;
1055     }
1056 
1057     /*
1058      * Check whether we should use the old or the new MODE syntax:
1059      * in the old syntax, the separators are both spaces and commas.
1060      */
1061     if (wcschr(ArgStr, L','))
1062         Success = BuildOldCommDCB(&dcb, ArgStr);
1063     else
1064         Success = BuildNewCommDCB(&dcb, &CommTimeouts, ArgStr);
1065 
1066     if (!Success)
1067     {
1068         ConResPrintf(StdErr, IDS_ERROR_INVALID_PARAMETER, ArgStr);
1069         return 1;
1070     }
1071 
1072     SerialPortQuery(nPortNum, &dcb, &CommTimeouts, TRUE);
1073     ShowSerialStatus(nPortNum);
1074 
1075     return 0;
1076 }
1077 
1078 
1079 /*****************************************************************************\
1080  **                          E N T R Y   P O I N T                          **
1081 \*****************************************************************************/
1082 
1083 static PCWSTR
1084 FindPortNum(PCWSTR argStr, PINT PortNum)
1085 {
1086     PWSTR endptr = NULL;
1087 
1088     *PortNum = wcstol(argStr, &endptr, 10);
1089     if (endptr == argStr)
1090     {
1091         *PortNum = -1;
1092         return NULL;
1093     }
1094 
1095     return endptr;
1096 }
1097 
1098 int EnumerateDevices(VOID)
1099 {
1100     PWSTR Buffer, ptr;
1101     PCWSTR argStr;
1102     DWORD dwLen = MAX_PATH;
1103     INT nPortNum;
1104 
1105     /* Pre-allocate a buffer for QueryDosDeviceW() */
1106     Buffer = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
1107     if (Buffer == NULL)
1108     {
1109         /* We failed, bail out */
1110         ConPuts(StdErr, L"ERROR: Not enough memory\n");
1111         return 0;
1112     }
1113 
1114     for (;;)
1115     {
1116         *Buffer = UNICODE_NULL;
1117         if (QueryDosDeviceW(NULL, Buffer, dwLen))
1118             break;
1119 
1120         if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
1121         {
1122             /* We failed, bail out */
1123             ConPrintf(StdErr, L"ERROR: QueryDosDeviceW(...) failed: 0x%lx\n", GetLastError());
1124             HeapFree(GetProcessHeap(), 0, Buffer);
1125             return 0;
1126         }
1127 
1128         /* The buffer was too small, try to re-allocate it */
1129         dwLen *= 2;
1130         ptr = HeapReAlloc(GetProcessHeap(), 0, Buffer, dwLen * sizeof(WCHAR));
1131         if (ptr == NULL)
1132         {
1133             /* We failed, bail out */
1134             ConPuts(StdErr, L"ERROR: Not enough memory\n");
1135             HeapFree(GetProcessHeap(), 0, Buffer);
1136             return 0;
1137         }
1138         Buffer = ptr;
1139     }
1140 
1141     for (ptr = Buffer; *ptr != UNICODE_NULL; ptr += wcslen(ptr) + 1)
1142     {
1143         if (_wcsnicmp(ptr, L"COM", 3) == 0)
1144         {
1145             argStr = FindPortNum(ptr+3, &nPortNum);
1146             if (!argStr || *argStr || nPortNum == -1)
1147                 continue;
1148 
1149             // ConResPrintf(StdOut, IDS_QUERY_SERIAL_FOUND, ptr);
1150             ShowSerialStatus(nPortNum);
1151         }
1152         else if (_wcsicmp(ptr, L"PRN") == 0)
1153         {
1154             ConResPrintf(StdOut, IDS_QUERY_PRINTER_FOUND, ptr);
1155         }
1156         else if (_wcsnicmp(ptr, L"LPT", 3) == 0)
1157         {
1158             argStr = FindPortNum(ptr+3, &nPortNum);
1159             if (!argStr || *argStr || nPortNum == -1)
1160                 continue;
1161 
1162             // ConResPrintf(StdOut, IDS_QUERY_PARALLEL_FOUND, ptr);
1163             ShowParallelStatus(nPortNum);
1164         }
1165         else if (_wcsicmp(ptr, L"AUX") == 0 || _wcsicmp(ptr, L"NUL") == 0)
1166         {
1167             ConResPrintf(StdOut, IDS_QUERY_DOSDEV_FOUND, ptr);
1168         }
1169         else
1170         {
1171             // ConResPrintf(StdOut, IDS_QUERY_MISC_FOUND, ptr);
1172         }
1173     }
1174 
1175     ShowConsoleStatus();
1176 
1177     /* Free the buffer and return success */
1178     HeapFree(GetProcessHeap(), 0, Buffer);
1179     return 1;
1180 }
1181 
1182 int wmain(int argc, WCHAR* argv[])
1183 {
1184     int ret = 0;
1185     int arg;
1186     SIZE_T ArgStrSize;
1187     PCWSTR ArgStr, argStr;
1188 
1189     INT nPortNum;
1190 
1191     /* Initialize the Console Standard Streams */
1192     ConInitStdStreams();
1193 
1194     /*
1195      * MODE.COM has a very peculiar way of parsing its arguments,
1196      * as they can be even not separated by any space. This extreme
1197      * behaviour certainly is present for backwards compatibility
1198      * with the oldest versions of the utility present on MS-DOS.
1199      *
1200      * For example, such a command:
1201      *   "MODE.COM COM1baud=9600parity=ndata=8stop=1xon=onto=on"
1202      * will be correctly understood as:
1203      *   "MODE.COM COM1 baud=9600 parity=n data=8 stop=1 xon=on to=on"
1204      *
1205      * Note also that the "/STATUS" switch is actually really "/STA".
1206      *
1207      * However we will not use GetCommandLine() because we do not want
1208      * to deal with the prepended application path and try to find
1209      * where the arguments start. Our approach here will consist in
1210      * flattening the arguments vector.
1211      */
1212     ArgStrSize = 0;
1213 
1214     /* Compute the space needed for the new string, and allocate it */
1215     for (arg = 1; arg < argc; arg++)
1216     {
1217         ArgStrSize += wcslen(argv[arg]) + 1; // 1 for space
1218     }
1219     ArgStr = HeapAlloc(GetProcessHeap(), 0, (ArgStrSize + 1) * sizeof(WCHAR));
1220     if (ArgStr == NULL)
1221     {
1222         ConPuts(StdErr, L"ERROR: Not enough memory\n");
1223         return 1;
1224     }
1225 
1226     /* Copy the contents and NULL-terminate the string */
1227     argStr = ArgStr;
1228     for (arg = 1; arg < argc; arg++)
1229     {
1230         wcscpy((PWSTR)argStr, argv[arg]);
1231         argStr += wcslen(argv[arg]);
1232         *(PWSTR)argStr++ = L' ';
1233     }
1234     *(PWSTR)argStr = L'\0';
1235 
1236     /* Parse the command line */
1237     argStr = ArgStr;
1238 
1239     while (*argStr == L' ') argStr++;
1240     if (!*argStr) goto show_status;
1241 
1242     if (wcsstr(argStr, L"/?") || wcsstr(argStr, L"-?"))
1243     {
1244         ConResPuts(StdOut, IDS_USAGE);
1245         goto Quit;
1246     }
1247     else if (_wcsnicmp(argStr, L"/STA", 4) == 0)
1248     {
1249         /* Skip this parameter */
1250         while (*argStr != L' ') argStr++;
1251         /* Skip any delimiter */
1252         while (*argStr == L' ') argStr++;
1253 
1254         /* The presence of any other parameter is invalid */
1255         if (*argStr)
1256             goto invalid_parameter;
1257 
1258         goto show_status;
1259     }
1260     else if (_wcsnicmp(argStr, L"LPT", 3) == 0)
1261     {
1262         argStr = FindPortNum(argStr+3, &nPortNum);
1263         if (!argStr || nPortNum == -1)
1264             goto invalid_parameter;
1265 
1266         if (*argStr == L':') argStr++;
1267         while (*argStr == L' ') argStr++;
1268 
1269         if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
1270             ret = ShowParallelStatus(nPortNum);
1271         else
1272             ConPuts(StdErr, L"ERROR: LPT port redirection is not implemented!\n");
1273         // TODO: Implement setting LPT port redirection using SetParallelState().
1274         goto Quit;
1275     }
1276     else if (_wcsnicmp(argStr, L"COM", 3) == 0)
1277     {
1278         argStr = FindPortNum(argStr+3, &nPortNum);
1279         if (!argStr || nPortNum == -1)
1280             goto invalid_parameter;
1281 
1282         if (*argStr == L':') argStr++;
1283         while (*argStr == L' ') argStr++;
1284 
1285         if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
1286             ret = ShowSerialStatus(nPortNum);
1287         else
1288             ret = SetSerialState(nPortNum, argStr);
1289         goto Quit;
1290     }
1291     else if (_wcsnicmp(argStr, L"CON", 3) == 0)
1292     {
1293         argStr += 3;
1294 
1295         if (*argStr == L':') argStr++;
1296         while (*argStr == L' ') argStr++;
1297 
1298         if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
1299         {
1300             ret = ShowConsoleStatus();
1301         }
1302         else if ( (_wcsnicmp(argStr, L"CP", 2) == 0 && (argStr += 2)) ||
1303                   (_wcsnicmp(argStr, L"CODEPAGE", 8) == 0 && (argStr += 8)) )
1304         {
1305             while (*argStr == L' ') argStr++;
1306 
1307             if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
1308                 ret = ShowConsoleCPStatus();
1309             else
1310                 ret = SetConsoleCPState(argStr);
1311         }
1312         else
1313         {
1314             ret = SetConsoleState(argStr);
1315         }
1316         goto Quit;
1317     }
1318     // else if (wcschr(argStr, L','))
1319     else
1320     {
1321         /* Old syntax: MODE [COLS],[LINES] */
1322         ret = SetConsoleStateOld(argStr);
1323         goto Quit;
1324     }
1325 
1326 show_status:
1327     EnumerateDevices();
1328     goto Quit;
1329 
1330 invalid_parameter:
1331     ConResPrintf(StdErr, IDS_ERROR_INVALID_PARAMETER, ArgStr);
1332     goto Quit;
1333 
1334 Quit:
1335     /* Free the string and quit */
1336     HeapFree(GetProcessHeap(), 0, (PWSTR)ArgStr);
1337     return ret;
1338 }
1339