66// License: MIT
77// -----------------------------------------------------------------------
88
9+ using SharpConsoleUI . Drivers . Input ;
910using SharpConsoleUI . Helpers ;
1011using System . Runtime . InteropServices ;
1112using System . Text ;
@@ -119,7 +120,7 @@ public void AddContent(int x, int y, string content)
119120
120121 // Single-pass state machine parser for ANSI sequences
121122 // FIX6: Also limit width in AddContent to prevent writing beyond console
122- int maxBufferX = _options . Fix6_WidthLimit ? Math . Min ( _width , Console . WindowWidth ) : _width ;
123+ int maxBufferX = _options . Fix6_WidthLimit ? Math . Min ( _width , GetCurrentWindowWidth ( ) ) : _width ;
123124 while ( contentPos < content . Length && bufferX < maxBufferX )
124125 {
125126 // State machine: detect ESC character
@@ -267,12 +268,13 @@ public void Render()
267268 CaptureConsoleBufferSnapshot ( ) ;
268269 }
269270
270- Console . CursorVisible = false ;
271+ // Hide cursor during render — use raw write on Unix to bypass .NET entirely
272+ WriteOutput ( "\x1b [?25l" ) ;
271273
272274 // FIX24: Drain input buffer to prevent mouse sequences from being echoed during rendering
273- // Race condition: If mouse events arrive while we're outputting cursor positioning,
274- // they get echoed at our current cursor position, mixing with our content
275- if ( _options . Fix24_DrainInputBeforeRender )
275+ // Skip when raw mode active — no echo means nothing to drain, and Console.ReadKey/KeyAvailable
276+ // would go through ConsolePal which we're trying to avoid
277+ if ( _options . Fix24_DrainInputBeforeRender && ! TerminalRawMode . IsRawModeActive )
276278 {
277279 int drained = 0 ;
278280 while ( Console . KeyAvailable )
@@ -291,19 +293,19 @@ public void Render()
291293 // FIX25: Temporarily disable mouse tracking during rendering
292294 // Problem: .NET Console.ReadKey() toggles echo on/off, creating windows where mouse sequences get echoed
293295 // Solution: Disable mouse tracking entirely during render to prevent new events from arriving
294- if ( _options . Fix25_DisableMouseDuringRender )
296+ // FIX25: Disable mouse tracking during render — skip in raw mode (unnecessary)
297+ if ( _options . Fix25_DisableMouseDuringRender && ! TerminalRawMode . IsRawModeActive )
295298 {
296- // Disable all mouse tracking modes (reverse order of enable)
297- Console . Out . Write ( "\x1b [?1003l" ) ; // Disable any event mouse
298- Console . Out . Write ( "\x1b [?1002l" ) ; // Disable button event tracking
299- Console . Out . Write ( "\x1b [?1000l" ) ; // Disable basic mouse reporting
300- Console . Out . Flush ( ) ;
299+ WriteOutput ( "\x1b [?1003l" ) ; // Disable any event mouse
300+ WriteOutput ( "\x1b [?1002l" ) ; // Disable button event tracking
301+ WriteOutput ( "\x1b [?1000l" ) ; // Disable basic mouse reporting
302+ FlushOutput ( ) ;
301303 }
302304
303305
304306 // FIX27: Periodic redraw of clean cells to clear terminal echo leaks (Linux/macOS only)
305- // Check if 1 second has elapsed since last full redraw (time-based, not frame-based)
306- if ( _options . Fix27_PeriodicFullRedraw && ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
307+ // Keep active even in raw mode as a visual cleanup backstop
308+ if ( _options . Fix27_PeriodicFullRedraw && ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) && ! TerminalRawMode . IsRawModeActive )
307309 {
308310 var elapsed = ( DateTime . Now - _lastFullRedraw ) . TotalSeconds ;
309311 if ( elapsed >= _options . Fix27_RedrawIntervalSeconds )
@@ -417,11 +419,12 @@ public void Render()
417419 }
418420
419421
420- // Single atomic write of entire screen - no cursor jumps, no flicker!
422+ // Single atomic write of entire screen via raw libc write() on Unix
423+ // Completely bypasses .NET's Console/StreamWriter/SyncTextWriter
421424 var output = screenBuilder . ToString ( ) ;
422425 if ( output . Length > 0 )
423426 {
424- Console . Write ( output ) ;
427+ WriteOutput ( output ) ;
425428 }
426429
427430 // Diagnostics: Capture output metrics
@@ -445,13 +448,12 @@ public void Render()
445448 }
446449
447450 // FIX25: Re-enable mouse tracking after rendering completes
448- if ( _options . Fix25_DisableMouseDuringRender )
451+ if ( _options . Fix25_DisableMouseDuringRender && ! TerminalRawMode . IsRawModeActive )
449452 {
450- // Re-enable mouse tracking in same order as NetConsoleDriver initialization
451- Console . Out . Write ( "\x1b [?1000h" ) ; // Enable basic mouse reporting
452- Console . Out . Write ( "\x1b [?1002h" ) ; // Enable button event tracking
453- Console . Out . Write ( "\x1b [?1003h" ) ; // Enable any event mouse
454- Console . Out . Flush ( ) ;
453+ WriteOutput ( "\x1b [?1000h" ) ; // Enable basic mouse reporting
454+ WriteOutput ( "\x1b [?1002h" ) ; // Enable button event tracking
455+ WriteOutput ( "\x1b [?1003h" ) ; // Enable any event mouse
456+ FlushOutput ( ) ;
455457 }
456458 }
457459 }
@@ -619,6 +621,37 @@ private bool IsLineDirty(int y)
619621 return ( true , false , regions ) ;
620622 }
621623
624+ /// <summary>
625+ /// Writes output using raw libc write() when raw mode is active, or Console.Out.Write as fallback.
626+ /// Raw write completely bypasses .NET's Console/StreamWriter/SyncTextWriter infrastructure,
627+ /// eliminating any possibility of .NET runtime code touching termios during output.
628+ /// </summary>
629+ private static void WriteOutput ( string text )
630+ {
631+ if ( TerminalRawMode . IsRawModeActive )
632+ TerminalRawMode . WriteStdout ( text ) ;
633+ else
634+ Console . Out . Write ( text ) ;
635+ }
636+
637+ /// <summary>
638+ /// Flushes stdout. No-op when raw mode is active (raw write is unbuffered).
639+ /// </summary>
640+ private static void FlushOutput ( )
641+ {
642+ if ( ! TerminalRawMode . IsRawModeActive )
643+ Console . Out . Flush ( ) ;
644+ }
645+
646+ /// <summary>
647+ /// Gets current window width using ioctl when raw mode is active, avoiding ConsolePal.
648+ /// </summary>
649+ private int GetCurrentWindowWidth ( )
650+ {
651+ var ( w , _) = TerminalRawMode . GetWindowSize ( ) ;
652+ return w ;
653+ }
654+
622655 private bool IsValidPosition ( int x , int y )
623656 => x >= 0 && x < _width && y >= 0 && y < _height ;
624657
@@ -668,7 +701,7 @@ private void AppendLineToBuilder(int y, StringBuilder builder)
668701 int cellsWritten = 0 ; // Count cells written
669702
670703 // FIX6: Limit to console window width to prevent writing beyond visible area
671- int maxWidth = _options . Fix6_WidthLimit ? Math . Min ( _width , Console . WindowWidth ) : _width ;
704+ int maxWidth = _options . Fix6_WidthLimit ? Math . Min ( _width , GetCurrentWindowWidth ( ) ) : _width ;
672705 for ( int x = 0 ; x < maxWidth ; x ++ )
673706 {
674707 ref var frontCell = ref _frontBuffer [ x , y ] ;
0 commit comments