@@ -56,12 +56,9 @@ public class ConsoleWindowSystem
5656 // Managed thread id of the main-loop (UI) thread; set at Run() start. -1 until then.
5757 private int _uiThreadId = - 1 ;
5858
59- // Software cursor: opt-in caret drawn by the app (blinking) with hardware cursor hidden.
60- // Resolved once at startup from the driver's options.
61- private bool _useSoftwareCursor ;
62- private int _cursorBlinkRateMs = 500 ;
63- private readonly Core . CursorBlinkClock _blinkClock = new ( ) ;
64- private System . Drawing . Point _lastSoftwareCursorPos = new ( - 1 , - 1 ) ;
59+ // System-wide default hardware-cursor blink behavior, resolved once at startup from the
60+ // driver's options. Per-cursor providers (ICursorBlinkProvider) override this per frame.
61+ private Core . CursorBlink _defaultCursorBlink = Core . CursorBlink . Blinking ;
6562
6663 // Signal to wake the main loop when input or UI actions arrive
6764 private readonly ManualResetEventSlim _wakeSignal = new ( false ) ;
@@ -415,12 +412,11 @@ public ConsoleWindowSystem(IConsoleDriver driver, ITheme theme, PluginConfigurat
415412 // Initialize panels from config or legacy StatusBarOptions
416413 _panelStateService . InitializePanels ( _options ) ;
417414
418- // Resolve software- cursor settings once from the driver's options (if it exposes them).
419- // Headless/other drivers leave the defaults (false / 500ms ).
415+ // Resolve the system-wide default cursor-blink behavior once from the driver's options
416+ // (if it exposes them). Headless/other drivers leave the default (Blinking ).
420417 if ( _consoleDriver is Drivers . NetConsoleDriver netDriver )
421418 {
422- _useSoftwareCursor = netDriver . Options . UseSoftwareCursor ;
423- _cursorBlinkRateMs = netDriver . Options . CursorBlinkRate ;
419+ _defaultCursorBlink = netDriver . Options . CursorBlink ;
424420 }
425421 }
426422
@@ -475,10 +471,6 @@ public IConsoleDriver ConsoleDriver
475471 /// </summary>
476472 internal CursorStateService CursorStateService => _cursorStateService ;
477473
478- /// <summary>True when a software caret should currently be drawn.</summary>
479- internal bool SoftwareCursorOn =>
480- _useSoftwareCursor && _cursorStateService . CurrentState . IsVisible && _blinkClock . IsOn ( _cursorBlinkRateMs ) ;
481-
482474 /// <summary>
483475 /// Gets the window state service for managing window lifecycle and state.
484476 /// </summary>
@@ -939,8 +931,6 @@ public int Run()
939931 var now = DateTime . UtcNow ;
940932 var elapsed = ( now - _lastRenderTime ) . TotalMilliseconds ;
941933
942- if ( _useSoftwareCursor ) _blinkClock . Advance ( elapsed ) ;
943-
944934 // Track performance metrics on EVERY iteration (independent of rendering)
945935 bool metricsNeedUpdate = false ;
946936 if ( Performance . IsPerformanceMetricsEnabled )
@@ -964,8 +954,7 @@ public int Run()
964954 }
965955
966956 // Frame pacing: render if windows are dirty OR metrics need update OR desktop needs render OR animations active
967- bool shouldRender = AnyWindowDirty ( ) || metricsNeedUpdate || Render . DesktopNeedsRender || Animations . HasActiveAnimations || Parsing . MarkupSpinnerClock . ShouldKeepRendering ( Animations . IsEnabled ) || Render . IsStatusBarDirty ( ) || _desktopPortalService . AnyPortalDirty ( )
968- || ( _useSoftwareCursor && _cursorStateService . CurrentState . IsVisible ) ;
957+ bool shouldRender = AnyWindowDirty ( ) || metricsNeedUpdate || Render . DesktopNeedsRender || Animations . HasActiveAnimations || Parsing . MarkupSpinnerClock . ShouldKeepRendering ( Animations . IsEnabled ) || Render . IsStatusBarDirty ( ) || _desktopPortalService . AnyPortalDirty ( ) ;
969958
970959 // Calculate recommended sleep duration once (used in both branches)
971960 var recommendedSleep = _inputStateService . GetRecommendedSleepDuration (
@@ -1008,9 +997,6 @@ public int Run()
1008997 UpdateCursor ( ) ;
1009998 if ( _uiActionsPending && _idleTime > Configuration . SystemDefaults . MinSleepDurationMs )
1010999 _idleTime = Configuration . SystemDefaults . MinSleepDurationMs ;
1011- // Keep the loop ticking fast enough for smooth caret blinking.
1012- if ( _useSoftwareCursor && _cursorStateService . CurrentState . IsVisible )
1013- _idleTime = Math . Min ( _idleTime , _cursorBlinkRateMs ) ;
10141000 _currentPhase = Core . MainLoopPhase . Idle ;
10151001 try
10161002 {
@@ -1320,6 +1306,8 @@ private void UpdateCursor()
13201306 // Get the owner control if available
13211307 IWindowControl ? ownerControl = null ;
13221308 CursorShape cursorShape = CursorShape . Block ;
1309+ // Seed blink from the system-wide default; a per-cursor provider may override it below.
1310+ CursorBlink cursorBlink = _defaultCursorBlink ;
13231311
13241312 if ( ActiveWindow . EventDispatcher != null && ActiveWindow . EventDispatcher . HasActiveInteractiveContent ( out var interactiveContent ) &&
13251313 interactiveContent is IWindowControl windowControl )
@@ -1340,6 +1328,18 @@ private void UpdateCursor()
13401328 {
13411329 cursorShape = shapeProvider . PreferredCursorShape . Value ;
13421330 }
1331+
1332+ // Blink resolves the same way: deepest provider first, then top-level control.
1333+ if ( deepestControl is ICursorBlinkProvider deepBlinkProvider &&
1334+ deepBlinkProvider . PreferredCursorBlink . HasValue )
1335+ {
1336+ cursorBlink = deepBlinkProvider . PreferredCursorBlink . Value ;
1337+ }
1338+ else if ( windowControl is ICursorBlinkProvider blinkProvider &&
1339+ blinkProvider . PreferredCursorBlink . HasValue )
1340+ {
1341+ cursorBlink = blinkProvider . PreferredCursorBlink . Value ;
1342+ }
13431343 }
13441344
13451345 // Update cursor state service with new state
@@ -1348,7 +1348,8 @@ private void UpdateCursor()
13481348 logicalPosition : cursorPosition ,
13491349 absolutePosition : new Point ( absoluteLeft , absoluteTop ) ,
13501350 ownerControl : ownerControl ,
1351- shape : cursorShape ) ;
1351+ shape : cursorShape ,
1352+ blink : cursorBlink ) ;
13521353 }
13531354 else
13541355 {
@@ -1360,18 +1361,6 @@ private void UpdateCursor()
13601361 _cursorStateService . HideCursor ( ) ;
13611362 }
13621363
1363- // Software cursor: reset the blink clock whenever the caret moves so the new
1364- // position starts in the "on" phase. CurrentState holds the real (visible) caret.
1365- if ( _useSoftwareCursor )
1366- {
1367- var caretPos = _cursorStateService . CurrentState . AbsolutePosition ;
1368- if ( caretPos != _lastSoftwareCursorPos )
1369- {
1370- _blinkClock . Reset ( ) ;
1371- _lastSoftwareCursorPos = caretPos ;
1372- }
1373- }
1374-
13751364 // Apply cursor state to the actual console
13761365 // CRITICAL: Protect Console I/O with lock to prevent concurrent writes
13771366 // from corrupting ANSI sequences during InputLoop's mouse parsing
@@ -1380,15 +1369,6 @@ private void UpdateCursor()
13801369 _cursorStateService . ApplyCursorToConsole (
13811370 _consoleDriver . ScreenSize . Width ,
13821371 _consoleDriver . ScreenSize . Height ) ;
1383-
1384- // Software cursor: keep CurrentState as the real (visible) caret for the overlay,
1385- // but force the HARDWARE cursor hidden so only the app-drawn caret is seen.
1386- // Only emit when the caret is logically visible (avoids churn; the overlay
1387- // redraws every frame while focused — see the keep-alive in the main loop).
1388- if ( _useSoftwareCursor && _cursorStateService . CurrentState . IsVisible )
1389- {
1390- _consoleDriver . SetCursorVisible ( false ) ;
1391- }
13921372 }
13931373 }
13941374
0 commit comments