This serie implements: - filename completion in cmd - CONSOLE_READCONSOLE_CONTROL parameter in ReadConsoleW
v2 -> v3: + rebased (the shift-tab patches from V2 have been committed) + fixed inverted logic in ordinate cursor computation + added FIXME for negative ordinate + some cleanups (white space, debugging cruft)
v1 -> v2: + added tests in conhost for that (Jacek: thanks for warning, but it's a real pain + minor updates thanks to the tests
A+ Signed-off-by: Eric Pouech eric.pouech@gmail.com ---
Eric Pouech (4): kernelbase/console, programs/conhost: support CONSOLE_READCONSOLE_CONTROL in ReadConsoleW dlls/kernelbase, programs/conhost: return key state in ReadConsoleW with control programs/conhost/tests: add tests for ReadConsoleW with control programs/cmd: implement filename completion
dlls/kernelbase/console.c | 36 ++++- include/wine/condrv.h | 1 + programs/cmd/batch.c | 252 ++++++++++++++++++++++++++++++++++- programs/conhost/conhost.c | 110 ++++++++++++--- programs/conhost/conhost.h | 2 + programs/conhost/tests/tty.c | 179 ++++++++++++++++++++++++- server/console.c | 1 + 7 files changed, 550 insertions(+), 31 deletions(-)
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- dlls/kernelbase/console.c | 32 +++++++++++++- include/wine/condrv.h | 1 programs/conhost/conhost.c | 104 ++++++++++++++++++++++++++++++++++++-------- programs/conhost/conhost.h | 1 server/console.c | 1 5 files changed, 118 insertions(+), 21 deletions(-)
diff --git a/dlls/kernelbase/console.c b/dlls/kernelbase/console.c index 1f9eda845a1..1f23292d467 100644 --- a/dlls/kernelbase/console.c +++ b/dlls/kernelbase/console.c @@ -2021,8 +2021,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_CONSOLE_CONTROL, + 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 4515249a673..1000e62e765 100644 --- a/include/wine/condrv.h +++ b/include/wine/condrv.h @@ -44,6 +44,7 @@ #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_GET_PROCESS_LIST CTL_CODE(FILE_DEVICE_CONSOLE, 23, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_CONDRV_READ_CONSOLE_CONTROL 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 ae1f4e35f41..b08d26c2641 100644 --- a/programs/conhost/conhost.c +++ b/programs/conhost/conhost.c @@ -492,8 +492,9 @@ 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_CONSOLE_CONTROL: 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_complete( console, STATUS_SUCCESS, console->read_buffer, out_size, console->record_count != 0 ); read_len = out_size / sizeof(WCHAR); break; case IOCTL_CONDRV_READ_FILE: @@ -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_CONSOLE_CONTROL: case IOCTL_CONDRV_READ_FILE: break; default: @@ -1284,6 +1290,19 @@ 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 (ctx->ctrl_mask && + ir.Event.KeyEvent.uChar.UnicodeChar && + ir.Event.KeyEvent.uChar.UnicodeChar < FIRST_NON_CONTROL_CHAR) + { + if (ctx->ctrl_mask & (1u << ir.Event.KeyEvent.uChar.UnicodeChar)) + { + ctrl_value = ir.Event.KeyEvent.uChar.UnicodeChar; + ctx->status = STATUS_SUCCESS; + TRACE("Found ctrl char in mask: ^%lc %x\n", ir.Event.KeyEvent.uChar.UnicodeChar + '@', ctx->ctrl_mask); + continue; + } + if (ir.Event.KeyEvent.uChar.UnicodeChar == 10) continue; + } func = NULL; for (map = console->edition_mode ? emacs_key_map : win32_key_map; map->entries != NULL; map++) { @@ -1326,7 +1345,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 +1355,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) { - 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 )); + edit_line_insert( console, &ctrl_value, 1 ); + } + else + { + 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 +1391,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 *initial, unsigned int initial_len, unsigned int ctrl_mask ) { + struct edit_line *ctx = &console->edit_line; TRACE("\n");
if (out_size > INT_MAX) @@ -1382,11 +1410,37 @@ 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 (initial_len && edit_line_grow( console, initial_len + 1 )) + { + unsigned offset = edit_line_string_width( initial, initial_len ); + if (offset > ctx->home_x) + { + int deltay; + offset -= ctx->home_x; + deltay = offset / console->active->width; + if (ctx->home_y >= deltay) + ctx->home_y -= deltay; + else + { + ctx->home_y = 0; + FIXME("Support for negative ordinates is missing\n"); + } + ctx->home_x = console->active->width - 1 - (offset % console->active->width); + } + else + ctx->home_x -= offset; + ctx->cursor = initial_len; + memcpy( ctx->buf, initial, initial_len * sizeof(WCHAR) ); + ctx->buf[initial_len] = 0; + ctx->len = initial_len; + ctx->end_offset = initial_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 ); @@ -2502,13 +2556,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_CONSOLE_CONTROL: + 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 3cbd2ed2d80..e5f70aa3ca4 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 3fb7d8ab237..5f3f50d006f 100644 --- a/server/console.c +++ b/server/console.c @@ -952,6 +952,7 @@ static int is_blocking_read_ioctl( unsigned int code ) { case IOCTL_CONDRV_READ_INPUT: case IOCTL_CONDRV_READ_CONSOLE: + case IOCTL_CONDRV_READ_CONSOLE_CONTROL: case IOCTL_CONDRV_READ_FILE: return 1; default:
Signed-off-by: Jacek Caban jacek@codeweavers.com
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- dlls/kernelbase/console.c | 12 ++++++++---- programs/conhost/conhost.c | 10 ++++++++-- programs/conhost/conhost.h | 1 + 3 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/dlls/kernelbase/console.c b/dlls/kernelbase/console.c index 1f23292d467..b2bb6c53fd9 100644 --- a/dlls/kernelbase/console.c +++ b/dlls/kernelbase/console.c @@ -2031,7 +2031,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; @@ -2041,9 +2041,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_CONSOLE_CONTROL, 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 b08d26c2641..11e97993fd8 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_CONSOLE_CONTROL) + 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) { @@ -1297,6 +1300,7 @@ static NTSTATUS process_console_input( struct console *console ) if (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; TRACE("Found ctrl char in mask: ^%lc %x\n", ir.Event.KeyEvent.uChar.UnicodeChar + '@', ctx->ctrl_mask); continue; @@ -1363,6 +1367,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 { @@ -1404,6 +1409,7 @@ static NTSTATUS read_console( struct console *console, unsigned int ioctl, size_ }
console->read_ioctl = ioctl; + console->key_state = 0; if (!out_size || console->read_buffer_count) { read_from_buffer( console, out_size ); @@ -2562,10 +2568,10 @@ static NTSTATUS console_input_ioctl( struct console *console, unsigned int code,
case IOCTL_CONDRV_READ_CONSOLE_CONTROL: 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 e5f70aa3ca4..35876689419 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: Jacek Caban jacek@codeweavers.com
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/conhost/tests/tty.c | 179 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 4 deletions(-)
diff --git a/programs/conhost/tests/tty.c b/programs/conhost/tests/tty.c index 1857cdd4aad..849986a1c29 100644 --- a/programs/conhost/tests/tty.c +++ b/programs/conhost/tests/tty.c @@ -153,6 +153,7 @@ enum req_type REQ_READ_CONSOLE, REQ_READ_CONSOLE_A, REQ_READ_CONSOLE_FILE, + REQ_READ_CONSOLE_CONTROL, REQ_SCROLL, REQ_SET_ACTIVE, REQ_SET_CURSOR, @@ -201,6 +202,12 @@ struct pseudoconsole_req DWORD count; COORD coord; } fill; + struct + { + size_t size; + DWORD mask; + WCHAR initial[1]; + } control; } u; };
@@ -415,6 +422,21 @@ static void child_read_console_file(HANDLE pipe, size_t size) ok(ret, "WriteFile failed: %lu\n", GetLastError()); }
+static void child_read_console_control(HANDLE pipe, size_t size, DWORD control, const WCHAR* recall) +{ + char tmp[4096]; + struct pseudoconsole_req *req = (void *)tmp; + DWORD count; + BOOL ret; + + req->type = REQ_READ_CONSOLE_CONTROL; + req->u.control.size = size; + req->u.control.mask = control; + wcscpy(req->u.control.initial, recall); + ret = WriteFile(pipe, req, sizeof(*req) + wcslen(recall) * sizeof(WCHAR), &count, NULL); + ok(ret, "WriteFile failed: %lu\n", GetLastError()); +} + #define child_expect_read_result(a,b) child_expect_read_result_(__LINE__,a,b) static void child_expect_read_result_(unsigned int line, HANDLE pipe, const WCHAR *expect) { @@ -431,6 +453,25 @@ static void child_expect_read_result_(unsigned int line, HANDLE pipe, const WCHA ok_(__FILE__,line)(!memcmp(expect, buf, count), "unexpected data %s\n", wine_dbgstr_w(buf)); }
+#define child_expect_read_control_result(a,b,c) child_expect_read_control_result_(__LINE__,a,b,c) +static void child_expect_read_control_result_(unsigned int line, HANDLE pipe, const WCHAR *expect, DWORD state) +{ + size_t exlen = wcslen(expect); + WCHAR buf[4096]; + WCHAR *ptr = (void *)((char *)buf + sizeof(DWORD)); + DWORD count; + BOOL ret; + + ret = ReadFile(pipe, buf, sizeof(buf), &count, NULL); + ok_(__FILE__,line)(ret, "ReadFile failed: %lu\n", GetLastError()); + ok_(__FILE__,line)(count == sizeof(DWORD) + exlen * sizeof(WCHAR), "got %lu, expected %Iu\n", + count, sizeof(DWORD) + exlen * sizeof(WCHAR)); + buf[count / sizeof(WCHAR)] = 0; + todo_wine_if(*(DWORD *)buf != state && *(DWORD *)buf == 0) + ok_(__FILE__,line)(*(DWORD *)buf == state, "keyboard state: got %lx, expected %lx\n", *(DWORD *)buf, state); + ok_(__FILE__,line)(!memcmp(expect, ptr, count - sizeof(DWORD)), "unexpected data %s %s\n", wine_dbgstr_w(ptr), wine_dbgstr_w(expect)); +} + #define child_expect_read_result_a(a,b) child_expect_read_result_a_(__LINE__,a,b) static void child_expect_read_result_a_(unsigned int line, HANDLE pipe, const char *expect) { @@ -552,19 +593,25 @@ static void expect_char_key_(unsigned int line, WCHAR ch) expect_key_pressed_(line, ch, ch, vk, ctrl); }
-#define test_cursor_pos(a,b) _test_cursor_pos(__LINE__,a,b) -static void _test_cursor_pos(unsigned line, int expect_x, int expect_y) +static void fetch_child_sb_info(CONSOLE_SCREEN_BUFFER_INFO *info) { struct pseudoconsole_req req = { REQ_GET_SB_INFO }; - CONSOLE_SCREEN_BUFFER_INFO info; DWORD read; BOOL ret;
ret = WriteFile(child_pipe, &req, sizeof(req), &read, NULL); ok(ret, "WriteFile failed: %lu\n", GetLastError());
- ret = ReadFile(child_pipe, &info, sizeof(info), &read, NULL); + ret = ReadFile(child_pipe, info, sizeof(*info), &read, NULL); ok(ret, "ReadFile failed: %lu\n", GetLastError()); +} + +#define test_cursor_pos(a,b) _test_cursor_pos(__LINE__,a,b) +static void _test_cursor_pos(unsigned line, int expect_x, int expect_y) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + + fetch_child_sb_info(&info);
ok_(__FILE__,line)(info.dwCursorPosition.X == expect_x, "dwCursorPosition.X = %u, expected %u\n", info.dwCursorPosition.X, expect_x); @@ -1276,6 +1323,108 @@ static void test_read_console(void) expect_empty_output(); }
+static void test_read_console_control(void) +{ + CONSOLE_SCREEN_BUFFER_INFO info1, info2; + char ctrl; + char buf[16]; + WCHAR bufw[16]; + + child_set_input_mode(child_pipe, ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | + ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT | ENABLE_INSERT_MODE | + ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_AUTO_POSITION); + + /* test simple behavior */ + for (ctrl = 0; ctrl < ' '; ctrl++) + { + /* don't play with fire */ + if (ctrl == 0 || ctrl == 3 || ctrl == '\n' || ctrl == 27) continue; + + /* Simulate initial characters + * Note: as ReadConsole with CONTROL structure will need to back up the cursor + * up to the length of the initial characters, all the following tests ensure that + * this backup doesn't imply backing up to the previous line. + * This is the "regular" behavior when using completion. + */ + child_string_request(REQ_WRITE_CONSOLE, L"\rabc"); + skip_sequence("\x1b[25l"); /* broken hide cursor */ + skip_sequence("\x1b[?25l"); /* hide cursor */ + skip_sequence("\x1b[H"); + skip_sequence("\r"); + expect_output_sequence("abc"); + skip_sequence("\x1b[?25h"); /* show cursor */ + + fetch_child_sb_info(&info1); + child_read_console_control(child_pipe, 100, 1ul << ctrl, L"abc"); + strcpy(buf, "def."); buf[3] = ctrl; + write_console_pipe(buf); + wcscpy(bufw, L"abcdef."); bufw[6] = (WCHAR)ctrl; + child_expect_read_control_result(child_pipe, bufw, + (ctrl == '\t' || ctrl == '\r') ? 0 + : ((ctrl == 30 || ctrl == 31) ? (LEFT_CTRL_PRESSED | SHIFT_PRESSED) + : LEFT_CTRL_PRESSED)); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("def"); + skip_sequence("\x1b[?25h"); /* show cursor */ + fetch_child_sb_info(&info2); + ok(info1.dwCursorPosition.X + 3 == info2.dwCursorPosition.X, + "Bad x-position: expected %u => %u but got => %u instead\n", + info1.dwCursorPosition.X, info1.dwCursorPosition.X + 3, info2.dwCursorPosition.X); + ok(info1.dwCursorPosition.Y == info2.dwCursorPosition.Y, "Cursor shouldn't have changed line\n"); + } + + /* test two different control characters in input */ + fetch_child_sb_info(&info1); + child_read_console_control(child_pipe, 100, 1ul << '\x01', L"abc"); + write_console_pipe("d\x02""ef\x01"); + child_expect_read_control_result(child_pipe, L"abcd\x02""ef\x01", LEFT_CTRL_PRESSED); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("d^Bef"); + skip_sequence("\x1b[?25h"); /* show cursor */ + fetch_child_sb_info(&info2); + ok(info1.dwCursorPosition.X + 5 == info2.dwCursorPosition.X, + "Bad x-position: expected %u => %u but got => %u instead\n", + info1.dwCursorPosition.X, info1.dwCursorPosition.X + 5, info2.dwCursorPosition.X); + ok(info1.dwCursorPosition.Y == info2.dwCursorPosition.Y, "Cursor shouldn't have changed line\n"); + + /* test that ctrl character not in mask is handled as without ctrl mask */ + child_read_console_control(child_pipe, 100, 1ul << '\x01', L"abc"); + write_console_pipe("d\ref\x01"); + child_expect_read_control_result(child_pipe, L"abcd\r\n", 0); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("d\r\n"); + skip_sequence("\x1b[?25h"); /* show cursor */ + expect_empty_output(); + /* see note above... ditto */ + child_string_request(REQ_WRITE_CONSOLE, L"abc"); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("abc"); + skip_sequence("\x1b[?25h"); /* show cursor */ + + child_read_console_control(child_pipe, 100, 1ul << '\x01', L"abc"); + child_expect_read_control_result(child_pipe, L"abcef\x01", LEFT_CTRL_PRESSED); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("ef"); + skip_sequence("\x1b[?25h"); /* show cursor */ + + /* test when output buffer becomes full before control event */ + child_read_console_control(child_pipe, 4, 1ul << '\x01', L"abc"); + write_console_pipe("def\x01"); + child_expect_read_control_result(child_pipe, L"abcd", LEFT_CTRL_PRESSED); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("def"); + skip_sequence("\x1b[?25h"); /* show cursor */ + expect_empty_output(); + child_read_console_control(child_pipe, 20, 1ul << '\x01', L"abc"); + child_expect_read_control_result(child_pipe, L"ef\x01", 0); + + /* TODO: add tests: + * - when initial characters go back to previous line + * - edition inside initial characters can occur + */ + expect_empty_output(); +} + static void test_tty_input(void) { INPUT_RECORD ir; @@ -1481,6 +1630,27 @@ static void child_process(HANDLE pipe) ok(ret, "WriteFile failed: %lu\n", GetLastError()); break;
+ case REQ_READ_CONSOLE_CONTROL: + { + CONSOLE_READCONSOLE_CONTROL crc; + WCHAR result[1024]; + WCHAR *ptr = (void *)((char *)result + sizeof(DWORD)); + + count = req->u.control.size; + memset(result, 0xcc, sizeof(result)); + crc.nLength = sizeof(crc); + crc.dwCtrlWakeupMask = req->u.control.mask; + crc.nInitialChars = wcslen(req->u.control.initial); + crc.dwConsoleKeyState = 0xa5; + memcpy(ptr, req->u.control.initial, crc.nInitialChars * sizeof(WCHAR)); + ret = ReadConsoleW(input, ptr, count, &count, &crc); + ok(ret, "ReadConsoleW failed: %lu\n", GetLastError()); + *(DWORD *)result = crc.dwConsoleKeyState; + ret = WriteFile(pipe, result, sizeof(DWORD) + count * sizeof(WCHAR), NULL, NULL); + ok(ret, "WriteFile failed: %lu\n", GetLastError()); + } + break; + case REQ_SCROLL: ret = ScrollConsoleScreenBufferW(output, &req->u.scroll.rect, NULL, req->u.scroll.dst, &req->u.scroll.fill); ok(ret, "ScrollConsoleScreenBuffer failed: %lu\n", GetLastError()); @@ -1641,6 +1811,7 @@ static void test_pseudoconsole(void) if (!broken_version) { test_tty_output(); + test_read_console_control(); test_read_console(); test_tty_input(); }
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=109521
Your paranoid android.
=== w10pro64_ja (64 bit report) ===
conhost.exe: tty: Timeout
Signed-off-by: Jacek Caban jacek@codeweavers.com
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) */