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 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 /* 159 * For whatever reason, x86 MSVC generates a ntdll!_allmul 160 * call when using Int32x32To64(), instead of an imul 161 * instruction. This leads the linker to error that _allmul 162 * is missing, since we do not link against ntdll. 163 * Everything is however OK with GCC... 164 * We therefore use the __emul() intrinsic which does 165 * the correct job. 166 */ 167 DueTime.QuadPart = __emul(1000 - timeElapsed, -10000); 168 SetWaitableTimer(hTimer, &DueTime, 0, NULL, NULL, FALSE); 169 dwWaitState = WaitForSingleObject(hTimer, INFINITE); 170 171 /* Check whether the timer has been signaled */ 172 if (dwWaitState != WAIT_OBJECT_0) 173 { 174 /* An error happened, bail out */ 175 PrintError(GetLastError()); 176 Status = EXIT_FAILURE; 177 break; 178 } 179 } 180 } 181 else 182 { 183 /* No timer is used: wait indefinitely */ 184 Sleep(INFINITE); 185 } 186 187 continue; 188 } 189 190 /* /NOBREAK is not used, check for user key presses */ 191 192 /* 193 * If the timer is used, use a passive wait of maximum 1 second 194 * while monitoring for incoming console input events, so that 195 * we are still able to display the timing count. 196 * Indeed, ReadConsoleInputW() indefinitely waits until an input 197 * event appears. ReadConsoleInputW() is however used to retrieve 198 * the input events where there are some, as well as for waiting 199 * indefinitely in case we do not use the timer. 200 */ 201 if (bUseTimer) 202 { 203 /* Wait a maximum of 1 second for input events */ 204 timeElapsed = GetTickCount() - dwStartTime; 205 if (timeElapsed < 1000) 206 dwWaitState = WaitForSingleObject(hInput, 1000 - timeElapsed); 207 else 208 dwWaitState = WAIT_TIMEOUT; 209 210 /* Check whether the input event has been signaled, or a timeout happened */ 211 if (dwWaitState == WAIT_TIMEOUT) 212 continue; 213 if (dwWaitState != WAIT_OBJECT_0) 214 { 215 /* An error happened, bail out */ 216 PrintError(GetLastError()); 217 Status = EXIT_FAILURE; 218 break; 219 } 220 221 /* Be sure there is something in the console input queue */ 222 if (!PeekConsoleInputW(hInput, InputRecords, ARRAYSIZE(InputRecords), &NumRecords)) 223 { 224 /* An error happened, bail out */ 225 ConResPrintf(StdErr, IDS_ERROR_READ_INPUT, GetLastError()); 226 Status = EXIT_FAILURE; 227 break; 228 } 229 230 if (NumRecords == 0) 231 continue; 232 } 233 234 /* 235 * Some events have been detected, pop them out from the input queue. 236 * In case we do not use the timer, wait indefinitely until an input 237 * event appears. 238 */ 239 if (!ReadConsoleInputW(hInput, InputRecords, ARRAYSIZE(InputRecords), &NumRecords)) 240 { 241 /* An error happened, bail out */ 242 ConResPrintf(StdErr, IDS_ERROR_READ_INPUT, GetLastError()); 243 Status = EXIT_FAILURE; 244 break; 245 } 246 247 /* Check the input events for a key press */ 248 for (i = 0; i < NumRecords; ++i) 249 { 250 /* Ignore any non-key event */ 251 if (InputRecords[i].EventType != KEY_EVENT) 252 continue; 253 254 /* Ignore any system key event */ 255 if ((InputRecords[i].Event.KeyEvent.wVirtualKeyCode == VK_CONTROL) || 256 // (InputRecords[i].Event.KeyEvent.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED )) || 257 // (InputRecords[i].Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) || 258 (InputRecords[i].Event.KeyEvent.wVirtualKeyCode == VK_MENU)) 259 { 260 continue; 261 } 262 263 /* This is a non-system key event, stop waiting */ 264 goto Stop; 265 } 266 } 267 268 Stop: 269 ConPuts(StdOut, L"\n"); 270 271 Quit: 272 if (bNoBreak) 273 SetConsoleCtrlHandler(NULL, FALSE); 274 275 if (bNoBreak && bUseTimer) 276 CloseHandle(hTimer); 277 278 return Status; 279 } 280 281 int wmain(int argc, WCHAR* argv[]) 282 { 283 INT timerValue = -1; 284 PWCHAR pszNext; 285 BOOL bDisableInput = FALSE, fTimerFlags = 0; 286 int index = 0; 287 288 /* Initialize the Console Standard Streams */ 289 ConInitStdStreams(); 290 291 if (argc == 1) 292 { 293 ConResPrintf(StdOut, IDS_USAGE); 294 return EXIT_SUCCESS; 295 } 296 297 /* Parse the command line for options */ 298 for (index = 1; index < argc; index++) 299 { 300 if (argv[index][0] == L'-' || argv[index][0] == L'/') 301 { 302 switch (towupper(argv[index][1])) 303 { 304 case L'?': /* Help */ 305 { 306 ConResPrintf(StdOut, IDS_USAGE); 307 return EXIT_SUCCESS; 308 } 309 310 case L'T': /* Timer */ 311 { 312 /* Consecutive /T switches are invalid */ 313 if (fTimerFlags & 2) 314 { 315 ConResPrintf(StdErr, IDS_ERROR_ONE_TIME); 316 return EXIT_FAILURE; 317 } 318 319 /* Remember that a /T switch has been encountered */ 320 fTimerFlags |= 2; 321 322 /* Go to the next (timer) value */ 323 continue; 324 } 325 } 326 327 /* This flag is used to ignore any keyboard keys but Ctrl-C */ 328 if (_wcsicmp(&argv[index][1], L"NOBREAK") == 0) 329 { 330 bDisableInput = TRUE; 331 332 /* Go to next value */ 333 continue; 334 } 335 } 336 337 /* The timer value can also be specified without the /T switch */ 338 339 /* Only one timer value is supported */ 340 if (fTimerFlags & 1) 341 { 342 ConResPrintf(StdErr, IDS_ERROR_ONE_TIME); 343 return EXIT_FAILURE; 344 } 345 346 timerValue = wcstol(argv[index], &pszNext, 10); 347 if (*pszNext) 348 { 349 ConResPrintf(StdErr, IDS_ERROR_OUT_OF_RANGE); 350 return EXIT_FAILURE; 351 } 352 353 /* Remember that the timer value has been set */ 354 fTimerFlags |= 1; 355 } 356 357 /* A timer value is mandatory in order to continue */ 358 if (!(fTimerFlags & 1)) 359 { 360 ConResPrintf(StdErr, IDS_ERROR_NO_TIMER_VALUE); 361 return EXIT_FAILURE; 362 } 363 364 /* Make sure the timer value is within range */ 365 if ((timerValue < -1) || (timerValue > 99999)) 366 { 367 ConResPrintf(StdErr, IDS_ERROR_OUT_OF_RANGE); 368 return EXIT_FAILURE; 369 } 370 371 return InputWait(bDisableInput, timerValue); 372 } 373