conhost: Use more standard hide cursor sequence.
[wine.git] / programs / conhost / tests / tty.c
blob6bcfa176f3527498eaca6afbc6bcc0bf29588bb8
1 /*
2 * 2020 Jacek Caban for CodeWeavers
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 #include "wine/test.h"
21 #include <windows.h>
23 static HRESULT (WINAPI *pCreatePseudoConsole)(COORD,HANDLE,HANDLE,DWORD,HPCON*);
24 static void (WINAPI *pClosePseudoConsole)(HPCON);
26 static char console_output[4096];
27 static unsigned int console_output_count;
28 static HANDLE console_pipe;
29 static HANDLE child_pipe;
31 #define fetch_console_output() fetch_console_output_(__LINE__)
32 static void fetch_console_output_(unsigned int line)
34 OVERLAPPED o;
35 DWORD count;
36 BOOL ret;
38 if (console_output_count == sizeof(console_output)) return;
40 o.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
41 ret = ReadFile(console_pipe, console_output + console_output_count,
42 sizeof(console_output) - console_output_count, NULL, &o);
43 if (!ret)
45 ok_(__FILE__,line)(GetLastError() == ERROR_IO_PENDING, "read failed: %u\n", GetLastError());
46 if (GetLastError() != ERROR_IO_PENDING) return;
47 WaitForSingleObject(o.hEvent, 1000);
49 ret = GetOverlappedResult(console_pipe, &o, &count, FALSE);
50 if (!ret && GetLastError() == ERROR_IO_PENDING)
51 CancelIoEx(console_pipe, &o);
53 ok_(__FILE__,line)(ret, "Read file failed: %u\n", GetLastError());
54 CloseHandle(o.hEvent);
55 if (ret) console_output_count += count;
58 #define expect_empty_output() expect_empty_output_(__LINE__)
59 static void expect_empty_output_(unsigned int line)
61 DWORD avail;
62 BOOL ret;
64 ret = PeekNamedPipe(console_pipe, NULL, 0, NULL, &avail, NULL);
65 ok_(__FILE__,line)(ret, "PeekNamedPipe failed: %u\n", GetLastError());
66 ok_(__FILE__,line)(!avail, "avail = %u\n", avail);
67 if (avail) fetch_console_output_(line);
68 ok_(__FILE__,line)(!console_output_count, "expected empty buffer, got %s\n",
69 wine_dbgstr_an(console_output, console_output_count));
70 console_output_count = 0;
73 #define expect_output_sequence(a) expect_output_sequence_(__LINE__,0,a)
74 #define expect_output_sequence_ctx(a,b) expect_output_sequence_(__LINE__,a,b)
75 static void expect_output_sequence_(unsigned int line, unsigned ctx, const char *expect)
77 size_t len = strlen(expect);
78 if (console_output_count < len) fetch_console_output_(line);
79 if (len <= console_output_count && !memcmp(console_output, expect, len))
81 console_output_count -= len;
82 memmove(console_output, console_output + len, console_output_count);
84 else ok_(__FILE__,line)(0, "%x: expected %s got %s\n", ctx, wine_dbgstr_a(expect),
85 wine_dbgstr_an(console_output, console_output_count));
88 #define skip_sequence(a) skip_sequence_(__LINE__,a)
89 static BOOL skip_sequence_(unsigned int line, const char *expect)
91 size_t len = strlen(expect);
92 DWORD avail;
93 BOOL r;
95 r = PeekNamedPipe(console_pipe, NULL, 0, NULL, &avail, NULL);
96 if (!console_output_count && r && !avail)
98 Sleep(50);
99 r = PeekNamedPipe(console_pipe, NULL, 0, NULL, &avail, NULL);
101 if (r && avail) fetch_console_output_(line);
103 if (len > console_output_count || memcmp(console_output, expect, len)) return FALSE;
104 console_output_count -= len;
105 memmove(console_output, console_output + len, console_output_count);
106 return TRUE;
109 #define skip_byte(a) skip_byte_(__LINE__,a)
110 static BOOL skip_byte_(unsigned int line, char ch)
112 if (!console_output_count || console_output[0] != ch) return FALSE;
113 console_output_count--;
114 memmove(console_output, console_output + 1, console_output_count);
115 return TRUE;
118 #define expect_hide_cursor() expect_hide_cursor_(__LINE__)
119 static void expect_hide_cursor_(unsigned int line)
121 if (!console_output_count) fetch_console_output_(line);
122 ok_(__FILE__,line)(skip_sequence_(line, "\x1b[?25l") || broken(skip_sequence_(line, "\x1b[25l")),
123 "expected hide cursor escape\n");
126 #define skip_hide_cursor() skip_hide_cursor_(__LINE__)
127 static BOOL skip_hide_cursor_(unsigned int line)
129 if (!console_output_count) fetch_console_output_(line);
130 return skip_sequence_(line, "\x1b[25l") || broken(skip_sequence_(line, "\x1b[?25l"));
133 #define expect_erase_line(a) expect_erase_line_(__LINE__,a)
134 static BOOL expect_erase_line_(unsigned line, unsigned int cnt)
136 char buf[16];
137 if (skip_sequence("\x1b[K")) return FALSE;
138 ok(broken(1), "expected erase line\n");
139 sprintf(buf, "\x1b[%uX", cnt);
140 expect_output_sequence(buf); /* erase the rest of the line */
141 sprintf(buf, "\x1b[%uC", cnt);
142 expect_output_sequence(buf); /* move cursor to the end of the line */
143 return TRUE;
146 enum req_type
148 REQ_CREATE_SCREEN_BUFFER,
149 REQ_FILL_CHAR,
150 REQ_GET_INPUT,
151 REQ_SCROLL,
152 REQ_SET_ACTIVE,
153 REQ_SET_CURSOR,
154 REQ_SET_TITLE,
155 REQ_WRITE_CHARACTERS,
156 REQ_WRITE_OUTPUT,
159 struct pseudoconsole_req
161 enum req_type type;
162 union
164 WCHAR string[1];
165 COORD coord;
166 HANDLE handle;
167 struct
169 COORD coord;
170 unsigned int len;
171 WCHAR buf[1];
172 } write_characters;
173 struct
175 COORD size;
176 COORD coord;
177 SMALL_RECT region;
178 CHAR_INFO buf[1];
179 } write_output;
180 struct
182 SMALL_RECT rect;
183 COORD dst;
184 CHAR_INFO fill;
185 } scroll;
186 struct
188 WCHAR ch;
189 DWORD count;
190 COORD coord;
191 } fill;
192 } u;
195 static void child_string_request(enum req_type type, const WCHAR *title)
197 char buf[4096];
198 struct pseudoconsole_req *req = (void *)buf;
199 size_t len = lstrlenW(title) + 1;
200 DWORD count;
201 BOOL ret;
203 req->type = type;
204 memcpy(req->u.string, title, len * sizeof(WCHAR));
205 ret = WriteFile(child_pipe, req, FIELD_OFFSET(struct pseudoconsole_req, u.string[len]),
206 &count, NULL);
207 ok(ret, "WriteFile failed: %u\n", GetLastError());
210 static void child_write_characters(const WCHAR *buf, unsigned int x, unsigned int y)
212 char req_buf[4096];
213 struct pseudoconsole_req *req = (void *)req_buf;
214 size_t len = lstrlenW(buf);
215 DWORD count;
216 BOOL ret;
218 req->type = REQ_WRITE_CHARACTERS;
219 req->u.write_characters.coord.X = x;
220 req->u.write_characters.coord.Y = y;
221 req->u.write_characters.len = len;
222 memcpy(req->u.write_characters.buf, buf, len * sizeof(WCHAR));
223 ret = WriteFile(child_pipe, req, FIELD_OFFSET(struct pseudoconsole_req, u.write_characters.buf[len + 1]),
224 &count, NULL);
225 ok(ret, "WriteFile failed: %u\n", GetLastError());
228 static void child_set_cursor(const unsigned int x, unsigned int y)
230 struct pseudoconsole_req req;
231 DWORD count;
232 BOOL ret;
234 req.type = REQ_SET_CURSOR;
235 req.u.coord.X = x;
236 req.u.coord.Y = y;
237 ret = WriteFile(child_pipe, &req, sizeof(req), &count, NULL);
238 ok(ret, "WriteFile failed: %u\n", GetLastError());
241 static HANDLE child_create_screen_buffer(void)
243 struct pseudoconsole_req req;
244 HANDLE handle;
245 DWORD count;
246 BOOL ret;
248 req.type = REQ_CREATE_SCREEN_BUFFER;
249 ret = WriteFile(child_pipe, &req, sizeof(req), &count, NULL);
250 ok(ret, "WriteFile failed: %u\n", GetLastError());
251 ret = ReadFile(child_pipe, &handle, sizeof(handle), &count, NULL);
252 ok(ret, "ReadFile failed: %u\n", GetLastError());
253 return handle;
256 static void child_set_active(HANDLE handle)
258 struct pseudoconsole_req req;
259 DWORD count;
260 BOOL ret;
262 req.type = REQ_SET_ACTIVE;
263 req.u.handle = handle;
264 ret = WriteFile(child_pipe, &req, sizeof(req), &count, NULL);
265 ok(ret, "WriteFile failed: %u\n", GetLastError());
268 #define child_write_output(a,b,c,d,e,f,g,h,j,k,l,m,n) child_write_output_(__LINE__,a,b,c,d,e,f,g,h,j,k,l,m,n)
269 static void child_write_output_(unsigned int line, CHAR_INFO *buf, unsigned int size_x, unsigned int size_y,
270 unsigned int coord_x, unsigned int coord_y, unsigned int left,
271 unsigned int top, unsigned int right, unsigned int bottom, unsigned int out_left,
272 unsigned int out_top, unsigned int out_right, unsigned int out_bottom)
274 char req_buf[4096];
275 struct pseudoconsole_req *req = (void *)req_buf;
276 SMALL_RECT region;
277 DWORD count;
278 BOOL ret;
280 req->type = REQ_WRITE_OUTPUT;
281 req->u.write_output.size.X = size_x;
282 req->u.write_output.size.Y = size_y;
283 req->u.write_output.coord.X = coord_x;
284 req->u.write_output.coord.Y = coord_y;
285 req->u.write_output.region.Left = left;
286 req->u.write_output.region.Top = top;
287 req->u.write_output.region.Right = right;
288 req->u.write_output.region.Bottom = bottom;
289 memcpy(req->u.write_output.buf, buf, size_x * size_y * sizeof(*buf));
290 ret = WriteFile(child_pipe, req, FIELD_OFFSET(struct pseudoconsole_req, u.write_output.buf[size_x * size_y]), &count, NULL);
291 ok_(__FILE__,line)(ret, "WriteFile failed: %u\n", GetLastError());
292 ret = ReadFile(child_pipe, &region, sizeof(region), &count, NULL);
293 ok_(__FILE__,line)(ret, "WriteFile failed: %u\n", GetLastError());
294 ok_(__FILE__,line)(region.Left == out_left, "Left = %u\n", region.Left);
295 ok_(__FILE__,line)(region.Top == out_top, "Top = %u\n", region.Top);
296 ok_(__FILE__,line)(region.Right == out_right, "Right = %u\n", region.Right);
297 ok_(__FILE__,line)(region.Bottom == out_bottom, "Bottom = %u\n", region.Bottom);
300 static void child_scroll(unsigned int src_left, unsigned int src_top, unsigned int src_right,
301 unsigned int src_bottom, unsigned int dst_x, unsigned int dst_y, WCHAR fill)
303 struct pseudoconsole_req req;
304 DWORD count;
305 BOOL ret;
307 req.type = REQ_SCROLL;
308 req.u.scroll.rect.Left = src_left;
309 req.u.scroll.rect.Top = src_top;
310 req.u.scroll.rect.Right = src_right;
311 req.u.scroll.rect.Bottom = src_bottom;
312 req.u.scroll.dst.X = dst_x;
313 req.u.scroll.dst.Y = dst_y;
314 req.u.scroll.fill.Char.UnicodeChar = fill;
315 req.u.scroll.fill.Attributes = 0;
316 ret = WriteFile(child_pipe, &req, sizeof(req), &count, NULL);
317 ok(ret, "WriteFile failed: %u\n", GetLastError());
320 static void child_fill_character(WCHAR ch, DWORD count, int x, int y)
322 struct pseudoconsole_req req;
323 BOOL ret;
325 req.type = REQ_FILL_CHAR;
326 req.u.fill.ch = ch;
327 req.u.fill.count = count;
328 req.u.fill.coord.X = x;
329 req.u.fill.coord.Y = y;
330 ret = WriteFile(child_pipe, &req, sizeof(req), &count, NULL);
331 ok(ret, "WriteFile failed: %u\n", GetLastError());
334 static void expect_input(unsigned int event_type, INPUT_RECORD *record)
336 struct pseudoconsole_req req = { REQ_GET_INPUT };
337 INPUT_RECORD input;
338 DWORD read;
339 BOOL ret;
341 ret = WriteFile(child_pipe, &req, sizeof(req), &read, NULL);
342 ok(ret, "WriteFile failed: %u\n", GetLastError());
344 ret = ReadFile(child_pipe, &input, sizeof(input), &read, NULL);
345 ok(ret, "ReadFile failed: %u\n", GetLastError());
347 ok(input.EventType == event_type, "EventType = %u, expected %u\n", input.EventType, event_type);
348 if (record) *record = input;
351 static BOOL get_key_input(unsigned int vt, INPUT_RECORD *record)
353 static INPUT_RECORD prev_record;
354 static BOOL have_prev_record;
356 if (!have_prev_record)
358 expect_input(KEY_EVENT, &prev_record);
359 have_prev_record = TRUE;
362 if (vt && prev_record.Event.KeyEvent.wVirtualKeyCode != vt) return FALSE;
363 *record = prev_record;
364 have_prev_record = FALSE;
365 return TRUE;
368 #define expect_key_input(a,b,c,d) expect_key_input_(__LINE__,0,a,b,c,d)
369 static void expect_key_input_(unsigned int line, unsigned int ctx, WCHAR ch, unsigned int vk,
370 BOOL down, unsigned int ctrl_state)
372 unsigned int vs = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC);
373 INPUT_RECORD record;
375 get_key_input(0, &record);
376 ok_(__FILE__,line)(record.Event.KeyEvent.bKeyDown == down, "%x: bKeyDown = %x\n",
377 ctx, record.Event.KeyEvent.bKeyDown);
378 ok_(__FILE__,line)(record.Event.KeyEvent.wRepeatCount == 1, "%x: wRepeatCount = %x\n",
379 ctx, record.Event.KeyEvent.wRepeatCount);
380 ok_(__FILE__,line)(record.Event.KeyEvent.uChar.UnicodeChar == ch, "%x: UnicodeChar = %x\n",
381 ctx, record.Event.KeyEvent.uChar.UnicodeChar);
382 ok_(__FILE__,line)(record.Event.KeyEvent.wVirtualKeyCode == vk,
383 "%x: wVirtualKeyCode = %x, expected %x\n", ctx,
384 record.Event.KeyEvent.wVirtualKeyCode, vk);
385 ok_(__FILE__,line)(record.Event.KeyEvent.wVirtualScanCode == vs,
386 "%x: wVirtualScanCode = %x expected %x\n", ctx,
387 record.Event.KeyEvent.wVirtualScanCode, vs);
388 ok_(__FILE__,line)(record.Event.KeyEvent.dwControlKeyState == ctrl_state,
389 "%x: dwControlKeyState = %x\n", ctx, record.Event.KeyEvent.dwControlKeyState);
392 #define get_input_key_vt() get_input_key_vt_(__LINE__)
393 static unsigned int get_input_key_vt_(unsigned int line)
395 INPUT_RECORD record;
397 get_key_input(0, &record);
398 ok_(__FILE__,line)(record.Event.KeyEvent.wRepeatCount == 1, "wRepeatCount = %x\n",
399 record.Event.KeyEvent.wRepeatCount);
400 return record.Event.KeyEvent.wVirtualKeyCode;
403 #define expect_key_pressed(a,b,c) expect_key_pressed_(__LINE__,0,a,b,c)
404 #define expect_key_pressed_ctx(a,b,c,d) expect_key_pressed_(__LINE__,a,b,c,d)
405 static void expect_key_pressed_(unsigned int line, unsigned int ctx, WCHAR ch, unsigned int vk,
406 unsigned int ctrl_state)
408 if (ctrl_state & SHIFT_PRESSED)
409 expect_key_input_(line, ctx, 0, VK_SHIFT, TRUE, SHIFT_PRESSED);
410 if (ctrl_state & LEFT_ALT_PRESSED)
411 expect_key_input_(line, ctx, 0, VK_MENU, TRUE,
412 LEFT_ALT_PRESSED | (ctrl_state & SHIFT_PRESSED));
413 if (ctrl_state & LEFT_CTRL_PRESSED)
414 expect_key_input_(line, ctx, 0, VK_CONTROL, TRUE,
415 LEFT_CTRL_PRESSED | (ctrl_state & (SHIFT_PRESSED | LEFT_ALT_PRESSED)));
416 expect_key_input_(line, ctx, ch, vk, TRUE, ctrl_state);
417 expect_key_input_(line, ctx, ch, vk, FALSE, ctrl_state);
418 if (ctrl_state & LEFT_CTRL_PRESSED)
419 expect_key_input_(line, ctx, 0, VK_CONTROL, FALSE,
420 ctrl_state & (SHIFT_PRESSED | LEFT_ALT_PRESSED));
421 if (ctrl_state & LEFT_ALT_PRESSED)
422 expect_key_input_(line, ctx, 0, VK_MENU, FALSE, ctrl_state & SHIFT_PRESSED);
423 if (ctrl_state & SHIFT_PRESSED)
424 expect_key_input_(line, ctx, 0, VK_SHIFT, FALSE, 0);
427 #define expect_char_key(a) expect_char_key_(__LINE__,a)
428 static void expect_char_key_(unsigned int line, WCHAR ch)
430 unsigned int ctrl = 0, vk;
431 vk = VkKeyScanW(ch);
432 if (vk == ~0) vk = 0;
433 if (vk & 0x0100) ctrl |= SHIFT_PRESSED;
434 if (vk & 0x0200) ctrl |= LEFT_CTRL_PRESSED;
435 vk &= 0xff;
436 expect_key_pressed_(line, ch, ch, vk, ctrl);
439 static void test_tty_output(void)
441 CHAR_INFO char_info_buf[2048], char_info;
442 HANDLE sb, sb2;
443 unsigned int i;
445 /* simple write chars */
446 child_write_characters(L"child", 3, 4);
447 expect_hide_cursor();
448 expect_output_sequence("\x1b[5;4H"); /* set cursor */
449 expect_output_sequence("child");
450 expect_output_sequence("\x1b[H"); /* set cursor */
451 expect_output_sequence("\x1b[?25h"); /* show cursor */
452 expect_empty_output();
454 /* wrapped write chars */
455 child_write_characters(L"bound", 28, 6);
456 expect_hide_cursor();
457 expect_output_sequence("\x1b[7;1H"); /* set cursor */
458 expect_output_sequence(" bo\r\nund");
459 expect_erase_line(27);
460 expect_output_sequence("\x1b[H"); /* set cursor */
461 expect_output_sequence("\x1b[?25h"); /* show cursor */
462 expect_empty_output();
464 /* fill line 4 with a few simple writes */
465 child_write_characters(L"xxx", 13, 4);
466 expect_hide_cursor();
467 expect_output_sequence("\x1b[5;14H"); /* set cursor */
468 expect_output_sequence("xxx");
469 expect_output_sequence("\x1b[H"); /* set cursor */
470 expect_output_sequence("\x1b[?25h"); /* show cursor */
471 expect_empty_output();
473 /* write one char at the end of row */
474 child_write_characters(L"y", 29, 4);
475 expect_hide_cursor();
476 expect_output_sequence("\x1b[5;30H"); /* set cursor */
477 expect_output_sequence("y");
478 expect_output_sequence("\x1b[H"); /* set cursor */
479 expect_output_sequence("\x1b[?25h"); /* show cursor */
480 expect_empty_output();
482 /* wrapped write chars */
483 child_write_characters(L"zz", 29, 4);
484 expect_hide_cursor();
485 expect_output_sequence("\x1b[5;1H"); /* set cursor */
486 expect_output_sequence(" child xxx z");
487 expect_output_sequence("\r\nz");
488 expect_erase_line(29);
489 expect_output_sequence("\x1b[H"); /* set cursor */
490 expect_output_sequence("\x1b[?25h"); /* show cursor */
491 expect_empty_output();
493 /* trailing spaces */
494 child_write_characters(L"child ", 3, 4);
495 expect_hide_cursor();
496 expect_output_sequence("\x1b[5;4H"); /* set cursor */
497 expect_output_sequence("child ");
498 expect_output_sequence("\x1b[H"); /* set cursor */
499 expect_output_sequence("\x1b[?25h"); /* show cursor */
500 expect_empty_output();
502 child_set_cursor(2, 3);
503 expect_hide_cursor();
504 expect_output_sequence("\x1b[4;3H"); /* set cursor */
505 expect_output_sequence("\x1b[?25h"); /* show cursor */
506 expect_empty_output();
508 child_string_request(REQ_SET_TITLE, L"new title");
509 fetch_console_output();
510 skip_sequence("\x1b[?25l"); /* hide cursor */
511 expect_output_sequence("\x1b]0;new title\x07"); /* set title */
512 skip_sequence("\x1b[?25h"); /* show cursor */
513 expect_empty_output();
515 for (i = 0; i < ARRAY_SIZE(char_info_buf); i++)
517 char_info_buf[i].Char.UnicodeChar = '0' + i % 10;
518 char_info_buf[i].Attributes = 0;
521 child_write_output(char_info_buf, /* size */ 7, 8, /* coord */ 1, 2,
522 /* region */ 3, 7, 5, 9, /* out region */ 3, 7, 5, 9);
523 expect_hide_cursor();
524 expect_output_sequence("\x1b[30m"); /* foreground black */
525 expect_output_sequence("\x1b[8;4H"); /* set cursor */
526 expect_output_sequence("567");
527 expect_output_sequence("\x1b[9;4H"); /* set cursor */
528 expect_output_sequence("234");
529 expect_output_sequence("\x1b[10;4H"); /* set cursor */
530 expect_output_sequence("901");
531 expect_output_sequence("\x1b[4;3H"); /* set cursor */
532 expect_output_sequence("\x1b[?25h"); /* show cursor */
533 expect_empty_output();
535 child_write_output(char_info_buf, /* size */ 2, 3, /* coord */ 1, 2,
536 /* region */ 3, 8, 15, 19, /* out region */ 3, 8, 3, 8);
537 expect_hide_cursor();
538 if (skip_sequence("\x1b[m")) /* default attr */
539 expect_output_sequence("\x1b[30m");/* foreground black */
540 expect_output_sequence("\x1b[9;4H"); /* set cursor */
541 expect_output_sequence("5");
542 expect_output_sequence("\x1b[4;3H"); /* set cursor */
543 expect_output_sequence("\x1b[?25h"); /* show cursor */
544 expect_empty_output();
546 child_write_output(char_info_buf, /* size */ 3, 4, /* coord */ 1, 2,
547 /* region */ 3, 8, 15, 19, /* out region */ 3, 8, 4, 9);
548 expect_hide_cursor();
549 if (skip_sequence("\x1b[m")) /* default attr */
550 expect_output_sequence("\x1b[30m");/* foreground black */
551 expect_output_sequence("\x1b[9;4H"); /* set cursor */
552 expect_output_sequence("78");
553 expect_output_sequence("\x1b[10;4H"); /* set cursor */
554 expect_output_sequence("01");
555 expect_output_sequence("\x1b[4;3H"); /* set cursor */
556 expect_output_sequence("\x1b[?25h"); /* show cursor */
557 expect_empty_output();
559 child_write_output(char_info_buf, /* size */ 7, 8, /* coord */ 2, 3,
560 /* region */ 28, 38, 31, 60, /* out region */ 28, 38, 29, 39);
561 expect_hide_cursor();
562 if (skip_sequence("\x1b[m")) /* default attr */
563 expect_output_sequence("\x1b[30m");/* foreground black */
564 expect_output_sequence("\x1b[39;29H"); /* set cursor */
565 expect_output_sequence("34");
566 expect_output_sequence("\x1b[40;29H"); /* set cursor */
567 expect_output_sequence("01");
568 expect_output_sequence("\x1b[4;3H"); /* set cursor */
569 expect_output_sequence("\x1b[?25h"); /* show cursor */
570 expect_empty_output();
572 child_write_output(char_info_buf, /* size */ 7, 8, /* coord */ 1, 2,
573 /* region */ 0, 7, 5, 9, /* out region */ 0, 7, 5, 9);
574 expect_hide_cursor();
575 if (skip_sequence("\x1b[m")) /* default attr */
576 expect_output_sequence("\x1b[30m");/* foreground black */
577 expect_output_sequence("\x1b[8;1H"); /* set cursor */
578 expect_output_sequence("567890\r\n");
579 expect_output_sequence("234567\r\n");
580 expect_output_sequence("901234");
581 expect_output_sequence("\x1b[4;3H"); /* set cursor */
582 expect_output_sequence("\x1b[?25h"); /* show cursor */
583 expect_empty_output();
585 child_scroll(/* scroll rect */ 0, 7, 2, 8, /* destination */ 2, 8, /* fill */ 'x');
586 expect_hide_cursor();
587 if (skip_sequence("\x1b[m")) /* default attr */
588 expect_output_sequence("\x1b[30m");/* foreground black */
589 expect_output_sequence("\x1b[8;1H"); /* set cursor */
590 expect_output_sequence("xxx89\r\n");
591 expect_output_sequence("xx567\r\n");
592 expect_output_sequence("90234");
593 expect_output_sequence("\x1b[4;3H"); /* set cursor */
594 expect_output_sequence("\x1b[?25h"); /* show cursor */
595 expect_empty_output();
597 child_write_characters(L"xxx", 3, 10);
598 expect_hide_cursor();
599 expect_output_sequence("\x1b[m"); /* default attributes */
600 expect_output_sequence("\x1b[11;4H"); /* set cursor */
601 expect_output_sequence("xxx");
602 expect_output_sequence("\x1b[4;3H"); /* set cursor */
603 expect_output_sequence("\x1b[?25h"); /* show cursor */
604 expect_empty_output();
606 /* test attributes */
607 for (i = 0; i < 0x100 - 0xff; i++)
609 unsigned int expect;
610 char expect_buf[16];
611 char_info.Char.UnicodeChar = 'a';
612 char_info.Attributes = i;
613 child_write_output(&char_info, /* size */ 1, 1, /* coord */ 0, 0,
614 /* region */ 12, 3, 12, 3, /* out region */ 12, 3, 12, 3);
615 expect_hide_cursor();
616 if (i != 0x190 && i && ((i & 0xff) != 8)) expect_output_sequence_ctx(i, "\x1b[m");
617 if ((i & 0x0f) != 7)
619 expect = 30;
620 if (i & FOREGROUND_BLUE) expect += 4;
621 if (i & FOREGROUND_GREEN) expect += 2;
622 if (i & FOREGROUND_RED) expect += 1;
623 if (i & FOREGROUND_INTENSITY) expect += 60;
624 sprintf(expect_buf, "\x1b[%um", expect);
625 expect_output_sequence_ctx(i, expect_buf);
627 if (i & 0xf0)
629 expect = 40;
630 if (i & BACKGROUND_BLUE) expect += 4;
631 if (i & BACKGROUND_GREEN) expect += 2;
632 if (i & BACKGROUND_RED) expect += 1;
633 if (i & BACKGROUND_INTENSITY) expect += 60;
634 sprintf(expect_buf, "\x1b[%um", expect);
635 expect_output_sequence_ctx(i, expect_buf);
637 if (!skip_sequence("\x1b[10C"))
638 expect_output_sequence_ctx(i, "\x1b[4;13H"); /* set cursor */
639 expect_output_sequence("a");
640 if (!skip_sequence("\x1b[11D"))
641 expect_output_sequence("\x1b[4;3H"); /* set cursor */
642 expect_output_sequence("\x1b[?25h"); /* show cursor */
643 expect_empty_output();
646 char_info_buf[0].Attributes = FOREGROUND_GREEN;
647 char_info_buf[1].Attributes = FOREGROUND_GREEN | BACKGROUND_RED;
648 char_info_buf[2].Attributes = BACKGROUND_RED;
649 child_write_output(char_info_buf, /* size */ 7, 8, /* coord */ 0, 0,
650 /* region */ 7, 0, 9, 0, /* out region */ 7, 0, 9, 0);
651 expect_hide_cursor();
652 skip_sequence("\x1b[m"); /* default attr */
653 expect_output_sequence("\x1b[32m"); /* foreground black */
654 expect_output_sequence("\x1b[1;8H"); /* set cursor */
655 expect_output_sequence("0");
656 expect_output_sequence("\x1b[41m"); /* backgorund red */
657 expect_output_sequence("1");
658 expect_output_sequence("\x1b[30m"); /* foreground black */
659 expect_output_sequence("2");
660 expect_output_sequence("\x1b[4;3H"); /* set cursor */
661 expect_output_sequence("\x1b[?25h"); /* show cursor */
662 expect_empty_output();
664 child_fill_character('i', 5, 15, 16);
665 expect_hide_cursor();
666 expect_output_sequence("\x1b[m"); /* default attributes */
667 expect_output_sequence("\x1b[17;16H"); /* set cursor */
668 expect_output_sequence("iiiii");
669 expect_output_sequence("\x1b[4;3H"); /* set cursor */
670 expect_output_sequence("\x1b[?25h"); /* show cursor */
671 expect_empty_output();
673 sb = child_create_screen_buffer();
674 child_set_active(sb);
675 expect_hide_cursor();
676 expect_output_sequence("\x1b[H"); /* set cursor */
677 for (i = 0; i < 40; i++)
679 expect_erase_line(30);
680 if (i != 39) expect_output_sequence("\r\n");
682 expect_output_sequence("\x1b[H"); /* set cursor */
683 expect_output_sequence("\x1b[?25h"); /* show cursor */
684 expect_empty_output();
686 child_write_characters(L"new sb", 0, 0);
687 skip_hide_cursor();
688 expect_output_sequence("new sb");
689 ok(skip_sequence("\x1b[H") || skip_sequence("\r"), "expected set cursor\n");
690 skip_sequence("\x1b[?25h"); /* show cursor */
691 expect_empty_output();
693 sb2 = child_create_screen_buffer();
694 child_set_active(sb2);
695 expect_hide_cursor();
696 for (i = 0; i < 40; i++)
698 expect_erase_line(30);
699 if (i != 39) expect_output_sequence("\r\n");
701 expect_output_sequence("\x1b[H"); /* set cursor */
702 expect_output_sequence("\x1b[?25h"); /* show cursor */
703 expect_empty_output();
705 child_set_active(sb);
706 expect_hide_cursor();
707 expect_output_sequence("new sb");
708 expect_erase_line(24);
709 expect_output_sequence("\r\n");
710 for (i = 1; i < 40; i++)
712 expect_erase_line(30);
713 if (i != 39) expect_output_sequence("\r\n");
715 expect_output_sequence("\x1b[H"); /* set cursor */
716 expect_output_sequence("\x1b[?25h"); /* show cursor */
717 expect_empty_output();
720 static void write_console_pipe(const char *buf)
722 DWORD written;
723 BOOL res;
724 res = WriteFile(console_pipe, buf, strlen(buf), &written, NULL);
725 ok(res, "WriteFile failed: %u\n", GetLastError());
728 static void test_tty_input(void)
730 INPUT_RECORD ir;
731 unsigned int i;
732 char buf[8];
734 static const struct
736 const char *str;
737 WCHAR ch;
738 unsigned int vk;
739 unsigned int ctrl;
740 } escape_test[] = {
741 { "\x1b[A", 0, VK_UP, 0 },
742 { "\x1b[B", 0, VK_DOWN, 0 },
743 { "\x1b[C", 0, VK_RIGHT, 0 },
744 { "\x1b[D", 0, VK_LEFT, 0 },
745 { "\x1b[H", 0, VK_HOME, 0 },
746 { "\x1b[F", 0, VK_END, 0 },
747 { "\x1b[2~", 0, VK_INSERT, 0 },
748 { "\x1b[3~", 0, VK_DELETE, 0 },
749 { "\x1b[5~", 0, VK_PRIOR, 0 },
750 { "\x1b[6~", 0, VK_NEXT, 0 },
751 { "\x1b[15~", 0, VK_F5, 0 },
752 { "\x1b[17~", 0, VK_F6, 0 },
753 { "\x1b[18~", 0, VK_F7, 0 },
754 { "\x1b[19~", 0, VK_F8, 0 },
755 { "\x1b[20~", 0, VK_F9, 0 },
756 { "\x1b[21~", 0, VK_F10, 0 },
757 /* 0x10 */
758 { "\x1b[23~", 0, VK_F11, 0 },
759 { "\x1b[24~", 0, VK_F12, 0 },
760 { "\x1bOP", 0, VK_F1, 0 },
761 { "\x1bOQ", 0, VK_F2, 0 },
762 { "\x1bOR", 0, VK_F3, 0 },
763 { "\x1bOS", 0, VK_F4, 0 },
764 { "\x1b[1;1A", 0, VK_UP, 0 },
765 { "\x1b[1;2A", 0, VK_UP, SHIFT_PRESSED },
766 { "\x1b[1;3A", 0, VK_UP, LEFT_ALT_PRESSED },
767 { "\x1b[1;4A", 0, VK_UP, SHIFT_PRESSED | LEFT_ALT_PRESSED },
768 { "\x1b[1;5A", 0, VK_UP, LEFT_CTRL_PRESSED },
769 { "\x1b[1;6A", 0, VK_UP, SHIFT_PRESSED | LEFT_CTRL_PRESSED },
770 { "\x1b[1;7A", 0, VK_UP, LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED },
771 { "\x1b[1;8A", 0, VK_UP, SHIFT_PRESSED | LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED },
772 { "\x1b[1;9A", 0, VK_UP, 0 },
773 { "\x1b[1;10A", 0, VK_UP, SHIFT_PRESSED },
774 /* 0x20 */
775 { "\x1b[1;11A", 0, VK_UP, LEFT_ALT_PRESSED },
776 { "\x1b[1;12A", 0, VK_UP, SHIFT_PRESSED | LEFT_ALT_PRESSED },
777 { "\x1b[1;13A", 0, VK_UP, LEFT_CTRL_PRESSED },
778 { "\x1b[1;14A", 0, VK_UP, SHIFT_PRESSED | LEFT_CTRL_PRESSED },
779 { "\x1b[1;15A", 0, VK_UP, LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED },
780 { "\x1b[1;16A", 0, VK_UP, SHIFT_PRESSED | LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED },
781 { "\x1b[1;2P", 0, VK_F1, SHIFT_PRESSED },
782 { "\x1b[2;3~", 0, VK_INSERT, LEFT_ALT_PRESSED },
783 { "\x1b[2;3;5;6~", 0, VK_INSERT, 0 },
784 { "\x1b[6;2;3;5;1~", 0, VK_NEXT, 0 },
785 { "\xe4\xb8\x80", 0x4e00, 0, 0 },
786 { "\x1b\x1b", 0x1b, VK_ESCAPE, LEFT_ALT_PRESSED },
787 { "\x1b""1", '1', '1', LEFT_ALT_PRESSED },
788 { "\x1b""x", 'x', 'X', LEFT_ALT_PRESSED },
789 { "\x1b""[", '[', VK_OEM_4, LEFT_ALT_PRESSED },
790 { "\x7f", '\b', VK_BACK, 0 },
793 write_console_pipe("x");
794 if (!get_input_key_vt())
796 skip("Skipping tests on settings that don't have VT mapping for 'x'\n");
797 get_input_key_vt();
798 return;
800 get_input_key_vt();
802 write_console_pipe("aBCd");
803 expect_char_key('a');
804 expect_char_key('B');
805 expect_char_key('C');
806 expect_char_key('d');
808 for (i = 1; i < 0x7f; i++)
810 if (i == 3 || i == '\n' || i == 0x1b || i == 0x1f) continue;
811 buf[0] = i;
812 buf[1] = 0;
813 write_console_pipe(buf);
814 if (i == 8)
815 expect_key_pressed('\b', 'H', LEFT_CTRL_PRESSED);
816 else if (i == 0x7f)
817 expect_char_key(8);
818 else
819 expect_char_key(i);
822 write_console_pipe("\r\n");
823 expect_key_pressed('\r', VK_RETURN, 0);
824 expect_key_pressed('\n', VK_RETURN, LEFT_CTRL_PRESSED);
826 write_console_pipe("\xc4\x85");
827 if (get_key_input(VK_MENU, &ir))
829 expect_key_input(0x105, 'A', TRUE, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
830 expect_key_input(0x105, 'A', FALSE, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);
831 expect_key_input(0, VK_MENU, FALSE, ENHANCED_KEY);
833 else
835 expect_key_input(0x105, 0, TRUE, 0);
836 expect_key_input(0x105, 0, FALSE, 0);
839 for (i = 0; i < ARRAY_SIZE(escape_test); i++)
841 write_console_pipe(escape_test[i].str);
842 expect_key_pressed_ctx(i, escape_test[i].ch, escape_test[i].vk, escape_test[i].ctrl);
845 for (i = 0x80; i < 0x100; i += 11)
847 buf[0] = i;
848 buf[1] = 0;
849 write_console_pipe(buf);
850 expect_empty_output();
854 static void child_process(HANDLE pipe)
856 HANDLE output, input;
857 DWORD size, count;
858 char buf[4096];
859 BOOL ret;
861 output = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0);
862 ok(output != INVALID_HANDLE_VALUE, "could not open console output\n");
864 input = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0);
865 ok(output != INVALID_HANDLE_VALUE, "could not open console output\n");
867 while(ReadFile(pipe, buf, sizeof(buf), &size, NULL))
869 const struct pseudoconsole_req *req = (void *)buf;
870 switch (req->type)
872 case REQ_CREATE_SCREEN_BUFFER:
874 HANDLE handle;
875 DWORD count;
876 SetLastError(0xdeadbeef);
877 handle = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
878 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
879 CONSOLE_TEXTMODE_BUFFER, NULL);
880 ok(handle != INVALID_HANDLE_VALUE, "CreateConsoleScreenBuffer failed: %u\n", GetLastError());
881 ret = WriteFile(pipe, &handle, sizeof(handle), &count, NULL);
882 ok(ret, "WriteFile failed: %u\n", GetLastError());
883 break;
886 case REQ_GET_INPUT:
888 INPUT_RECORD record;
889 ret = ReadConsoleInputW(input, &record, 1, &count);
890 ok(ret, "ReadConsoleInputW failed: %u\n", GetLastError());
891 ok(count == 1, "count = %u\n", count);
892 ret = WriteFile(pipe, &record, sizeof(record), &count, NULL);
893 ok(ret, "WriteFile failed: %u\n", GetLastError());
894 break;
897 case REQ_SCROLL:
898 ret = ScrollConsoleScreenBufferW(output, &req->u.scroll.rect, NULL, req->u.scroll.dst, &req->u.scroll.fill);
899 ok(ret, "ScrollConsoleScreenBuffer failed: %u\n", GetLastError());
900 break;
902 case REQ_FILL_CHAR:
903 ret = FillConsoleOutputCharacterW(output, req->u.fill.ch, req->u.fill.count, req->u.fill.coord, &count);
904 ok(ret, "FillConsoleOutputCharacter failed: %u\n", GetLastError());
905 ok(count == req->u.fill.count, "count = %u, expected %u\n", count, req->u.fill.count);
906 break;
908 case REQ_SET_ACTIVE:
909 output = req->u.handle;
910 ret = SetConsoleActiveScreenBuffer(output);
911 ok(ret, "SetConsoleActiveScreenBuffer failed: %u\n", GetLastError());
912 break;
914 case REQ_SET_CURSOR:
915 ret = SetConsoleCursorPosition(output, req->u.coord);
916 ok(ret, "SetConsoleCursorPosition failed: %u\n", GetLastError());
917 break;
919 case REQ_SET_TITLE:
920 ret = SetConsoleTitleW(req->u.string);
921 ok(ret, "SetConsoleTitleW failed: %u\n", GetLastError());
922 break;
924 case REQ_WRITE_CHARACTERS:
925 ret = WriteConsoleOutputCharacterW(output, req->u.write_characters.buf,
926 req->u.write_characters.len,
927 req->u.write_characters.coord, &count);
928 ok(ret, "WriteConsoleOutputCharacterW failed: %u\n", GetLastError());
929 break;
931 case REQ_WRITE_OUTPUT:
933 SMALL_RECT region = req->u.write_output.region;
934 ret = WriteConsoleOutputW(output, req->u.write_output.buf, req->u.write_output.size, req->u.write_output.coord, &region);
935 ok(ret, "WriteConsoleOutput failed: %u\n", GetLastError());
936 ret = WriteFile(pipe, &region, sizeof(region), &count, NULL);
937 ok(ret, "WriteFile failed: %u\n", GetLastError());
938 break;
941 default:
942 ok(0, "unexpected request type %u\n", req->type);
945 ok(GetLastError() == ERROR_BROKEN_PIPE, "ReadFile failed: %u\n", GetLastError());
946 CloseHandle(output);
947 CloseHandle(input);
950 static HANDLE run_child(HANDLE console, HANDLE pipe)
952 STARTUPINFOEXA startup = {{ sizeof(startup) }};
953 char **argv, cmdline[MAX_PATH];
954 PROCESS_INFORMATION info;
955 SIZE_T size;
956 BOOL ret;
958 InitializeProcThreadAttributeList(NULL, 1, 0, &size);
959 startup.lpAttributeList = HeapAlloc(GetProcessHeap(), 0, size);
960 InitializeProcThreadAttributeList(startup.lpAttributeList, 1, 0, &size);
961 UpdateProcThreadAttribute(startup.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, console,
962 sizeof(console), NULL, NULL);
964 winetest_get_mainargs(&argv);
965 sprintf(cmdline, "\"%s\" %s child %p", argv[0], argv[1], pipe);
966 ret = CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL,
967 &startup.StartupInfo, &info);
968 ok(ret, "CreateProcessW failed: %u\n", GetLastError());
970 CloseHandle(info.hThread);
971 HeapFree(GetProcessHeap(), 0, startup.lpAttributeList);
972 return info.hProcess;
975 static HPCON create_pseudo_console(HANDLE *console_pipe_end, HANDLE *child_process)
977 SECURITY_ATTRIBUTES sec_attr = { sizeof(sec_attr), NULL, TRUE };
978 HANDLE child_pipe_end;
979 COORD size = { 30, 40 };
980 DWORD read_mode;
981 HPCON console;
982 HRESULT hres;
983 BOOL r;
985 console_pipe = CreateNamedPipeW(L"\\\\.\\pipe\\pseudoconsoleconn", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
986 PIPE_WAIT | PIPE_TYPE_BYTE, 1, 4096, 4096, NMPWAIT_USE_DEFAULT_WAIT, NULL);
987 ok(console_pipe != INVALID_HANDLE_VALUE, "CreateNamedPipeW failed: %u\n", GetLastError());
989 *console_pipe_end = CreateFileW(L"\\\\.\\pipe\\pseudoconsoleconn", GENERIC_READ | GENERIC_WRITE,
990 0, &sec_attr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
991 ok(*console_pipe_end != INVALID_HANDLE_VALUE, "CreateFile failed: %u\n", GetLastError());
993 child_pipe = CreateNamedPipeW(L"\\\\.\\pipe\\pseudoconsoleserver", PIPE_ACCESS_DUPLEX,
994 PIPE_WAIT | PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 1, 5000, 6000,
995 NMPWAIT_USE_DEFAULT_WAIT, NULL);
996 ok(child_pipe != INVALID_HANDLE_VALUE, "CreateNamedPipeW failed: %u\n", GetLastError());
998 child_pipe_end = CreateFileW(L"\\\\.\\pipe\\pseudoconsoleserver", GENERIC_READ | GENERIC_WRITE, 0,
999 &sec_attr, OPEN_EXISTING, 0, NULL);
1000 ok(child_pipe_end != INVALID_HANDLE_VALUE, "CreateFile failed: %u\n", GetLastError());
1002 read_mode = PIPE_READMODE_MESSAGE;
1003 r = SetNamedPipeHandleState(child_pipe_end, &read_mode, NULL, NULL);
1004 ok(r, "SetNamedPipeHandleState failed: %u\n", GetLastError());
1006 hres = pCreatePseudoConsole(size, *console_pipe_end, *console_pipe_end, 0, &console);
1007 ok(hres == S_OK, "CreatePseudoConsole failed: %08x\n", hres);
1009 *child_process = run_child(console, child_pipe_end);
1010 CloseHandle(child_pipe_end);
1011 return console;
1014 static void test_pseudoconsole(void)
1016 HANDLE console_pipe_end, child_process;
1017 BOOL broken_version;
1018 HPCON console;
1020 console = create_pseudo_console(&console_pipe_end, &child_process);
1022 child_string_request(REQ_SET_TITLE, L"test title");
1023 expect_output_sequence("\x1b[2J"); /* erase display */
1024 skip_hide_cursor();
1025 expect_output_sequence("\x1b[m"); /* default attributes */
1026 expect_output_sequence("\x1b[H"); /* set cursor */
1027 skip_sequence("\x1b[H"); /* some windows versions emit it twice */
1028 expect_output_sequence("\x1b]0;test title"); /* set title */
1029 broken_version = skip_byte(0); /* some win versions emit nullbyte */
1030 expect_output_sequence("\x07");
1031 skip_sequence("\x1b[?25h"); /* show cursor */
1032 expect_empty_output();
1034 if (!broken_version)
1036 test_tty_output();
1037 test_tty_input();
1039 else win_skip("Skipping tty output tests on broken Windows version\n");
1041 pClosePseudoConsole(console);
1042 CloseHandle(console_pipe_end);
1043 CloseHandle(console_pipe);
1046 START_TEST(tty)
1048 HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
1049 char **argv;
1050 int argc;
1052 argc = winetest_get_mainargs(&argv);
1053 if (argc > 3)
1055 HANDLE pipe;
1056 sscanf(argv[3], "%p", &pipe);
1057 child_process(pipe);
1058 return;
1061 pCreatePseudoConsole = (void *)GetProcAddress(kernel32, "CreatePseudoConsole");
1062 pClosePseudoConsole = (void *)GetProcAddress(kernel32, "ClosePseudoConsole");
1063 if (!pCreatePseudoConsole)
1065 win_skip("CreatePseudoConsole is not available\n");
1066 return;
1069 test_pseudoconsole();