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 */
