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
PrintError(DWORD dwError)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
CtrlCIntercept(DWORD dwCtrlType)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
InputWait(BOOL bNoBreak,INT timerValue)47 INT InputWait(BOOL bNoBreak, INT timerValue)
48 {
49 INT Status = EXIT_SUCCESS;
50 HANDLE hInput;
51 BOOL bUseTimer = (timerValue != -1);
52 HANDLE hTimer = NULL;
53 DWORD dwStartTime;
54 LONG timeElapsed;
55 DWORD dwWaitState;
56 INPUT_RECORD InputRecords[5];
57 ULONG NumRecords, i;
58 BOOL DisplayMsg = TRUE;
59 UINT WaitMsgId = (bNoBreak ? IDS_NOBREAK_INPUT : IDS_USER_INPUT);
60 UINT WaitCountMsgId = (bNoBreak ? IDS_NOBREAK_INPUT_COUNT : IDS_USER_INPUT_COUNT);
61
62 /* Retrieve the current input handle */
63 hInput = ConStreamGetOSHandle(StdIn);
64 if (hInput == INVALID_HANDLE_VALUE)
65 {
66 ConResPrintf(StdErr, IDS_ERROR_INVALID_HANDLE_VALUE, GetLastError());
67 return EXIT_FAILURE;
68 }
69
70 /* Start a new wait if we use the timer */
71 if (bNoBreak && bUseTimer)
72 {
73 hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
74 if (hTimer == NULL)
75 {
76 /* A problem happened, bail out */
77 PrintError(GetLastError());
78 return EXIT_FAILURE;
79 }
80 }
81 if (bUseTimer)
82 dwStartTime = GetTickCount();
83
84 /* If /NOBREAK is used, monitor for Ctrl-C input */
85 if (bNoBreak)
86 SetConsoleCtrlHandler(CtrlCIntercept, TRUE);
87
88 /* Initially flush the console input queue to remove any pending events */
89 if (!GetNumberOfConsoleInputEvents(hInput, &NumRecords) ||
90 !FlushConsoleInputBuffer(hInput))
91 {
92 /* A problem happened, bail out */
93 PrintError(GetLastError());
94 Status = EXIT_FAILURE;
95 goto Quit;
96 }
97
98 ConPuts(StdOut, L"\n");
99
100 /* If the timer is not used, just show the message */
101 if (!bUseTimer)
102 {
103 ConPuts(StdOut, L"\r");
104 ConResPuts(StdOut, WaitMsgId);
105 }
106
107 while (TRUE)
108 {
109 /* Decrease the timer if we use it */
110 if (bUseTimer)
111 {
112 /*
113 * Compute how much time the previous operations took.
114 * This allows us in particular to take account for any time
115 * elapsed if something slowed down, or if the console has been
116 * paused in the meantime.
117 */
118 timeElapsed = GetTickCount() - dwStartTime;
119 if (timeElapsed >= 1000)
120 {
121 /* Increase dwStartTime by steps of 1 second */
122 timeElapsed /= 1000;
123 dwStartTime += (1000 * timeElapsed);
124
125 if (timeElapsed <= timerValue)
126 timerValue -= timeElapsed;
127 else
128 timerValue = 0;
129
130 DisplayMsg = TRUE;
131 }
132
133 if (DisplayMsg)
134 {
135 ConPuts(StdOut, L"\r");
136 ConResPrintf(StdOut, WaitCountMsgId, timerValue);
137 ConPuts(StdOut, L" \b");
138
139 DisplayMsg = FALSE;
140 }
141
142 /* Stop when the timer reaches zero */
143 if (timerValue <= 0)
144 break;
145 }
146
147 /* If /NOBREAK is used, only allow Ctrl-C input which is handled by the console handler */
148 if (bNoBreak)
149 {
150 if (bUseTimer)
151 {
152 LARGE_INTEGER DueTime;
153
154 /* We use the timer: use a passive wait of maximum 1 second */
155 timeElapsed = GetTickCount() - dwStartTime;
156 if (timeElapsed < 1000)
157 {
158 DueTime.QuadPart = Int32x32To64(1000 - timeElapsed, -10000);
159 SetWaitableTimer(hTimer, &DueTime, 0, NULL, NULL, FALSE);
160 dwWaitState = WaitForSingleObject(hTimer, INFINITE);
161
162 /* Check whether the timer has been signaled */
163 if (dwWaitState != WAIT_OBJECT_0)
164 {
165 /* An error happened, bail out */
166 PrintError(GetLastError());
167 Status = EXIT_FAILURE;
168 break;
169 }
170 }
171 }
172 else
173 {
174 /* No timer is used: wait indefinitely */
175 Sleep(INFINITE);
176 }
177
178 continue;
179 }
180
181 /* /NOBREAK is not used, check for user key presses */
182
183 /*
184 * If the timer is used, use a passive wait of maximum 1 second
185 * while monitoring for incoming console input events, so that
186 * we are still able to display the timing count.
187 * Indeed, ReadConsoleInputW() indefinitely waits until an input
188 * event appears. ReadConsoleInputW() is however used to retrieve
189 * the input events where there are some, as well as for waiting
190 * indefinitely in case we do not use the timer.
191 */
192 if (bUseTimer)
193 {
194 /* Wait a maximum of 1 second for input events */
195 timeElapsed = GetTickCount() - dwStartTime;
196 if (timeElapsed < 1000)
197 dwWaitState = WaitForSingleObject(hInput, 1000 - timeElapsed);
198 else
199 dwWaitState = WAIT_TIMEOUT;
200
201 /* Check whether the input event has been signaled, or a timeout happened */
202 if (dwWaitState == WAIT_TIMEOUT)
203 continue;
204 if (dwWaitState != WAIT_OBJECT_0)
205 {
206 /* An error happened, bail out */
207 PrintError(GetLastError());
208 Status = EXIT_FAILURE;
209 break;
210 }
211
212 /* Be sure there is something in the console input queue */
213 if (!PeekConsoleInputW(hInput, InputRecords, ARRAYSIZE(InputRecords), &NumRecords))
214 {
215 /* An error happened, bail out */
216 ConResPrintf(StdErr, IDS_ERROR_READ_INPUT, GetLastError());
217 Status = EXIT_FAILURE;
218 break;
219 }
220
221 if (NumRecords == 0)
222 continue;
223 }
224
225 /*
226 * Some events have been detected, pop them out from the input queue.
227 * In case we do not use the timer, wait indefinitely until an input
228 * event appears.
229 */
230 if (!ReadConsoleInputW(hInput, InputRecords, ARRAYSIZE(InputRecords), &NumRecords))
231 {
232 /* An error happened, bail out */
233 ConResPrintf(StdErr, IDS_ERROR_READ_INPUT, GetLastError());
234 Status = EXIT_FAILURE;
235 break;
236 }
237
238 /* Check the input events for a key press */
239 for (i = 0; i < NumRecords; ++i)
240 {
241 /* Ignore any non-key event */
242 if (InputRecords[i].EventType != KEY_EVENT)
243 continue;
244
245 /* Ignore any system key event */
246 if ((InputRecords[i].Event.KeyEvent.wVirtualKeyCode == VK_CONTROL) ||
247 // (InputRecords[i].Event.KeyEvent.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED )) ||
248 // (InputRecords[i].Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) ||
249 (InputRecords[i].Event.KeyEvent.wVirtualKeyCode == VK_MENU))
250 {
251 continue;
252 }
253
254 /* This is a non-system key event, stop waiting */
255 goto Stop;
256 }
257 }
258
259 Stop:
260 ConPuts(StdOut, L"\n");
261
262 Quit:
263 if (bNoBreak)
264 SetConsoleCtrlHandler(NULL, FALSE);
265
266 if (bNoBreak && bUseTimer)
267 CloseHandle(hTimer);
268
269 return Status;
270 }
271
wmain(int argc,WCHAR * argv[])272 int wmain(int argc, WCHAR* argv[])
273 {
274 INT timerValue = -1;
275 PWCHAR pszNext;
276 BOOL bDisableInput = FALSE, fTimerFlags = 0;
277 int index = 0;
278
279 /* Initialize the Console Standard Streams */
280 ConInitStdStreams();
281
282 if (argc == 1)
283 {
284 ConResPrintf(StdOut, IDS_USAGE);
285 return EXIT_SUCCESS;
286 }
287
288 /* Parse the command line for options */
289 for (index = 1; index < argc; index++)
290 {
291 if (argv[index][0] == L'-' || argv[index][0] == L'/')
292 {
293 switch (towupper(argv[index][1]))
294 {
295 case L'?': /* Help */
296 {
297 ConResPrintf(StdOut, IDS_USAGE);
298 return EXIT_SUCCESS;
299 }
300
301 case L'T': /* Timer */
302 {
303 /* Consecutive /T switches are invalid */
304 if (fTimerFlags & 2)
305 {
306 ConResPrintf(StdErr, IDS_ERROR_ONE_TIME);
307 return EXIT_FAILURE;
308 }
309
310 /* Remember that a /T switch has been encountered */
311 fTimerFlags |= 2;
312
313 /* Go to the next (timer) value */
314 continue;
315 }
316 }
317
318 /* This flag is used to ignore any keyboard keys but Ctrl-C */
319 if (_wcsicmp(&argv[index][1], L"NOBREAK") == 0)
320 {
321 bDisableInput = TRUE;
322
323 /* Go to next value */
324 continue;
325 }
326 }
327
328 /* The timer value can also be specified without the /T switch */
329
330 /* Only one timer value is supported */
331 if (fTimerFlags & 1)
332 {
333 ConResPrintf(StdErr, IDS_ERROR_ONE_TIME);
334 return EXIT_FAILURE;
335 }
336
337 timerValue = wcstol(argv[index], &pszNext, 10);
338 if (*pszNext)
339 {
340 ConResPrintf(StdErr, IDS_ERROR_OUT_OF_RANGE);
341 return EXIT_FAILURE;
342 }
343
344 /* Remember that the timer value has been set */
345 fTimerFlags |= 1;
346 }
347
348 /* A timer value is mandatory in order to continue */
349 if (!(fTimerFlags & 1))
350 {
351 ConResPrintf(StdErr, IDS_ERROR_NO_TIMER_VALUE);
352 return EXIT_FAILURE;
353 }
354
355 /* Make sure the timer value is within range */
356 if ((timerValue < -1) || (timerValue > 99999))
357 {
358 ConResPrintf(StdErr, IDS_ERROR_OUT_OF_RANGE);
359 return EXIT_FAILURE;
360 }
361
362 return InputWait(bDisableInput, timerValue);
363 }
364