diff --git a/programs/conhost/conhost.c b/programs/conhost/conhost.c index 8298743553f..cc0d3e92ed8 100644 --- a/programs/conhost/conhost.c +++ b/programs/conhost/conhost.c @@ -31,6 +31,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(console); static const char_info_t empty_char_info = { ' ', 0x0007 }; /* white on black space */ + static CRITICAL_SECTION console_section; static CRITICAL_SECTION_DEBUG critsect_debug = { @@ -69,6 +70,7 @@ static void destroy_screen_buffer( struct screen_buffer *screen_buffer ) screen_buffer->console->active = NULL; wine_rb_remove( &screen_buffer_map, &screen_buffer->entry ); free( screen_buffer->font.face_name ); + free( screen_buffer->alt_data ); free( screen_buffer->data ); free( screen_buffer ); } @@ -130,6 +132,9 @@ static struct screen_buffer *create_screen_buffer( struct console *console, int return NULL; } + screen_buffer->scroll_top = 0; + screen_buffer->scroll_bottom = screen_buffer->height - 1; + if (!(screen_buffer->data = malloc( screen_buffer->width * screen_buffer->height * sizeof(*screen_buffer->data) ))) { @@ -269,6 +274,16 @@ static void set_tty_attr( struct console *console, unsigned int attr ) { char buf[8]; + if (attr == console->tty_attr) return; + + if (attr == 7) + { + /* shortcut: reset all attributes to default */ + tty_write( console, "\x1b[m", 3 ); + console->tty_attr = attr; + return; + } + if ((attr & 0x0f) != (console->tty_attr & 0x0f)) { if ((attr & 0x0f) != 7) @@ -281,10 +296,16 @@ static void set_tty_attr( struct console *console, unsigned int attr ) sprintf(buf, "\x1b[%um", n); tty_write( console, buf, strlen(buf) ); } - else tty_write( console, "\x1b[m", 3 ); + else + { + tty_write( console, "\x1b[m", 3 ); + /* ESC[m resets all attributes; update tracked state so the + * background comparison below sees the actual terminal state */ + console->tty_attr = 7; + } } - if ((attr & 0xf0) != (console->tty_attr & 0xf0) && attr != 7) + if ((attr & 0xf0) != (console->tty_attr & 0xf0)) { unsigned int n = 40; if (attr & BACKGROUND_BLUE) n += 4; @@ -321,8 +342,8 @@ static void init_tty_output( struct console *console ) if (!console->is_unix) { /* initialize tty output, but don't flush */ - tty_write( console, "\x1b[2J", 4 ); /* clear screen */ set_tty_attr( console, console->active->attr ); + tty_write( console, "\x1b[2J", 4 ); /* clear screen */ tty_write( console, "\x1b[H", 3 ); /* move cursor to (0,0) */ } else console->tty_attr = empty_char_info.attr; @@ -360,7 +381,7 @@ static void update_output( struct screen_buffer *screen_buffer, RECT *rect ) char_info_t *ch; char buf[8]; WCHAR wch; - const unsigned int mask = (1u << '\0') | (1u << '\b') | (1u << '\t') | (1u << '\n') | (1u << '\a') | (1u << '\r'); + const unsigned int mask = (1u << '\0') | (1u << '\b') | (1u << '\t') | (1u << '\n') | (1u << '\a') | (1u << '\r') | (1u << '\x1b'); if (!is_active( screen_buffer ) || rect->top > rect->bottom || rect->right < rect->left) return; @@ -1587,12 +1608,15 @@ static void set_key_input_record( INPUT_RECORD *record, WCHAR ch, unsigned int v record->Event.KeyEvent.wVirtualKeyCode = vk; record->Event.KeyEvent.wVirtualScanCode = MapVirtualKeyW( vk, MAPVK_VK_TO_VSC ); record->Event.KeyEvent.dwControlKeyState = ctrl_state; + TRACE( "key %s vk=%04x scan=%04x ch=%04x ctrl=%08x\n", + is_down ? "down" : "up", vk, + record->Event.KeyEvent.wVirtualScanCode, ch, ctrl_state ); } static NTSTATUS key_press( struct console *console, WCHAR ch, unsigned int vk, unsigned int ctrl_state ) { INPUT_RECORD records[8]; - unsigned int count = 0, ctrl = 0; + unsigned int count = 0, ctrl = ctrl_state & ENHANCED_KEY; if (ctrl_state & SHIFT_PRESSED) { @@ -1650,12 +1674,12 @@ static unsigned int escape_char_to_vk( WCHAR ch, unsigned int *ctrl, WCHAR *outu switch (ch) { - case 'A': return VK_UP; - case 'B': return VK_DOWN; - case 'C': return VK_RIGHT; - case 'D': return VK_LEFT; - case 'H': return VK_HOME; - case 'F': return VK_END; + case 'A': if (ctrl) *ctrl = ENHANCED_KEY; return VK_UP; + case 'B': if (ctrl) *ctrl = ENHANCED_KEY; return VK_DOWN; + case 'C': if (ctrl) *ctrl = ENHANCED_KEY; return VK_RIGHT; + case 'D': if (ctrl) *ctrl = ENHANCED_KEY; return VK_LEFT; + case 'H': if (ctrl) *ctrl = ENHANCED_KEY; return VK_HOME; + case 'F': if (ctrl) *ctrl = ENHANCED_KEY; return VK_END; case 'P': return VK_F1; case 'Q': return VK_F2; case 'R': return VK_F3; @@ -1723,9 +1747,13 @@ static unsigned int process_csi_sequence( struct console *console, const WCHAR * switch (buf[count]) { case '~': - vk = escape_number_to_vk( params[0] ); - key_press( console, 0, vk, params_cnt == 2 ? convert_modifiers( params[1] ) : 0 ); - return count + 1; + { + unsigned int enhanced = 0; + vk = escape_number_to_vk( params[0] ); + if (params[0] >= 2 && params[0] <= 6) enhanced = ENHANCED_KEY; /* insert, delete, pgup, pgdn */ + key_press( console, 0, vk, (params_cnt == 2 ? convert_modifiers( params[1] ) : 0) | enhanced ); + return count + 1; + } default: FIXME( "unhandled sequence %s\n", debugstr_wn( buf, size )); @@ -1774,6 +1802,7 @@ static DWORD WINAPI tty_input( void *param ) DWORD count, i; BOOL signaled; NTSTATUS status; + DWORD read_bytes; if (console->is_unix) { @@ -1794,18 +1823,67 @@ static DWORD WINAPI tty_input( void *param ) status = io.Status; } if (status) break; + read_bytes = io.Information; + + /* If the read ends with ESC (0x1b), it might be the start of an escape sequence + * split across reads. Try a short timed read to get the rest. */ + if (read_bytes > 0 && read_buf[read_bytes - 1] == 0x1b) + { + IO_STATUS_BLOCK io2; + LARGE_INTEGER timeout; + timeout.QuadPart = -500000; /* 50ms */ + ResetEvent( event ); + status = NtReadFile( console->tty_input, event, NULL, NULL, &io2, + read_buf + read_bytes, sizeof(read_buf) - read_bytes, NULL, NULL ); + if (status == STATUS_PENDING) + { + status = NtWaitForSingleObject( event, FALSE, &timeout ); + if (!status) + read_bytes += io2.Information; + else + { + IO_STATUS_BLOCK cancel_io; + NtCancelIoFileEx( console->tty_input, &io2, &cancel_io ); + NtWaitForSingleObject( event, FALSE, NULL ); /* wait for cancel to complete */ + } + } + else if (!status) + read_bytes += io2.Information; + } EnterCriticalSection( &console_section ); signaled = console->record_count != 0; /* FIXME: Handle partial char read */ - count = MultiByteToWideChar( get_tty_cp( console ), 0, read_buf, io.Information, buf, ARRAY_SIZE(buf) ); + count = MultiByteToWideChar( get_tty_cp( console ), 0, read_buf, read_bytes, buf, ARRAY_SIZE(buf) ); TRACE( "%s\n", debugstr_wn(buf, count) ); for (i = 0; i < count; i++) { WCHAR ch = buf[i]; + + /* When ENABLE_VIRTUAL_TERMINAL_INPUT is set, pass through all bytes + * as raw character KEY_EVENTs. The MSYS2/Cygwin runtime expects VT + * escape sequences (e.g. ESC [ A for arrow-up) delivered this way, + * rather than translated to VK codes. */ + if (console->mode & ENABLE_VIRTUAL_TERMINAL_INPUT) + { + INPUT_RECORD record; + if (!console->is_unix && ch == 3) + { + LeaveCriticalSection( &console_section ); + goto done; + } + memset( &record, 0, sizeof(record) ); + record.EventType = KEY_EVENT; + record.Event.KeyEvent.bKeyDown = TRUE; + record.Event.KeyEvent.wRepeatCount = 1; + record.Event.KeyEvent.uChar.UnicodeChar = ch; + write_console_input( console, &record, 1, FALSE ); + continue; + } + switch (ch) { case 3: /* end of text */ @@ -1979,6 +2057,8 @@ NTSTATUS change_screen_buffer_size( struct screen_buffer *screen_buffer, int new screen_buffer->data = new_data; screen_buffer->width = new_width; screen_buffer->height = new_height; + screen_buffer->scroll_top = 0; + screen_buffer->scroll_bottom = new_height - 1; return STATUS_SUCCESS; } @@ -2103,6 +2183,778 @@ static NTSTATUS set_output_info( struct screen_buffer *screen_buffer, return STATUS_SUCCESS; } +/* Map ANSI color index (0-7) to Windows console attribute bits. + * ANSI: 0=black, 1=red, 2=green, 3=yellow, 4=blue, 5=magenta, 6=cyan, 7=white + * Windows: bit0=BLUE, bit1=GREEN, bit2=RED — need to swap red and blue. */ +static unsigned short ansi_to_win_color( unsigned int ansi ) +{ + static const unsigned short map[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + return map[ansi & 7]; +} + +static void process_sgr( struct screen_buffer *screen_buffer, const unsigned int *params, unsigned int count ) +{ + unsigned int i; + unsigned short attr = screen_buffer->attr; + + for (i = 0; i < count; i++) + { + switch (params[i]) + { + case 0: + attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + break; + case 1: + attr |= FOREGROUND_INTENSITY; + break; + case 2: case 22: + attr &= ~FOREGROUND_INTENSITY; + break; + case 4: case 24: /* underline on/off — no console equivalent, ignore */ + break; + case 5: case 25: /* blink on/off — no console equivalent, ignore */ + break; + case 7: /* reverse */ + attr = ((attr & 0x0f) << 4) | ((attr & 0xf0) >> 4); + break; + case 27: /* reverse off — revert to non-reversed default, best-effort */ + break; + case 8: case 28: /* hidden on/off, ignore */ + break; + case 30: case 31: case 32: case 33: + case 34: case 35: case 36: case 37: + attr = (attr & ~0x07) | ansi_to_win_color( params[i] - 30 ); + attr &= ~FOREGROUND_INTENSITY; + break; + case 38: /* extended foreground — 256/truecolor, skip sub-params */ + if (i + 1 < count && params[i + 1] == 5 && i + 2 < count) + { + unsigned int c = params[i + 2]; + if (c < 8) attr = (attr & ~0x0f) | ansi_to_win_color( c ); + else if (c < 16) attr = (attr & ~0x0f) | ansi_to_win_color( c - 8 ) | FOREGROUND_INTENSITY; + i += 2; + } + else if (i + 1 < count && params[i + 1] == 2 && i + 4 < count) + i += 4; /* skip r,g,b */ + break; + case 39: + attr = (attr & ~0x0f) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + break; + case 40: case 41: case 42: case 43: + case 44: case 45: case 46: case 47: + attr = (attr & ~0x70) | (ansi_to_win_color( params[i] - 40 ) << 4); + attr &= ~BACKGROUND_INTENSITY; + break; + case 48: /* extended background */ + if (i + 1 < count && params[i + 1] == 5 && i + 2 < count) + { + unsigned int c = params[i + 2]; + if (c < 8) attr = (attr & ~0xf0) | (ansi_to_win_color( c ) << 4); + else if (c < 16) attr = (attr & ~0xf0) | (ansi_to_win_color( c - 8 ) << 4) | BACKGROUND_INTENSITY; + i += 2; + } + else if (i + 1 < count && params[i + 1] == 2 && i + 4 < count) + i += 4; + break; + case 49: + attr &= ~0xf0; + break; + case 90: case 91: case 92: case 93: + case 94: case 95: case 96: case 97: + attr = (attr & ~0x0f) | ansi_to_win_color( params[i] - 90 ) | FOREGROUND_INTENSITY; + break; + case 100: case 101: case 102: case 103: + case 104: case 105: case 106: case 107: + attr = (attr & ~0xf0) | (ansi_to_win_color( params[i] - 100 ) << 4) | BACKGROUND_INTENSITY; + break; + } + } + screen_buffer->attr = attr; +} + +static void vt_erase_screen( struct screen_buffer *screen_buffer, unsigned int mode, RECT *update_rect ) +{ + unsigned int start, end, i; + + switch (mode) + { + case 0: + start = screen_buffer->cursor_y * screen_buffer->width + screen_buffer->cursor_x; + end = screen_buffer->height * screen_buffer->width; + break; + case 1: + start = 0; + end = screen_buffer->cursor_y * screen_buffer->width + screen_buffer->cursor_x + 1; + break; + case 2: case 3: + start = 0; + end = screen_buffer->height * screen_buffer->width; + break; + default: + return; + } + + if (end > screen_buffer->height * screen_buffer->width) + end = screen_buffer->height * screen_buffer->width; + + for (i = start; i < end; i++) + { + screen_buffer->data[i].ch = ' '; + screen_buffer->data[i].attr = screen_buffer->attr; + } + + update_rect->left = 0; + update_rect->top = min( update_rect->top, start / screen_buffer->width ); + update_rect->right = screen_buffer->width - 1; + update_rect->bottom = max( update_rect->bottom, (int)((end ? end - 1 : 0) / screen_buffer->width) ); +} + +static void vt_erase_line( struct screen_buffer *screen_buffer, unsigned int mode, RECT *update_rect ) +{ + unsigned int start, end, x, y = screen_buffer->cursor_y; + + switch (mode) + { + case 0: + start = screen_buffer->cursor_x; + end = screen_buffer->width; + break; + case 1: + start = 0; + end = screen_buffer->cursor_x + 1; + break; + case 2: + start = 0; + end = screen_buffer->width; + break; + default: + return; + } + + if (end > screen_buffer->width) end = screen_buffer->width; + + for (x = start; x < end; x++) + { + screen_buffer->data[y * screen_buffer->width + x].ch = ' '; + screen_buffer->data[y * screen_buffer->width + x].attr = screen_buffer->attr; + } + + update_rect->left = min( update_rect->left, (int)start ); + update_rect->top = min( update_rect->top, (int)y ); + update_rect->right = max( update_rect->right, (int)(end - 1) ); + update_rect->bottom = max( update_rect->bottom, (int)y ); +} + +static void vt_scroll_up( struct screen_buffer *screen_buffer, unsigned int lines, RECT *update_rect ) +{ + unsigned int top = screen_buffer->scroll_top; + unsigned int bottom = screen_buffer->scroll_bottom; + unsigned int width = screen_buffer->width; + unsigned int i, region_height = bottom - top + 1; + + if (lines >= region_height) lines = region_height; + if (!lines) return; + + /* move lines up */ + if (lines < region_height) + memmove( &screen_buffer->data[top * width], + &screen_buffer->data[(top + lines) * width], + (region_height - lines) * width * sizeof(char_info_t) ); + + /* clear vacated lines at bottom of region */ + for (i = (bottom - lines + 1) * width; i <= bottom * width + width - 1; i++) + { + screen_buffer->data[i].ch = ' '; + screen_buffer->data[i].attr = screen_buffer->attr; + } + + update_rect->left = 0; + update_rect->top = min( update_rect->top, (int)top ); + update_rect->right = width - 1; + update_rect->bottom = max( update_rect->bottom, (int)bottom ); +} + +static void vt_scroll_down( struct screen_buffer *screen_buffer, unsigned int lines, RECT *update_rect ) +{ + unsigned int top = screen_buffer->scroll_top; + unsigned int bottom = screen_buffer->scroll_bottom; + unsigned int width = screen_buffer->width; + unsigned int i, region_height = bottom - top + 1; + + if (lines >= region_height) lines = region_height; + if (!lines) return; + + if (lines < region_height) + memmove( &screen_buffer->data[(top + lines) * width], + &screen_buffer->data[top * width], + (region_height - lines) * width * sizeof(char_info_t) ); + + for (i = top * width; i < (top + lines) * width; i++) + { + screen_buffer->data[i].ch = ' '; + screen_buffer->data[i].attr = screen_buffer->attr; + } + + update_rect->left = 0; + update_rect->top = min( update_rect->top, (int)top ); + update_rect->right = width - 1; + update_rect->bottom = max( update_rect->bottom, (int)bottom ); +} + +static void vt_insert_lines( struct screen_buffer *screen_buffer, unsigned int lines, RECT *update_rect ) +{ + unsigned int y = screen_buffer->cursor_y; + unsigned int bottom = screen_buffer->scroll_bottom; + unsigned int width = screen_buffer->width; + unsigned int avail, i; + + if (y < screen_buffer->scroll_top || y > bottom) return; + avail = bottom - y + 1; + if (lines > avail) lines = avail; + if (!lines) return; + + if (lines < avail) + memmove( &screen_buffer->data[(y + lines) * width], + &screen_buffer->data[y * width], + (avail - lines) * width * sizeof(char_info_t) ); + + for (i = y * width; i < (y + lines) * width; i++) + { + screen_buffer->data[i].ch = ' '; + screen_buffer->data[i].attr = screen_buffer->attr; + } + + update_rect->left = 0; + update_rect->top = min( update_rect->top, (int)y ); + update_rect->right = width - 1; + update_rect->bottom = max( update_rect->bottom, (int)bottom ); +} + +static void vt_delete_lines( struct screen_buffer *screen_buffer, unsigned int lines, RECT *update_rect ) +{ + unsigned int y = screen_buffer->cursor_y; + unsigned int bottom = screen_buffer->scroll_bottom; + unsigned int width = screen_buffer->width; + unsigned int avail, i; + + if (y < screen_buffer->scroll_top || y > bottom) return; + avail = bottom - y + 1; + if (lines > avail) lines = avail; + if (!lines) return; + + if (lines < avail) + memmove( &screen_buffer->data[y * width], + &screen_buffer->data[(y + lines) * width], + (avail - lines) * width * sizeof(char_info_t) ); + + for (i = (bottom - lines + 1) * width; i <= bottom * width + width - 1; i++) + { + screen_buffer->data[i].ch = ' '; + screen_buffer->data[i].attr = screen_buffer->attr; + } + + update_rect->left = 0; + update_rect->top = min( update_rect->top, (int)y ); + update_rect->right = width - 1; + update_rect->bottom = max( update_rect->bottom, (int)bottom ); +} + +static void vt_delete_chars( struct screen_buffer *screen_buffer, unsigned int count, RECT *update_rect ) +{ + unsigned int x = screen_buffer->cursor_x; + unsigned int y = screen_buffer->cursor_y; + unsigned int width = screen_buffer->width; + unsigned int avail = width - x, i; + + if (count > avail) count = avail; + if (!count) return; + + if (count < avail) + memmove( &screen_buffer->data[y * width + x], + &screen_buffer->data[y * width + x + count], + (avail - count) * sizeof(char_info_t) ); + + for (i = 0; i < count; i++) + { + screen_buffer->data[y * width + width - 1 - i].ch = ' '; + screen_buffer->data[y * width + width - 1 - i].attr = screen_buffer->attr; + } + + update_rect->left = min( update_rect->left, (int)x ); + update_rect->top = min( update_rect->top, (int)y ); + update_rect->right = width - 1; + update_rect->bottom = max( update_rect->bottom, (int)y ); +} + +static void vt_insert_chars( struct screen_buffer *screen_buffer, unsigned int count, RECT *update_rect ) +{ + unsigned int x = screen_buffer->cursor_x; + unsigned int y = screen_buffer->cursor_y; + unsigned int width = screen_buffer->width; + unsigned int avail = width - x, i; + + if (count > avail) count = avail; + if (!count) return; + + if (count < avail) + memmove( &screen_buffer->data[y * width + x + count], + &screen_buffer->data[y * width + x], + (avail - count) * sizeof(char_info_t) ); + + for (i = 0; i < count; i++) + { + screen_buffer->data[y * width + x + i].ch = ' '; + screen_buffer->data[y * width + x + i].attr = screen_buffer->attr; + } + + update_rect->left = min( update_rect->left, (int)x ); + update_rect->top = min( update_rect->top, (int)y ); + update_rect->right = width - 1; + update_rect->bottom = max( update_rect->bottom, (int)y ); +} + +static void vt_erase_chars( struct screen_buffer *screen_buffer, unsigned int count, RECT *update_rect ) +{ + unsigned int x = screen_buffer->cursor_x; + unsigned int y = screen_buffer->cursor_y; + unsigned int width = screen_buffer->width; + unsigned int i; + + if (x + count > width) count = width - x; + + for (i = 0; i < count; i++) + { + screen_buffer->data[y * width + x + i].ch = ' '; + screen_buffer->data[y * width + x + i].attr = screen_buffer->attr; + } + + update_rect->left = min( update_rect->left, (int)x ); + update_rect->top = min( update_rect->top, (int)y ); + update_rect->right = max( update_rect->right, (int)(x + count - 1) ); + update_rect->bottom = max( update_rect->bottom, (int)y ); +} + +static void vt_switch_alt_screen( struct screen_buffer *screen_buffer, BOOL enable, RECT *update_rect ) +{ + unsigned int total; + + if (enable && !screen_buffer->alt_data) + { + /* save main screen and switch to alternate */ + screen_buffer->alt_data = screen_buffer->data; + screen_buffer->alt_width = screen_buffer->width; + screen_buffer->alt_height = screen_buffer->height; + screen_buffer->alt_cursor_x = screen_buffer->cursor_x; + screen_buffer->alt_cursor_y = screen_buffer->cursor_y; + screen_buffer->alt_attr = screen_buffer->attr; + + total = screen_buffer->width * screen_buffer->height; + screen_buffer->data = malloc( total * sizeof(char_info_t) ); + if (screen_buffer->data) + { + unsigned int i; + for (i = 0; i < total; i++) screen_buffer->data[i] = empty_char_info; + } + else + { + /* fallback: reuse main buffer */ + screen_buffer->data = screen_buffer->alt_data; + screen_buffer->alt_data = NULL; + return; + } + screen_buffer->cursor_x = 0; + screen_buffer->cursor_y = 0; + } + else if (!enable && screen_buffer->alt_data) + { + /* restore main screen */ + free( screen_buffer->data ); + screen_buffer->data = screen_buffer->alt_data; + screen_buffer->alt_data = NULL; + screen_buffer->width = screen_buffer->alt_width; + screen_buffer->height = screen_buffer->alt_height; + screen_buffer->cursor_x = screen_buffer->alt_cursor_x; + screen_buffer->cursor_y = screen_buffer->alt_cursor_y; + screen_buffer->attr = screen_buffer->alt_attr; + } + else return; + + screen_buffer->scroll_top = 0; + screen_buffer->scroll_bottom = screen_buffer->height - 1; + + update_rect->left = 0; + update_rect->top = 0; + update_rect->right = screen_buffer->width - 1; + update_rect->bottom = screen_buffer->height - 1; +} + +/* Process a CSI sequence (ESC[ already consumed). + * Returns characters consumed from buffer, or 0 if incomplete. */ +static size_t process_csi_output( struct screen_buffer *screen_buffer, const WCHAR *buf, size_t len, RECT *update_rect ) +{ + unsigned int params[16], param_count = 0, n = 0; + size_t pos = 0; + BOOL private_mode = FALSE; + WCHAR prefix = 0; + + if (!len) return 0; + + /* CSI parameter prefix bytes: '?' (DEC private), '>', '<', '=' */ + if (buf[pos] == '?' || buf[pos] == '>' || buf[pos] == '<' || buf[pos] == '=') + { + prefix = buf[pos]; + if (buf[pos] == '?') private_mode = TRUE; + pos++; + } + if (pos >= len) return 0; + + /* parse numeric parameters separated by ';' */ + for (;;) + { + n = 0; + while (pos < len && buf[pos] >= '0' && buf[pos] <= '9') + n = n * 10 + buf[pos++] - '0'; + if (param_count < ARRAY_SIZE(params)) params[param_count++] = n; + if (pos >= len) return 0; + if (buf[pos] != ';') break; + pos++; + if (pos >= len) return 0; + } + + /* skip intermediate bytes (0x20-0x2F: space, !, $, etc.) */ + while (pos < len && buf[pos] >= 0x20 && buf[pos] <= 0x2f) pos++; + if (pos >= len) return 0; + + /* buf[pos] must be the final command character (0x40-0x7E) */ + if (buf[pos] < 0x40 || buf[pos] > 0x7e) return pos + 1; /* skip malformed */ + + /* Unrecognized prefix — consume the whole sequence but don't act on it */ + if (prefix && !private_mode) + { + TRACE( "skipping CSI %c ... %c sequence\n", (char)prefix, (char)buf[pos] ); + return pos + 1; + } + + if (private_mode) + { + unsigned int j; + switch (buf[pos]) + { + case 'h': + for (j = 0; j < param_count; j++) + { + switch (params[j]) + { + case 25: screen_buffer->cursor_visible = TRUE; break; + case 1049: + vt_switch_alt_screen( screen_buffer, TRUE, update_rect ); + if (screen_buffer->console->is_unix) + { + tty_write( screen_buffer->console, "\x1b[?1049h", 8 ); + tty_flush( screen_buffer->console ); + /* After switching alt screen, the host terminal's cursor + * position and attrs are unknown — reset tracked state */ + screen_buffer->console->tty_cursor_x = 0; + screen_buffer->console->tty_cursor_y = 0; + screen_buffer->console->tty_attr = 7; + } + break; + case 7: screen_buffer->mode |= ENABLE_WRAP_AT_EOL_OUTPUT; break; + case 1: /* DECCKM — forward to real terminal so it sends ESC O A instead of ESC [ A */ + if (screen_buffer->console->is_unix) + tty_write( screen_buffer->console, "\x1b[?1h", 5 ); + break; + } + } + return pos + 1; + case 'l': + for (j = 0; j < param_count; j++) + { + switch (params[j]) + { + case 25: screen_buffer->cursor_visible = FALSE; break; + case 1049: + vt_switch_alt_screen( screen_buffer, FALSE, update_rect ); + if (screen_buffer->console->is_unix) + { + tty_write( screen_buffer->console, "\x1b[?1049l", 8 ); + tty_flush( screen_buffer->console ); + screen_buffer->console->tty_cursor_x = 0; + screen_buffer->console->tty_cursor_y = 0; + screen_buffer->console->tty_attr = 7; + } + break; + case 7: screen_buffer->mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; break; + case 1: /* DECCKM reset — forward to real terminal */ + if (screen_buffer->console->is_unix) + tty_write( screen_buffer->console, "\x1b[?1l", 5 ); + break; + } + } + return pos + 1; + default: + FIXME( "unhandled CSI ? %c sequence\n", (char)buf[pos] ); + return pos + 1; + } + } + + n = param_count ? params[0] : 0; + + switch (buf[pos]) + { + case 'A': /* CUU — cursor up */ + if (!n) n = 1; + screen_buffer->cursor_y = screen_buffer->cursor_y >= n ? screen_buffer->cursor_y - n : 0; + break; + case 'B': /* CUD — cursor down */ + if (!n) n = 1; + screen_buffer->cursor_y = min( screen_buffer->cursor_y + n, screen_buffer->height - 1 ); + break; + case 'C': /* CUF — cursor forward */ + if (!n) n = 1; + screen_buffer->cursor_x = min( screen_buffer->cursor_x + n, screen_buffer->width - 1 ); + break; + case 'D': /* CUB — cursor back */ + if (!n) n = 1; + screen_buffer->cursor_x = screen_buffer->cursor_x >= n ? screen_buffer->cursor_x - n : 0; + break; + case 'E': /* CNL — cursor next line */ + if (!n) n = 1; + screen_buffer->cursor_x = 0; + screen_buffer->cursor_y = min( screen_buffer->cursor_y + n, screen_buffer->height - 1 ); + break; + case 'F': /* CPL — cursor previous line */ + if (!n) n = 1; + screen_buffer->cursor_x = 0; + screen_buffer->cursor_y = screen_buffer->cursor_y >= n ? screen_buffer->cursor_y - n : 0; + break; + case 'G': /* CHA — cursor horizontal absolute */ + screen_buffer->cursor_x = min( n ? n - 1 : 0, screen_buffer->width - 1 ); + break; + case 'H': case 'f': /* CUP / HVP — cursor position */ + { + unsigned int row = (param_count >= 1 && params[0]) ? params[0] - 1 : 0; + unsigned int col = (param_count >= 2 && params[1]) ? params[1] - 1 : 0; + screen_buffer->cursor_y = min( row, screen_buffer->height - 1 ); + screen_buffer->cursor_x = min( col, screen_buffer->width - 1 ); + break; + } + case 'J': /* ED — erase in display */ + vt_erase_screen( screen_buffer, n, update_rect ); + break; + case 'K': /* EL — erase in line */ + vt_erase_line( screen_buffer, n, update_rect ); + break; + case 'L': /* IL — insert lines */ + if (!n) n = 1; + vt_insert_lines( screen_buffer, n, update_rect ); + break; + case 'M': /* DL — delete lines */ + if (!n) n = 1; + vt_delete_lines( screen_buffer, n, update_rect ); + break; + case 'P': /* DCH — delete characters */ + if (!n) n = 1; + vt_delete_chars( screen_buffer, n, update_rect ); + break; + case '@': /* ICH — insert characters */ + if (!n) n = 1; + vt_insert_chars( screen_buffer, n, update_rect ); + break; + case 'X': /* ECH — erase characters */ + if (!n) n = 1; + vt_erase_chars( screen_buffer, n, update_rect ); + break; + case 'S': /* SU — scroll up */ + if (!n) n = 1; + vt_scroll_up( screen_buffer, n, update_rect ); + break; + case 'T': /* SD — scroll down */ + if (!n) n = 1; + vt_scroll_down( screen_buffer, n, update_rect ); + break; + case 'd': /* VPA — vertical position absolute */ + screen_buffer->cursor_y = min( n ? n - 1 : 0, screen_buffer->height - 1 ); + break; + case 'm': /* SGR — select graphic rendition */ + if (!param_count) { unsigned int zero = 0; process_sgr( screen_buffer, &zero, 1 ); } + else process_sgr( screen_buffer, params, param_count ); + break; + case 'r': /* DECSTBM — set scrolling region */ + { + unsigned int top = (param_count >= 1 && params[0]) ? params[0] - 1 : 0; + unsigned int bot = (param_count >= 2 && params[1]) ? params[1] - 1 : screen_buffer->height - 1; + if (top < screen_buffer->height && bot < screen_buffer->height && top <= bot) + { + screen_buffer->scroll_top = top; + screen_buffer->scroll_bottom = bot; + } + screen_buffer->cursor_x = 0; + screen_buffer->cursor_y = 0; + break; + } + case 's': /* SCP — save cursor position */ + screen_buffer->saved_cursor_x = screen_buffer->cursor_x; + screen_buffer->saved_cursor_y = screen_buffer->cursor_y; + screen_buffer->saved_attr = screen_buffer->attr; + break; + case 'u': /* RCP — restore cursor position */ + screen_buffer->cursor_x = min( screen_buffer->saved_cursor_x, screen_buffer->width - 1 ); + screen_buffer->cursor_y = min( screen_buffer->saved_cursor_y, screen_buffer->height - 1 ); + screen_buffer->attr = screen_buffer->saved_attr; + break; + case 'n': /* DSR — device status report */ + if (n == 6) + { + /* cursor position report: respond with ESC[row;colR */ + char response[32]; + sprintf( response, "\x1b[%u;%uR", screen_buffer->cursor_y + 1, screen_buffer->cursor_x + 1 ); + /* inject as input */ + { + int j; + for (j = 0; response[j]; j++) + char_key_press( screen_buffer->console, response[j], 0 ); + } + } + break; + case 'h': /* SM — set mode */ + if (n == 4) screen_buffer->mode |= 0x0020; /* insert mode — not standard console flag, unused here */ + break; + case 'l': /* RM — reset mode */ + if (n == 4) screen_buffer->mode &= ~0x0020; + break; + default: + FIXME( "unhandled CSI sequence: %c (0x%02x)\n", (char)buf[pos], buf[pos] ); + break; + } + + return pos + 1; +} + +/* Process an OSC sequence (ESC] already consumed). + * Returns characters consumed, or 0 if incomplete. */ +static size_t process_osc_output( struct screen_buffer *screen_buffer, const WCHAR *buf, size_t len ) +{ + size_t i; + for (i = 0; i < len; i++) + { + if (buf[i] == 0x07) return i + 1; /* BEL terminates */ + if (buf[i] == 0x1b) + { + if (i + 1 < len && buf[i + 1] == '\\') return i + 2; /* ST (ESC\) terminates */ + /* ESC without \ at end of buffer: might be start of ST split across writes. + * Consume everything before the ESC; the caller will buffer just the ESC. */ + return i ? i : 0; + } + } + /* No terminator found — consume all (OSC content is discarded anyway) */ + return len; +} + +/* DEC Special Graphics character set mapping (0x60-0x7E → Unicode) */ +static WCHAR dec_graphics_map( WCHAR ch ) +{ + static const WCHAR map[] = { + 0x25C6, /* ` → ◆ */ + 0x2592, /* a → ▒ */ + 0x2409, /* b → HT symbol */ + 0x240C, /* c → FF symbol */ + 0x240D, /* d → CR symbol */ + 0x240A, /* e → LF symbol */ + 0x00B0, /* f → ° */ + 0x00B1, /* g → ± */ + 0x2424, /* h → NL symbol */ + 0x240B, /* i → VT symbol */ + 0x2518, /* j → ┘ */ + 0x2510, /* k → ┐ */ + 0x250C, /* l → ┌ */ + 0x2514, /* m → └ */ + 0x253C, /* n → ┼ */ + 0x23BA, /* o → ⎺ */ + 0x23BB, /* p → ⎻ */ + 0x2500, /* q → ─ */ + 0x23BC, /* r → ⎼ */ + 0x23BD, /* s → ⎽ */ + 0x251C, /* t → ├ */ + 0x2524, /* u → ┤ */ + 0x2534, /* v → ┴ */ + 0x252C, /* w → ┬ */ + 0x2502, /* x → │ */ + 0x2264, /* y → ≤ */ + 0x2265, /* z → ≥ */ + 0x03C0, /* { → π */ + 0x2260, /* | → ≠ */ + 0x00A3, /* } → £ */ + 0x00B7, /* ~ → · */ + }; + if (ch >= 0x60 && ch <= 0x7E) + return map[ch - 0x60]; + return ch; +} + +/* Process VT escape sequences in the output. + * buffer[0] is ESC. Returns total chars consumed, or 0 if incomplete. */ +static size_t process_vt_output( struct screen_buffer *screen_buffer, const WCHAR *buffer, size_t len, RECT *update_rect ) +{ + size_t consumed; + + if (len < 2) return 0; + + switch (buffer[1]) + { + case '[': /* CSI */ + consumed = process_csi_output( screen_buffer, buffer + 2, len - 2, update_rect ); + return consumed ? consumed + 2 : 0; + case ']': /* OSC */ + consumed = process_osc_output( screen_buffer, buffer + 2, len - 2 ); + return consumed ? consumed + 2 : 0; + case 'M': /* RI — reverse index */ + if (screen_buffer->cursor_y > screen_buffer->scroll_top) + screen_buffer->cursor_y--; + else + vt_scroll_down( screen_buffer, 1, update_rect ); + return 2; + case '7': /* DECSC — save cursor */ + screen_buffer->saved_cursor_x = screen_buffer->cursor_x; + screen_buffer->saved_cursor_y = screen_buffer->cursor_y; + screen_buffer->saved_attr = screen_buffer->attr; + return 2; + case '8': /* DECRC — restore cursor */ + screen_buffer->cursor_x = min( screen_buffer->saved_cursor_x, screen_buffer->width - 1 ); + screen_buffer->cursor_y = min( screen_buffer->saved_cursor_y, screen_buffer->height - 1 ); + screen_buffer->attr = screen_buffer->saved_attr; + return 2; + case '=': /* DECKPAM — keypad application mode */ + case '>': /* DECKPNM — keypad numeric mode */ + return 2; + case 'c': /* RIS — full reset */ + screen_buffer->g0_is_dec = FALSE; + screen_buffer->g1_is_dec = FALSE; + screen_buffer->use_g1 = FALSE; + screen_buffer->attr = 0x0007; /* default: white on black */ + screen_buffer->cursor_x = 0; + screen_buffer->cursor_y = 0; + screen_buffer->scroll_top = 0; + screen_buffer->scroll_bottom = screen_buffer->height - 1; + return 2; + case '(': /* G0 character set designation */ + if (len < 3) return 0; + screen_buffer->g0_is_dec = (buffer[2] == '0'); + TRACE( "G0 charset = %s\n", screen_buffer->g0_is_dec ? "DEC" : "ASCII" ); + return 3; + case ')': /* G1 character set designation */ + if (len < 3) return 0; + screen_buffer->g1_is_dec = (buffer[2] == '0'); + TRACE( "G1 charset = %s\n", screen_buffer->g1_is_dec ? "DEC" : "ASCII" ); + return 3; + case '*': case '+': /* G2-G3 character set designation, skip */ + return len >= 3 ? 3 : 0; + default: + TRACE( "unhandled ESC %c sequence\n", (char)buffer[1] ); + return 2; + } +} + static NTSTATUS write_console( struct screen_buffer *screen_buffer, const WCHAR *buffer, size_t len ) { RECT update_rect; @@ -2112,8 +2964,76 @@ static NTSTATUS write_console( struct screen_buffer *screen_buffer, const WCHAR empty_update_rect( screen_buffer, &update_rect ); + /* If there's a buffered incomplete VT sequence from a previous write, try to complete it */ + if (screen_buffer->vt_buf_len && (screen_buffer->mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) + { + WCHAR combined[32]; + size_t combined_len, take_from_new, consumed; + + combined_len = min( screen_buffer->vt_buf_len + len, ARRAY_SIZE(combined) ); + memcpy( combined, screen_buffer->vt_buf, screen_buffer->vt_buf_len * sizeof(WCHAR) ); + memcpy( combined + screen_buffer->vt_buf_len, buffer, + (combined_len - screen_buffer->vt_buf_len) * sizeof(WCHAR) ); + + consumed = process_vt_output( screen_buffer, combined, combined_len, &update_rect ); + if (consumed) + { + take_from_new = consumed > screen_buffer->vt_buf_len ? consumed - screen_buffer->vt_buf_len : 0; + screen_buffer->vt_buf_len = 0; + buffer += take_from_new; + len -= take_from_new; + } + else if (combined_len == ARRAY_SIZE(combined)) + { + /* buffer full but still no complete sequence — discard */ + screen_buffer->vt_buf_len = 0; + } + else + { + /* still incomplete, all new data absorbed into vt_buf */ + if (combined_len > ARRAY_SIZE(screen_buffer->vt_buf)) + { + /* too large for vt_buf — discard */ + screen_buffer->vt_buf_len = 0; + } + else + { + memcpy( screen_buffer->vt_buf, combined, combined_len * sizeof(WCHAR) ); + screen_buffer->vt_buf_len = combined_len; + return STATUS_SUCCESS; + } + } + } + for (i = 0; i < len; i++) { + if ((screen_buffer->mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) && buffer[i] == 0x0e) /* SO — shift out, activate G1 */ + { + screen_buffer->use_g1 = TRUE; + TRACE( "SO: activate G1 (dec=%d)\n", screen_buffer->g1_is_dec ); + continue; + } + if ((screen_buffer->mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) && buffer[i] == 0x0f) /* SI — shift in, activate G0 */ + { + screen_buffer->use_g1 = FALSE; + TRACE( "SI: activate G0 (dec=%d)\n", screen_buffer->g0_is_dec ); + continue; + } + if ((screen_buffer->mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) && buffer[i] == 0x1b) + { + size_t consumed = process_vt_output( screen_buffer, buffer + i, len - i, &update_rect ); + if (consumed) + { + i += consumed - 1; /* -1 because for loop increments */ + continue; + } + /* incomplete sequence at end of buffer — save for next write */ + screen_buffer->vt_buf_len = len - i; + if (screen_buffer->vt_buf_len > ARRAY_SIZE(screen_buffer->vt_buf)) + screen_buffer->vt_buf_len = ARRAY_SIZE(screen_buffer->vt_buf); + memcpy( screen_buffer->vt_buf, buffer + i, screen_buffer->vt_buf_len * sizeof(WCHAR) ); + break; + } if (screen_buffer->mode & ENABLE_PROCESSED_OUTPUT) { switch (buffer[i]) @@ -2129,7 +3049,13 @@ static NTSTATUS write_console( struct screen_buffer *screen_buffer, const WCHAR continue; case '\n': screen_buffer->cursor_x = 0; - if (++screen_buffer->cursor_y == screen_buffer->height) + if ((screen_buffer->mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) && + screen_buffer->cursor_y == screen_buffer->scroll_bottom) + { + update_output( screen_buffer, &update_rect ); + vt_scroll_up( screen_buffer, 1, &update_rect ); + } + else if (++screen_buffer->cursor_y == screen_buffer->height) new_line( screen_buffer, &update_rect ); else if (screen_buffer->mode & ENABLE_WRAP_AT_EOL_OUTPUT) { @@ -2145,9 +3071,46 @@ static NTSTATUS write_console( struct screen_buffer *screen_buffer, const WCHAR continue; } } - if (screen_buffer->cursor_x == screen_buffer->width && !(screen_buffer->mode & ENABLE_WRAP_AT_EOL_OUTPUT)) - screen_buffer->cursor_x = update_rect.left; - write_char( screen_buffer, buffer[i], &update_rect, NULL ); + if (screen_buffer->mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + { + /* VT-mode character write with scroll-region awareness */ + if (screen_buffer->cursor_x >= screen_buffer->width) + { + if (screen_buffer->mode & ENABLE_WRAP_AT_EOL_OUTPUT) + { + screen_buffer->cursor_x = 0; + if (screen_buffer->cursor_y == screen_buffer->scroll_bottom) + { + update_output( screen_buffer, &update_rect ); + vt_scroll_up( screen_buffer, 1, &update_rect ); + } + else if (screen_buffer->cursor_y < screen_buffer->height - 1) + screen_buffer->cursor_y++; + /* else at very bottom of buffer, no scroll region — stay */ + } + else + screen_buffer->cursor_x = screen_buffer->width - 1; + } + { + BOOL dec = screen_buffer->use_g1 ? screen_buffer->g1_is_dec : screen_buffer->g0_is_dec; + WCHAR ch = dec ? dec_graphics_map( buffer[i] ) : buffer[i]; + screen_buffer->data[screen_buffer->cursor_y * screen_buffer->width + + screen_buffer->cursor_x].ch = ch; + screen_buffer->data[screen_buffer->cursor_y * screen_buffer->width + + screen_buffer->cursor_x].attr = screen_buffer->attr; + } + update_rect.left = min( update_rect.left, (int)screen_buffer->cursor_x ); + update_rect.top = min( update_rect.top, (int)screen_buffer->cursor_y ); + update_rect.right = max( update_rect.right, (int)screen_buffer->cursor_x ); + update_rect.bottom = max( update_rect.bottom, (int)screen_buffer->cursor_y ); + screen_buffer->cursor_x++; + } + else + { + if (screen_buffer->cursor_x == screen_buffer->width && !(screen_buffer->mode & ENABLE_WRAP_AT_EOL_OUTPUT)) + screen_buffer->cursor_x = update_rect.left; + write_char( screen_buffer, buffer[i], &update_rect, NULL ); + } } if (screen_buffer->cursor_x == screen_buffer->width) @@ -2185,7 +3148,6 @@ static NTSTATUS write_output( struct screen_buffer *screen_buffer, const struct entry_cnt = (in_size - sizeof(*params)) / entry_size; TRACE( "(%u,%u) cnt %u\n", params->x, params->y, entry_cnt ); - if (params->x >= screen_buffer->width) { *out_size = 0; @@ -2287,7 +3249,6 @@ static NTSTATUS read_output( struct screen_buffer *screen_buffer, const struct c mode = params->mode; width = params->width; TRACE( "(%u %u) mode %u width %u\n", x, y, mode, width ); - switch(mode) { case CHAR_INFO_MODE_TEXT: @@ -2359,7 +3320,6 @@ static NTSTATUS fill_output( struct screen_buffer *screen_buffer, const struct c DWORD i, count, *result; TRACE( "(%u %u) mode %u\n", params->x, params->y, params->mode ); - enter_absolute_mode( screen_buffer->console ); if (params->y >= screen_buffer->height) return STATUS_SUCCESS; dest = screen_buffer->data + min( params->y * screen_buffer->width + params->x, diff --git a/programs/conhost/conhost.h b/programs/conhost/conhost.h index be17cf5eaae..86de75a09d7 100644 --- a/programs/conhost/conhost.h +++ b/programs/conhost/conhost.h @@ -132,6 +132,22 @@ struct screen_buffer RECT win; /* current visible window on the screen buffer */ struct font_info font; /* console font information */ struct wine_rb_entry entry; /* map entry */ + WCHAR vt_buf[16]; /* incomplete VT sequence buffer */ + unsigned int vt_buf_len; /* bytes used in vt_buf */ + unsigned int saved_cursor_x; /* DEC saved cursor position */ + unsigned int saved_cursor_y; + unsigned short saved_attr; /* DEC saved attributes */ + unsigned int scroll_top; /* scroll region top (0-based, inclusive) */ + unsigned int scroll_bottom; /* scroll region bottom (0-based, inclusive) */ + BOOL g0_is_dec; /* G0 is DEC Special Graphics */ + BOOL g1_is_dec; /* G1 is DEC Special Graphics */ + BOOL use_g1; /* G1 is the active charset (via SO/SI) */ + char_info_t *alt_data; /* alternate screen buffer data */ + unsigned int alt_width; + unsigned int alt_height; + unsigned int alt_cursor_x; + unsigned int alt_cursor_y; + unsigned short alt_attr; }; /* conhost.c */ diff --git a/server/file.h b/server/file.h index e1485e4d5fc..b77cffbb442 100644 --- a/server/file.h +++ b/server/file.h @@ -297,7 +297,7 @@ static inline int async_queued( struct async_queue *queue ) #define FILE_UNIX_READ_ACCESS (FILE_READ_DATA|FILE_READ_ATTRIBUTES|FILE_READ_EA) /* access rights that require Unix write permission */ -#define FILE_UNIX_WRITE_ACCESS (FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_WRITE_ATTRIBUTES|FILE_WRITE_EA) +#define FILE_UNIX_WRITE_ACCESS (FILE_WRITE_DATA|FILE_APPEND_DATA) /* magic file access rights for mappings */ #define FILE_MAPPING_IMAGE 0x80000000 /* set for SEC_IMAGE mappings */