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