The first 7 patches have already been sent to wine-devel, but felt into the void (adding interactive tests framework to cmd, and fixing a couple of bugs in interactive more handling, including tests). So, resubmitting.
The rest (4) patches implement tab and shift tab completion for filenames in cmd (trying to mimic native behavior).
NB: I intentionnaly left ioctl 23 unused (as it appears in Roman's patches about GetConsoleProcessList). Won't resolve the potential merge conflict though.
A+
---
Eric Pouech (11): programs/cmd/tests: don't output more data than available programs/cmd/tests: added ability to run tests in interactive mode programs/cmd/tests: some interactive test about input echo programs/cmd/tests: added support for multi-line input programs/cmd: handle white space only lines within ( ) block programs/cmd: expand commandline before parsing it (interactive mode) programs/cmd: detect badly quoted operand in 'if' statement kernelbase/console, programs/conhost: support CONSOLE_READCONSOLE_CONTROL in ReadConsoleW dlls/kernelbase, programs/conhost: return key state in ReadConsoleW with completion programs/cmd: implement filename completion programs/conhost: handle csi escape sequence for shift tab
dlls/kernelbase/console.c | 36 ++- include/wine/condrv.h | 1 + programs/cmd/batch.c | 252 +++++++++++++++++- programs/cmd/builtins.c | 10 + programs/cmd/tests/batch.c | 111 ++++++-- programs/cmd/tests/interactive_builtins.cmd | 42 +++ .../cmd/tests/interactive_builtins.cmd.exp | 67 +++++ programs/cmd/tests/rsrc.rc | 10 + programs/cmd/wcmdmain.c | 34 ++- programs/conhost/conhost.c | 114 ++++++-- programs/conhost/conhost.h | 2 + server/console.c | 1 + 12 files changed, 618 insertions(+), 62 deletions(-) create mode 100644 programs/cmd/tests/interactive_builtins.cmd create mode 100644 programs/cmd/tests/interactive_builtins.cmd.exp
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/tests/batch.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/programs/cmd/tests/batch.c b/programs/cmd/tests/batch.c index 162e9cfa151..0e8fbf89d6d 100644 --- a/programs/cmd/tests/batch.c +++ b/programs/cmd/tests/batch.c @@ -344,8 +344,10 @@ static void test_output(const char *out_data, DWORD out_size, const char *exp_da } }
- ok(exp_ptr >= exp_data+exp_size, "unexpected end of output in line %d, missing %s\n", line, exp_ptr); - ok(out_ptr >= out_data+out_size, "too long output, got additional %s\n", out_ptr); + ok(exp_ptr >= exp_data+exp_size, "unexpected end of output in line %d, missing %.*s\n", + line, exp_data + exp_size - exp_ptr, exp_ptr); + ok(out_ptr >= out_data+out_size, "too long output, got additional %.*s\n", + out_data + out_size - out_ptr, out_ptr); }
static void run_test(const char *cmd_data, DWORD cmd_size, const char *exp_data, DWORD exp_size)
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/tests/batch.c | 58 +++++++++++++++-------- programs/cmd/tests/interactive_builtins.cmd | 1 programs/cmd/tests/interactive_builtins.cmd.exp | 1 programs/cmd/tests/rsrc.rc | 10 ++++ programs/cmd/wcmdmain.c | 9 +++- 5 files changed, 58 insertions(+), 21 deletions(-) create mode 100644 programs/cmd/tests/interactive_builtins.cmd create mode 100644 programs/cmd/tests/interactive_builtins.cmd.exp
diff --git a/programs/cmd/tests/batch.c b/programs/cmd/tests/batch.c index 0e8fbf89d6d..0d6832df30f 100644 --- a/programs/cmd/tests/batch.c +++ b/programs/cmd/tests/batch.c @@ -74,32 +74,39 @@ static const char* convert_input_data(const char *data, DWORD size, DWORD *new_s return new_data; }
-static BOOL run_cmd(const char *cmd_data, DWORD cmd_size) +static BOOL run_cmd(const char *cmd_data, DWORD cmd_size, BOOL interactive) { SECURITY_ATTRIBUTES sa = {sizeof(sa), 0, TRUE}; char command[] = "test.cmd"; + char interactive_command[] = "cmd /k"; /* so that we don't get version info before interactive mode */ STARTUPINFOA si = {sizeof(si)}; PROCESS_INFORMATION pi; - HANDLE file,fileerr; + HANDLE filein,fileout,fileerr; DWORD size; BOOL bres;
- file = CreateFileA("test.cmd", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, NULL); - ok(file != INVALID_HANDLE_VALUE, "CreateFile failed\n"); - if(file == INVALID_HANDLE_VALUE) + filein = CreateFileA("test.cmd", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, + interactive ? &sa : NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + ok(filein != INVALID_HANDLE_VALUE, "CreateFile failed\n"); + if(filein == INVALID_HANDLE_VALUE) return FALSE;
- bres = WriteFile(file, cmd_data, cmd_size, &size, NULL); - CloseHandle(file); + bres = WriteFile(filein, cmd_data, cmd_size, &size, NULL); ok(bres, "Could not write to file: %u\n", GetLastError()); if(!bres) return FALSE; + if (interactive) + { + SetEndOfFile(filein); + SetFilePointer(filein, 0, NULL, FILE_BEGIN); + } + else + CloseHandle(filein);
- file = CreateFileA("test.out", GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, &sa, CREATE_ALWAYS, + fileout = CreateFileA("test.out", GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - ok(file != INVALID_HANDLE_VALUE, "CreateFile failed\n"); - if(file == INVALID_HANDLE_VALUE) + ok(fileout != INVALID_HANDLE_VALUE, "CreateFile failed\n"); + if(fileout == INVALID_HANDLE_VALUE) return FALSE;
fileerr = CreateFileA("test.err", GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, &sa, CREATE_ALWAYS, @@ -109,9 +116,10 @@ static BOOL run_cmd(const char *cmd_data, DWORD cmd_size) return FALSE;
si.dwFlags = STARTF_USESTDHANDLES; - si.hStdOutput = file; + si.hStdInput = interactive ? filein : INVALID_HANDLE_VALUE; + si.hStdOutput = fileout; si.hStdError = fileerr; - bres = CreateProcessA(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); + bres = CreateProcessA(NULL, interactive ? interactive_command : command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); ok(bres, "CreateProcess failed: %u\n", GetLastError()); if(!bres) { DeleteFileA("test.out"); @@ -121,7 +129,9 @@ static BOOL run_cmd(const char *cmd_data, DWORD cmd_size) WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); - CloseHandle(file); + if (interactive) + CloseHandle(filein); + CloseHandle(fileout); CloseHandle(fileerr); DeleteFileA("test.cmd"); return TRUE; @@ -350,7 +360,7 @@ static void test_output(const char *out_data, DWORD out_size, const char *exp_da out_data + out_size - out_ptr, out_ptr); }
-static void run_test(const char *cmd_data, DWORD cmd_size, const char *exp_data, DWORD exp_size) +static void run_test(const char *cmd_data, DWORD cmd_size, const char *exp_data, DWORD exp_size, BOOL interactive) { const char *out_data, *actual_cmd_data; DWORD out_size, actual_cmd_size; @@ -359,7 +369,7 @@ static void run_test(const char *cmd_data, DWORD cmd_size, const char *exp_data, if(!actual_cmd_size || !actual_cmd_data) goto cleanup;
- if(!run_cmd(actual_cmd_data, actual_cmd_size)) + if(!run_cmd(actual_cmd_data, actual_cmd_size, interactive)) goto cleanup;
out_size = map_file("test.out", &out_data); @@ -394,7 +404,7 @@ static void run_from_file(const char *file_name) return; }
- run_test(test_data, test_size, out_data, out_size); + run_test(test_data, test_size, out_data, out_size, FALSE);
UnmapViewOfFile(test_data); UnmapViewOfFile(out_data); @@ -433,11 +443,11 @@ static BOOL WINAPI test_enum_proc(HMODULE module, LPCSTR type, LPSTR name, LONG_ return TRUE;
sprintf(res_name, "%s.exp", name); - out_size = load_resource(res_name, "TESTOUT", &out_data); + out_size = load_resource(res_name, param ? "INTERACTIVEOUT" : "TESTOUT", &out_data); if(!out_size) return TRUE;
- run_test(cmd_data, cmd_size, out_data, out_size); + run_test(cmd_data, cmd_size, out_data, out_size, param); return TRUE; }
@@ -483,7 +493,15 @@ START_TEST(batch)
argc = winetest_get_mainargs(&argv); if(argc > 2) - run_from_file(argv[2]); + { + if (!strcmp(argv[2], "--interactive")) + EnumResourceNamesA(NULL, "INTERACTIVECMD", test_enum_proc, 1); + else + run_from_file(argv[2]); + } else + { EnumResourceNamesA(NULL, "TESTCMD", test_enum_proc, 0); + EnumResourceNamesA(NULL, "INTERACTIVECMD", test_enum_proc, 1); + } } diff --git a/programs/cmd/tests/interactive_builtins.cmd b/programs/cmd/tests/interactive_builtins.cmd new file mode 100644 index 00000000000..546bad4eda5 --- /dev/null +++ b/programs/cmd/tests/interactive_builtins.cmd @@ -0,0 +1 @@ +@exit 0 diff --git a/programs/cmd/tests/interactive_builtins.cmd.exp b/programs/cmd/tests/interactive_builtins.cmd.exp new file mode 100644 index 00000000000..fe6f88e5414 --- /dev/null +++ b/programs/cmd/tests/interactive_builtins.cmd.exp @@ -0,0 +1 @@ +@pwd@>@exit 0 diff --git a/programs/cmd/tests/rsrc.rc b/programs/cmd/tests/rsrc.rc index affe04c2bdd..2d14bd25473 100644 --- a/programs/cmd/tests/rsrc.rc +++ b/programs/cmd/tests/rsrc.rc @@ -16,6 +16,10 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+/* the TEST(CMD|OUT) are run in batch mode, + * while the INTERACTIVE(CMD|OUT) are run in interactive mode. + */ + /* @makedep: test_builtins.cmd */ test_builtins.cmd TESTCMD "test_builtins.cmd"
@@ -27,3 +31,9 @@ test_cmdline.cmd TESTCMD "test_cmdline.cmd"
/* @makedep: test_cmdline.cmd.exp */ test_cmdline.cmd.exp TESTOUT "test_cmdline.cmd.exp" + +/* @makedep: interactive_builtins.cmd */ +interactive_builtins.cmd INTERACTIVECMD "interactive_builtins.cmd" + +/* @makedep: interactive_builtins.cmd.exp */ +interactive_builtins.cmd.exp INTERACTIVEOUT "interactive_builtins.cmd.exp" diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 65601348b05..ee863856ec9 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1897,6 +1897,13 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE WCMD_output_asis(L"\r\n"); }
+ /* echo input stream if naturally not echoed */ + if (!context && !optionalcmd && GetFileType(readFrom) != FILE_TYPE_CHAR) + { + WCMD_output_asis(curPos); + WCMD_output_asis(L"\r\n"); + /* FIXME: same quirk as above for trailing space? */ + } /* Skip repeated 'no echo' characters */ while (*curPos == '@') curPos++;
@@ -2762,7 +2769,7 @@ int __cdecl wmain (int argc, WCHAR *argvW[]) */
interactive = TRUE; - if (!opt_k) WCMD_version (); + if (!opt_k) WCMD_version (); else promptNewLine = FALSE; while (TRUE) {
/* Read until EOF (which for std input is never, but if redirect
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/tests/interactive_builtins.cmd | 9 ++++++++- programs/cmd/tests/interactive_builtins.cmd.exp | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/programs/cmd/tests/interactive_builtins.cmd b/programs/cmd/tests/interactive_builtins.cmd index 546bad4eda5..d1e308f159a 100644 --- a/programs/cmd/tests/interactive_builtins.cmd +++ b/programs/cmd/tests/interactive_builtins.cmd @@ -1 +1,8 @@ -@exit 0 +@echo --------- testing echo +echo foo +@echo foo +echo off +echo foo +@echo foo +echo --------- done +exit 0 diff --git a/programs/cmd/tests/interactive_builtins.cmd.exp b/programs/cmd/tests/interactive_builtins.cmd.exp index fe6f88e5414..64482e85c04 100644 --- a/programs/cmd/tests/interactive_builtins.cmd.exp +++ b/programs/cmd/tests/interactive_builtins.cmd.exp @@ -1 +1,17 @@ -@pwd@>@exit 0 +@pwd@>@echo --------- testing echo +--------- testing echo + +@pwd@>echo foo +foo + +@pwd@>@echo foo +foo + +@pwd@>echo off +echo foo +foo +@echo foo +foo +echo --------- done +--------- done +exit 0
- added a @more@ keyword to detect the 'More?' localized string (first occurence is stored, when all subsequent occurences are compared against the stored value) - some basic tests
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/tests/batch.c | 47 +++++++++++++++++++++++ programs/cmd/tests/interactive_builtins.cmd | 24 ++++++++++++ programs/cmd/tests/interactive_builtins.cmd.exp | 36 ++++++++++++++++++ programs/cmd/wcmdmain.c | 7 +++ 4 files changed, 114 insertions(+)
diff --git a/programs/cmd/tests/batch.c b/programs/cmd/tests/batch.c index 0d6832df30f..bcaeb0190ac 100644 --- a/programs/cmd/tests/batch.c +++ b/programs/cmd/tests/batch.c @@ -30,6 +30,8 @@ static char path[MAX_PATH]; static DWORD path_len; static char shortpath[MAX_PATH]; static DWORD shortpath_len; +static char more[MAX_PATH]; +static DWORD more_len;
/* Convert to DOS line endings, and substitute escaped whitespace chars with real ones */ static const char* convert_input_data(const char *data, DWORD size, DWORD *new_size) @@ -164,6 +166,31 @@ static DWORD map_file(const char *file_name, const char **ret) return size; }
+static const char *compare_line(const char *out_line, const char *out_end, const char *exp_line, + const char *exp_end); + +static BOOL guess_string(const char *out_ptr, const char *out_end, + const char *exp_ptr, const char *exp_end, + char* storage, DWORD* storage_len) +{ + /* as some output depends on locale (like 'More? ', 'Are you sure (Y|N)? '...), try to guess its value from first input */ + const char* out_try = out_ptr; + if (exp_ptr == exp_end) + { + *storage_len = min(out_end - out_ptr, MAX_PATH); + memcpy(storage, out_ptr, *storage_len); + return TRUE; + } + while (out_try < out_end) + { + storage[(*storage_len)++] = *out_try++; + if (compare_line(out_try, out_end, exp_ptr, exp_end) == NULL) + return TRUE; + } + *storage_len = 0; + return FALSE; +} + static const char *compare_line(const char *out_line, const char *out_end, const char *exp_line, const char *exp_end) { @@ -174,6 +201,7 @@ static const char *compare_line(const char *out_line, const char *out_end, const static const char drive_cmd[] = {'@','d','r','i','v','e','@'}; static const char path_cmd[] = {'@','p','a','t','h','@'}; static const char shortpath_cmd[] = {'@','s','h','o','r','t','p','a','t','h','@'}; + static const char more_cmd[] = {'@','m','o','r','e','@'}; static const char space_cmd[] = {'@','s','p','a','c','e','@'}; static const char spaces_cmd[] = {'@','s','p','a','c','e','s','@'}; static const char tab_cmd[] = {'@','t','a','b','@'}; @@ -225,6 +253,25 @@ static const char *compare_line(const char *out_line, const char *out_end, const out_ptr += shortpath_len; continue; } + } else if(exp_ptr+sizeof(more_cmd) <= exp_end + && !memcmp(exp_ptr, more_cmd, sizeof(more_cmd))) { + exp_ptr += sizeof(more_cmd); + if (!more_len) + { + if (guess_string(out_ptr, out_end, exp_ptr, exp_end, more, &more_len)) + { + trace("match @more@ with [%.*s]\n", more_len, more); + return NULL; + } + err = out_ptr; + }else if(out_end-out_ptr < more_len + || (CompareStringA(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, + out_ptr, more_len, more, more_len) != CSTR_EQUAL)) { + err = out_ptr; + }else { + out_ptr += more_len; + continue; + } }else if(exp_ptr+sizeof(space_cmd) <= exp_end && !memcmp(exp_ptr, space_cmd, sizeof(space_cmd))) { exp_ptr += sizeof(space_cmd); diff --git a/programs/cmd/tests/interactive_builtins.cmd b/programs/cmd/tests/interactive_builtins.cmd index d1e308f159a..f5ab45d7bb1 100644 --- a/programs/cmd/tests/interactive_builtins.cmd +++ b/programs/cmd/tests/interactive_builtins.cmd @@ -1,8 +1,32 @@ @echo --------- testing echo echo foo @echo foo +if exist c:\windows ( + echo bar +) +@if exist c:\windows ( + echo bar +) +if exist c:\windows ( + @echo bar +) +@if exist c:\windows ( + @echo bar +) echo off echo foo @echo foo +if exist c:\windows ( + echo bar +) +@if exist c:\windows ( + echo bar +) +if exist c:\windows ( + @echo bar +) +@if exist c:\windows ( + @echo bar +) echo --------- done exit 0 diff --git a/programs/cmd/tests/interactive_builtins.cmd.exp b/programs/cmd/tests/interactive_builtins.cmd.exp index 64482e85c04..4280ec186c9 100644 --- a/programs/cmd/tests/interactive_builtins.cmd.exp +++ b/programs/cmd/tests/interactive_builtins.cmd.exp @@ -7,11 +7,47 @@ foo @pwd@>@echo foo foo
+@pwd@>if exist c:\windows ( +@more@ echo bar +@more@) +bar + +@pwd@>@if exist c:\windows ( +@more@ echo bar +@more@) +bar + +@pwd@>if exist c:\windows ( +@more@ @echo bar +@more@) +bar + +@pwd@>@if exist c:\windows ( +@more@ @echo bar +@more@) +bar + @pwd@>echo off echo foo foo @echo foo foo +if exist c:\windows ( +@more@ echo bar +@more@) +bar +@if exist c:\windows ( +@more@ echo bar +@more@) +bar +if exist c:\windows ( +@more@ @echo bar +@more@) +bar +@if exist c:\windows ( +@more@ @echo bar +@more@) +bar echo --------- done --------- done exit 0 diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index ee863856ec9..286dc6b9ff0 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -2315,6 +2315,13 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE if (!WCMD_fgets(extraData, MAXSTRING, readFrom)) break;
+ /* echo input stream if naturally not echoed */ + if (!context && !optionalcmd && GetFileType(readFrom) != FILE_TYPE_CHAR) + { + WCMD_output_asis(extraData); + WCMD_output_asis(L"\r\n"); + } + /* Edge case for carets - a completely blank line (i.e. was just CRLF) is oddly added as an LF but then more data is received (but only once more!) */
Wine-bugs: https://bugs.winehq.org/show_bug.cgi?id=51599 Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/tests/interactive_builtins.cmd | 5 +++++ programs/cmd/tests/interactive_builtins.cmd.exp | 7 +++++++ programs/cmd/wcmdmain.c | 8 ++++++-- 3 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/programs/cmd/tests/interactive_builtins.cmd b/programs/cmd/tests/interactive_builtins.cmd index f5ab45d7bb1..1a06c29d103 100644 --- a/programs/cmd/tests/interactive_builtins.cmd +++ b/programs/cmd/tests/interactive_builtins.cmd @@ -28,5 +28,10 @@ if exist c:\windows ( @if exist c:\windows ( @echo bar ) +echo --------- testing syntax errors +if exist c:\windows ( + echo bar + +) echo --------- done exit 0 diff --git a/programs/cmd/tests/interactive_builtins.cmd.exp b/programs/cmd/tests/interactive_builtins.cmd.exp index 4280ec186c9..331e9b54b38 100644 --- a/programs/cmd/tests/interactive_builtins.cmd.exp +++ b/programs/cmd/tests/interactive_builtins.cmd.exp @@ -48,6 +48,13 @@ bar @more@ @echo bar @more@) bar +echo --------- testing syntax errors +--------- testing syntax errors +if exist c:\windows ( +@more@ echo bar +@more@ +@more@) +bar echo --------- done --------- done exit 0 diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 286dc6b9ff0..bfd72953639 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1840,6 +1840,7 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE BOOL lastWasCaret = FALSE; BOOL ignoreBracket = FALSE; /* Some expressions after if (set) require */ /* handling brackets as a normal character */ + BOOL atEOF = FALSE; int lineCurDepth; /* Bracket depth when line was read in */ BOOL resetAtEndOfLine = FALSE; /* Do we need to reset curdepth at EOL */
@@ -2297,8 +2298,8 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE
/* If we have reached the end of the string, see if bracketing or final caret is outstanding */ - if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) && - readFrom != INVALID_HANDLE_VALUE) { + while (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) && + readFrom != INVALID_HANDLE_VALUE && !atEOF) { WCHAR *extraData;
WINE_TRACE("Need to read more data as outstanding brackets or carets\n"); @@ -2313,7 +2314,10 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE WINE_TRACE("Read more input\n"); if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT)); if (!WCMD_fgets(extraData, MAXSTRING, readFrom)) + { + atEOF = TRUE; break; + }
/* echo input stream if naturally not echoed */ if (!context && !optionalcmd && GetFileType(readFrom) != FILE_TYPE_CHAR)
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52344 Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/tests/interactive_builtins.cmd | 3 +++ programs/cmd/tests/interactive_builtins.cmd.exp | 5 +++++ programs/cmd/wcmdmain.c | 10 +++++----- 3 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/programs/cmd/tests/interactive_builtins.cmd b/programs/cmd/tests/interactive_builtins.cmd index 1a06c29d103..96d53f371fd 100644 --- a/programs/cmd/tests/interactive_builtins.cmd +++ b/programs/cmd/tests/interactive_builtins.cmd @@ -28,6 +28,9 @@ if exist c:\windows ( @if exist c:\windows ( @echo bar ) +echo --------- testing expansion for variables +set myownecho=echo +%myownecho% foo echo --------- testing syntax errors if exist c:\windows ( echo bar diff --git a/programs/cmd/tests/interactive_builtins.cmd.exp b/programs/cmd/tests/interactive_builtins.cmd.exp index 331e9b54b38..81114ccbd86 100644 --- a/programs/cmd/tests/interactive_builtins.cmd.exp +++ b/programs/cmd/tests/interactive_builtins.cmd.exp @@ -48,6 +48,11 @@ bar @more@ @echo bar @more@) bar +echo --------- testing expansion for variables +--------- testing expansion for variables +set myownecho=echo +%myownecho% foo +foo echo --------- testing syntax errors --------- testing syntax errors if exist c:\windows ( diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index bfd72953639..b79c9361fc1 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1309,6 +1309,11 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, new_redir = heap_xalloc(MAXSTRING * sizeof(WCHAR)); redir = new_redir;
+ /* Expand variables in command line mode only (batch mode will + be expanded as the line is read in, except for 'for' loops) */ + handleExpansion(new_cmd, (context != NULL), delayedsubst); + handleExpansion(new_redir, (context != NULL), delayedsubst); + /* Strip leading whitespaces, and a '@' if supplied */ whichcmd = WCMD_skip_leading_spaces(cmd); WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd)); @@ -1359,11 +1364,6 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, lstrcpyW(new_redir, redirects); }
- /* Expand variables in command line mode only (batch mode will - be expanded as the line is read in, except for 'for' loops) */ - handleExpansion(new_cmd, (context != NULL), delayedsubst); - handleExpansion(new_redir, (context != NULL), delayedsubst); - /* * Changing default drive has to be handled as a special case, anything * else if it exists after whitespace is ignored
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52345 Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/builtins.c | 10 ++++++++++ programs/cmd/tests/interactive_builtins.cmd | 2 ++ programs/cmd/tests/interactive_builtins.cmd.exp | 2 ++ 3 files changed, 14 insertions(+)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 2fc7e07f7aa..84befa9fc43 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -2704,6 +2704,12 @@ void WCMD_popd (void) { LocalFree (temp); }
+/* check that operand is either unquoted, or with opening and ending quotes */ +static BOOL is_properly_quoted(const WCHAR* str) +{ + return str[0] != '"' || (str[1] && str[wcslen(str) - 1] == '"'); +} + /******************************************************************* * evaluate_if_comparison * @@ -2729,8 +2735,12 @@ static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operato
/* == is a special case, as it always compares strings */ if (!lstrcmpiW(operator, L"==")) + { + if (!is_properly_quoted(leftOperand) || !is_properly_quoted(rightOperand)) + return -1; return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0 : lstrcmpW (leftOperand, rightOperand) == 0; + }
/* Check if we have plain integers (in decimal, octal or hexadecimal notation) */ leftOperand_int = wcstol(leftOperand, &endptr_leftOp, 0); diff --git a/programs/cmd/tests/interactive_builtins.cmd b/programs/cmd/tests/interactive_builtins.cmd index 96d53f371fd..91d62ed6959 100644 --- a/programs/cmd/tests/interactive_builtins.cmd +++ b/programs/cmd/tests/interactive_builtins.cmd @@ -36,5 +36,7 @@ if exist c:\windows ( echo bar
) +if x == " echo f +rem " echo --------- done exit 0 diff --git a/programs/cmd/tests/interactive_builtins.cmd.exp b/programs/cmd/tests/interactive_builtins.cmd.exp index 81114ccbd86..df4c278ca39 100644 --- a/programs/cmd/tests/interactive_builtins.cmd.exp +++ b/programs/cmd/tests/interactive_builtins.cmd.exp @@ -60,6 +60,8 @@ if exist c:\windows ( @more@ @more@) bar +if x == " echo f +rem " echo --------- done --------- done exit 0
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- dlls/kernelbase/console.c | 32 ++++++++++++++- include/wine/condrv.h | 1 programs/conhost/conhost.c | 93 +++++++++++++++++++++++++++++++++++--------- programs/conhost/conhost.h | 1 server/console.c | 1 5 files changed, 107 insertions(+), 21 deletions(-)
diff --git a/dlls/kernelbase/console.c b/dlls/kernelbase/console.c index a7eeb439232..52949ab7541 100644 --- a/dlls/kernelbase/console.c +++ b/dlls/kernelbase/console.c @@ -1598,8 +1598,36 @@ BOOL WINAPI ReadConsoleW( HANDLE handle, void *buffer, DWORD length, DWORD *coun return FALSE; }
- ret = console_ioctl( handle, IOCTL_CONDRV_READ_CONSOLE, NULL, 0, buffer, - length * sizeof(WCHAR), count ); + if (reserved) + { + CONSOLE_READCONSOLE_CONTROL* crc = reserved; + char *tmp; + + if (crc->nLength != sizeof(*crc) || crc->nInitialChars >= length) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return FALSE; + } + if (!(tmp = HeapAlloc( GetProcessHeap(), 0, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR) ))) + { + SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + return FALSE; + } + + memcpy( tmp, &crc->dwCtrlWakeupMask, sizeof(DWORD) ); + memcpy( tmp + sizeof(DWORD), buffer, crc->nInitialChars * sizeof(WCHAR) ); + ret = console_ioctl( handle, IOCTL_CONDRV_READ_COMPLETION, + tmp, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR), + buffer, length * sizeof(WCHAR), + count ); + crc->dwConsoleKeyState = 0; + HeapFree( GetProcessHeap(), 0, tmp ); + } + else + { + ret = console_ioctl( handle, IOCTL_CONDRV_READ_CONSOLE, NULL, 0, buffer, + length * sizeof(WCHAR), count ); + } if (ret) *count /= sizeof(WCHAR); return ret; } diff --git a/include/wine/condrv.h b/include/wine/condrv.h index 4d2332a1ee9..610fb859511 100644 --- a/include/wine/condrv.h +++ b/include/wine/condrv.h @@ -43,6 +43,7 @@ #define IOCTL_CONDRV_BEEP CTL_CODE(FILE_DEVICE_CONSOLE, 20, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_CONDRV_FLUSH CTL_CODE(FILE_DEVICE_CONSOLE, 21, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_CONDRV_GET_WINDOW CTL_CODE(FILE_DEVICE_CONSOLE, 22, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_CONDRV_READ_COMPLETION CTL_CODE(FILE_DEVICE_CONSOLE, 24, METHOD_BUFFERED, FILE_READ_ACCESS)
/* console output ioctls */ #define IOCTL_CONDRV_WRITE_CONSOLE CTL_CODE(FILE_DEVICE_CONSOLE, 30, METHOD_BUFFERED, FILE_WRITE_ACCESS) diff --git a/programs/conhost/conhost.c b/programs/conhost/conhost.c index 2b137c40fb0..b7f59a25e0d 100644 --- a/programs/conhost/conhost.c +++ b/programs/conhost/conhost.c @@ -492,6 +492,7 @@ static void read_from_buffer( struct console *console, size_t out_size ) switch( console->read_ioctl ) { case IOCTL_CONDRV_READ_CONSOLE: + case IOCTL_CONDRV_READ_COMPLETION: out_size = min( out_size, console->read_buffer_count * sizeof(WCHAR) ); read_complete( console, STATUS_SUCCESS, console->read_buffer, out_size, console->record_count != 0 ); read_len = out_size / sizeof(WCHAR); @@ -1155,7 +1156,7 @@ static unsigned int edit_line_string_width( const WCHAR *str, unsigned int len) return offset; }
-static void update_read_output( struct console *console ) +static void update_read_output( struct console *console, BOOL newline ) { struct screen_buffer *screen_buffer = console->active; struct edit_line *ctx = &console->edit_line; @@ -1203,7 +1204,7 @@ static void update_read_output( struct console *console ) } }
- if (!ctx->status) + if (newline) { offset = edit_line_string_width( ctx->buf, ctx->len ); screen_buffer->cursor_x = 0; @@ -1241,10 +1242,14 @@ static void update_read_output( struct console *console ) update_window_config( screen_buffer->console, TRUE ); }
+/* can end on any ctrl-character: from 0x00 up to 0x1F) */ +#define FIRST_NON_CONTROL_CHAR (L' ') + static NTSTATUS process_console_input( struct console *console ) { struct edit_line *ctx = &console->edit_line; unsigned int i; + WCHAR ctrl_value = FIRST_NON_CONTROL_CHAR;
switch (console->read_ioctl) { @@ -1252,6 +1257,7 @@ static NTSTATUS process_console_input( struct console *console ) if (console->record_count) read_console_input( console, console->pending_read ); return STATUS_SUCCESS; case IOCTL_CONDRV_READ_CONSOLE: + case IOCTL_CONDRV_READ_COMPLETION: case IOCTL_CONDRV_READ_FILE: break; default: @@ -1264,7 +1270,7 @@ static NTSTATUS process_console_input( struct console *console ) ctx->update_begin = ctx->len + 1; ctx->update_end = 0;
- for (i = 0; i < console->record_count && ctx->status == STATUS_PENDING; i++) + for (i = 0; i < console->record_count && ctx->status == STATUS_PENDING && ctrl_value == FIRST_NON_CONTROL_CHAR; i++) { void (*func)( struct console *console ) = NULL; INPUT_RECORD ir = console->records[i]; @@ -1284,6 +1290,14 @@ static NTSTATUS process_console_input( struct console *console ) /* mask out some bits which don't interest us */ state = ir.Event.KeyEvent.dwControlKeyState & ~(NUMLOCK_ON|SCROLLLOCK_ON|CAPSLOCK_ON|ENHANCED_KEY);
+ if (ir.Event.KeyEvent.uChar.UnicodeChar && + ir.Event.KeyEvent.uChar.UnicodeChar < FIRST_NON_CONTROL_CHAR && + (ctx->ctrl_mask & (1u << ir.Event.KeyEvent.uChar.UnicodeChar))) + { + ctrl_value = ir.Event.KeyEvent.uChar.UnicodeChar; + ctx->status = STATUS_SUCCESS; + continue; + } func = NULL; for (map = console->edition_mode ? emacs_key_map : win32_key_map; map->entries != NULL; map++) { @@ -1326,7 +1340,7 @@ static NTSTATUS process_console_input( struct console *console ) } else if (ctx->len >= console->pending_read / sizeof(WCHAR)) ctx->status = STATUS_SUCCESS; - } + } }
if (console->record_count > i) memmove( console->records, console->records + i, @@ -1336,19 +1350,26 @@ static NTSTATUS process_console_input( struct console *console ) if (ctx->status == STATUS_PENDING && !(console->mode & ENABLE_LINE_INPUT) && ctx->len) ctx->status = STATUS_SUCCESS;
- if (console->mode & ENABLE_ECHO_INPUT) update_read_output( console ); + if (console->mode & ENABLE_ECHO_INPUT) update_read_output( console, !ctx->status && ctrl_value == FIRST_NON_CONTROL_CHAR ); if (ctx->status == STATUS_PENDING) return STATUS_SUCCESS;
if (!ctx->status && (console->mode & ENABLE_LINE_INPUT)) { - if (ctx->len) append_input_history( console, ctx->buf, ctx->len * sizeof(WCHAR) ); - if (edit_line_grow(console, 2)) + if (ctrl_value < FIRST_NON_CONTROL_CHAR) + { + edit_line_insert( console, &ctrl_value, 1 ); + } + else { - ctx->buf[ctx->len++] = '\r'; - ctx->buf[ctx->len++] = '\n'; - ctx->buf[ctx->len] = 0; - TRACE( "return %s\n", debugstr_wn( ctx->buf, ctx->len )); + if (ctx->len) append_input_history( console, ctx->buf, ctx->len * sizeof(WCHAR) ); + if (edit_line_grow(console, 2)) + { + ctx->buf[ctx->len++] = '\r'; + ctx->buf[ctx->len++] = '\n'; + ctx->buf[ctx->len] = 0; + } } + TRACE( "return %s\n", debugstr_wn( ctx->buf, ctx->len )); }
console->read_buffer = ctx->buf; @@ -1365,8 +1386,10 @@ static NTSTATUS process_console_input( struct console *console ) return STATUS_SUCCESS; }
-static NTSTATUS read_console( struct console *console, unsigned int ioctl, size_t out_size ) +static NTSTATUS read_console( struct console *console, unsigned int ioctl, size_t out_size, + const WCHAR *keep, unsigned int keep_len, unsigned int ctrl_mask ) { + struct edit_line *ctx = &console->edit_line; TRACE("\n");
if (out_size > INT_MAX) @@ -1382,11 +1405,31 @@ static NTSTATUS read_console( struct console *console, unsigned int ioctl, size_ return STATUS_SUCCESS; }
- console->edit_line.history_index = console->history_index; - console->edit_line.home_x = console->active->cursor_x; - console->edit_line.home_y = console->active->cursor_y; - console->edit_line.status = STATUS_PENDING; - if (edit_line_grow( console, 1 )) console->edit_line.buf[0] = 0; + ctx->history_index = console->history_index; + ctx->home_x = console->active->cursor_x; + ctx->home_y = console->active->cursor_y; + ctx->status = STATUS_PENDING; + if (keep_len && edit_line_grow( console, keep_len + 1 )) + { + unsigned offset = edit_line_string_width( keep, keep_len ); + if (offset > ctx->home_x) + { + int deltay; + offset -= ctx->home_x; + deltay = offset / console->active->width; + ctx->home_y = (deltay >= ctx->home_y) ? ctx->home_y - deltay : 0; + ctx->home_x = console->active->width - 1 - (offset % console->active->width); + } + else + ctx->home_x -= offset; + ctx->cursor = keep_len; + memcpy( ctx->buf, keep, keep_len * sizeof(WCHAR) ); + ctx->buf[keep_len] = 0; + ctx->len = keep_len; + ctx->end_offset = keep_len; + } + else if (edit_line_grow( console, 1 )) ctx->buf[0] = 0; + ctx->ctrl_mask = ctrl_mask;
console->pending_read = out_size; return process_console_input( console ); @@ -2477,13 +2520,25 @@ static NTSTATUS console_input_ioctl( struct console *console, unsigned int code, case IOCTL_CONDRV_READ_CONSOLE: if (in_size || *out_size % sizeof(WCHAR)) return STATUS_INVALID_PARAMETER; ensure_tty_input_thread( console ); - status = read_console( console, code, *out_size ); + status = read_console( console, code, *out_size, NULL, 0, 0 ); + *out_size = 0; + return status; + + case IOCTL_CONDRV_READ_COMPLETION: + if ((in_size < sizeof(DWORD)) || ((in_size - sizeof(DWORD)) % sizeof(WCHAR)) || + (*out_size % sizeof(WCHAR))) + return STATUS_INVALID_PARAMETER; + ensure_tty_input_thread( console ); + status = read_console( console, code, *out_size, + (const WCHAR*)((const char*)in_data + sizeof(DWORD)), + (in_size - sizeof(DWORD)) / sizeof(WCHAR), + *(DWORD*)in_data ); *out_size = 0; return status;
case IOCTL_CONDRV_READ_FILE: ensure_tty_input_thread( console ); - status = read_console( console, code, *out_size ); + status = read_console( console, code, *out_size, NULL, 0, 0 ); *out_size = 0; return status;
diff --git a/programs/conhost/conhost.h b/programs/conhost/conhost.h index 5e9b999380c..d150fd0acad 100644 --- a/programs/conhost/conhost.h +++ b/programs/conhost/conhost.h @@ -69,6 +69,7 @@ struct edit_line unsigned int end_offset; /* offset of the last written char */ unsigned int home_x; /* home position */ unsigned int home_y; + unsigned int ctrl_mask; /* mask for ctrl characters for completion */ };
struct console diff --git a/server/console.c b/server/console.c index 5407fba1411..609362e2d5d 100644 --- a/server/console.c +++ b/server/console.c @@ -931,6 +931,7 @@ static int is_blocking_read_ioctl( unsigned int code ) { case IOCTL_CONDRV_READ_INPUT: case IOCTL_CONDRV_READ_CONSOLE: + case IOCTL_CONDRV_READ_COMPLETION: case IOCTL_CONDRV_READ_FILE: return 1; default:
Hi Eric,
On 2/9/22 16:44, Eric Pouech wrote:
Signed-off-by: Eric Pouecheric.pouech@gmail.com
dlls/kernelbase/console.c | 32 ++++++++++++++- include/wine/condrv.h | 1 programs/conhost/conhost.c | 93 +++++++++++++++++++++++++++++++++++--------- programs/conhost/conhost.h | 1 server/console.c | 1 5 files changed, 107 insertions(+), 21 deletions(-)
diff --git a/dlls/kernelbase/console.c b/dlls/kernelbase/console.c index a7eeb439232..52949ab7541 100644 --- a/dlls/kernelbase/console.c +++ b/dlls/kernelbase/console.c @@ -1598,8 +1598,36 @@ BOOL WINAPI ReadConsoleW( HANDLE handle, void *buffer, DWORD length, DWORD *coun return FALSE; }
- ret = console_ioctl( handle, IOCTL_CONDRV_READ_CONSOLE, NULL, 0, buffer,
length * sizeof(WCHAR), count );
- if (reserved)
- {
CONSOLE_READCONSOLE_CONTROL* crc = reserved;
char *tmp;
if (crc->nLength != sizeof(*crc) || crc->nInitialChars >= length)
{
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
if (!(tmp = HeapAlloc( GetProcessHeap(), 0, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR) )))
{
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
return FALSE;
}
memcpy( tmp, &crc->dwCtrlWakeupMask, sizeof(DWORD) );
memcpy( tmp + sizeof(DWORD), buffer, crc->nInitialChars * sizeof(WCHAR) );
ret = console_ioctl( handle, IOCTL_CONDRV_READ_COMPLETION,
tmp, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR),
buffer, length * sizeof(WCHAR),
count );
crc->dwConsoleKeyState = 0;
HeapFree( GetProcessHeap(), 0, tmp );
- }
- else
- {
ret = console_ioctl( handle, IOCTL_CONDRV_READ_CONSOLE, NULL, 0, buffer,
length * sizeof(WCHAR), count );
- } if (ret) *count /= sizeof(WCHAR); return ret;
Could we just use IOCTL_CONDRV_READ_CONSOLE in both cases and distinguish the actual mode by checking input size in conhost?
Also, it would be interesting to try to add a test for this, see conhost/tests/tty.c for an example of other ReadConsole() tests. A fair warning: it may prove to be tricky. When I wrote those tests, I observed some weird or inconsistent Windows behaviour. Pseudo consoles were not matured on Windows back then, hopefully things will be better now. There is a huge chance that it's not one of those cases and it will be straightforward. If things get ugly, I'm fine skipping tests, but it's worth to try.
Thanks,
Jacek
Le 20/02/2022 à 14:03, Jacek Caban a écrit :
Hi Eric,
On 2/9/22 16:44, Eric Pouech wrote:
Signed-off-by: Eric Pouecheric.pouech@gmail.com
dlls/kernelbase/console.c | 32 ++++++++++++++- include/wine/condrv.h | 1 programs/conhost/conhost.c | 93 +++++++++++++++++++++++++++++++++++--------- programs/conhost/conhost.h | 1 server/console.c | 1 5 files changed, 107 insertions(+), 21 deletions(-)
diff --git a/dlls/kernelbase/console.c b/dlls/kernelbase/console.c index a7eeb439232..52949ab7541 100644 --- a/dlls/kernelbase/console.c +++ b/dlls/kernelbase/console.c @@ -1598,8 +1598,36 @@ BOOL WINAPI ReadConsoleW( HANDLE handle, void *buffer, DWORD length, DWORD *coun return FALSE; } - ret = console_ioctl( handle, IOCTL_CONDRV_READ_CONSOLE, NULL, 0, buffer, - length * sizeof(WCHAR), count ); + if (reserved) + { + CONSOLE_READCONSOLE_CONTROL* crc = reserved; + char *tmp;
+ if (crc->nLength != sizeof(*crc) || crc->nInitialChars >= length) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return FALSE; + } + if (!(tmp = HeapAlloc( GetProcessHeap(), 0, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR) ))) + { + SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + return FALSE; + }
+ memcpy( tmp, &crc->dwCtrlWakeupMask, sizeof(DWORD) ); + memcpy( tmp + sizeof(DWORD), buffer, crc->nInitialChars * sizeof(WCHAR) ); + ret = console_ioctl( handle, IOCTL_CONDRV_READ_COMPLETION, + tmp, sizeof(DWORD) + crc->nInitialChars
- sizeof(WCHAR),
+ buffer, length * sizeof(WCHAR), + count ); + crc->dwConsoleKeyState = 0; + HeapFree( GetProcessHeap(), 0, tmp ); + } + else + { + ret = console_ioctl( handle, IOCTL_CONDRV_READ_CONSOLE, NULL, 0, buffer, + length * sizeof(WCHAR), count ); + } if (ret) *count /= sizeof(WCHAR); return ret;
Could we just use IOCTL_CONDRV_READ_CONSOLE in both cases and distinguish the actual mode by checking input size in conhost?
that's possible ;-) (first version of the patch were written that way)
but there's a different structure on input and on output between the two modes. We need to differentiate anyway in conhost between the two modes.
I thought it was clearer this way
I also considered the other way around (only using the new mode) but thought the extra copy on return wasn't worth it
Also, it would be interesting to try to add a test for this, see conhost/tests/tty.c for an example of other ReadConsole() tests. A fair warning: it may prove to be tricky. When I wrote those tests, I observed some weird or inconsistent Windows behaviour. Pseudo consoles were not matured on Windows back then, hopefully things will be better now. There is a huge chance that it's not one of those cases and it will be straightforward. If things get ugly, I'm fine skipping tests, but it's worth to try.
ok I'll give it a try
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- dlls/kernelbase/console.c | 12 ++++++++---- programs/conhost/conhost.c | 9 +++++++-- programs/conhost/conhost.h | 1 + 3 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/dlls/kernelbase/console.c b/dlls/kernelbase/console.c index 52949ab7541..25e2447e61e 100644 --- a/dlls/kernelbase/console.c +++ b/dlls/kernelbase/console.c @@ -1608,7 +1608,7 @@ BOOL WINAPI ReadConsoleW( HANDLE handle, void *buffer, DWORD length, DWORD *coun SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } - if (!(tmp = HeapAlloc( GetProcessHeap(), 0, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR) ))) + if (!(tmp = HeapAlloc( GetProcessHeap(), 0, sizeof(DWORD) + length * sizeof(WCHAR) ))) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return FALSE; @@ -1618,9 +1618,13 @@ BOOL WINAPI ReadConsoleW( HANDLE handle, void *buffer, DWORD length, DWORD *coun memcpy( tmp + sizeof(DWORD), buffer, crc->nInitialChars * sizeof(WCHAR) ); ret = console_ioctl( handle, IOCTL_CONDRV_READ_COMPLETION, tmp, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR), - buffer, length * sizeof(WCHAR), - count ); - crc->dwConsoleKeyState = 0; + tmp, sizeof(DWORD) + length * sizeof(WCHAR), count ); + if (ret) + { + memcpy( &crc->dwConsoleKeyState, tmp, sizeof(DWORD) ); + *count -= sizeof(DWORD); + memcpy( buffer, tmp + sizeof(DWORD), *count ); + } HeapFree( GetProcessHeap(), 0, tmp ); } else diff --git a/programs/conhost/conhost.c b/programs/conhost/conhost.c index b7f59a25e0d..70ff054372e 100644 --- a/programs/conhost/conhost.c +++ b/programs/conhost/conhost.c @@ -457,6 +457,8 @@ static NTSTATUS read_complete( struct console *console, NTSTATUS status, const v req->signal = signal; req->read = 1; req->status = status; + if (console->read_ioctl == IOCTL_CONDRV_READ_COMPLETION) + wine_server_add_data( req, &console->key_state, sizeof(console->key_state) ); wine_server_add_data( req, buf, size ); status = wine_server_call( req ); } @@ -1250,6 +1252,7 @@ static NTSTATUS process_console_input( struct console *console ) struct edit_line *ctx = &console->edit_line; unsigned int i; WCHAR ctrl_value = FIRST_NON_CONTROL_CHAR; + unsigned int ctrl_keyvalue = 0;
switch (console->read_ioctl) { @@ -1295,6 +1298,7 @@ static NTSTATUS process_console_input( struct console *console ) (ctx->ctrl_mask & (1u << ir.Event.KeyEvent.uChar.UnicodeChar))) { ctrl_value = ir.Event.KeyEvent.uChar.UnicodeChar; + ctrl_keyvalue = ir.Event.KeyEvent.dwControlKeyState; ctx->status = STATUS_SUCCESS; continue; } @@ -1358,6 +1362,7 @@ static NTSTATUS process_console_input( struct console *console ) if (ctrl_value < FIRST_NON_CONTROL_CHAR) { edit_line_insert( console, &ctrl_value, 1 ); + console->key_state = ctrl_keyvalue; } else { @@ -2526,10 +2531,10 @@ static NTSTATUS console_input_ioctl( struct console *console, unsigned int code,
case IOCTL_CONDRV_READ_COMPLETION: if ((in_size < sizeof(DWORD)) || ((in_size - sizeof(DWORD)) % sizeof(WCHAR)) || - (*out_size % sizeof(WCHAR))) + (*out_size < sizeof(DWORD)) || ((*out_size - sizeof(DWORD)) % sizeof(WCHAR))) return STATUS_INVALID_PARAMETER; ensure_tty_input_thread( console ); - status = read_console( console, code, *out_size, + status = read_console( console, code, *out_size - sizeof(DWORD), (const WCHAR*)((const char*)in_data + sizeof(DWORD)), (in_size - sizeof(DWORD)) / sizeof(WCHAR), *(DWORD*)in_data ); diff --git a/programs/conhost/conhost.h b/programs/conhost/conhost.h index d150fd0acad..479a1aa065e 100644 --- a/programs/conhost/conhost.h +++ b/programs/conhost/conhost.h @@ -88,6 +88,7 @@ struct console unsigned int read_ioctl; /* current read ioctl */ size_t pending_read; /* size of pending read buffer */ struct edit_line edit_line; /* edit line context */ + unsigned int key_state; struct console_window *window; WCHAR *title; /* console title */ struct history_line **history; /* lines history */
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/batch.c | 252 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 6 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 9a262c5fec5..34385a12a0e 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -227,6 +227,158 @@ WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **start, BOOL raw, return WCMD_parameter_with_delims (s, n, start, raw, wholecmdline, L" \t,=;"); }
+struct completion_info +{ + WCHAR** subfiles; + DWORD index_search; + DWORD num_results; +}; + +static WCHAR* dup_quoted(const WCHAR* path, const WCHAR* in) +{ + const WCHAR* last = wcsrchr(path, L'/'); + size_t dirlen; + size_t len; + WCHAR* ret; + + if (last) + { + WCHAR* last1 = wcschr(last + 1, L'\'); + if (last1) last = last1; + } + else last = wcsrchr(path, L'\'); + dirlen = last ? (last - path) + 1 : 0; + len = wcslen(in); + ret = malloc((dirlen + len + 1) * sizeof(WCHAR)); + if (ret) + { + if (dirlen) + { + memcpy(ret, path, (dirlen - 1) * sizeof(WCHAR)); + ret[dirlen - 1] = *last; + } + memcpy(ret + dirlen, in, len * sizeof(WCHAR)); + ret[dirlen + len] = L'\0'; + if (wcspbrk(ret, L" \t"")) /* need quotes */ + { + WCHAR* new = realloc(ret, (1 + dirlen + len + 1 + 1) * sizeof(WCHAR)); + if (!new) + { + free(ret); + return NULL; + } + ret = new; + memmove(ret + 1, ret, (dirlen + len) * sizeof(WCHAR)); + ret[0] = ret[1 + dirlen + len] = L'"'; + ret[1 + dirlen + len + 1] = L'\0'; + } + } + return ret; +} + +static BOOL init_completion(struct completion_info* ci, const WCHAR* from, DWORD len, BOOL forward) +{ + WCHAR* ptr; + HANDLE hff; + WIN32_FIND_DATAW fd; + BOOL in_quotes = FALSE; + DWORD i, j = 0; + + ptr = malloc((len + 1 + 1) * sizeof(WCHAR)); + if (!ptr) return FALSE; + for (i = 0; i < len; i++) + { + switch (from[i]) + { + case L'"': + in_quotes = !in_quotes; + continue; + case L'\': + if (!in_quotes && i + 1 < len && wcschr(L" \t"\", from[i + 1]) != NULL) + ++i; + break; + case L' ': + case L'\t': + /* shouldn't happen, as 'from' should contain a single argument */ + if (!in_quotes) + { + free(ptr); + return FALSE; + } + break; + } + ptr[j++] = from[i]; + } + ptr[j] = L'*'; + ptr[j + 1] = L'\0'; + hff = FindFirstFileW(ptr, &fd); + ci->num_results = 0; + ci->subfiles = NULL; + if (hff != INVALID_HANDLE_VALUE) + { + do + { + WCHAR** new; + if (!wcscmp(fd.cFileName, L".") || !wcscmp(fd.cFileName, L"..")) + continue; + new = realloc(ci->subfiles, (ci->num_results + 1) * sizeof(*ci->subfiles)); + if (!new) + { + FindClose(hff); + free(ptr); + return FALSE; + } + ci->subfiles = new; + if (!(ci->subfiles[ci->num_results++] = dup_quoted(ptr, fd.cFileName))) + { + FindClose(hff); + free(ptr); + return FALSE; + } + } while (FindNextFileW(hff, &fd)); + FindClose(hff); + } + free(ptr); + if (!ci->num_results) return FALSE; + ci->index_search = forward ? 0 : ci->num_results - 1; + return TRUE; +} + +static void dispose_completion(struct completion_info* ci) +{ + if (ci->subfiles) + { + unsigned i; + for (i = 0; i < ci->num_results; i++) + free(ci->subfiles[i]); + free(ci->subfiles); + } +} + +static WCHAR* next_completion(struct completion_info* ci, const WCHAR* from, DWORD len, BOOL forward) +{ + if (!ci->subfiles || len != wcslen(ci->subfiles[ci->index_search]) || /* FIXME better check no wcslen, just check last char */ + memcmp(ci->subfiles[ci->index_search], from, len * sizeof(WCHAR))) + { + dispose_completion(ci); + if (!init_completion(ci, from, len, forward)) return NULL; + } + else + { + if (forward) + { + if (++ci->index_search >= ci->num_results) + ci->index_search = 0; + } + else + { + if (ci->index_search-- == 0) + ci->index_search = ci->num_results - 1; + } + } + return ci->subfiles[ci->index_search]; +} + /**************************************************************************** * WCMD_fgets * @@ -249,7 +401,7 @@ WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h) /* We can't use the native f* functions because of the filename syntax differences between DOS and Unix. Also need to lose the LF (or CRLF) from the line. */
- if (!ReadConsoleW(h, buf, noChars, &charsRead, NULL)) { + if (!VerifyConsoleIoHandle(h)) { LARGE_INTEGER filepos; char *bufA; UINT cp; @@ -282,13 +434,101 @@ WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h) heap_free(bufA); } else { - if (!charsRead) return NULL; + CONSOLE_READCONSOLE_CONTROL crc; + CONSOLE_SCREEN_BUFFER_INFO sbi; + struct completion_info ci = {NULL, 0, 0}; + DWORD nwritten; + HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); + + crc.nLength = sizeof(crc); + crc.nInitialChars = 0; + crc.dwCtrlWakeupMask = 1 << '\t'; + crc.dwConsoleKeyState = 0; + GetConsoleScreenBufferInfo(hout, &sbi); + for (;;) + { + WCHAR* ptr; + /* we need to 0-terminate the string anyway */ + if (!ReadConsoleW(h, buf, noChars - 1, &charsRead, &crc) || !charsRead) + { + dispose_completion(&ci); + return NULL; + }
- /* Find first EOL */ - for (i = 0; i < charsRead; i++) { - if (buf[i] == '\n' || buf[i] == '\r') - break; + ptr = wmemchr(buf, L'\t', charsRead); + if (ptr) + { + int n = 0; + WCHAR* start; + WCHAR* completion; + + /* find start of word containing ptr (if any) */ + buf[charsRead] = '\0'; + for (;;) + { + WCHAR* next; + WCHAR* pmt = WCMD_parameter(buf, n, &next, TRUE, FALSE); + if (!pmt[0] || next > ptr) + { + start = ptr; + break; + } + if (ptr <= next + wcslen(pmt)) + { + start = next; + break; + } + n++; + } + completion = next_completion(&ci, start, ptr - start, !(crc.dwConsoleKeyState & SHIFT_PRESSED)); + if (completion) + { + DWORD newlen; + if (start - buf + wcslen(completion) + 1 > noChars) + { + memcpy(start, completion, (start - buf + noChars - 1) * sizeof(WCHAR)); + buf[noChars - 1] = L'\0'; + newlen = noChars - 1; + } + else + { + wcscpy(start, completion); + newlen = wcslen(buf); + } + SetConsoleCursorPosition(hout, sbi.dwCursorPosition); + WriteConsoleW(hout, buf, newlen, &nwritten, NULL); + if (newlen < charsRead) + { + /* clear remaining stuff on input line (FIXME doesn't handle escaped characters) */ + WCHAR space = L' '; + DWORD i; + CONSOLE_SCREEN_BUFFER_INFO saved_sbi; + + GetConsoleScreenBufferInfo(hout, &saved_sbi); + for (i = newlen; i < charsRead; i++) + WriteConsoleW(hout, &space, 1, &nwritten, NULL); + SetConsoleCursorPosition(hout, saved_sbi.dwCursorPosition); + } + charsRead = newlen; + buf[charsRead] = L'\0'; + } + else + { + /* Beep() -- not loud enough, maybe user won't hear it */ + charsRead = ptr - buf; + } + crc.nInitialChars = charsRead; + continue; + } + + /* Find first EOL */ + for (i = 0; i < charsRead; i++) { + if (buf[i] == '\n' || buf[i] == '\r') + break; + } + if (i < charsRead) break; } + dispose_completion(&ci); }
/* Truncate at EOL (or end of buffer) */
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/conhost/conhost.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/programs/conhost/conhost.c b/programs/conhost/conhost.c index 70ff054372e..aa869587159 100644 --- a/programs/conhost/conhost.c +++ b/programs/conhost/conhost.c @@ -1555,8 +1555,11 @@ static void char_key_press( struct console *console, WCHAR ch, unsigned int ctrl key_press( console, ch, vk, ctrl ); }
-static unsigned int escape_char_to_vk( WCHAR ch ) +static unsigned int escape_char_to_vk( WCHAR ch, unsigned int *ctrl, WCHAR *outuch ) { + if (ctrl) *ctrl = 0; + if (outuch) *outuch = '\0'; + switch (ch) { case 'A': return VK_UP; @@ -1569,6 +1572,8 @@ static unsigned int escape_char_to_vk( WCHAR ch ) case 'Q': return VK_F2; case 'R': return VK_F3; case 'S': return VK_F4; + case 'Z': if (ctrl && outuch) {*ctrl = SHIFT_PRESSED; *outuch = L'\t'; return VK_TAB;} + return 0; default: return 0; } } @@ -1606,7 +1611,8 @@ static unsigned int convert_modifiers( unsigned int n )
static unsigned int process_csi_sequence( struct console *console, const WCHAR *buf, size_t size ) { - unsigned int n, count = 0, params[8], params_cnt = 0, vk; + unsigned int n, count = 0, params[8], params_cnt = 0, vk, ctrl; + WCHAR outuch;
for (;;) { @@ -1620,9 +1626,9 @@ static unsigned int process_csi_sequence( struct console *console, const WCHAR * if (++count == size) return 0; }
- if ((vk = escape_char_to_vk( buf[count] ))) + if ((vk = escape_char_to_vk( buf[count], &ctrl, &outuch ))) { - key_press( console, 0, vk, params_cnt >= 2 ? convert_modifiers( params[1] ) : 0 ); + key_press( console, outuch, vk, params_cnt >= 2 ? convert_modifiers( params[1] ) : ctrl ); return count + 1; }
@@ -1658,7 +1664,7 @@ static unsigned int process_input_escape( struct console *console, const WCHAR *
case 'O': if (++count == size) break; - vk = escape_char_to_vk( buf[1] ); + vk = escape_char_to_vk( buf[1], NULL, NULL ); if (vk) { key_press( console, 0, vk, 0 );
Hi Eric,
On 2/9/22 16:44, Eric Pouech wrote:
Signed-off-by: Eric Pouecheric.pouech@gmail.com
programs/conhost/conhost.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-)
Similar to patch 8, it would be nice to have a test.
Thanks,
Jacek