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?
-- v2: timeout: Add new program.
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 | 266 ++++++++++++++++++++++++++++++++ 3 files changed, 273 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..8e165138e37 --- /dev/null +++ b/programs/timeout/timeout_main.c @@ -0,0 +1,266 @@ +/* + * 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 <math.h> + +#include <windows.h> + +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 HANDLE get_stdin(void) +{ + static HANDLE hStdin = NULL; + + if (!hStdin) + hStdin = GetStdHandle(STD_INPUT_HANDLE); + + return hStdin; +} + +static BOOL is_piped(void) +{ + DWORD count = 0; + return !GetNumberOfConsoleInputEvents(get_stdin(), &count); +} + +static BOOL is_key_hit(void) +{ + INPUT_RECORD *records = NULL; + DWORD count = 0, i; + BOOL retval = FALSE; + + GetNumberOfConsoleInputEvents(get_stdin(), &count); + if (!count) + return FALSE; + + records = malloc(count * sizeof(INPUT_RECORD)); + if (!records) + return FALSE; + + if (!ReadConsoleInputA(get_stdin(), records, count, &count)) + return FALSE; + + for (i = 0; i < count; i++) + { + if (records[i].EventType == KEY_EVENT && + records[i].Event.KeyEvent.bKeyDown) + { + retval = TRUE; + break; + } + } + + free(records); + + return retval; +} + +static BOOL wait_for_keypress(DWORD timeout_millis) +{ + return WaitForSingleObject(get_stdin(), timeout_millis) == WAIT_OBJECT_0 && is_key_hit(); +} + +static void sleep_without_timeout(BOOL nobreak) +{ + if (!nobreak) + { + printf("Press any key to continue ..."); + + for (;;) + { + if (wait_for_keypress(INFINITE)) + break; + } + } + else + { + printf("Press CTRL+C to quit ..."); + + for (;;) + { + Sleep(10000); + } + } +} + +static void sleep_with_timeout(int timeout, BOOL nobreak) +{ + ULONGLONG start_ticks = GetTickCount64(); + ULONGLONG end_ticks = start_ticks + (timeout * 1000); + LONGLONG ticks_remaining, wait_millis; + + do + { + ticks_remaining = end_ticks - GetTickCount64(); + + wait_millis = ticks_remaining; + if (wait_millis > 1000) + wait_millis = 1000; + + printf("\rWaiting for %d seconds, ", (int)roundf(ticks_remaining / 1000.)); + + if (!nobreak) + { + printf("press a key to continue ..."); + + if (wait_millis > 0 && wait_for_keypress(wait_millis)) + break; + } + else + { + printf("press CTRL+C to quit ..."); + + if (wait_millis > 0) + Sleep(wait_millis); + } + } while (ticks_remaining > 0); +} + +static BOOL str_is_number(char* str) +{ + if (str[0] == '-') + str++; + + while (*str) + { + if (!isdigit(*str++)) + return FALSE; + } + + return TRUE; +} + +static BOOL check_timeout_arg(char* str, BOOL timeout_set) +{ + if (timeout_set) + { + printf("Timeout value cannot be specified more than once.\n"); + return FALSE; + } + + if (!str_is_number(str)) + { + printf("Invalid timeout: %s\n", str); + return FALSE; + } + + return TRUE; +} + +int __cdecl main(int argc, char** argv) +{ + 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); + } + + if (!check_timeout_arg(argv[++i], timeout_set)) + return 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")) + { + timeout = -1; + timeout_set = TRUE; + } + else + { + printf("Invalid argument: %s\n", argv[i]); + usage(); + return 1; + } + } + else + { + if (!check_timeout_arg(argv[++i], timeout_set)) + return 1; + + timeout = atoi(argv[i]); + timeout_set = TRUE; + } + } + + if (!timeout_set) + { + usage(); + return 1; + } + + if (timeout < -1 || timeout > 99999) + { + printf("Timeout value out of range. Valid range is -1 to 99999.\n"); + return 1; + } + + if (is_piped()) + { + printf("Input redirection is not supported.\n"); + return 1; + } + + printf("\n"); + + if (timeout < 0) + sleep_without_timeout(nobreak); + else + sleep_with_timeout(timeout, nobreak); + + printf("\n"); + + return 0; +}
On Mon May 20 21:32:35 2024 +0000, eric pouech wrote:
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
Sorry for the noob question, but can you point me to an example where this is done? I don't know how to do this.
On Mon May 20 21:34:08 2024 +0000, eric pouech wrote:
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)
Thanks for taking the time to look at this. I've fixed everything except for the resources.
There are two more differences I've noticed: CTRL+C in `nobreak` mode is actually trapped, and a newline is printed. Also, `waiting for N seconds` is whitespace-padded when the number is larger than 10.
However, implementing either of these feels like more work than it's worth to me, as they're just minor visual changes.
On Mon May 20 21:32:35 2024 +0000, Myah Caron wrote:
Sorry for the noob question, but can you point me to an example where this is done? I don't know how to do this.
a bunch of entries in programs/ subdirectory (eg. programs/start)
On Mon May 20 21:34:08 2024 +0000, Myah Caron wrote:
Thanks for taking the time to look at this. I've fixed everything except for the resources. There are two more differences I've noticed: CTRL+C in `nobreak` mode is actually trapped, and a newline is printed. Also, `waiting for N seconds` is whitespace-padded when the number is larger than 10. However, implementing either of these feels like more work than it's worth to me, as they're just minor visual changes.
agreed (except perhaps for the padding of the N seconds... not that it would make sense to replicate the padding, but I'm not sure the printf has a trailing whitespace so that it erases the last visible char when the width of N is reduced)