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