[TIMEOUT]: Implement the TIMEOUT utility (found on Win2k3 and upwards). This is an...
[reactos.git] / reactos / base / applications / cmdutils / timeout / timeout.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Timeout utility
4 * FILE: base/applications/cmdutils/timeout/timeout.c
5 * PURPOSE: An enhanced alternative to the Pause command.
6 * PROGRAMMERS: Lee Schroeder (spaceseel at gmail dot com)
7 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
8 */
9
10 #include <stdio.h>
11 #include <stdlib.h>
12
13 #include <windef.h>
14 #include <winbase.h>
15 #include <wincon.h>
16 #include <winuser.h>
17
18 #include <conutils.h>
19
20 #include "resource.h"
21
22 VOID PrintError(DWORD dwError)
23 {
24 if (dwError == ERROR_SUCCESS)
25 return;
26
27 ConMsgPuts(StdErr, FORMAT_MESSAGE_FROM_SYSTEM,
28 NULL, dwError, LANG_USER_DEFAULT);
29 ConPuts(StdErr, L"\n");
30 }
31
32 BOOL
33 WINAPI
34 CtrlCIntercept(DWORD dwCtrlType)
35 {
36 switch (dwCtrlType)
37 {
38 case CTRL_C_EVENT:
39 ConPuts(StdOut, L"\n");
40 SetConsoleCtrlHandler(NULL, FALSE);
41 ExitProcess(EXIT_FAILURE);
42 return TRUE;
43 }
44 return FALSE;
45 }
46
47 INT InputWait(BOOL bNoBreak, INT timerValue)
48 {
49 INT Status = EXIT_SUCCESS;
50 HANDLE hInput;
51 BOOL bUseTimer = (timerValue != -1);
52 DWORD dwStartTime;
53 LONG timeElapsed;
54 DWORD dwWaitState;
55 INPUT_RECORD InputRecords[5];
56 ULONG NumRecords, i;
57 BOOL DisplayMsg = TRUE;
58 UINT WaitMsgId = (bNoBreak ? IDS_NOBREAK_INPUT : IDS_USER_INPUT);
59 UINT WaitCountMsgId = (bNoBreak ? IDS_NOBREAK_INPUT_COUNT : IDS_USER_INPUT_COUNT);
60
61 /* Retrieve the current input handle */
62 hInput = ConStreamGetOSHandle(StdIn);
63 if (hInput == INVALID_HANDLE_VALUE)
64 {
65 ConResPrintf(StdErr, IDS_ERROR_INVALID_HANDLE_VALUE, GetLastError());
66 return EXIT_FAILURE;
67 }
68
69 /* Start a new wait if we use the timer */
70 if (bUseTimer)
71 dwStartTime = GetTickCount();
72
73 /* If /NOBREAK is used, monitor for Ctrl-C input */
74 if (bNoBreak)
75 SetConsoleCtrlHandler(CtrlCIntercept, TRUE);
76
77 /* Initially flush the console input queue to remove any pending events */
78 if (!GetNumberOfConsoleInputEvents(hInput, &NumRecords) ||
79 !FlushConsoleInputBuffer(hInput))
80 {
81 /* A problem happened, bail out */
82 PrintError(GetLastError());
83 Status = EXIT_FAILURE;
84 goto Quit;
85 }
86
87 ConPuts(StdOut, L"\n");
88
89 /* If the timer is not used, just show the message */
90 if (!bUseTimer)
91 {
92 ConPuts(StdOut, L"\r");
93 ConResPuts(StdOut, WaitMsgId);
94 }
95
96 while (TRUE)
97 {
98 /* Decrease the timer if we use it */
99 if (bUseTimer)
100 {
101 /*
102 * Compute how much time the previous operations took.
103 * This allows us in particular to take account for any time
104 * elapsed if something slowed down, or if the console has been
105 * paused in the meantime.
106 */
107 timeElapsed = GetTickCount() - dwStartTime;
108 if (timeElapsed >= 1000)
109 {
110 /* Increase dwStartTime by steps of 1 second */
111 timeElapsed /= 1000;
112 dwStartTime += (1000 * timeElapsed);
113
114 if (timeElapsed <= timerValue)
115 timerValue -= timeElapsed;
116 else
117 timerValue = 0;
118
119 DisplayMsg = TRUE;
120 }
121
122 if (DisplayMsg)
123 {
124 ConPuts(StdOut, L"\r");
125 ConResPrintf(StdOut, WaitCountMsgId, timerValue);
126 ConPuts(StdOut, L" \b");
127
128 DisplayMsg = FALSE;
129 }
130
131 /* Stop when the timer reaches zero */
132 if (timerValue <= 0)
133 break;
134 }
135
136 /* If /NOBREAK is used, only allow Ctrl-C input which is handled by the console handler */
137 if (bNoBreak)
138 {
139 if (bUseTimer)
140 {
141 /* We use the timer: wait a little bit before updating it */
142 Sleep(100);
143 }
144 else
145 {
146 /* No timer is used: wait indefinitely */
147 Sleep(INFINITE);
148 }
149
150 continue;
151 }
152
153 /* /NOBREAK is not used, check for user key presses */
154
155 /*
156 * If the timer is used, use a passive wait of maximum 1 second
157 * while monitoring for incoming console input events, so that
158 * we are still able to display the timing count.
159 * Indeed, ReadConsoleInputW() indefinitely waits until an input
160 * event appears. ReadConsoleInputW() is however used to retrieve
161 * the input events where there are some, as well as for waiting
162 * indefinitely in case we do not use the timer.
163 */
164 if (bUseTimer)
165 {
166 /* Wait a maximum of 1 second for input events */
167 timeElapsed = GetTickCount() - dwStartTime;
168 if (timeElapsed < 1000)
169 dwWaitState = WaitForSingleObject(hInput, 1000 - timeElapsed);
170 else
171 dwWaitState = WAIT_TIMEOUT;
172
173 /* Check whether the input handle has been signaled, or a timeout happened */
174 if (dwWaitState == WAIT_TIMEOUT)
175 continue;
176 if (dwWaitState != WAIT_OBJECT_0)
177 {
178 /* An error happened, bail out */
179 PrintError(GetLastError());
180 Status = EXIT_FAILURE;
181 break;
182 }
183
184 /* Be sure there is someting in the console input queue */
185 if (!PeekConsoleInputW(hInput, InputRecords, ARRAYSIZE(InputRecords), &NumRecords))
186 {
187 /* An error happened, bail out */
188 ConResPrintf(StdErr, IDS_ERROR_READ_INPUT, GetLastError());
189 Status = EXIT_FAILURE;
190 break;
191 }
192
193 if (NumRecords == 0)
194 continue;
195 }
196
197 /*
198 * Some events have been detected, pop them out from the input queue.
199 * In case we do not use the timer, wait indefinitely until an input
200 * event appears.
201 */
202 if (!ReadConsoleInputW(hInput, InputRecords, ARRAYSIZE(InputRecords), &NumRecords))
203 {
204 /* An error happened, bail out */
205 ConResPrintf(StdErr, IDS_ERROR_READ_INPUT, GetLastError());
206 Status = EXIT_FAILURE;
207 break;
208 }
209
210 /* Check the input events for a key press */
211 for (i = 0; i < NumRecords; ++i)
212 {
213 /* Ignore any non-key event */
214 if (InputRecords[i].EventType != KEY_EVENT)
215 continue;
216
217 /* Ignore any system key event */
218 if ((InputRecords[i].Event.KeyEvent.wVirtualKeyCode == VK_CONTROL) ||
219 // (InputRecords[i].Event.KeyEvent.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED )) ||
220 // (InputRecords[i].Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) ||
221 (InputRecords[i].Event.KeyEvent.wVirtualKeyCode == VK_MENU))
222 {
223 continue;
224 }
225
226 /* This is a non-system key event, stop waiting */
227 goto Stop;
228 }
229 }
230
231 Stop:
232 ConPuts(StdOut, L"\n");
233
234 Quit:
235 if (bNoBreak)
236 SetConsoleCtrlHandler(NULL, FALSE);
237
238 return Status;
239 }
240
241 int wmain(int argc, WCHAR* argv[])
242 {
243 INT timerValue = -1;
244 PWCHAR pszNext;
245 BOOL bDisableInput = FALSE, fTimerFlags = 0;
246 int index = 0;
247
248 /* Initialize the Console Standard Streams */
249 ConInitStdStreams();
250
251 if (argc == 1)
252 {
253 ConResPrintf(StdOut, IDS_USAGE);
254 return EXIT_SUCCESS;
255 }
256
257 /* Parse the command line for options */
258 for (index = 1; index < argc; index++)
259 {
260 if (argv[index][0] == L'-' || argv[index][0] == L'/')
261 {
262 switch (towupper(argv[index][1]))
263 {
264 case L'?': /* Help */
265 {
266 ConResPrintf(StdOut, IDS_USAGE);
267 return EXIT_SUCCESS;
268 }
269
270 case L'T': /* Timer */
271 {
272 /* Consecutive /T switches are invalid */
273 if (fTimerFlags & 2)
274 {
275 ConResPrintf(StdErr, IDS_ERROR_ONE_TIME);
276 return EXIT_FAILURE;
277 }
278
279 /* Remember that a /T switch has been encountered */
280 fTimerFlags |= 2;
281
282 /* Go to the next (timer) value */
283 continue;
284 }
285 }
286
287 /* This flag is used to ignore any keyboard keys but Ctrl-C */
288 if (_wcsicmp(&argv[index][1], L"NOBREAK") == 0)
289 {
290 bDisableInput = TRUE;
291
292 /* Go to next value */
293 continue;
294 }
295 }
296
297 /* The timer value can also be specified without the /T switch */
298
299 /* Only one timer value is supported */
300 if (fTimerFlags & 1)
301 {
302 ConResPrintf(StdErr, IDS_ERROR_ONE_TIME);
303 return EXIT_FAILURE;
304 }
305
306 timerValue = wcstol(argv[index], &pszNext, 10);
307 if (*pszNext)
308 {
309 ConResPrintf(StdErr, IDS_ERROR_OUT_OF_RANGE);
310 return EXIT_FAILURE;
311 }
312
313 /* Remember that the timer value has been set */
314 fTimerFlags |= 1;
315 }
316
317 /* A timer value is mandatory in order to continue */
318 if (!(fTimerFlags & 1))
319 {
320 ConResPrintf(StdErr, IDS_ERROR_NO_TIMER_VALUE);
321 return EXIT_FAILURE;
322 }
323
324 /* Make sure the timer value is within range */
325 if ((timerValue < -1) || (timerValue > 99999))
326 {
327 ConResPrintf(StdErr, IDS_ERROR_OUT_OF_RANGE);
328 return EXIT_FAILURE;
329 }
330
331 return InputWait(bDisableInput, timerValue);
332 }