--- /dev/null
+/*
+ * COPYRIGHT: See COPYING in the top level directory
+ * PROJECT: ReactOS Timeout utility
+ * FILE: base/applications/cmdutils/timeout/timeout.c
+ * PURPOSE: An enhanced alternative to the Pause command.
+ * PROGRAMMERS: Lee Schroeder (spaceseel at gmail dot com)
+ * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <windef.h>
+#include <winbase.h>
+#include <wincon.h>
+#include <winuser.h>
+
+#include <conutils.h>
+
+#include "resource.h"
+
+VOID PrintError(DWORD dwError)
+{
+ if (dwError == ERROR_SUCCESS)
+ return;
+
+ ConMsgPuts(StdErr, FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL, dwError, LANG_USER_DEFAULT);
+ ConPuts(StdErr, L"\n");
+}
+
+BOOL
+WINAPI
+CtrlCIntercept(DWORD dwCtrlType)
+{
+ switch (dwCtrlType)
+ {
+ case CTRL_C_EVENT:
+ ConPuts(StdOut, L"\n");
+ SetConsoleCtrlHandler(NULL, FALSE);
+ ExitProcess(EXIT_FAILURE);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+INT InputWait(BOOL bNoBreak, INT timerValue)
+{
+ INT Status = EXIT_SUCCESS;
+ HANDLE hInput;
+ BOOL bUseTimer = (timerValue != -1);
+ DWORD dwStartTime;
+ LONG timeElapsed;
+ DWORD dwWaitState;
+ INPUT_RECORD InputRecords[5];
+ ULONG NumRecords, i;
+ BOOL DisplayMsg = TRUE;
+ UINT WaitMsgId = (bNoBreak ? IDS_NOBREAK_INPUT : IDS_USER_INPUT);
+ UINT WaitCountMsgId = (bNoBreak ? IDS_NOBREAK_INPUT_COUNT : IDS_USER_INPUT_COUNT);
+
+ /* Retrieve the current input handle */
+ hInput = ConStreamGetOSHandle(StdIn);
+ if (hInput == INVALID_HANDLE_VALUE)
+ {
+ ConResPrintf(StdErr, IDS_ERROR_INVALID_HANDLE_VALUE, GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ /* Start a new wait if we use the timer */
+ if (bUseTimer)
+ dwStartTime = GetTickCount();
+
+ /* If /NOBREAK is used, monitor for Ctrl-C input */
+ if (bNoBreak)
+ SetConsoleCtrlHandler(CtrlCIntercept, TRUE);
+
+ /* Initially flush the console input queue to remove any pending events */
+ if (!GetNumberOfConsoleInputEvents(hInput, &NumRecords) ||
+ !FlushConsoleInputBuffer(hInput))
+ {
+ /* A problem happened, bail out */
+ PrintError(GetLastError());
+ Status = EXIT_FAILURE;
+ goto Quit;
+ }
+
+ ConPuts(StdOut, L"\n");
+
+ /* If the timer is not used, just show the message */
+ if (!bUseTimer)
+ {
+ ConPuts(StdOut, L"\r");
+ ConResPuts(StdOut, WaitMsgId);
+ }
+
+ while (TRUE)
+ {
+ /* Decrease the timer if we use it */
+ if (bUseTimer)
+ {
+ /*
+ * Compute how much time the previous operations took.
+ * This allows us in particular to take account for any time
+ * elapsed if something slowed down, or if the console has been
+ * paused in the meantime.
+ */
+ timeElapsed = GetTickCount() - dwStartTime;
+ if (timeElapsed >= 1000)
+ {
+ /* Increase dwStartTime by steps of 1 second */
+ timeElapsed /= 1000;
+ dwStartTime += (1000 * timeElapsed);
+
+ if (timeElapsed <= timerValue)
+ timerValue -= timeElapsed;
+ else
+ timerValue = 0;
+
+ DisplayMsg = TRUE;
+ }
+
+ if (DisplayMsg)
+ {
+ ConPuts(StdOut, L"\r");
+ ConResPrintf(StdOut, WaitCountMsgId, timerValue);
+ ConPuts(StdOut, L" \b");
+
+ DisplayMsg = FALSE;
+ }
+
+ /* Stop when the timer reaches zero */
+ if (timerValue <= 0)
+ break;
+ }
+
+ /* If /NOBREAK is used, only allow Ctrl-C input which is handled by the console handler */
+ if (bNoBreak)
+ {
+ if (bUseTimer)
+ {
+ /* We use the timer: wait a little bit before updating it */
+ Sleep(100);
+ }
+ else
+ {
+ /* No timer is used: wait indefinitely */
+ Sleep(INFINITE);
+ }
+
+ continue;
+ }
+
+ /* /NOBREAK is not used, check for user key presses */
+
+ /*
+ * If the timer is used, use a passive wait of maximum 1 second
+ * while monitoring for incoming console input events, so that
+ * we are still able to display the timing count.
+ * Indeed, ReadConsoleInputW() indefinitely waits until an input
+ * event appears. ReadConsoleInputW() is however used to retrieve
+ * the input events where there are some, as well as for waiting
+ * indefinitely in case we do not use the timer.
+ */
+ if (bUseTimer)
+ {
+ /* Wait a maximum of 1 second for input events */
+ timeElapsed = GetTickCount() - dwStartTime;
+ if (timeElapsed < 1000)
+ dwWaitState = WaitForSingleObject(hInput, 1000 - timeElapsed);
+ else
+ dwWaitState = WAIT_TIMEOUT;
+
+ /* Check whether the input handle has been signaled, or a timeout happened */
+ if (dwWaitState == WAIT_TIMEOUT)
+ continue;
+ if (dwWaitState != WAIT_OBJECT_0)
+ {
+ /* An error happened, bail out */
+ PrintError(GetLastError());
+ Status = EXIT_FAILURE;
+ break;
+ }
+
+ /* Be sure there is someting in the console input queue */
+ if (!PeekConsoleInputW(hInput, InputRecords, ARRAYSIZE(InputRecords), &NumRecords))
+ {
+ /* An error happened, bail out */
+ ConResPrintf(StdErr, IDS_ERROR_READ_INPUT, GetLastError());
+ Status = EXIT_FAILURE;
+ break;
+ }
+
+ if (NumRecords == 0)
+ continue;
+ }
+
+ /*
+ * Some events have been detected, pop them out from the input queue.
+ * In case we do not use the timer, wait indefinitely until an input
+ * event appears.
+ */
+ if (!ReadConsoleInputW(hInput, InputRecords, ARRAYSIZE(InputRecords), &NumRecords))
+ {
+ /* An error happened, bail out */
+ ConResPrintf(StdErr, IDS_ERROR_READ_INPUT, GetLastError());
+ Status = EXIT_FAILURE;
+ break;
+ }
+
+ /* Check the input events for a key press */
+ for (i = 0; i < NumRecords; ++i)
+ {
+ /* Ignore any non-key event */
+ if (InputRecords[i].EventType != KEY_EVENT)
+ continue;
+
+ /* Ignore any system key event */
+ if ((InputRecords[i].Event.KeyEvent.wVirtualKeyCode == VK_CONTROL) ||
+ // (InputRecords[i].Event.KeyEvent.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED )) ||
+ // (InputRecords[i].Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) ||
+ (InputRecords[i].Event.KeyEvent.wVirtualKeyCode == VK_MENU))
+ {
+ continue;
+ }
+
+ /* This is a non-system key event, stop waiting */
+ goto Stop;
+ }
+ }
+
+Stop:
+ ConPuts(StdOut, L"\n");
+
+Quit:
+ if (bNoBreak)
+ SetConsoleCtrlHandler(NULL, FALSE);
+
+ return Status;
+}
+
+int wmain(int argc, WCHAR* argv[])
+{
+ INT timerValue = -1;
+ PWCHAR pszNext;
+ BOOL bDisableInput = FALSE, fTimerFlags = 0;
+ int index = 0;
+
+ /* Initialize the Console Standard Streams */
+ ConInitStdStreams();
+
+ if (argc == 1)
+ {
+ ConResPrintf(StdOut, IDS_USAGE);
+ return EXIT_SUCCESS;
+ }
+
+ /* Parse the command line for options */
+ for (index = 1; index < argc; index++)
+ {
+ if (argv[index][0] == L'-' || argv[index][0] == L'/')
+ {
+ switch (towupper(argv[index][1]))
+ {
+ case L'?': /* Help */
+ {
+ ConResPrintf(StdOut, IDS_USAGE);
+ return EXIT_SUCCESS;
+ }
+
+ case L'T': /* Timer */
+ {
+ /* Consecutive /T switches are invalid */
+ if (fTimerFlags & 2)
+ {
+ ConResPrintf(StdErr, IDS_ERROR_ONE_TIME);
+ return EXIT_FAILURE;
+ }
+
+ /* Remember that a /T switch has been encountered */
+ fTimerFlags |= 2;
+
+ /* Go to the next (timer) value */
+ continue;
+ }
+ }
+
+ /* This flag is used to ignore any keyboard keys but Ctrl-C */
+ if (_wcsicmp(&argv[index][1], L"NOBREAK") == 0)
+ {
+ bDisableInput = TRUE;
+
+ /* Go to next value */
+ continue;
+ }
+ }
+
+ /* The timer value can also be specified without the /T switch */
+
+ /* Only one timer value is supported */
+ if (fTimerFlags & 1)
+ {
+ ConResPrintf(StdErr, IDS_ERROR_ONE_TIME);
+ return EXIT_FAILURE;
+ }
+
+ timerValue = wcstol(argv[index], &pszNext, 10);
+ if (*pszNext)
+ {
+ ConResPrintf(StdErr, IDS_ERROR_OUT_OF_RANGE);
+ return EXIT_FAILURE;
+ }
+
+ /* Remember that the timer value has been set */
+ fTimerFlags |= 1;
+ }
+
+ /* A timer value is mandatory in order to continue */
+ if (!(fTimerFlags & 1))
+ {
+ ConResPrintf(StdErr, IDS_ERROR_NO_TIMER_VALUE);
+ return EXIT_FAILURE;
+ }
+
+ /* Make sure the timer value is within range */
+ if ((timerValue < -1) || (timerValue > 99999))
+ {
+ ConResPrintf(StdErr, IDS_ERROR_OUT_OF_RANGE);
+ return EXIT_FAILURE;
+ }
+
+ return InputWait(bDisableInput, timerValue);
+}