[TIMEOUT]: Implement the TIMEOUT utility (found on Win2k3 and upwards). This is an...
authorHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Mon, 25 Sep 2017 23:31:44 +0000 (23:31 +0000)
committerHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Mon, 25 Sep 2017 23:31:44 +0000 (23:31 +0000)
Based from a patch by Lee Schröder, with modifications by myself.
CORE-10044 #resolve

svn path=/trunk/; revision=75968

reactos/base/applications/cmdutils/CMakeLists.txt
reactos/base/applications/cmdutils/timeout/CMakeLists.txt [new file with mode: 0644]
reactos/base/applications/cmdutils/timeout/lang/en-US.rc [new file with mode: 0644]
reactos/base/applications/cmdutils/timeout/lang/fr-FR.rc [new file with mode: 0644]
reactos/base/applications/cmdutils/timeout/resource.h [new file with mode: 0644]
reactos/base/applications/cmdutils/timeout/timeout.c [new file with mode: 0644]
reactos/base/applications/cmdutils/timeout/timeout.rc [new file with mode: 0644]

index 72fe696..2ca3a2b 100644 (file)
@@ -18,6 +18,7 @@ add_subdirectory(reg)
 add_subdirectory(schtasks)
 add_subdirectory(sort)
 add_subdirectory(taskkill)
+add_subdirectory(timeout)
 add_subdirectory(tree)
 add_subdirectory(whoami)
 add_subdirectory(wmic)
diff --git a/reactos/base/applications/cmdutils/timeout/CMakeLists.txt b/reactos/base/applications/cmdutils/timeout/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c1eb31d
--- /dev/null
@@ -0,0 +1,8 @@
+
+include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/conutils)
+
+add_executable(timeout timeout.c timeout.rc)
+set_module_type(timeout win32cui UNICODE)
+target_link_libraries(timeout conutils ${PSEH_LIB})
+add_importlibs(timeout msvcrt kernel32)
+add_cd_file(TARGET timeout DESTINATION reactos/system32 FOR all)
diff --git a/reactos/base/applications/cmdutils/timeout/lang/en-US.rc b/reactos/base/applications/cmdutils/timeout/lang/en-US.rc
new file mode 100644 (file)
index 0000000..6a5cc79
--- /dev/null
@@ -0,0 +1,33 @@
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+STRINGTABLE
+BEGIN
+    IDS_USAGE "ReactOS Timeout Utility\n\
+\n\
+TIMEOUT [/?] [/T] delay [/NOBREAK]\n\
+\n\
+Description:\n\
+    This tool waits until a specified time period (in seconds) has elapsed,\n\
+    or until any key is pressed. A parameter to ignore the key press is also\n\
+    accepted.\n\
+\n\
+Parameters:\n\
+    /?        Display this help screen.\n\
+\n\
+    /T delay  Specify the number of seconds to wait (-1 to 99999).\n\
+              A value of -1 means the program will wait until a key is pressed.\n\
+              Note that the ""/T"" specification is optional, you can just\n\
+              specify the delay value without it.\n\
+\n\
+    /NOBREAK  Ignore all keyboard input except for Ctrl+C.\n\
+"
+    IDS_ERROR_OUT_OF_RANGE "ERROR: The timer value must be within range (-1 to 99999).\n"
+    IDS_ERROR_INVALID_HANDLE_VALUE "ERROR: Unable to get the standard handle for the console (error %lu).\n"
+    IDS_ERROR_READ_INPUT "ERROR: Unable to read the console input (error %lu).\n"
+    IDS_ERROR_NO_TIMER_VALUE "ERROR: The timer value must be specified (-1 to 99999).\n"
+    IDS_ERROR_ONE_TIME "ERROR: Only one timer value is needed.\n"
+    IDS_NOBREAK_INPUT "Press Ctrl+C to quit..."
+    IDS_USER_INPUT "Press any key to continue..."
+    IDS_NOBREAK_INPUT_COUNT "Waiting for %d second(s), press Ctrl+C to quit..."
+    IDS_USER_INPUT_COUNT "Waiting for %d second(s), press any key to continue..."
+END
diff --git a/reactos/base/applications/cmdutils/timeout/lang/fr-FR.rc b/reactos/base/applications/cmdutils/timeout/lang/fr-FR.rc
new file mode 100644 (file)
index 0000000..73ba3ef
--- /dev/null
@@ -0,0 +1,34 @@
+LANGUAGE LANG_FRENCH, SUBLANG_NEUTRAL
+
+STRINGTABLE
+BEGIN
+    IDS_USAGE "ReactOS Timeout Utility\n\
+\n\
+TIMEOUT [/?] [/T] délai [/NOBREAK]\n\
+\n\
+Description :\n\
+    Cet outil permet d'attendre jusqu'à ce qu'un délai (en secondes) s'écoule,\n\
+    ou bien jusqu'à ce qu'une touche de clavier soit pressée. Un paramètre\n\
+    permettant d'ignorer l'appui de touche de clavier est accepté.\n\
+\n\
+Liste de paramètres :\n\
+    /?        Affiche cet écran d'aide.\n\
+\n\
+    /T délai  Spécifie le délai d'attente en secondes (entre -1 et 99999).\n\
+              La valeur -1 signifie que le programme attend jusqu'à ce qu'une\n\
+              touche soit pressée.\n\
+              Veuillez noter que la spécification ""/T"" est optionnelle, vous\n\
+              pouvez spécifier la valeur de délai sans celle-ci.\n\
+\n\
+    /NOBREAK  Ignore toute frappe de clavier, sauf pour Ctrl+C.\n\
+"
+    IDS_ERROR_OUT_OF_RANGE "ERREUR: Le délai d'attente doit être compris dans la plage (entre -1 et 99999).\n"
+    IDS_ERROR_INVALID_HANDLE_VALUE "ERREUR: Impossible d'obtenir le handle standard pour la console (erreur %lu).\n"
+    IDS_ERROR_READ_INPUT "ERREUR: Impossible de lire l'entrée de console (erreur %lu).\n"
+    IDS_ERROR_NO_TIMER_VALUE "ERREUR: Le délai d'attente doit être spécifié (entre -1 et 99999).\n"
+    IDS_ERROR_ONE_TIME "ERREUR: Une seule valeur de délai est nécessaire.\n"
+    IDS_NOBREAK_INPUT "Appuyez sur Ctrl+C pour quitter..."
+    IDS_USER_INPUT "Appuyez sur une touche pour continuer..."
+    IDS_NOBREAK_INPUT_COUNT "Attendre %d seconde(s), appuyez sur Ctrl+C pour quitter..."
+    IDS_USER_INPUT_COUNT "Attendre %d seconde(s), appuyez sur une touche pour continuer..."
+END
diff --git a/reactos/base/applications/cmdutils/timeout/resource.h b/reactos/base/applications/cmdutils/timeout/resource.h
new file mode 100644 (file)
index 0000000..b02ee2f
--- /dev/null
@@ -0,0 +1,12 @@
+#pragma once
+
+#define IDS_USAGE                       0
+#define IDS_ERROR_OUT_OF_RANGE          1
+#define IDS_ERROR_INVALID_HANDLE_VALUE  2
+#define IDS_ERROR_READ_INPUT            3
+#define IDS_ERROR_NO_TIMER_VALUE        4
+#define IDS_ERROR_ONE_TIME              5
+#define IDS_NOBREAK_INPUT               6
+#define IDS_USER_INPUT                  7
+#define IDS_NOBREAK_INPUT_COUNT         8
+#define IDS_USER_INPUT_COUNT            9
diff --git a/reactos/base/applications/cmdutils/timeout/timeout.c b/reactos/base/applications/cmdutils/timeout/timeout.c
new file mode 100644 (file)
index 0000000..5901098
--- /dev/null
@@ -0,0 +1,332 @@
+/*
+ * 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);
+}
diff --git a/reactos/base/applications/cmdutils/timeout/timeout.rc b/reactos/base/applications/cmdutils/timeout/timeout.rc
new file mode 100644 (file)
index 0000000..aa23a33
--- /dev/null
@@ -0,0 +1,21 @@
+#include <windef.h>
+// #include <winuser.h>
+
+#include "resource.h"
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+
+#define REACTOS_STR_FILE_DESCRIPTION    "ReactOS Timeout Utility"
+#define REACTOS_STR_INTERNAL_NAME       "timeout"
+#define REACTOS_STR_ORIGINAL_FILENAME   "timeout.exe"
+#include <reactos/version.rc>
+
+/* UTF-8 */
+#pragma code_page(65001)
+
+#ifdef LANGUAGE_EN_US
+    #include "lang/en-US.rc"
+#endif
+#ifdef LANGUAGE_FR_FR
+    #include "lang/fr-FR.rc"
+#endif