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?
-- v7: 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 | 8 + programs/timeout/resources.h | 34 ++++ programs/timeout/timeout.c | 329 +++++++++++++++++++++++++++++++++++ programs/timeout/timeout.rc | 57 ++++++ 5 files changed, 429 insertions(+) create mode 100644 programs/timeout/Makefile.in create mode 100644 programs/timeout/resources.h create mode 100644 programs/timeout/timeout.c create mode 100644 programs/timeout/timeout.rc
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..84afc8537d8 --- /dev/null +++ b/programs/timeout/Makefile.in @@ -0,0 +1,8 @@ +MODULE = timeout.exe +IMPORTS = user32 + +EXTRADLLFLAGS = -mconsole + +SOURCES = \ + timeout.c\ + timeout.rc diff --git a/programs/timeout/resources.h b/programs/timeout/resources.h new file mode 100644 index 00000000000..7c1b58410c8 --- /dev/null +++ b/programs/timeout/resources.h @@ -0,0 +1,34 @@ +/* + * Resource id definitions + * + * Copyright (C) 2024 Myah Caron + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <windef.h> + +#define STRING_USAGE 101 +#define STRING_INFWAIT_PRESS_ANY_KEY 102 +#define STRING_INFWAIT_PRESS_CTRL_C 103 +#define STRING_WAITING_FOR_SECONDS 104 +#define STRING_WAIT_PRESS_KEY 105 +#define STRING_WAIT_PRESS_CTRL_C 106 +#define STRING_TIMEOUT_ONLY_ONCE 107 +#define STRING_INVALID_TIMEOUT 108 +#define STRING_MISSING_VALUE_FOR_OPTION 109 +#define STRING_INVALID_ARGUMENT 110 +#define STRING_TIMEOUT_RANGE 111 +#define STRING_NO_INPUT_REDIRECTION 112 diff --git a/programs/timeout/timeout.c b/programs/timeout/timeout.c new file mode 100644 index 00000000000..08acede7328 --- /dev/null +++ b/programs/timeout/timeout.c @@ -0,0 +1,329 @@ +/* + * 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> + +#include "resources.h" + +#include <wine/debug.h> + +WINE_DEFAULT_DEBUG_CHANNEL(timeout); + +static char* get_string(int which) +{ + char* msg; + int len; + WCHAR wmsg[2048]; + + if (!LoadStringW(GetModuleHandleW(NULL), which, wmsg, ARRAY_SIZE(wmsg))) + { + WINE_ERR("LoadString failed for %d, error %ld\n", which, GetLastError()); + return NULL; + } + + len = WideCharToMultiByte(GetOEMCP(), 0, wmsg, -1, NULL, 0, NULL, NULL); + msg = malloc(len); + if (!msg) + return NULL; + + WideCharToMultiByte(GetOEMCP(), 0, wmsg, -1, msg, len, NULL, NULL); + + return msg; +} + +#define RPRINTF(id) do\ + {\ + char* msg = get_string(id);\ + if (!msg)\ + break;\ + printf(msg);\ + free(msg);\ + } while (0) + +#define RPRINTF_VA(id, ...) do\ + {\ + char* msg = get_string(id);\ + if (!msg)\ + break;\ + printf(msg, __VA_ARGS__);\ + free(msg);\ + } while (0) + +static void usage(void) +{ + RPRINTF(STRING_USAGE); +} + +static BOOL is_piped(void) +{ + DWORD count = 0; + return !GetNumberOfConsoleInputEvents(GetStdHandle(STD_INPUT_HANDLE), &count); +} + +static BOOL is_key_hit(void) +{ + INPUT_RECORD *records = NULL; + DWORD count = 0, i; + BOOL retval = FALSE; + + if (!GetNumberOfConsoleInputEvents(GetStdHandle(STD_INPUT_HANDLE), &count) || !count) + return FALSE; + + records = malloc(count * sizeof(INPUT_RECORD)); + if (!records) + return FALSE; + + if (!ReadConsoleInputA(GetStdHandle(STD_INPUT_HANDLE), records, count, &count)) + goto cleanup; + + for (i = 0; i < count; i++) + { + if (records[i].EventType == KEY_EVENT && + records[i].Event.KeyEvent.bKeyDown) + { + retval = TRUE; + break; + } + } + +cleanup: + free(records); + return retval; +} + +static BOOL wait_for_keypress(DWORD timeout_millis) +{ + return WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE), timeout_millis) == WAIT_OBJECT_0 && is_key_hit(); +} + +static void sleep_without_timeout(BOOL nobreak) +{ + if (!nobreak) + { + RPRINTF(STRING_INFWAIT_PRESS_ANY_KEY); + + for (;;) + { + if (wait_for_keypress(INFINITE)) + break; + } + } + else + { + RPRINTF(STRING_INFWAIT_PRESS_CTRL_C); + + for (;;) + { + Sleep(10000); + } + } +} + +static int int_strlen(int number) +{ + if (number == 0) + return 1; + if (number < 0) + return int_strlen(-number) + 1; + + return (int)log10((float) number) + 1; +} + +static void pad_number(char* out, int number, int pad) +{ + int i, digits = int_strlen(number); + + for (i = 0; i < pad - digits; i++) + { + out[i] = ' '; + } + + sprintf(out + i, "%d", number); +} + +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; + int digits = int_strlen(timeout); + char padded_timeout[10]; + + pad_number(padded_timeout, timeout, digits); + RPRINTF_VA(STRING_WAITING_FOR_SECONDS, padded_timeout); + + if (!nobreak) + RPRINTF(STRING_WAIT_PRESS_KEY); + else + RPRINTF(STRING_WAIT_PRESS_CTRL_C); + + do + { + ticks_remaining = end_ticks - GetTickCount64(); + + wait_millis = ticks_remaining; + if (wait_millis > 1000) + wait_millis = 1000; + + printf("\r"); + pad_number(padded_timeout, (int)((ticks_remaining + 500) / 1000), digits); + RPRINTF_VA(STRING_WAITING_FOR_SECONDS, padded_timeout); + + if (wait_millis <= 0) + break; + + if (!nobreak) + { + if (wait_for_keypress(wait_millis)) + break; + } + else + { + 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) + { + RPRINTF(STRING_TIMEOUT_ONLY_ONCE); + return FALSE; + } + + if (!str_is_number(str)) + { + RPRINTF_VA(STRING_INVALID_TIMEOUT, 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) + { + RPRINTF_VA(STRING_MISSING_VALUE_FOR_OPTION, 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 + { + RPRINTF_VA(STRING_INVALID_ARGUMENT, 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) + { + RPRINTF(STRING_TIMEOUT_RANGE); + return 1; + } + + if (is_piped()) + { + RPRINTF(STRING_NO_INPUT_REDIRECTION); + return 1; + } + + printf("\n"); + + if (timeout < 0) + sleep_without_timeout(nobreak); + else + sleep_with_timeout(timeout, nobreak); + + printf("\n"); + + return 0; +} diff --git a/programs/timeout/timeout.rc b/programs/timeout/timeout.rc new file mode 100644 index 00000000000..9b7852fbece --- /dev/null +++ b/programs/timeout/timeout.rc @@ -0,0 +1,57 @@ +/* + * 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 "resources.h" + +#pragma makedep po + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT + +STRINGTABLE +{ +STRING_USAGE, "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" + +STRING_INFWAIT_PRESS_ANY_KEY, "Press any key to continue ..." + +STRING_INFWAIT_PRESS_CTRL_C, "Press CTRL+C to quit ..." + +STRING_WAITING_FOR_SECONDS, "Waiting for %s seconds, " + +STRING_WAIT_PRESS_KEY, "press a key to continue ..." + +STRING_WAIT_PRESS_CTRL_C, "press CTRL+C to quit ..." + +STRING_TIMEOUT_ONLY_ONCE, "Timeout value cannot be specified more than once.\n" + +STRING_INVALID_TIMEOUT, "Invalid timeout: %s\n" + +STRING_MISSING_VALUE_FOR_OPTION, "Missing value for option %s\n" + +STRING_INVALID_ARGUMENT, "Invalid argument: %s\n" + +STRING_TIMEOUT_RANGE, "Timeout value out of range. Valid range is -1 to 99999.\n" + +STRING_NO_INPUT_REDIRECTION "Input redirection is not supported.\n" + +}
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=145732
Your paranoid android.
=== debian11b (64 bit WoW report) ===
winmm: mci: Timeout
Could you add some tests? You can see https://gitlab.winehq.org/wine/wine/-/merge_requests/2688/ for examples. The po files are automatically generated so don't worry about that.
On Tue May 21 10:23:12 2024 +0000, Myah Caron wrote:
Thank you for the hints, I'm very new to the resources system. I'll fix this when I get a chance. It's true that `RPRINTF_VA` could be rewritten as a function, but I believe that would require parsing the format string, which feels like it'd be more work than this macro.
you can copy output_write() and output_string() from programs/fsutil/fsutil.c
On Tue May 21 10:17:34 2024 +0000, Myah Caron wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/5691/diffs?diff_id=114559&start_sha=a7d466081a9279a1c760c8dfbf3779b2c58c80d1#b89156592bd479846dd2609962175f3ea7c35b2d_201_0)
ok I thought they were on two lines, so this makes... not sure we need to mimic so precisely native behavior, but since it's written, you can leave it as is
On Tue May 21 10:17:31 2024 +0000, Myah Caron wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/5691/diffs?diff_id=114559&start_sha=a7d466081a9279a1c760c8dfbf3779b2c58c80d1#b89156592bd479846dd2609962175f3ea7c35b2d_310_0)
you have to check the endptr... it must be different from the passed string pointer (at least one digit has been read) and *endptr must be '\0' (to ensure all string has been converted into integer)
writing tests will be tricky as: - it seems native aligns waits on wall clock seconds, not on elapsed milliseconds (so a N second wait can really take between )N-1,N+1( seconds); can be worked around by taking some latitude in the measured elapse interval - as Myah stated, output is refreshed at least once per second (didn't check if it was more), but not sure we want to mimic that char by char. moreover, timeout.exe is more about its behavior rather than its output (contrary to the MR you're referring to) - timeout fails if input is not bound to a console (so harder to write tests to interrupt a timeout by keystrokes); console keystrokes can be simulated though but not very straightforward
so this leaves us with: - simple child process creation with something like "timeout /t 6" and measure child run time and assume success for say runtime between say 4 and 8 seconds (I wouldn't even care for output at this stage)