Fixes this bug: https://bugs.winehq.org/show_bug.cgi?id=56068
Note that `is_key_hit` is virtually identical to `_kbhit`, except that the `for` loop runs until `count` instead of `count - 1`. This is needed because otherwise, it misses the last event.
Is `_kbhit`'s current behavior intended, or is it a bug?
From: Myah Caron qsniyg@protonmail.com
Wine-bug: https://bugs.winehq.org/show_bug.cgi?id=56068 Signed-off-by: Myah Caron qsniyg@protonmail.com --- configure.ac | 1 + programs/timeout/Makefile.in | 6 + programs/timeout/timeout_main.c | 218 ++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 programs/timeout/Makefile.in create mode 100644 programs/timeout/timeout_main.c
diff --git a/configure.ac b/configure.ac index 3f54230b46b..3ae541232ca 100644 --- a/configure.ac +++ b/configure.ac @@ -3523,6 +3523,7 @@ WINE_CONFIG_MAKEFILE(programs/tasklist) WINE_CONFIG_MAKEFILE(programs/tasklist/tests) WINE_CONFIG_MAKEFILE(programs/taskmgr) WINE_CONFIG_MAKEFILE(programs/termsv) +WINE_CONFIG_MAKEFILE(programs/timeout) WINE_CONFIG_MAKEFILE(programs/uninstaller) WINE_CONFIG_MAKEFILE(programs/unlodctr) WINE_CONFIG_MAKEFILE(programs/view) diff --git a/programs/timeout/Makefile.in b/programs/timeout/Makefile.in new file mode 100644 index 00000000000..4502a291175 --- /dev/null +++ b/programs/timeout/Makefile.in @@ -0,0 +1,6 @@ +MODULE = timeout.exe + +EXTRADLLFLAGS = -mconsole + +SOURCES = \ + timeout_main.c diff --git a/programs/timeout/timeout_main.c b/programs/timeout/timeout_main.c new file mode 100644 index 00000000000..9b99385c927 --- /dev/null +++ b/programs/timeout/timeout_main.c @@ -0,0 +1,218 @@ +/* + * timeout program + * + * Copyright (C) 2024 Myah Caron + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <conio.h> + +#include <windows.h> + +HANDLE hStdin; + +static void usage(void) +{ + printf("Usage: timeout [-t] seconds [-nobreak]\n\n" + "Options:\n" + " -t Timeout in seconds to wait.\n" + " -nobreak Ignore key presses.\n" + " -? Display this help message.\n"); +} + +static BOOL is_key_hit(void) +{ + INPUT_RECORD *records = NULL; + DWORD count = 0, i; + BOOL retval = FALSE; + + GetNumberOfConsoleInputEvents(hStdin, &count); + if (!count) + return FALSE; + + records = malloc(count * sizeof(INPUT_RECORD)); + if (!records) + return FALSE; + + if (!ReadConsoleInputA(hStdin, records, count, &count)) + return FALSE; + + for (i = 0; i < count; i++) + { + if (records[i].EventType == KEY_EVENT && + records[i].Event.KeyEvent.bKeyDown && + records[i].Event.KeyEvent.uChar.AsciiChar) + { + retval = TRUE; + break; + } + } + + free(records); + + return retval; +} + +static void wait_for_input(void) +{ + for (;;) + { + if (WaitForSingleObject(hStdin, INFINITE) == WAIT_OBJECT_0 && is_key_hit()) + break; + } +} + +static BOOL wait_for_input_with_timeout(int timeout_millis) +{ + ULONGLONG starting_ticks = GetTickCount64(); + + while (timeout_millis > 0) + { + if (WaitForSingleObject(hStdin, timeout_millis) == WAIT_OBJECT_0 && is_key_hit()) + return TRUE; + + timeout_millis -= GetTickCount64() - starting_ticks; + } + + return FALSE; +} + +int __cdecl main(int argc, char** argv) +{ + ULONGLONG starting_ticks = GetTickCount64(); + LONGLONG wait_millis; + int i, timeout = 0; + BOOL timeout_set = FALSE, nobreak = FALSE; + + if (argc == 1) + { + usage(); + return 1; + } + + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-' || argv[i][0] == '/') + { + if (!strcmp(argv[i] + 1, "t")) + { + if (i == argc - 1) + { + printf("Missing value for option %s\n", argv[i]); + exit(1); + } + + timeout = atoi(argv[++i]); + timeout_set = TRUE; + } + else if (!strcmp(argv[i] + 1, "nobreak")) + { + nobreak = TRUE; + } + else if (!strcmp(argv[i] + 1, "?")) + { + usage(); + return 0; + } + else if (!strcmp(argv[i] + 1, "1")) + { + timeout = -1; + timeout_set = TRUE; + } + else + { + printf("Invalid argument: %s\n", argv[i]); + usage(); + return 1; + } + } + else + { + timeout = atoi(argv[i]); + timeout_set = TRUE; + } + } + + if (!timeout_set) + { + usage(); + return 1; + } + + if (timeout < -1 || timeout > 99999) + { + printf("Timeout out of range: Valid range is -1 to 99999.\n"); + return 1; + } + + hStdin = GetStdHandle(STD_INPUT_HANDLE); + + printf("\n"); + + if (timeout < 0) + { + if (!nobreak) + { + printf("Press any key to continue ..."); + wait_for_input(); + } + else + { + printf("Press CTRL+C to quit ..."); + + for (;;) + { + Sleep(10000); + } + } + + return 0; + } + + for (;;) + { + wait_millis = (timeout * 1000) - (GetTickCount64() - starting_ticks); + if (wait_millis > 1000) + wait_millis = 1000; + if (wait_millis < 0) + wait_millis = 0; + + if (!nobreak) + { + printf("\rWaiting for %d seconds, press a key to continue ...", timeout); + + if (wait_millis > 0 && wait_for_input_with_timeout(wait_millis)) + break; + } + else + { + printf("\rWaiting for %d seconds, press CTRL+C to quit ...", timeout); + + if (wait_millis > 0) + Sleep(wait_millis); + } + + timeout--; + + if (timeout <= 0) + break; + } + + return 0; +}
unlike Wine's _kbhit() implementation (didn't check native CRT), native timeout.exe exits on any key down message (even the ones that don't produce ascii (or unicode) chars)
native timeout.exe produces an error message if argument is not a valid integer
I don't see that /1 is supported as an option
eric pouech (@epo) commented about programs/timeout/timeout_main.c:
}
else if (!strcmp(argv[i] + 1, "1"))
{
timeout = -1;
timeout_set = TRUE;
}
else
{
printf("Invalid argument: %s\n", argv[i]);
usage();
return 1;
}
}
else
{
timeout = atoi(argv[i]);
to be pedantic, native only allows one timeout value and prints error when two are given
I wonder if this could be simplified a bit. IMO the loops in the wait_for_input helpers could be avoided (and even the two helpers merged into one), but that would require (sometimes) to printf more than one line per second
you likely miss a printf("\n"); at the end
native timeout.exe prints an error message if stdin is not bound to a console and exits this could be done here by checking the return of GetNumberOfConsoleInputEvents
please avoid a global variable, it's useless here
could you put the help strings into resources so that they can be translated at some point ditto for the error messages throughout the rest of this file
native timeout.exe prints an error message if stdin is not bound to a console and exits
a couple of comments (and yes msvcrt's kbhit looks wrong, as it expects the last event to be a key up, which is not always the case; as noted above, _kbhit() doesn't behave as timeout.exe expects)