فهرست منبع

Merge branch 'gui-cs:v2_develop' into v2_develop

Tig 1 ماه پیش
والد
کامیت
9985557aae
100فایلهای تغییر یافته به همراه4039 افزوده شده و 7959 حذف شده
  1. 3 21
      .github/workflows/build-validation.yml
  2. 77 36
      .github/workflows/integration-tests.yml
  3. 43 0
      .github/workflows/quick-build.yml
  4. 104 26
      .github/workflows/unit-tests.yml
  5. 5 0
      .gitignore
  6. 58 49
      CONTRIBUTING.md
  7. 2 2
      Examples/NativeAot/Program.cs
  8. 1 1
      Examples/SelfContained/Program.cs
  9. 9 16
      Examples/UICatalog/Scenario.cs
  10. 1 1
      Examples/UICatalog/Scenarios/GraphViewExample.cs
  11. 1 4
      Examples/UICatalog/Scenarios/Keys.cs
  12. 1 1
      Examples/UICatalog/Scenarios/Mazing.cs
  13. 14 12
      Examples/UICatalog/Scenarios/Navigation.cs
  14. 34 33
      Examples/UICatalog/Scenarios/Notepad.cs
  15. 2 2
      Examples/UICatalog/Scenarios/NumericUpDownDemo.cs
  16. 246 107
      Examples/UICatalog/Scenarios/TableEditor.cs
  17. 1 1
      Examples/UICatalog/Scenarios/TextInputControls.cs
  18. 12 15
      Examples/UICatalog/Scenarios/TimeAndDate.cs
  19. 1 1
      Examples/UICatalog/Scenarios/TreeViewFileSystem.cs
  20. 9 10
      Examples/UICatalog/Scenarios/Unicode.cs
  21. 5 5
      Examples/UICatalog/UICatalog.cs
  22. 1 1
      README.md
  23. 109 0
      Scripts/Run-LocalCoverage.ps1
  24. 62 56
      Terminal.Gui.Analyzers.Tests/ProjectBuilder.cs
  25. 36 22
      Terminal.Gui/App/Application.Driver.cs
  26. 13 57
      Terminal.Gui/App/Application.Keyboard.cs
  27. 25 77
      Terminal.Gui/App/Application.Lifecycle.cs
  28. 4 6
      Terminal.Gui/App/Application.Mouse.cs
  29. 10 10
      Terminal.Gui/App/Application.Navigation.cs
  30. 50 462
      Terminal.Gui/App/Application.Run.cs
  31. 8 35
      Terminal.Gui/App/Application.Screen.cs
  32. 2 12
      Terminal.Gui/App/Application.Toplevel.cs
  33. 4 4
      Terminal.Gui/App/Application.cd
  34. 7 111
      Terminal.Gui/App/Application.cs
  35. 176 0
      Terminal.Gui/App/ApplicationImpl.Driver.cs
  36. 251 0
      Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
  37. 347 0
      Terminal.Gui/App/ApplicationImpl.Run.cs
  38. 177 0
      Terminal.Gui/App/ApplicationImpl.Screen.cs
  39. 43 488
      Terminal.Gui/App/ApplicationImpl.cs
  40. 2 2
      Terminal.Gui/App/Clipboard/ClipboardBase.cs
  41. 1 1
      Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs
  42. 440 163
      Terminal.Gui/App/IApplication.cs
  43. 1 1
      Terminal.Gui/App/IterationEventArgs.cs
  44. 2 2
      Terminal.Gui/App/Keyboard/IKeyboard.cs
  45. 4 1
      Terminal.Gui/App/Keyboard/KeyboardImpl.cs
  46. 35 39
      Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs
  47. 54 19
      Terminal.Gui/App/MainLoop/IApplicationMainLoop.cs
  48. 1 2
      Terminal.Gui/App/MainLoop/IMainLoopCoordinator.cs
  49. 97 80
      Terminal.Gui/App/MainLoop/MainLoopCoordinator.cs
  50. 2 0
      Terminal.Gui/App/Mouse/MouseImpl.cs
  51. 0 12
      Terminal.Gui/App/RunStateEventArgs.cs
  52. 19 19
      Terminal.Gui/App/SessionToken.cs
  53. 12 0
      Terminal.Gui/App/SessionTokenEventArgs.cs
  54. 1 1
      Terminal.Gui/App/Timeout/ITimedEvents.cs
  55. 1 1
      Terminal.Gui/Drawing/Cell.cs
  56. 3 3
      Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs
  57. 1 1
      Terminal.Gui/Drawing/Ruler.cs
  58. 1 1
      Terminal.Gui/Drawing/Sixel/SixelToRender.cs
  59. 1 1
      Terminal.Gui/Drawing/Thickness.cs
  60. 1 1
      Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequenceRequest.cs
  61. 16 17
      Terminal.Gui/Drivers/AnsiHandling/AnsiResponseParser.cs
  62. 4 4
      Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqUtils.cs
  63. 5 5
      Terminal.Gui/Drivers/AnsiHandling/GenericHeld.cs
  64. 0 26
      Terminal.Gui/Drivers/ComponentFactory.cs
  65. 25 0
      Terminal.Gui/Drivers/ComponentFactoryImpl.cs
  66. 0 753
      Terminal.Gui/Drivers/ConsoleDriver.cs
  67. 0 79
      Terminal.Gui/Drivers/ConsoleInput.cs
  68. 95 0
      Terminal.Gui/Drivers/ConsoleKeyInfoExtensions.cs
  69. 118 2315
      Terminal.Gui/Drivers/ConsoleKeyMapping.cs
  70. 4 1
      Terminal.Gui/Drivers/DotNetDriver/INetInput.cs
  71. 8 17
      Terminal.Gui/Drivers/DotNetDriver/NetComponentFactory.cs
  72. 81 51
      Terminal.Gui/Drivers/DotNetDriver/NetInput.cs
  73. 3 45
      Terminal.Gui/Drivers/DotNetDriver/NetInputProcessor.cs
  74. 4 2
      Terminal.Gui/Drivers/DotNetDriver/NetKeyConverter.cs
  75. 57 20
      Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs
  76. 1 1
      Terminal.Gui/Drivers/DotNetDriver/NetWinVTConsole.cs
  77. 166 102
      Terminal.Gui/Drivers/DriverImpl.cs
  78. 23 23
      Terminal.Gui/Drivers/FakeDriver/FakeComponentFactory.cs
  79. 0 1708
      Terminal.Gui/Drivers/FakeDriver/FakeConsole.cs
  80. 0 42
      Terminal.Gui/Drivers/FakeDriver/FakeConsoleInput.cs
  81. 0 379
      Terminal.Gui/Drivers/FakeDriver/FakeDriver.cs
  82. 47 0
      Terminal.Gui/Drivers/FakeDriver/FakeInput.cs
  83. 47 0
      Terminal.Gui/Drivers/FakeDriver/FakeInputProcessor.cs
  84. 39 14
      Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs
  85. 29 20
      Terminal.Gui/Drivers/IComponentFactory.cs
  86. 0 26
      Terminal.Gui/Drivers/IConsoleDriverFacade.cs
  87. 0 29
      Terminal.Gui/Drivers/IConsoleInput.cs
  88. 61 31
      Terminal.Gui/Drivers/IDriver.cs
  89. 172 0
      Terminal.Gui/Drivers/IInput.cs
  90. 58 39
      Terminal.Gui/Drivers/IInputProcessor.cs
  91. 15 7
      Terminal.Gui/Drivers/IKeyConverter.cs
  92. 28 21
      Terminal.Gui/Drivers/IOutput.cs
  93. 74 59
      Terminal.Gui/Drivers/IOutputBuffer.cs
  94. 1 1
      Terminal.Gui/Drivers/ISizeMonitor.cs
  95. 15 0
      Terminal.Gui/Drivers/ITestableInput.cs
  96. 89 0
      Terminal.Gui/Drivers/InputImpl.cs
  97. 117 79
      Terminal.Gui/Drivers/InputProcessorImpl.cs
  98. 1 1
      Terminal.Gui/Drivers/KeyCode.cs
  99. 2 7
      Terminal.Gui/Drivers/OutputBase.cs
  100. 1 1
      Terminal.Gui/Drivers/OutputBufferImpl.cs

+ 3 - 21
.github/workflows/build.yml → .github/workflows/build-validation.yml

@@ -1,4 +1,4 @@
-name: Build Solution
+name: Build Validation
 
 on:
   push:
@@ -9,18 +9,11 @@ on:
     branches: [ v2_release, v2_develop ]
     paths-ignore:
       - '**.md'
-  workflow_call:
-    outputs:
-      artifact-name:
-        description: "Name of the build artifacts"
-        value: ${{ jobs.build.outputs.artifact-name }}
       
 jobs:
-  build:
-    name: Build Debug & Release
+  build-validation:
+    name: Build All Configurations
     runs-on: ubuntu-latest
-    outputs:
-      artifact-name: build-artifacts
     
     timeout-minutes: 10
     steps:
@@ -63,14 +56,3 @@ jobs:
 
     - name: Build Release Solution
       run: dotnet build --configuration Release --no-restore -property:NoWarn=0618%3B0612
-
-    - name: Upload build artifacts
-      uses: actions/upload-artifact@v4
-      with:
-        name: build-artifacts
-        path: |
-          **/bin/Debug/**
-          **/obj/Debug/**
-          **/bin/Release/**
-          **/obj/Release/**
-        retention-days: 1

+ 77 - 36
.github/workflows/integration-tests.yml

@@ -1,5 +1,4 @@
 name: Build & Run Integration Tests
-
 on:
   push:
     branches: [ v2_release, v2_develop ]
@@ -9,57 +8,99 @@ on:
     branches: [ v2_release, v2_develop ]
     paths-ignore:
       - '**.md'
-      
+
 jobs:
-  # Call the build workflow to build the solution once
   build:
-    uses: ./.github/workflows/build.yml
+    uses: ./.github/workflows/quick-build.yml
 
   integration_tests:
     name: Integration Tests
     runs-on: ${{ matrix.os }}
     needs: build
     strategy:
-      # Turn off fail-fast to let all runners run even if there are errors
-      fail-fast: true
+      fail-fast: false  # Let all OSes finish even if one fails
       matrix:
         os: [ ubuntu-latest, windows-latest, macos-latest ]
+    timeout-minutes: 15
 
-    timeout-minutes: 10
     steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Setup .NET Core
+        uses: actions/setup-dotnet@v4
+        with:
+          dotnet-version: 8.x
+          dotnet-quality: ga
+
+      - name: Download build artifacts
+        uses: actions/download-artifact@v4
+        with:
+          name: test-build-artifacts
+          path: .
 
-    - name: Checkout code
-      uses: actions/checkout@v4
+      - name: Restore NuGet packages
+        run: dotnet restore
 
-    - name: Setup .NET Core
-      uses: actions/setup-dotnet@v4
-      with:
-        dotnet-version: 8.x
-        dotnet-quality: 'ga'
+      - name: Disable Windows Defender (Windows only)
+        if: runner.os == 'Windows'
+        shell: powershell
+        run: |
+          Add-MpPreference -ExclusionPath "${{ github.workspace }}"
+          Add-MpPreference -ExclusionProcess "dotnet.exe"
+          Add-MpPreference -ExclusionProcess "testhost.exe"
+          Add-MpPreference -ExclusionProcess "VSTest.Console.exe"
 
-    - name: Download build artifacts
-      uses: actions/download-artifact@v4
-      with:
-        name: build-artifacts
-        path: .
+      - name: Set VSTEST_DUMP_PATH
+        shell: bash
+        run: echo "VSTEST_DUMP_PATH=logs/IntegrationTests/${{ runner.os }}/" >> $GITHUB_ENV
 
-    - name: Restore NuGet packages
-      run: dotnet restore
+      - name: Run IntegrationTests
+        shell: bash
+        run: |
+          if [ "${{ runner.os }}" == "Linux" ]; then
+            # Run with coverage on Linux only
+            dotnet test Tests/IntegrationTests \
+              --no-build \
+              --verbosity minimal \
+              --collect:"XPlat Code Coverage" \
+              --settings Tests/IntegrationTests/runsettings.coverage.xml \
+              --diag:logs/IntegrationTests/${{ runner.os }}/logs.txt \
+              --blame \
+              --blame-crash \
+              --blame-hang \
+              --blame-hang-timeout 60s \
+              --blame-crash-collect-always
+          else
+            # Run without coverage on Windows/macOS for speed
+            dotnet test Tests/IntegrationTests \
+              --no-build \
+              --verbosity minimal \
+              --settings Tests/IntegrationTests/runsettings.xml \
+              --diag:logs/IntegrationTests/${{ runner.os }}/logs.txt \
+              --blame \
+              --blame-crash \
+              --blame-hang \
+              --blame-hang-timeout 60s \
+              --blame-crash-collect-always
+          fi
 
-    - name: Set VSTEST_DUMP_PATH
-      shell: bash
-      run: echo "{VSTEST_DUMP_PATH}={logs/${{ runner.os }}/}" >> $GITHUB_ENV
+      - name: Upload Integration Test Logs
+        if: always()
+        uses: actions/upload-artifact@v4
+        with:
+          name: integration_tests-logs-${{ runner.os }}
+          path: |
+            logs/IntegrationTests/
+            TestResults/
 
-    - name: Run IntegrationTests
-      run: |
-       dotnet test Tests/IntegrationTests --no-build --verbosity normal --diag:logs/${{ runner.os }}/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always -- xunit.stopOnFail=true
-     
-    - name: Upload Test Logs
-      if: always()
-      uses: actions/upload-artifact@v4
-      with:
-        name: integration-test-logs-${{ runner.os }}
-        path: |
-          logs/    
-          TestResults/IntegrationTests/
+      - name: Upload Integration Tests Coverage to Codecov
+        if: matrix.os == 'ubuntu-latest' && always()
+        uses: codecov/codecov-action@v4
+        with:
+          files: TestResults/**/coverage.cobertura.xml
+          flags: integrationtests
+          name: IntegrationTests-${{ runner.os }}
+          token: ${{ secrets.CODECOV_TOKEN }}
+          fail_ci_if_error: false
 

+ 43 - 0
.github/workflows/quick-build.yml

@@ -0,0 +1,43 @@
+name: Quick Build for Tests
+
+on:
+  workflow_call:
+    outputs:
+      artifact-name:
+        description: "Name of the build artifacts"
+        value: ${{ jobs.quick-build.outputs.artifact-name }}
+      
+jobs:
+  quick-build:
+    name: Build Debug Only
+    runs-on: ubuntu-latest
+    outputs:
+      artifact-name: test-build-artifacts
+    
+    timeout-minutes: 5
+    steps:
+
+    - name: Checkout code
+      uses: actions/checkout@v4
+
+    - name: Setup .NET Core
+      uses: actions/setup-dotnet@v4
+      with:
+        dotnet-version: 8.x
+        dotnet-quality: 'ga'
+
+    - name: Restore dependencies
+      run: dotnet restore
+
+    # Suppress CS0618 (member is obsolete) and CS0612 (member is obsolete without message)
+    - name: Build Debug
+      run: dotnet build --configuration Debug --no-restore -property:NoWarn=0618%3B0612
+
+    - name: Upload build artifacts
+      uses: actions/upload-artifact@v4
+      with:
+        name: test-build-artifacts
+        path: |
+          **/bin/Debug/**
+          **/obj/Debug/**
+        retention-days: 1

+ 104 - 26
.github/workflows/unit-tests.yml

@@ -11,9 +11,9 @@ on:
       - '**.md'
       
 jobs:
-  # Call the build workflow to build the solution once
+  # Call the quick-build workflow to build Debug configuration only
   build:
-    uses: ./.github/workflows/build.yml
+    uses: ./.github/workflows/quick-build.yml
 
   non_parallel_unittests:
     name: Non-Parallel Unit Tests  
@@ -21,11 +21,11 @@ jobs:
     needs: build
     strategy:
       # Turn off fail-fast to let all runners run even if there are errors
-      fail-fast: true
+      fail-fast: false
       matrix:
         os: [ ubuntu-latest, windows-latest, macos-latest ]
 
-    timeout-minutes: 10
+    timeout-minutes: 15  # Increased from 10 for Windows
     steps:
 
     - name: Checkout code
@@ -40,26 +40,56 @@ jobs:
     - name: Download build artifacts
       uses: actions/download-artifact@v4
       with:
-        name: build-artifacts
+        name: test-build-artifacts
         path: .
 
+    # KEEP THIS - It's needed for --no-build to work
     - name: Restore NuGet packages
       run: dotnet restore
 
-# Test
-    # Note: The --blame and VSTEST_DUMP_PATH stuff is needed to diagnose the test runner crashing on ubuntu/mac
-    # See https://github.com/microsoft/vstest/issues/2952 for why the --blame stuff below is needed.
-    # Without it, the test runner crashes on ubuntu (but not Windows or mac)
+    # Optimize Windows performance
+    - name: Disable Windows Defender (Windows only)
+      if: runner.os == 'Windows'
+      shell: powershell
+      run: |
+        Add-MpPreference -ExclusionPath "${{ github.workspace }}"
+        Add-MpPreference -ExclusionProcess "dotnet.exe"
+        Add-MpPreference -ExclusionProcess "testhost.exe"
+        Add-MpPreference -ExclusionProcess "VSTest.Console.exe"
 
     - name: Set VSTEST_DUMP_PATH
       shell: bash
-      run: echo "{VSTEST_DUMP_PATH}={logs/UnitTests/${{ runner.os }}/}" >> $GITHUB_ENV
+      run: echo "VSTEST_DUMP_PATH=logs/UnitTests/${{ runner.os }}/" >> $GITHUB_ENV
 
     - name: Run UnitTests
+      shell: bash
       run: |
-       dotnet test Tests/UnitTests --no-build --verbosity normal --collect:"XPlat Code Coverage" --settings Tests/UnitTests/coverlet.runsettings --diag:logs/UnitTests/${{ runner.os }}/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always -- xunit.stopOnFail=false
-     
-       # mv -v Tests/UnitTests/TestResults/*/*.* TestResults/UnitTests/
+        if [ "${{ runner.os }}" == "Linux" ]; then
+          # Run with coverage on Linux only
+          dotnet test Tests/UnitTests \
+            --no-build \
+            --verbosity normal \
+            --collect:"XPlat Code Coverage" \
+            --settings Tests/UnitTests/runsettings.xml \
+            --diag:logs/UnitTests/${{ runner.os }}/logs.txt \
+            --blame \
+            --blame-crash \
+            --blame-hang \
+            --blame-hang-timeout 60s \
+            --blame-crash-collect-always
+        else
+          # Run without coverage on Windows/macOS for speed
+          dotnet test Tests/UnitTests \
+            --no-build \
+            --verbosity normal \
+            --settings Tests/UnitTests/runsettings.xml \
+            --diag:logs/UnitTests/${{ runner.os }}/logs.txt \
+            --blame \
+            --blame-crash \
+            --blame-hang \
+            --blame-hang-timeout 120s \
+            --blame-crash-collect-always
+        fi
 
     - name: Upload Test Logs
       if: always()
@@ -68,19 +98,29 @@ jobs:
         name: non_parallel_unittests-logs-${{ runner.os }}
         path: |
           logs/UnitTests    
-          TestResults/UnitTests/
+          TestResults/
   
+    - name: Upload Non-Parallel UnitTests Coverage to Codecov
+      if: matrix.os == 'ubuntu-latest' && always()
+      uses: codecov/codecov-action@v4
+      with:
+        files: TestResults/**/coverage.cobertura.xml
+        flags: unittests-nonparallel
+        name: UnitTests-${{ runner.os }}
+        token: ${{ secrets.CODECOV_TOKEN }}
+        fail_ci_if_error: false
+
   parallel_unittests:
     name: Parallel Unit Tests  
     runs-on: ${{ matrix.os }}
     needs: build
     strategy:
       # Turn off fail-fast to let all runners run even if there are errors
-      fail-fast: true
+      fail-fast: false
       matrix:
         os: [ ubuntu-latest, windows-latest, macos-latest ]
 
-    timeout-minutes: 10
+    timeout-minutes: 15
     steps:
 
     - name: Checkout code
@@ -95,26 +135,54 @@ jobs:
     - name: Download build artifacts
       uses: actions/download-artifact@v4
       with:
-        name: build-artifacts
+        name: test-build-artifacts
         path: .
 
     - name: Restore NuGet packages
       run: dotnet restore
 
-# Test
-    # Note: The --blame and VSTEST_DUMP_PATH stuff is needed to diagnose the test runner crashing on ubuntu/mac
-    # See https://github.com/microsoft/vstest/issues/2952 for why the --blame stuff below is needed.
-    # Without it, the test runner crashes on ubuntu (but not Windows or mac)
+    - name: Disable Windows Defender (Windows only)
+      if: runner.os == 'Windows'
+      shell: powershell
+      run: |
+        Add-MpPreference -ExclusionPath "${{ github.workspace }}"
+        Add-MpPreference -ExclusionProcess "dotnet.exe"
+        Add-MpPreference -ExclusionProcess "testhost.exe"
+        Add-MpPreference -ExclusionProcess "VSTest.Console.exe"
 
     - name: Set VSTEST_DUMP_PATH
       shell: bash
-      run: echo "{VSTEST_DUMP_PATH}={logs/UnitTestsParallelizable/${{ runner.os }}/}" >> $GITHUB_ENV
+      run: echo "VSTEST_DUMP_PATH=logs/UnitTestsParallelizable/${{ runner.os }}/" >> $GITHUB_ENV
 
     - name: Run UnitTestsParallelizable
+      shell: bash
       run: |
-       dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal --collect:"XPlat Code Coverage" --settings Tests/UnitTestsParallelizable/coverlet.runsettings --diag:logs/UnitTestsParallelizable/${{ runner.os }}/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always -- xunit.stopOnFail=false
-     
-       # mv -v Tests/UnitTestsParallelizable/TestResults/*/*.* TestResults/UnitTestsParallelizable/
+        if [ "${{ runner.os }}" == "Linux" ]; then
+          # Run with coverage on Linux only
+          dotnet test Tests/UnitTestsParallelizable \
+            --no-build \
+            --verbosity normal \
+            --collect:"XPlat Code Coverage" \
+            --settings Tests/UnitTests/runsettings.coverage.xml \
+            --diag:logs/UnitTestsParallelizable/${{ runner.os }}/logs.txt \
+            --blame \
+            --blame-crash \
+            --blame-hang \
+            --blame-hang-timeout 60s \
+            --blame-crash-collect-always
+        else
+          # Run without coverage on Windows/macOS for speed
+          dotnet test Tests/UnitTestsParallelizable \
+            --no-build \
+            --verbosity normal \
+            --settings Tests/UnitTestsParallelizable/runsettings.xml \
+            --diag:logs/UnitTestsParallelizable/${{ runner.os }}/logs.txt \
+            --blame \
+            --blame-crash \
+            --blame-hang \
+            --blame-hang-timeout 60s \
+            --blame-crash-collect-always
+        fi
 
     - name: Upload UnitTestsParallelizable Logs
       if: always()
@@ -123,4 +191,14 @@ jobs:
         name: parallel_unittests-logs-${{ runner.os }}
         path: |
           logs/UnitTestsParallelizable/
-          TestResults/UnitTestsParallelizable/
+          TestResults/
+  
+    - name: Upload Parallelizable UnitTests Coverage to Codecov
+      if: matrix.os == 'ubuntu-latest' && always()
+      uses: codecov/codecov-action@v4
+      with:
+        files: TestResults/**/coverage.cobertura.xml
+        flags: unittests-parallel
+        name: UnitTestsParallelizable-${{ runner.os }}
+        token: ${{ secrets.CODECOV_TOKEN }}
+        fail_ci_if_error: false

+ 5 - 0
.gitignore

@@ -68,3 +68,8 @@ BenchmarkDotNet.Artifacts/
 *.log.*
 
 log.*
+
+/Tests/coverage/
+!/Tests/coverage/.gitkeep   # keep folder in repo
+/Tests/report/
+*.cobertura.xml

+ 58 - 49
CONTRIBUTING.md

@@ -23,11 +23,10 @@ Welcome! This guide provides everything you need to know to contribute effective
 
 ## Project Overview
 
-**Terminal.Gui** is a cross-platform UI toolkit for creating console-based graphical user interfaces in .NET. It's a large codebase (~1,050 C# files, 333MB) providing a comprehensive framework for building interactive console applications with support for keyboard and mouse input, customizable views, and a robust event system.
+**Terminal.Gui** is a cross-platform UI toolkit for creating console-based graphical user interfaces in .NET. It's a large codebase (~1,050 C# files) providing a comprehensive framework for building interactive console applications with support for keyboard and mouse input, customizable views, and a robust event system.
 
 **Key characteristics:**
 - **Language**: C# (net8.0)
-- **Size**: ~496 source files in core library, ~1,050 total C# files
 - **Platform**: Cross-platform (Windows, macOS, Linux)
 - **Architecture**: Console UI toolkit with driver-based architecture
 - **Version**: v2 (Alpha), v1 (maintenance mode)
@@ -88,25 +87,12 @@ Welcome! This guide provides everything you need to know to contribute effective
    dotnet test Tests/IntegrationTests --no-build --verbosity normal
    ```
 
-**Important**: Tests may take significant time. CI uses blame flags for crash detection:
-```bash
---diag:logs/UnitTests/logs.txt --blame --blame-crash --blame-hang --blame-hang-timeout 60s --blame-crash-collect-always
-```
-
 ### Common Build Issues
 
 #### Issue: Build Warnings
-- **Expected**: ~326 warnings (nullable refs, unused vars, xUnit suggestions)
+- **Expected**: None warnings (~100 currently).
 - **Action**: Don't add new warnings; fix warnings in code you modify
 
-#### Issue: Test Timeouts
-- **Expected**: Tests can take 5-10 minutes
-- **Action**: Use appropriate timeout values (60-120 seconds for test commands)
-
-#### Issue: Restore Failures
-- **Solution**: Ensure `dotnet restore` completes before building
-- **Note**: Takes 15-20 seconds on first run
-
 #### Issue: NativeAot/SelfContained Build
 - **Solution**: Restore these projects explicitly:
   ```bash
@@ -135,19 +121,18 @@ dotnet run --project Examples/UICatalog/UICatalog.csproj
 
 ### Code Formatting
 
+**⚠️ CRITICAL - These rules MUST be followed in ALL new or modified code:**
+
 - **Do NOT add formatting tools** - Use existing `.editorconfig` and `Terminal.sln.DotSettings`
 - Format code with:
   1. ReSharper/Rider (`Ctrl-E-C`)
   2. JetBrains CleanupCode CLI tool (free)
   3. Visual Studio (`Ctrl-K-D`) as fallback
 - **Only format files you modify**
-
-### Critical Coding Rules
-
-**⚠️ CRITICAL - These rules MUST be followed in ALL new or modified code:**
-
-#### Type Declarations and Object Creation
-
+- Follow `.editorconfig` settings (e.g., braces on new lines, spaces after keywords)
+- 4-space indentation
+- No trailing whitespace
+- File-scoped namespaces
 - **ALWAYS use explicit types** - Never use `var` except for built-in simple types (`int`, `string`, `bool`, `double`, `float`, `decimal`, `char`, `byte`)
   ```csharp
   // ✅ CORRECT - Explicit types
@@ -163,7 +148,7 @@ dotnet run --project Examples/UICatalog/UICatalog.csproj
   var views = new List<View?>();
   ```
 
-- **ALWAYS use target-typed `new()`** - Use `new ()` instead of `new TypeName()` when the type is already declared
+- **ALWAYS use target-typed `new ()`** - Use `new ()` instead of `new TypeName()` when the type is already declared
   ```csharp
   // ✅ CORRECT - Target-typed new
   View view = new () { Width = 10 };
@@ -174,13 +159,6 @@ dotnet run --project Examples/UICatalog/UICatalog.csproj
   MouseEventArgs args = new MouseEventArgs();
   ```
 
-#### Other Conventions
-
-- Follow `.editorconfig` settings (e.g., braces on new lines, spaces after keywords)
-- 4-space indentation
-- No trailing whitespace
-- File-scoped namespaces
-
 **⚠️ CRITICAL - These conventions apply to ALL code - production code, test code, examples, and samples.**
 
 ---
@@ -191,6 +169,11 @@ dotnet run --project Examples/UICatalog/UICatalog.csproj
 
 - **Never decrease code coverage** - PRs must maintain or increase coverage
 - Target: 70%+ coverage for new code
+- **Coverage collection**:
+- Centralized in `TestResults/` directory at repository root
+- Collected only on Linux (ubuntu-latest) runners in CI for performance
+- Windows and macOS runners skip coverage collection to reduce execution time
+- Coverage reports uploaded to Codecov automatically from Linux runner
 - CI monitors coverage on each PR
 
 ### Test Patterns
@@ -258,33 +241,58 @@ The repository uses multiple GitHub Actions workflows. What runs and when:
 - **Triggers**: push and pull_request to `v2_release`, `v2_develop` (ignores `**.md`); supports `workflow_call`
 - **Runner/timeout**: `ubuntu-latest`, 10 minutes
 - **Steps**:
-  - Checkout and setup .NET 8.x GA
-  - `dotnet restore`
-  - Build Debug: `dotnet build --configuration Debug --no-restore -property:NoWarn=0618%3B0612`
-  - Build Release (library): `dotnet build Terminal.Gui/Terminal.Gui.csproj --configuration Release --no-incremental --force -property:NoWarn=0618%3B0612`
-  - Pack Release: `dotnet pack Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ./local_packages -property:NoWarn=0618%3B0612`
-  - Restore NativeAot/SelfContained examples, then restore solution again
-  - Build Release for `Examples/NativeAot` and `Examples/SelfContained`
-  - Build Release solution
-  - Upload artifacts named `build-artifacts`, retention 1 day
+- Checkout and setup .NET 8.x GA
+- `dotnet restore`
+- Build Debug: `dotnet build --configuration Debug --no-restore -property:NoWarn=0618%3B0612`
+- Build Release (library): `dotnet build Terminal.Gui/Terminal.Gui.csproj --configuration Release --no-incremental --force -property:NoWarn=0618%3B0612`
+- Pack Release: `dotnet pack Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ./local_packages -property:NoWarn=0618%3B0612`
+- Restore NativeAot/SelfContained examples, then restore solution again
+- Build Release for `Examples/NativeAot` and `Examples/SelfContained`
+- Build Release solution
+- Upload artifacts named `build-artifacts`, retention 1 day
 
 ### 2) Build & Run Unit Tests (`.github/workflows/unit-tests.yml`)
 
 - **Triggers**: push and pull_request to `v2_release`, `v2_develop` (ignores `**.md`)
-- **Process**: Calls build workflow, then runs:
-  - Non-parallel UnitTests on Ubuntu/Windows/macOS matrix with coverage and blame/diag flags; `xunit.stopOnFail=false`
-  - Parallel UnitTestsParallelizable similarly with coverage; `xunit.stopOnFail=false`
-  - Uploads logs per-OS
+- **Matrix**: Ubuntu/Windows/macOS
+- **Timeout**: 15 minutes per job
+- **Process**:
+1. Calls build workflow to build solution once
+2. Downloads build artifacts
+3. Runs `dotnet restore` (required for `--no-build` to work)
+4. **Performance optimizations**:
+   - Disables Windows Defender on Windows runners (significant speedup)
+   - Collects code coverage **only on Linux** (ubuntu-latest) for performance
+   - Windows and macOS skip coverage collection to reduce test time
+   - Increased blame-hang-timeout to 120s for Windows/macOS (60s for Linux)
+5. Runs two test jobs:
+   - **Non-parallel UnitTests**: `Tests/UnitTests` with blame/diag flags; `xunit.stopOnFail=false`
+   - **Parallel UnitTestsParallelizable**: `Tests/UnitTestsParallelizable` with blame/diag flags; `xunit.stopOnFail=false`
+6. Uploads test logs and diagnostic data from all runners
+7. **Uploads code coverage to Codecov only from Linux runner**
+
+**Test results**: All tests output to unified `TestResults/` directory at repository root
 
 ### 3) Build & Run Integration Tests (`.github/workflows/integration-tests.yml`)
 
 - **Triggers**: push and pull_request to `v2_release`, `v2_develop` (ignores `**.md`)
-- **Process**: Calls build workflow, then runs IntegrationTests on matrix with blame/diag; `xunit.stopOnFail=true`
-- Uploads logs per-OS
+- **Matrix**: Ubuntu/Windows/macOS
+- **Timeout**: 15 minutes
+- **Process**:
+1. Calls build workflow
+2. Downloads build artifacts
+3. Runs `dotnet restore`
+4. **Performance optimizations** (same as unit tests):
+   - Disables Windows Defender on Windows runners
+   - Collects code coverage **only on Linux**
+   - Increased blame-hang-timeout to 120s for Windows/macOS
+5. Runs IntegrationTests with blame/diag flags; `xunit.stopOnFail=true`
+6. Uploads logs per-OS
+7. **Uploads coverage to Codecov only from Linux runner**
 
 ### 4) Publish to NuGet (`.github/workflows/publish.yml`)
 
-- **Triggers**: push to `v2_release`, `v2_develop`, and tags `v*` (ignores `**.md`)
+- **Triggers**: push to `v2_release`, `v2_develop`, and tags `v*`(ignores `**.md`)
 - Uses GitVersion to compute SemVer, builds Release, packs with symbols, and pushes to NuGet.org using `NUGET_API_KEY`
 
 ### 5) Build and publish API docs (`.github/workflows/api-docs.yml`)
@@ -292,6 +300,7 @@ The repository uses multiple GitHub Actions workflows. What runs and when:
 - **Triggers**: push to `v1_release` and `v2_develop`
 - Builds DocFX site on Windows and deploys to GitHub Pages when `ref_name` is `v2_release` or `v2_develop`
 
+
 ### Replicating CI Locally
 
 ```bash
@@ -323,9 +332,9 @@ dotnet build --configuration Release --no-restore
 ### Main Directories
 
 **`/Terminal.Gui/`** - Core library (496 C# files):
-- `App/` - Application lifecycle (`Application.cs` static class, `RunState`, `MainLoop`)
+- `App/` - Application lifecycle (`Application.cs` static class, `SessionToken`, `MainLoop`)
 - `Configuration/` - `ConfigurationManager` for settings
-- `Drivers/` - Console driver implementations (`IConsoleDriver`, `NetDriver`, `UnixDriver`, `WindowsDriver`)
+- `Drivers/` - Console driver implementations (`Dotnet`, `Windows`, `Unix`, `Fake`)
 - `Drawing/` - Rendering system (attributes, colors, glyphs)
 - `Input/` - Keyboard and mouse input handling
 - `ViewBase/` - Core `View` class hierarchy and layout

+ 2 - 2
Examples/NativeAot/Program.cs

@@ -12,8 +12,8 @@ namespace NativeAot;
 
 public static class Program
 {
-    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(IConsoleDriver, String)")]
-    [RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(IConsoleDriver, String)")]
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(IDriver, String)")]
+    [RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(IDriver, String)")]
     private static void Main (string [] args)
     {
         ConfigurationManager.Enable(ConfigLocations.All);

+ 1 - 1
Examples/SelfContained/Program.cs

@@ -12,7 +12,7 @@ namespace SelfContained;
 
 public static class Program
 {
-    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Run<T>(Func<Exception, Boolean>, IConsoleDriver)")]
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Run<T>(Func<Exception, Boolean>, IDriver)")]
     private static void Main (string [] args)
     {
         ConfigurationManager.Enable (ConfigLocations.All);

+ 9 - 16
Examples/UICatalog/Scenario.cs

@@ -189,32 +189,25 @@ public class Scenario : IDisposable
             }
 
             Application.Iteration += OnApplicationOnIteration;
-            Application.Driver!.ClearedContents += (sender, args) => BenchmarkResults.ClearedContentCount++;
 
-            if (Application.Driver is ConsoleDriver cd)
-            {
-                cd.Refreshed += (sender, args) =>
-                                                 {
-                                                     BenchmarkResults.RefreshedCount++;
-                                                     if (args.Value)
-                                                     {
-                                                         BenchmarkResults.UpdatedCount++;
-                                                     }
-                                                 };
-
-            }
-            Application.NotifyNewRunState += OnApplicationNotifyNewRunState;
+            Application.Driver!.ClearedContents += OnClearedContents;
+            Application.SessionBegun += OnApplicationSessionBegun;
 
 
             _stopwatch = Stopwatch.StartNew ();
         }
         else
         {
-            Application.NotifyNewRunState -= OnApplicationNotifyNewRunState;
+            Application.Driver!.ClearedContents -= OnClearedContents;
+            Application.SessionBegun -= OnApplicationSessionBegun;
             Application.Iteration -= OnApplicationOnIteration;
             BenchmarkResults.Duration = _stopwatch!.Elapsed;
             _stopwatch?.Stop ();
         }
+
+        return;
+
+        void OnClearedContents (object? sender, EventArgs args) => BenchmarkResults.ClearedContentCount++;
     }
 
     private void OnApplicationOnIteration (object? s, IterationEventArgs a)
@@ -226,7 +219,7 @@ public class Scenario : IDisposable
         }
     }
 
-    private void OnApplicationNotifyNewRunState (object? sender, RunStateEventArgs e)
+    private void OnApplicationSessionBegun (object? sender, SessionTokenEventArgs e)
     {
         SubscribeAllSubViews (Application.Top!);
 

+ 1 - 1
Examples/UICatalog/Scenarios/GraphViewExample.cs

@@ -999,7 +999,7 @@ public class GraphViewExample : Scenario
 
         protected override void DrawBarLine (GraphView graph, Point start, Point end, BarSeriesBar beingDrawn)
         {
-            IConsoleDriver driver = Application.Driver;
+            IDriver driver = Application.Driver;
 
             int x = start.X;
 

+ 1 - 4
Examples/UICatalog/Scenarios/Keys.cs

@@ -158,10 +158,7 @@ public class Keys : Scenario
         appKeyListView.SchemeName = "TopLevel";
         win.Add (onSwallowedListView);
 
-        if (Application.Driver is IConsoleDriverFacade fac)
-        {
-            fac.InputProcessor.AnsiSequenceSwallowed += (s, e) => { swallowedList.Add (e.Replace ("\x1b","Esc")); };
-        }
+        Application.Driver!.InputProcessor.AnsiSequenceSwallowed += (s, e) => { swallowedList.Add (e.Replace ("\x1b", "Esc")); };
 
         Application.KeyDown += (s, a) => KeyDownPressUp (a, "Down");
         Application.KeyUp += (s, a) => KeyDownPressUp (a, "Up");

+ 1 - 1
Examples/UICatalog/Scenarios/Mazing.cs

@@ -129,7 +129,7 @@ public class Mazing : Scenario
             return;
         }
 
-        Point newPos = _m.Player;
+        Point newPos = _m!.Player;
 
         Command? command = e.Context?.Command;
 

+ 14 - 12
Examples/UICatalog/Scenarios/Navigation.cs

@@ -107,18 +107,7 @@ public class Navigation : Scenario
         //                 };
         //timer.Start ();
 
-        Application.Iteration += (sender, args) =>
-                                 {
-                                     if (progressBar.Fraction == 1.0)
-                                     {
-                                         progressBar.Fraction = 0;
-                                     }
-
-                                     progressBar.Fraction += 0.01f;
-
-                                     Application.Invoke (() => { });
-
-                                 };
+        Application.Iteration += OnApplicationIteration;
 
         View overlappedView2 = CreateOverlappedView (3, 8, 10);
 
@@ -214,12 +203,25 @@ public class Navigation : Scenario
 
         testFrame.SetFocus ();
         Application.Run (app);
+        Application.Iteration -= OnApplicationIteration;
         // timer.Close ();
         app.Dispose ();
         Application.Shutdown ();
 
         return;
 
+        void OnApplicationIteration (object sender, IterationEventArgs args)
+        {
+            if (progressBar.Fraction == 1.0)
+            {
+                progressBar.Fraction = 0;
+            }
+
+            progressBar.Fraction += 0.01f;
+
+            Application.Invoke (() => { });
+        }
+
         void ColorPicker_ColorChanged (object sender, ResultEventArgs<Color> e)
         {
             testFrame.SetScheme (testFrame.GetScheme () with { Normal = new (testFrame.GetAttributeForRole (VisualRole.Normal).Foreground, e.Result) });

+ 34 - 33
Examples/UICatalog/Scenarios/Notepad.cs

@@ -1,6 +1,4 @@
-using System.IO;
-using System.Linq;
-
+#nullable enable
 namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("Notepad", "Multi-tab text editor using the TabView control.")]
@@ -9,10 +7,10 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("TextView")]
 public class Notepad : Scenario
 {
-    private TabView _focusedTabView;
-    public Shortcut LenShortcut { get; private set; }
+    private TabView? _focusedTabView;
     private int _numNewTabs = 1;
-    private TabView _tabView;
+    private TabView? _tabView;
+    public Shortcut? LenShortcut { get; private set; }
 
     public override void Main ()
     {
@@ -67,14 +65,15 @@ public class Notepad : Scenario
         top.Add (_tabView);
         LenShortcut = new (Key.Empty, "Len: ", null);
 
-        var statusBar = new StatusBar (new [] {
-                                           new (Application.QuitKey, $"Quit", Quit),
-                                           new Shortcut(Key.F2, "Open", Open),
-                                           new Shortcut(Key.F1, "New", New),
+        var statusBar = new StatusBar (
+                                       [
+                                           new (Application.QuitKey, "Quit", Quit),
+                                           new (Key.F2, "Open", Open),
+                                           new (Key.F1, "New", New),
                                            new (Key.F3, "Save", Save),
                                            new (Key.F6, "Close", Close),
                                            LenShortcut
-                                       }
+                                       ]
                                       )
         {
             AlignmentModes = AlignmentModes.IgnoreFirstOrLast
@@ -97,7 +96,7 @@ public class Notepad : Scenario
         Application.Shutdown ();
     }
 
-    public void Save () { Save (_focusedTabView, _focusedTabView.SelectedTab); }
+    public void Save () { Save (_focusedTabView!, _focusedTabView!.SelectedTab!); }
 
     public void Save (TabView tabViewToSave, Tab tabToSave)
     {
@@ -119,7 +118,7 @@ public class Notepad : Scenario
 
     public bool SaveAs ()
     {
-        var tab = _focusedTabView.SelectedTab as OpenedFile;
+        var tab = _focusedTabView!.SelectedTab as OpenedFile;
 
         if (tab == null)
         {
@@ -152,7 +151,7 @@ public class Notepad : Scenario
         return true;
     }
 
-    private void Close () { Close (_focusedTabView, _focusedTabView.SelectedTab); }
+    private void Close () { Close (_focusedTabView!, _focusedTabView!.SelectedTab!); }
 
     private void Close (TabView tv, Tab tabToClose)
     {
@@ -196,7 +195,7 @@ public class Notepad : Scenario
 
         // close and dispose the tab
         tv.RemoveTab (tab);
-        tab.View.Dispose ();
+        tab.View?.Dispose ();
         _focusedTabView = tv;
 
         // If last tab is closed, open a new one
@@ -217,7 +216,7 @@ public class Notepad : Scenario
         return tv;
     }
 
-    private void New () { Open (null, $"new {_numNewTabs++}"); }
+    private void New () { Open (null!, $"new {_numNewTabs++}"); }
 
     private void Open ()
     {
@@ -246,26 +245,27 @@ public class Notepad : Scenario
 
     /// <summary>Creates a new tab with initial text</summary>
     /// <param name="fileInfo">File that was read or null if a new blank document</param>
+    /// <param name="tabName"></param>
     private void Open (FileInfo fileInfo, string tabName)
     {
         var tab = new OpenedFile (this) { DisplayText = tabName, File = fileInfo };
         tab.View = tab.CreateTextView (fileInfo);
         tab.SavedText = tab.View.Text;
-        tab.RegisterTextViewEvents (_focusedTabView);
+        tab.RegisterTextViewEvents (_focusedTabView!);
 
-        _focusedTabView.AddTab (tab, true);
+        _focusedTabView!.AddTab (tab, true);
     }
 
     private void Quit () { Application.RequestStop (); }
 
-    private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)
+    private void TabView_SelectedTabChanged (object? sender, TabChangedEventArgs e)
     {
-        LenShortcut.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
+        LenShortcut!.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
 
         e.NewTab?.View?.SetFocus ();
     }
 
-    private void TabView_TabClicked (object sender, TabMouseEventArgs e)
+    private void TabView_TabClicked (object? sender, TabMouseEventArgs e)
     {
         // we are only interested in right clicks
         if (!e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked))
@@ -281,12 +281,12 @@ public class Notepad : Scenario
         }
         else
         {
-            var tv = (TabView)sender;
+            var tv = (TabView)sender!;
             var t = (OpenedFile)e.Tab;
 
             items =
             [
-                new MenuItemv2 ("Save", "", () => Save (_focusedTabView, e.Tab)),
+                new MenuItemv2 ("Save", "", () => Save (_focusedTabView!, e.Tab)),
                 new MenuItemv2 ("Close", "", () => Close (tv, e.Tab))
             ];
 
@@ -303,12 +303,12 @@ public class Notepad : Scenario
 
     private class OpenedFile (Notepad notepad) : Tab
     {
-        private Notepad _notepad = notepad;
+        private readonly Notepad _notepad = notepad;
 
         public OpenedFile CloneTo (TabView other)
         {
             var newTab = new OpenedFile (_notepad) { DisplayText = base.Text, File = File };
-            newTab.View = newTab.CreateTextView (newTab.File);
+            newTab.View = newTab.CreateTextView (newTab.File!);
             newTab.SavedText = newTab.View.Text;
             newTab.RegisterTextViewEvents (other);
             other.AddTab (newTab, true);
@@ -316,11 +316,11 @@ public class Notepad : Scenario
             return newTab;
         }
 
-        public View CreateTextView (FileInfo file)
+        public View CreateTextView (FileInfo? file)
         {
             var initialText = string.Empty;
 
-            if (file != null && file.Exists)
+            if (file is { Exists: true })
             {
                 initialText = System.IO.File.ReadAllText (file.FullName);
             }
@@ -336,11 +336,11 @@ public class Notepad : Scenario
             };
         }
 
-        public FileInfo File { get; set; }
+        public FileInfo? File { get; set; }
 
         public void RegisterTextViewEvents (TabView parent)
         {
-            var textView = (TextView)View;
+            var textView = (TextView)View!;
 
             // when user makes changes rename tab to indicate unsaved
             textView.ContentsChanged += (s, k) =>
@@ -362,19 +362,20 @@ public class Notepad : Scenario
                                                     DisplayText = Text.TrimEnd ('*');
                                                 }
                                             }
-                                            _notepad.LenShortcut.Title = $"Len:{textView.Text.Length}";
+
+                                            _notepad.LenShortcut!.Title = $"Len:{textView.Text.Length}";
                                         };
         }
 
         /// <summary>The text of the tab the last time it was saved</summary>
         /// <value></value>
-        public string SavedText { get; set; }
+        public string? SavedText { get; set; }
 
-        public bool UnsavedChanges => !string.Equals (SavedText, View.Text);
+        public bool UnsavedChanges => !string.Equals (SavedText, View!.Text);
 
         internal void Save ()
         {
-            string newText = View.Text;
+            string newText = View!.Text;
 
             if (File is null || string.IsNullOrWhiteSpace (File.FullName))
             {

+ 2 - 2
Examples/UICatalog/Scenarios/NumericUpDownDemo.cs

@@ -266,14 +266,14 @@ internal class NumericUpDownEditor<T> : View where T : notnull
 
             void NumericUpDownOnIncrementChanged (object? o, EventArgs<T> eventArgs)
             {
-                _increment.Text = _numericUpDown.Increment.ToString ();
+                _increment.Text = _numericUpDown!.Increment?.ToString ();
             }
 
             Add (_numericUpDown);
 
             _value.Text = _numericUpDown.Text;
             _format.Text = _numericUpDown.Format;
-            _increment.Text = _numericUpDown.Increment.ToString ();
+            _increment.Text = _numericUpDown!.Increment?.ToString ();
         }
     }
 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 246 - 107
Examples/UICatalog/Scenarios/TableEditor.cs


+ 1 - 1
Examples/UICatalog/Scenarios/TextInputControls.cs

@@ -498,5 +498,5 @@ public class TextInputControls : Scenario
 
 
 
-    private void TimeChanged (object sender, DateTimeEventArgs<TimeSpan> e) { _labelMirroringTimeField.Text = _timeField.Text; }
+    private void TimeChanged (object sender, EventArgs<TimeSpan> e) { _labelMirroringTimeField.Text = _timeField.Text; }
 }

+ 12 - 15
Examples/UICatalog/Scenarios/TimeAndDate.cs

@@ -1,4 +1,5 @@
-using System;
+#nullable enable
+using System;
 
 namespace UICatalog.Scenarios;
 
@@ -7,12 +8,12 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("DateTime")]
 public class TimeAndDate : Scenario
 {
-    private Label _lblDateFmt;
-    private Label _lblNewDate;
-    private Label _lblNewTime;
-    private Label _lblOldDate;
-    private Label _lblOldTime;
-    private Label _lblTimeFmt;
+    private Label? _lblDateFmt;
+    private Label? _lblNewDate;
+    private Label? _lblNewTime;
+    private Label? _lblOldDate;
+    private Label? _lblOldTime;
+    private Label? _lblTimeFmt;
 
     public override void Main ()
     {
@@ -143,17 +144,13 @@ public class TimeAndDate : Scenario
         Application.Shutdown ();
     }
 
-    private void DateChanged (object sender, DateTimeEventArgs<DateTime> e)
+    private void DateChanged (object? sender, EventArgs<DateTime> e)
     {
-        _lblOldDate.Text = $"Old Date: {e.OldValue}";
-        _lblNewDate.Text = $"New Date: {e.NewValue}";
-        _lblDateFmt.Text = $"Date Format: {e.Format}";
+        _lblNewDate!.Text = $"New Date: {e.Value}";
     }
 
-    private void TimeChanged (object sender, DateTimeEventArgs<TimeSpan> e)
+    private void TimeChanged (object? sender, EventArgs<TimeSpan> e)
     {
-        _lblOldTime.Text = $"Old Time: {e.OldValue}";
-        _lblNewTime.Text = $"New Time: {e.NewValue}";
-        _lblTimeFmt.Text = $"Time Format: {e.Format}";
+        _lblNewTime!.Text = $"New Time: {e.Value}";
     }
 }

+ 1 - 1
Examples/UICatalog/Scenarios/TreeViewFileSystem.cs

@@ -414,7 +414,7 @@ public class TreeViewFileSystem : Scenario
 
     private void ShowContextMenu (Point screenPoint, IFileSystemInfo forObject)
     {
-        PopoverMenu? contextMenu = new ([new ("Properties", $"Show {forObject.Name} properties", () => ShowPropertiesOf (forObject))]);
+        PopoverMenu contextMenu = new ([new ("Properties", $"Show {forObject.Name} properties", () => ShowPropertiesOf (forObject))]);
 
         // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused
         // and the context menu is disposed when it is closed.

+ 9 - 10
Examples/UICatalog/Scenarios/Unicode.cs

@@ -71,8 +71,7 @@ public class UnicodeInMenu : Scenario
         appWindow.Add (menu);
 
         var statusBar = new StatusBar (
-                                       new Shortcut []
-                                       {
+                                       [
                                            new (
                                                 Application.QuitKey,
                                                 "Выход",
@@ -80,7 +79,7 @@ public class UnicodeInMenu : Scenario
                                                ),
                                            new (Key.F2, "Создать", null),
                                            new (Key.F3, "Со_хранить", null)
-                                       }
+                                       ]
                                       );
         appWindow.Add (statusBar);
 
@@ -145,13 +144,13 @@ public class UnicodeInMenu : Scenario
         };
         appWindow.Add (checkBox, checkBoxRight);
 
-        label = new () { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1, Text = "ComboBox:" };
-        appWindow.Add (label);
-        var comboBox = new ComboBox { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) };
-        comboBox.SetSource (new ObservableCollection<string> { gitString, "Со_хранить" });
+        //label = new () { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1, Text = "ComboBox:" };
+        //appWindow.Add (label);
+        //var comboBox = new ComboBox { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) };
+        //comboBox.SetSource (new ObservableCollection<string> { gitString, "Со_хранить" });
 
-        appWindow.Add (comboBox);
-        comboBox.Text = gitString;
+        //appWindow.Add (comboBox);
+        //comboBox.Text = gitString;
 
         label = new () { X = Pos.X (label), Y = Pos.Bottom (label) + 2, Text = "HexView:" };
         appWindow.Add (label);
@@ -185,7 +184,7 @@ public class UnicodeInMenu : Scenario
             X = 20,
             Y = Pos.Y (label),
             Width = Dim.Percent (60),
-            RadioLabels = new [] { "item #1", gitString, "Со_хранить", "𝔽𝕆𝕆𝔹𝔸ℝ" }
+            RadioLabels = ["item #1", gitString, "Со_хранить", "𝔽𝕆𝕆𝔹𝔸ℝ"]
         };
         appWindow.Add (radioGroup);
 

+ 5 - 5
Examples/UICatalog/UICatalog.cs

@@ -80,7 +80,7 @@ public class UICatalog
         // Get allowed driver names
         string? [] allowedDrivers = Application.GetDriverTypes ().Item2.ToArray ();
 
-        Option<string> driverOption = new Option<string> ("--driver", "The IConsoleDriver to use.")
+        Option<string> driverOption = new Option<string> ("--driver", "The IDriver to use.")
             .FromAmong (allowedDrivers!);
         driverOption.SetDefaultValue (string.Empty);
         driverOption.AddAlias ("-d");
@@ -635,7 +635,7 @@ public class UICatalog
         if (!View.EnableDebugIDisposableAsserts)
         {
             View.Instances.Clear ();
-            RunState.Instances.Clear ();
+            SessionToken.Instances.Clear ();
 
             return;
         }
@@ -650,15 +650,15 @@ public class UICatalog
 
         View.Instances.Clear ();
 
-        // Validate there are no outstanding Application.RunState-based instances 
+        // Validate there are no outstanding Application sessions
         // after a scenario was selected to run. This proves the main UI Catalog
         // 'app' closed cleanly.
-        foreach (RunState? inst in RunState.Instances)
+        foreach (SessionToken? inst in SessionToken.Instances)
         {
             Debug.Assert (inst.WasDisposed);
         }
 
-        RunState.Instances.Clear ();
+        SessionToken.Instances.Clear ();
 #endif
     }
 }

+ 1 - 1
README.md

@@ -1,6 +1,6 @@
 ![.NET Core](https://github.com/gui-cs/Terminal.Gui/workflows/.NET%20Core/badge.svg?branch=develop)
 [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui)
-![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27/raw/code-coverage.json)
+[![codecov](https://codecov.io/gh/gui-cs/Terminal.Gui/branch/v2_develop/graph/badge.svg)](https://codecov.io/gh/gui-cs/Terminal.Gui)
 [![Downloads](https://img.shields.io/nuget/dt/Terminal.Gui)](https://www.nuget.org/packages/Terminal.Gui)
 [![License](https://img.shields.io/github/license/gui-cs/gui.cs.svg)](LICENSE)
 ![Bugs](https://img.shields.io/github/issues/gui-cs/gui.cs/bug)

+ 109 - 0
Scripts/Run-LocalCoverage.ps1

@@ -0,0 +1,109 @@
+# ------------------------------------------------------------
+# Run-LocalCoverage.ps1
+# Local-only: Unit + Parallel + Integration tests
+# Quiet, merged coverage in /Tests
+# ------------------------------------------------------------
+
+# 1. Define paths
+$testDir    = Join-Path $PWD "Tests"
+$covDir     = Join-Path $testDir "coverage"
+$reportDir  = Join-Path $testDir "report"
+$resultsDir = Join-Path $testDir "TestResults"
+$mergedFile = Join-Path $covDir "coverage.merged.cobertura.xml"
+
+# 2. Clean old results - INCLUDING TestResults directory
+Write-Host "Cleaning old coverage files and test results..."
+Remove-Item -Recurse -Force $covDir, $reportDir, $resultsDir -ErrorAction SilentlyContinue
+New-Item -ItemType Directory -Path $covDir, $reportDir -Force | Out-Null
+
+dotnet build --configuration Debug --no-restore
+
+# ------------------------------------------------------------
+# 3. Run UNIT TESTS (non-parallel)
+# ------------------------------------------------------------
+Write-Host "`nRunning UnitTests (quiet)..."
+dotnet test Tests/UnitTests `
+  --no-build `
+  --verbosity minimal `
+  --collect:"XPlat Code Coverage" `
+  --settings Tests/UnitTests/runsettings.coverage.xml `
+  --blame-hang-timeout 10s
+
+# ------------------------------------------------------------
+# 4. Run UNIT TESTS (parallel)
+# ------------------------------------------------------------
+Write-Host "`nRunning UnitTestsParallelizable (quiet)..."
+dotnet test Tests/UnitTestsParallelizable `
+  --no-build `
+  --verbosity minimal `
+  --collect:"XPlat Code Coverage" `
+  --settings Tests/UnitTestsParallelizable/runsettings.coverage.xml
+
+# ------------------------------------------------------------
+# 5. Run INTEGRATION TESTS
+# ------------------------------------------------------------
+Write-Host "`nRunning IntegrationTests (quiet)..."
+dotnet test Tests/IntegrationTests `
+  --no-build `
+  --verbosity minimal `
+  --collect:"XPlat Code Coverage" `
+  --settings Tests/IntegrationTests/runsettings.coverage.xml
+
+# ------------------------------------------------------------
+# 6. Find ALL coverage files (from all 3 projects) - NOW SCOPED TO Tests/TestResults
+# ------------------------------------------------------------
+Write-Host "`nCollecting coverage files..."
+$covFiles = Get-ChildItem -Path $resultsDir -Recurse -Filter coverage.cobertura.xml -File -ErrorAction SilentlyContinue
+
+if (-not $covFiles) {
+    Write-Error "No coverage files found in $resultsDir. Did all tests run successfully?"
+    exit 1
+}
+
+# ------------------------------------------------------------
+# 7. Move to Tests/coverage
+# ------------------------------------------------------------
+Write-Host "Moving $($covFiles.Count) coverage file(s) to $covDir..."
+$fileIndex = 1
+foreach ($f in $covFiles) {
+    $destFile = Join-Path $covDir "coverage.$fileIndex.cobertura.xml"
+    Copy-Item $f.FullName -Destination $destFile -Force
+    $fileIndex++
+}
+
+# ------------------------------------------------------------
+# 8. Merge into one file
+# ------------------------------------------------------------
+Write-Host "Merging coverage from all test projects..."
+dotnet-coverage merge `
+    -o $mergedFile `
+    -f cobertura `
+    "$covDir\*.cobertura.xml"
+
+# ------------------------------------------------------------
+# 9. Generate HTML + text report
+# ------------------------------------------------------------
+Write-Host "Generating final HTML report..."
+reportgenerator `
+    -reports:$mergedFile `
+    -targetdir:$reportDir `
+    -reporttypes:"Html;TextSummary"
+
+# ------------------------------------------------------------
+# 10. Show summary + open report
+# ------------------------------------------------------------
+Write-Host "`n=== Final Coverage Summary (Unit + Integration) ==="
+Get-Content "$reportDir\Summary.txt"
+
+$indexHtml = Join-Path $reportDir "index.html"
+if (Test-Path $indexHtml) {
+    Write-Host "Opening report in browser..."
+    Start-Process $indexHtml
+} else {
+    Write-Warning "HTML report not found at $indexHtml"
+}
+
+Write-Host "`nCoverage artifacts:"
+Write-Host "  - Merged coverage: $mergedFile"
+Write-Host "  - HTML report:     $reportDir\index.html"
+Write-Host "  - Test results:    $resultsDir"

+ 62 - 56
Terminal.Gui.Analyzers.Tests/ProjectBuilder.cs

@@ -17,10 +17,10 @@ using JetBrains.Annotations;
 
 public sealed class ProjectBuilder
 {
-    private string _sourceCode;
-    private string _expectedFixedCode;
-    private DiagnosticAnalyzer _analyzer;
-    private CodeFixProvider _codeFix;
+    private string? _sourceCode;
+    private string? _expectedFixedCode;
+    private DiagnosticAnalyzer? _analyzer;
+    private CodeFixProvider? _codeFix;
 
     public ProjectBuilder WithSourceCode (string source)
     {
@@ -59,20 +59,22 @@ public sealed class ProjectBuilder
         }
 
         // Parse original document
-        var document = CreateDocument (_sourceCode);
-        var compilation = await document.Project.GetCompilationAsync ();
+        Document document = CreateDocument (_sourceCode);
+        Compilation? compilation = await document.Project.GetCompilationAsync ();
 
-        var diagnostics = compilation.GetDiagnostics ();
-        var errors = diagnostics.Where (d => d.Severity == DiagnosticSeverity.Error);
+        ImmutableArray<Diagnostic> diagnostics = compilation!.GetDiagnostics ();
+        IEnumerable<Diagnostic> errors = diagnostics.Where (d => d.Severity == DiagnosticSeverity.Error);
 
-        if (errors.Any ())
+        IEnumerable<Diagnostic> enumerable = errors as Diagnostic [] ?? errors.ToArray ();
+
+        if (enumerable.Any ())
         {
-            var errorMessages = string.Join (Environment.NewLine, errors.Select (e => e.ToString ()));
+            string errorMessages = string.Join (Environment.NewLine, enumerable.Select (e => e.ToString ()));
             throw new Exception ("Compilation failed with errors:" + Environment.NewLine + errorMessages);
         }
 
         // Run analyzer
-        var analyzerDiagnostics = await GetAnalyzerDiagnosticsAsync (compilation, _analyzer);
+        ImmutableArray<Diagnostic> analyzerDiagnostics = await GetAnalyzerDiagnosticsAsync (compilation, _analyzer);
 
         Assert.NotEmpty (analyzerDiagnostics);
 
@@ -83,83 +85,87 @@ public sealed class ProjectBuilder
                 throw new InvalidOperationException ("Expected code fix but none was set.");
             }
 
-            var fixedDocument = await ApplyCodeFixAsync (document, analyzerDiagnostics.First (), _codeFix);
+            Document? fixedDocument = await ApplyCodeFixAsync (document, analyzerDiagnostics.First (), _codeFix);
 
-            var formattedDocument = await Formatter.FormatAsync (fixedDocument);
-            var fixedSource = (await formattedDocument.GetTextAsync ()).ToString ();
+            if (fixedDocument is { })
+            {
+                Document formattedDocument = await Formatter.FormatAsync (fixedDocument);
+                string fixedSource = (await formattedDocument.GetTextAsync ()).ToString ();
 
-            Assert.Equal (_expectedFixedCode, fixedSource);
+                Assert.Equal (_expectedFixedCode, fixedSource);
+            }
         }
     }
 
     private static Document CreateDocument (string source)
     {
-        var dd = typeof (Enumerable).GetTypeInfo ().Assembly.Location;
-        var coreDir = Directory.GetParent (dd) ?? throw new Exception ($"Could not find parent directory of dotnet sdk.  Sdk directory was {dd}");
-
-        var workspace = new AdhocWorkspace ();
-        var projectId = ProjectId.CreateNewId ();
-        var documentId = DocumentId.CreateNewId (projectId);
-
-        var references = new List<MetadataReference> ()
-        {
-            MetadataReference.CreateFromFile(typeof(Button).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(View).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(System.IO.FileSystemInfo).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(MarshalByValueComponent).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(ObservableCollection<string>).Assembly.Location),
+        string dd = typeof (Enumerable).GetTypeInfo ().Assembly.Location;
+        DirectoryInfo coreDir = Directory.GetParent (dd) ?? throw new Exception ($"Could not find parent directory of dotnet sdk.  Sdk directory was {dd}");
+
+        AdhocWorkspace workspace = new AdhocWorkspace ();
+        ProjectId projectId = ProjectId.CreateNewId ();
+        DocumentId documentId = DocumentId.CreateNewId (projectId);
+
+        List<MetadataReference> references =
+        [
+            MetadataReference.CreateFromFile (typeof (Button).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (View).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (System.IO.FileSystemInfo).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (System.Linq.Enumerable).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (object).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (MarshalByValueComponent).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (ObservableCollection<string>).Assembly.Location),
 
             // New assemblies required by Terminal.Gui version 2
-            MetadataReference.CreateFromFile(typeof(Size).Assembly.Location),
-            MetadataReference.CreateFromFile(typeof(CanBeNullAttribute).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (Size).Assembly.Location),
+            MetadataReference.CreateFromFile (typeof (CanBeNullAttribute).Assembly.Location),
+
 
+            MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "mscorlib.dll")),
+            MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "System.Runtime.dll")),
+            MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "System.Collections.dll")),
+            MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "System.Data.Common.dll"))
 
-            MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "mscorlib.dll")),
-            MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Runtime.dll")),
-            MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Collections.dll")),
-            MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Data.Common.dll")),
             // Add more as necessary
-        };
+        ];
 
 
-        var projectInfo = ProjectInfo.Create (
-                                              projectId,
-                                              VersionStamp.Create (),
-                                              "TestProject",
-                                              "TestAssembly",
-                                              LanguageNames.CSharp,
-                                              compilationOptions: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary),
-                                              metadataReferences: references);
+        ProjectInfo projectInfo = ProjectInfo.Create (
+                                                      projectId,
+                                                      VersionStamp.Create (),
+                                                      "TestProject",
+                                                      "TestAssembly",
+                                                      LanguageNames.CSharp,
+                                                      compilationOptions: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary),
+                                                      metadataReferences: references);
 
-        var solution = workspace.CurrentSolution
-                                .AddProject (projectInfo)
-                                .AddDocument (documentId, "Test.cs", SourceText.From (source));
+        Solution solution = workspace.CurrentSolution
+                                     .AddProject (projectInfo)
+                                     .AddDocument (documentId, "Test.cs", SourceText.From (source));
 
         return solution.GetDocument (documentId)!;
     }
 
     private static async Task<ImmutableArray<Diagnostic>> GetAnalyzerDiagnosticsAsync (Compilation compilation, DiagnosticAnalyzer analyzer)
     {
-        var compilationWithAnalyzers = compilation.WithAnalyzers (ImmutableArray.Create (analyzer));
+        CompilationWithAnalyzers compilationWithAnalyzers = compilation.WithAnalyzers ([analyzer]);
         return await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync ();
     }
 
-    private static async Task<Document> ApplyCodeFixAsync (Document document, Diagnostic diagnostic, CodeFixProvider codeFix)
+    private static async Task<Document?> ApplyCodeFixAsync (Document document, Diagnostic diagnostic, CodeFixProvider codeFix)
     {
-        CodeAction _codeAction = null;
-        var context = new CodeFixContext ((TextDocument)document, diagnostic, (action, _) => _codeAction = action, CancellationToken.None);
+        CodeAction? codeAction = null;
+        var context = new CodeFixContext ((TextDocument)document, diagnostic, (action, _) => codeAction = action, CancellationToken.None);
 
         await codeFix.RegisterCodeFixesAsync (context);
 
-        if (_codeAction == null)
+        if (codeAction == null)
         {
             throw new InvalidOperationException ("Code fix did not register a fix.");
         }
 
-        var operations = await _codeAction.GetOperationsAsync (CancellationToken.None);
-        var solution = operations.OfType<ApplyChangesOperation> ().First ().ChangedSolution;
+        ImmutableArray<CodeActionOperation> operations = await codeAction.GetOperationsAsync (CancellationToken.None);
+        Solution solution = operations.OfType<ApplyChangesOperation> ().First ().ChangedSolution;
         return solution.GetDocument (document.Id);
     }
 }

+ 36 - 22
Terminal.Gui/App/Application.Driver.cs

@@ -1,23 +1,19 @@
 #nullable enable
 
+using System.Diagnostics.CodeAnalysis;
+
 namespace Terminal.Gui.App;
 
 public static partial class Application // Driver abstractions
 {
-    internal static bool _forceFakeConsole;
-
-    /// <summary>Gets the <see cref="IConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.</summary>
-    public static IConsoleDriver? Driver
+    /// <inheritdoc cref="IApplication.Driver"/>
+    public static IDriver? Driver
     {
         get => ApplicationImpl.Instance.Driver;
         internal set => ApplicationImpl.Instance.Driver = value;
     }
 
-    /// <summary>
-    ///     Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in
-    ///     <see cref="ColorName16"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be output
-    ///     as long as the selected <see cref="IConsoleDriver"/> supports TrueColor.
-    /// </summary>
+    /// <inheritdoc cref="IApplication.Force16Colors"/>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static bool Force16Colors
     {
@@ -25,14 +21,7 @@ public static partial class Application // Driver abstractions
         set => ApplicationImpl.Instance.Force16Colors = value;
     }
 
-    /// <summary>
-    ///     Forces the use of the specified driver (one of "fake", "dotnet", "windows", or "unix"). If not
-    ///     specified, the driver is selected based on the platform.
-    /// </summary>
-    /// <remarks>
-    ///     Note, <see cref="Application.Init(IConsoleDriver, string)"/> will override this configuration setting if called
-    ///     with either `driver` or `driverName` specified.
-    /// </remarks>
+    /// <inheritdoc cref="IApplication.ForceDriver"/>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static string ForceDriver
     {
@@ -40,9 +29,34 @@ public static partial class Application // Driver abstractions
         set => ApplicationImpl.Instance.ForceDriver = value;
     }
 
-    /// <summary>
-    /// Collection of sixel images to write out to screen when updating.
-    /// Only add to this collection if you are sure terminal supports sixel format.
-    /// </summary>
+    /// <inheritdoc cref="IApplication.Sixel"/>
     public static List<SixelToRender> Sixel => ApplicationImpl.Instance.Sixel;
-}
+
+    /// <summary>Gets a list of <see cref="IDriver"/> types and type names that are available.</summary>
+    /// <returns></returns>
+    [RequiresUnreferencedCode ("AOT")]
+    public static (List<Type?>, List<string?>) GetDriverTypes ()
+    {
+        // use reflection to get the list of drivers
+        List<Type?> driverTypes = new ();
+
+        // Only inspect the IDriver assembly
+        var asm = typeof (IDriver).Assembly;
+
+        foreach (Type? type in asm.GetTypes ())
+        {
+            if (typeof (IDriver).IsAssignableFrom (type) && type is { IsAbstract: false, IsClass: true })
+            {
+                driverTypes.Add (type);
+            }
+        }
+
+        List<string?> driverTypeNames = driverTypes
+                                        .Where (d => !typeof (IDriver).IsAssignableFrom (d))
+                                        .Select (d => d!.Name)
+                                        .Union (["dotnet", "windows", "unix", "fake"])
+                                        .ToList ()!;
+
+        return (driverTypes, driverTypeNames);
+    }
+}

+ 13 - 57
Terminal.Gui/App/Application.Keyboard.cs

@@ -4,9 +4,7 @@ namespace Terminal.Gui.App;
 
 public static partial class Application // Keyboard handling
 {
-    /// <summary>
-    /// Static reference to the current <see cref="IApplication"/> <see cref="IKeyboard"/>.
-    /// </summary>
+    /// <inheritdoc cref="IApplication.Keyboard"/>
     public static IKeyboard Keyboard
     {
         get => ApplicationImpl.Instance.Keyboard;
@@ -14,40 +12,14 @@ public static partial class Application // Keyboard handling
                                                            throw new ArgumentNullException(nameof(value));
     }
 
-    /// <summary>
-    ///     Called when the user presses a key (by the <see cref="IConsoleDriver"/>). Raises the cancelable
-    ///     <see cref="KeyDown"/> event, then calls <see cref="View.NewKeyDownEvent"/> on all top level views, and finally
-    ///     if the key was not handled, invokes any Application-scoped <see cref="KeyBindings"/>.
-    /// </summary>
-    /// <remarks>Can be used to simulate key press events.</remarks>
-    /// <param name="key"></param>
-    /// <returns><see langword="true"/> if the key was handled.</returns>
-    public static bool RaiseKeyDownEvent (Key key) => Keyboard.RaiseKeyDownEvent (key);
+    /// <inheritdoc cref="IKeyboard.RaiseKeyDownEvent"/>
+    public static bool RaiseKeyDownEvent (Key key) => ApplicationImpl.Instance.Keyboard.RaiseKeyDownEvent (key);
 
-    /// <summary>
-    ///     Invokes any commands bound at the Application-level to <paramref name="key"/>.
-    /// </summary>
-    /// <param name="key"></param>
-    /// <returns>
-    ///     <see langword="null"/> if no command was found; input processing should continue.
-    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input processing should continue.
-    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
-    /// </returns>
-    public static bool? InvokeCommandsBoundToKey (Key key) => Keyboard.InvokeCommandsBoundToKey (key);
+    /// <inheritdoc cref="IKeyboard.InvokeCommandsBoundToKey"/>
+    public static bool? InvokeCommandsBoundToKey (Key key) => ApplicationImpl.Instance.Keyboard.InvokeCommandsBoundToKey (key);
 
-    /// <summary>
-    ///     Invokes an Application-bound command.
-    /// </summary>
-    /// <param name="command">The Command to invoke</param>
-    /// <param name="key">The Application-bound Key that was pressed.</param>
-    /// <param name="binding">Describes the binding.</param>
-    /// <returns>
-    ///     <see langword="null"/> if no command was found; input processing should continue.
-    ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); input processing should continue.
-    ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
-    /// </returns>
-    /// <exception cref="NotSupportedException"></exception>
-    public static bool? InvokeCommand (Command command, Key key, KeyBinding binding) => Keyboard.InvokeCommand (command, key, binding);
+    /// <inheritdoc cref="IKeyboard.InvokeCommand"/>
+    public static bool? InvokeCommand (Command command, Key key, KeyBinding binding) => ApplicationImpl.Instance.Keyboard.InvokeCommand (command, key, binding);
 
     /// <summary>
     ///     Raised when the user presses a key.
@@ -63,29 +35,13 @@ public static partial class Application // Keyboard handling
     /// </remarks>
     public static event EventHandler<Key>? KeyDown
     {
-        add => Keyboard.KeyDown += value;
-        remove => Keyboard.KeyDown -= value;
+        add => ApplicationImpl.Instance.Keyboard.KeyDown += value;
+        remove => ApplicationImpl.Instance.Keyboard.KeyDown -= value;
     }
 
-    /// <summary>
-    ///     Called when the user releases a key (by the <see cref="IConsoleDriver"/>). Raises the cancelable
-    ///     <see cref="KeyUp"/>
-    ///     event
-    ///     then calls <see cref="View.NewKeyUpEvent"/> on all top level views. Called after <see cref="RaiseKeyDownEvent"/>.
-    /// </summary>
-    /// <remarks>Can be used to simulate key release events.</remarks>
-    /// <param name="key"></param>
-    /// <returns><see langword="true"/> if the key was handled.</returns>
-    public static bool RaiseKeyUpEvent (Key key) => Keyboard.RaiseKeyUpEvent (key);
-
-    /// <summary>Gets the Application-scoped key bindings.</summary>
-    public static KeyBindings KeyBindings => Keyboard.KeyBindings;
+    /// <inheritdoc cref="IKeyboard.RaiseKeyUpEvent"/>
+    public static bool RaiseKeyUpEvent (Key key) => ApplicationImpl.Instance.Keyboard.RaiseKeyUpEvent (key);
 
-    internal static void AddKeyBindings ()
-    {
-        if (Keyboard is KeyboardImpl keyboard)
-        {
-            keyboard.AddKeyBindings ();
-        }
-    }
+    /// <inheritdoc cref="IKeyboard.KeyBindings"/>
+    public static KeyBindings KeyBindings => ApplicationImpl.Instance.Keyboard.KeyBindings;
 }

+ 25 - 77
Terminal.Gui/App/Application.Lifecycle.cs

@@ -2,6 +2,10 @@
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
+using Microsoft.VisualBasic;
+using Terminal.Gui.App;
+using Terminal.Gui.Drivers;
+using Terminal.Gui.Views;
 
 namespace Terminal.Gui.App;
 
@@ -11,7 +15,7 @@ public static partial class Application // Lifecycle (Init/Shutdown)
     /// <summary>Initializes a new instance of a Terminal.Gui Application. <see cref="Shutdown"/> must be called when the application is closing.</summary>
     /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
     /// <para>
-    ///     This function loads the right <see cref="IConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
+    ///     This function loads the right <see cref="IDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
     ///     assigns it to <see cref="Top"/>
     /// </para>
     /// <para>
@@ -22,90 +26,36 @@ public static partial class Application // Lifecycle (Init/Shutdown)
     /// </para>
     /// <para>
     ///     The <see cref="Run{T}"/> function combines
-    ///     <see cref="Init(IConsoleDriver,string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
+    ///     <see cref="Init(IDriver,string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
     ///     into a single
     ///     call. An application can use <see cref="Run{T}"/> without explicitly calling
-    ///     <see cref="Init(IConsoleDriver,string)"/>.
+    ///     <see cref="Init(IDriver,string)"/>.
     /// </para>
     /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or
+    ///     The <see cref="IDriver"/> to use. If neither <paramref name="driver"/> or
     ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
     /// </param>
     /// <param name="driverName">
     ///     The short name (e.g. "dotnet", "windows", "unix", or "fake") of the
-    ///     <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
+    ///     <see cref="IDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
     ///     specified the default driver for the platform will be used.
     /// </param>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public static void Init (IConsoleDriver? driver = null, string? driverName = null)
+    public static void Init (IDriver? driver = null, string? driverName = null)
     {
         ApplicationImpl.Instance.Init (driver, driverName ?? ForceDriver);
     }
 
-    internal static int MainThreadId
+    /// <summary>
+    ///     Gets or sets the main thread ID for the application.
+    /// </summary>
+    public static int? MainThreadId
     {
         get => ((ApplicationImpl)ApplicationImpl.Instance).MainThreadId;
         set => ((ApplicationImpl)ApplicationImpl.Instance).MainThreadId = value;
     }
 
-    internal static void SubscribeDriverEvents ()
-    {
-        ArgumentNullException.ThrowIfNull (Driver);
-
-        Driver.SizeChanged += Driver_SizeChanged;
-        Driver.KeyDown += Driver_KeyDown;
-        Driver.KeyUp += Driver_KeyUp;
-        Driver.MouseEvent += Driver_MouseEvent;
-    }
-
-    internal static void UnsubscribeDriverEvents ()
-    {
-        ArgumentNullException.ThrowIfNull (Driver);
-
-        Driver.SizeChanged -= Driver_SizeChanged;
-        Driver.KeyDown -= Driver_KeyDown;
-        Driver.KeyUp -= Driver_KeyUp;
-        Driver.MouseEvent -= Driver_MouseEvent;
-    }
-
-    private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e)
-    {
-        RaiseScreenChangedEvent (new Rectangle (new (0, 0), e.Size!.Value));
-    }
-    private static void Driver_KeyDown (object? sender, Key e) { RaiseKeyDownEvent (e); }
-    private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); }
-    private static void Driver_MouseEvent (object? sender, MouseEventArgs e) { RaiseMouseEvent (e); }
-
-    /// <summary>Gets a list of <see cref="IConsoleDriver"/> types and type names that are available.</summary>
-    /// <returns></returns>
-    [RequiresUnreferencedCode ("AOT")]
-    public static (List<Type?>, List<string?>) GetDriverTypes ()
-    {
-        // use reflection to get the list of drivers
-        List<Type?> driverTypes = new ();
-
-        // Only inspect the IConsoleDriver assembly
-        var asm = typeof (IConsoleDriver).Assembly;
-
-        foreach (Type? type in asm.GetTypes ())
-        {
-            if (typeof (IConsoleDriver).IsAssignableFrom (type) &&
-                type is { IsAbstract: false, IsClass: true })
-            {
-                driverTypes.Add (type);
-            }
-        }
-
-        List<string?> driverTypeNames = driverTypes
-                                        .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
-                                        .Select (d => d!.Name)
-                                        .Union (["dotnet", "windows", "unix", "fake"])
-                                        .ToList ()!;
-
-        return (driverTypes, driverTypeNames);
-    }
-
     /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
     /// <remarks>
     ///     Shutdown must be called for every call to <see cref="Init"/> or
@@ -129,19 +79,17 @@ public static partial class Application // Lifecycle (Init/Shutdown)
         internal set => ApplicationImpl.Instance.Initialized = value;
     }
 
-    /// <summary>
-    ///     This event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
-    /// </summary>
-    /// <remarks>
-    ///     Intended to support unit tests that need to know when the application has been initialized.
-    /// </remarks>
-    public static event EventHandler<EventArgs<bool>>? InitializedChanged;
-
-    /// <summary>
-    ///  Raises the <see cref="InitializedChanged"/> event.
-    /// </summary>
-    internal static void OnInitializedChanged (object sender, EventArgs<bool> e)
+    /// <inheritdoc cref="IApplication.InitializedChanged"/>
+    public static event EventHandler<EventArgs<bool>>? InitializedChanged
     {
-        Application.InitializedChanged?.Invoke (sender, e);
+        add => ApplicationImpl.Instance.InitializedChanged += value;
+        remove => ApplicationImpl.Instance.InitializedChanged -= value;
     }
+
+    // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
+    // Encapsulate all setting of initial state for Application; Having
+    // this in a function like this ensures we don't make mistakes in
+    // guaranteeing that the state of this singleton is deterministic when Init
+    // starts running and after Shutdown returns.
+    internal static void ResetState (bool ignoreDisposed = false) => ApplicationImpl.Instance?.ResetState (ignoreDisposed);
 }

+ 4 - 6
Terminal.Gui/App/Application.Mouse.cs

@@ -92,10 +92,8 @@ public static partial class Application // Mouse handling
     /// </summary>
     /// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
     /// <param name="mouseEvent">The mouse event with coordinates relative to the screen.</param>
-    internal static void RaiseMouseEvent (MouseEventArgs mouseEvent) { Mouse.RaiseMouseEvent (mouseEvent); }
-
-    /// <summary>
-    ///     INTERNAL: Clears mouse state during application reset.
-    /// </summary>
-    internal static void ResetMouseState () { Mouse.ResetState (); }
+    internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
+    {
+        Mouse.RaiseMouseEvent (mouseEvent);
+    }
 }

+ 10 - 10
Terminal.Gui/App/Application.Navigation.cs

@@ -17,16 +17,16 @@ public static partial class Application // Navigation stuff
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key NextTabGroupKey
     {
-        get => Keyboard.NextTabGroupKey;
-        set => Keyboard.NextTabGroupKey = value;
+        get => ApplicationImpl.Instance.Keyboard.NextTabGroupKey;
+        set => ApplicationImpl.Instance.Keyboard.NextTabGroupKey = value;
     }
 
     /// <summary>Alternative key to navigate forwards through views. Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key NextTabKey
     {
-        get => Keyboard.NextTabKey;
-        set => Keyboard.NextTabKey = value;
+        get => ApplicationImpl.Instance.Keyboard.NextTabKey;
+        set => ApplicationImpl.Instance.Keyboard.NextTabKey = value;
     }
 
     /// <summary>
@@ -43,23 +43,23 @@ public static partial class Application // Navigation stuff
     /// </remarks>
     public static event EventHandler<Key>? KeyUp
     {
-        add => Keyboard.KeyUp += value;
-        remove => Keyboard.KeyUp -= value;
+        add => ApplicationImpl.Instance.Keyboard.KeyUp += value;
+        remove => ApplicationImpl.Instance.Keyboard.KeyUp -= value;
     }
 
     /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key PrevTabGroupKey
     {
-        get => Keyboard.PrevTabGroupKey;
-        set => Keyboard.PrevTabGroupKey = value;
+        get => ApplicationImpl.Instance.Keyboard.PrevTabGroupKey;
+        set => ApplicationImpl.Instance.Keyboard.PrevTabGroupKey = value;
     }
 
     /// <summary>Alternative key to navigate backwards through views. Shift+Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key PrevTabKey
     {
-        get => Keyboard.PrevTabKey;
-        set => Keyboard.PrevTabKey = value;
+        get => ApplicationImpl.Instance.Keyboard.PrevTabKey;
+        set => ApplicationImpl.Instance.Keyboard.PrevTabKey = value;
     }
 }

+ 50 - 462
Terminal.Gui/App/Application.Run.cs

@@ -1,5 +1,4 @@
 #nullable enable
-using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 
 namespace Terminal.Gui.App;
@@ -10,498 +9,87 @@ public static partial class Application // Run (Begin -> Run -> Layout/Draw -> E
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key QuitKey
     {
-        get => Keyboard.QuitKey;
-        set => Keyboard.QuitKey = value;
+        get => ApplicationImpl.Instance.Keyboard.QuitKey;
+        set => ApplicationImpl.Instance.Keyboard.QuitKey = value;
     }
 
     /// <summary>Gets or sets the key to activate arranging views using the keyboard.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key ArrangeKey
     {
-        get => Keyboard.ArrangeKey;
-        set => Keyboard.ArrangeKey = value;
+        get => ApplicationImpl.Instance.Keyboard.ArrangeKey;
+        set => ApplicationImpl.Instance.Keyboard.ArrangeKey = value;
     }
 
-    /// <summary>
-    ///     Notify that a new <see cref="RunState"/> was created (<see cref="Begin(Toplevel)"/> was called). The token is
-    ///     created in <see cref="Begin(Toplevel)"/> and this event will be fired before that function exits.
-    /// </summary>
-    /// <remarks>
-    ///     If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to <see cref="Begin(Toplevel)"/>
-    ///     must also subscribe to <see cref="NotifyStopRunState"/> and manually dispose of the <see cref="RunState"/> token
-    ///     when the application is done.
-    /// </remarks>
-    public static event EventHandler<RunStateEventArgs>? NotifyNewRunState;
+    /// <inheritdoc cref="IApplication.Begin"/>
+    public static SessionToken Begin (Toplevel toplevel) => ApplicationImpl.Instance.Begin (toplevel);
 
-    /// <summary>Notify that an existent <see cref="RunState"/> is stopping (<see cref="End(RunState)"/> was called).</summary>
-    /// <remarks>
-    ///     If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to <see cref="Begin(Toplevel)"/>
-    ///     must also subscribe to <see cref="NotifyStopRunState"/> and manually dispose of the <see cref="RunState"/> token
-    ///     when the application is done.
-    /// </remarks>
-#pragma warning disable CS0067 // Event is never used
-#pragma warning disable CS0414 // Event is never used
-    public static event EventHandler<ToplevelEventArgs>? NotifyStopRunState;
-#pragma warning restore CS0414 // Event is never used
-#pragma warning restore CS0067 // Event is never used
+    /// <inheritdoc cref="IApplication.PositionCursor"/>
+    public static bool PositionCursor () => ApplicationImpl.Instance.PositionCursor ();
 
-    /// <summary>Building block API: Prepares the provided <see cref="Toplevel"/> for execution.</summary>
-    /// <returns>
-    ///     The <see cref="RunState"/> handle that needs to be passed to the <see cref="End(RunState)"/> method upon
-    ///     completion.
-    /// </returns>
-    /// <param name="toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
-    /// <remarks>
-    ///     This method prepares the provided <see cref="Toplevel"/> for running with the focus, it adds this to the list
-    ///     of <see cref="Toplevel"/>s, lays out the SubViews, focuses the first element, and draws the <see cref="Toplevel"/>
-    ///     in the screen. This is usually followed by executing the <see cref="RunLoop"/> method, and then the
-    ///     <see cref="End(RunState)"/> method upon termination which will undo these changes.
-    /// </remarks>
-    public static RunState Begin (Toplevel toplevel)
-    {
-        ArgumentNullException.ThrowIfNull (toplevel);
-
-        // Ensure the mouse is ungrabbed.
-        if (Mouse.MouseGrabView is { })
-        {
-            Mouse.UngrabMouse ();
-        }
-
-        var rs = new RunState (toplevel);
-
-#if DEBUG_IDISPOSABLE
-        if (View.EnableDebugIDisposableAsserts && Top is { } && toplevel != Top && !TopLevels.Contains (Top))
-        {
-            // This assertion confirm if the Top was already disposed
-            Debug.Assert (Top.WasDisposed);
-            Debug.Assert (Top == CachedRunStateToplevel);
-        }
-#endif
-
-        lock (TopLevels)
-        {
-            if (Top is { } && toplevel != Top && !TopLevels.Contains (Top))
-            {
-                // If Top was already disposed and isn't on the Toplevels Stack,
-                // clean it up here if is the same as _cachedRunStateToplevel
-                if (Top == CachedRunStateToplevel)
-                {
-                    Top = null;
-                }
-                else
-                {
-                    // Probably this will never hit
-                    throw new ObjectDisposedException (Top.GetType ().FullName);
-                }
-            }
-
-            // BUGBUG: We should not depend on `Id` internally.
-            // BUGBUG: It is super unclear what this code does anyway.
-            if (string.IsNullOrEmpty (toplevel.Id))
-            {
-                var count = 1;
-                var id = (TopLevels.Count + count).ToString ();
-
-                while (TopLevels.Count > 0 && TopLevels.FirstOrDefault (x => x.Id == id) is { })
-                {
-                    count++;
-                    id = (TopLevels.Count + count).ToString ();
-                }
-
-                toplevel.Id = (TopLevels.Count + count).ToString ();
-
-                TopLevels.Push (toplevel);
-            }
-            else
-            {
-                Toplevel? dup = TopLevels.FirstOrDefault (x => x.Id == toplevel.Id);
-
-                if (dup is null)
-                {
-                    TopLevels.Push (toplevel);
-                }
-            }
-        }
-
-        if (Top is null)
-        {
-            Top = toplevel;
-        }
-
-        if ((Top?.Modal == false && toplevel.Modal)
-            || (Top?.Modal == false && !toplevel.Modal)
-            || (Top?.Modal == true && toplevel.Modal))
-        {
-            if (toplevel.Visible)
-            {
-                if (Top is { HasFocus: true })
-                {
-                    Top.HasFocus = false;
-                }
-
-                // Force leave events for any entered views in the old Top
-                if (GetLastMousePosition () is { })
-                {
-                    RaiseMouseEnterLeaveEvents (GetLastMousePosition ()!.Value, new ());
-                }
-
-                Top?.OnDeactivate (toplevel);
-                Toplevel previousTop = Top!;
-
-                Top = toplevel;
-                Top.OnActivate (previousTop);
-            }
-        }
-
-        // View implements ISupportInitializeNotification which is derived from ISupportInitialize
-        if (!toplevel.IsInitialized)
-        {
-            toplevel.BeginInit ();
-            toplevel.EndInit (); // Calls Layout
-        }
-
-        // Try to set initial focus to any TabStop
-        if (!toplevel.HasFocus)
-        {
-            toplevel.SetFocus ();
-        }
-
-        toplevel.OnLoaded ();
-
-        ApplicationImpl.Instance.LayoutAndDraw (true);
-
-        if (PositionCursor ())
-        {
-            Driver?.UpdateCursor ();
-        }
-
-        NotifyNewRunState?.Invoke (toplevel, new (rs));
-
-        return rs;
-    }
-
-    /// <summary>
-    ///     Calls <see cref="View.PositionCursor"/> on the most focused view.
-    /// </summary>
-    /// <remarks>
-    ///     Does nothing if there is no most focused view.
-    ///     <para>
-    ///         If the most focused view is not visible within it's superview, the cursor will be hidden.
-    ///     </para>
-    /// </remarks>
-    /// <returns><see langword="true"/> if a view positioned the cursor and the position is visible.</returns>
-    internal static bool PositionCursor ()
-    {
-        // Find the most focused view and position the cursor there.
-        View? mostFocused = Navigation?.GetFocused ();
-
-        // If the view is not visible or enabled, don't position the cursor
-        if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
-        {
-            var current = CursorVisibility.Invisible;
-            Driver?.GetCursorVisibility (out current);
-
-            if (current != CursorVisibility.Invisible)
-            {
-                Driver?.SetCursorVisibility (CursorVisibility.Invisible);
-            }
-
-            return false;
-        }
-
-        // If the view is not visible within it's superview, don't position the cursor
-        Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty });
-
-        Rectangle superViewViewport =
-            mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver!.Screen;
-
-        if (!superViewViewport.IntersectsWith (mostFocusedViewport))
-        {
-            return false;
-        }
-
-        Point? cursor = mostFocused.PositionCursor ();
-
-        Driver!.GetCursorVisibility (out CursorVisibility currentCursorVisibility);
-
-        if (cursor is { })
-        {
-            // Convert cursor to screen coords
-            cursor = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = cursor.Value }).Location;
-
-            // If the cursor is not in a visible location in the SuperView, hide it
-            if (!superViewViewport.Contains (cursor.Value))
-            {
-                if (currentCursorVisibility != CursorVisibility.Invisible)
-                {
-                    Driver.SetCursorVisibility (CursorVisibility.Invisible);
-                }
-
-                return false;
-            }
-
-            // Show it
-            if (currentCursorVisibility == CursorVisibility.Invisible)
-            {
-                Driver.SetCursorVisibility (mostFocused.CursorVisibility);
-            }
-
-            return true;
-        }
-
-        if (currentCursorVisibility != CursorVisibility.Invisible)
-        {
-            Driver.SetCursorVisibility (CursorVisibility.Invisible);
-        }
-
-        return false;
-    }
-
-    /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
-    /// </summary>
-    /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
-    ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
-    ///         ensure resources are cleaned up and terminal settings restored.
-    ///     </para>
-    ///     <para>
-    ///         The caller is responsible for disposing the object returned by this method.
-    ///     </para>
-    /// </remarks>
-    /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
+    /// <inheritdoc cref="IApplication.Run(Func{Exception, bool}, string)"/>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public static Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-    {
-        return ApplicationImpl.Instance.Run (errorHandler, driver);
-    }
+    public static Toplevel Run (Func<Exception, bool>? errorHandler = null, string? driver = null) => ApplicationImpl.Instance.Run (errorHandler, driver);
 
-    /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
-    /// </summary>
-    /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
-    ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
-    ///         ensure resources are cleaned up and terminal settings restored.
-    ///     </para>
-    ///     <para>
-    ///         The caller is responsible for disposing the object returned by this method.
-    ///     </para>
-    /// </remarks>
-    /// <param name="errorHandler"></param>
-    /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
-    ///     be used. Must be <see langword="null"/> if <see cref="Init"/> has already been called.
-    /// </param>
-    /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
+    /// <inheritdoc cref="IApplication.Run{TView}(Func{Exception, bool}, string)"/>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-        where T : Toplevel, new()
-    {
-        return ApplicationImpl.Instance.Run<T> (errorHandler, driver);
-    }
+    public static TView Run<TView> (Func<Exception, bool>? errorHandler = null, string? driver = null)
+        where TView : Toplevel, new() => ApplicationImpl.Instance.Run<TView> (errorHandler, driver);
 
-    /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         This method is used to start processing events for the main application, but it is also used to run other
-    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
-    ///     </para>
-    ///     <para>
-    ///         To make a <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> stop execution, call
-    ///         <see cref="Application.RequestStop"/>.
-    ///     </para>
-    ///     <para>
-    ///         Calling <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
-    ///         <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState)"/>, and then calling
-    ///         <see cref="End(RunState)"/>.
-    ///     </para>
-    ///     <para>
-    ///         Alternatively, to have a program control the main loop and process events manually, call
-    ///         <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
-    ///         <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers handlers and then
-    ///         return control immediately.
-    ///     </para>
-    ///     <para>
-    ///         When using <see cref="Run{T}"/> or
-    ///         <see cref="Run(System.Func{System.Exception,bool},IConsoleDriver)"/>
-    ///         <see cref="Init"/> will be called automatically.
-    ///     </para>
-    ///     <para>
-    ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
-    ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
-    ///         returns <see langword="true"/> the <see cref="RunLoop(RunState)"/> will resume; otherwise this method will
-    ///         exit.
-    ///     </para>
-    /// </remarks>
-    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
-    /// <param name="errorHandler">
-    ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
-    ///     rethrows when null).
-    /// </param>
-    public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = null) { ApplicationImpl.Instance.Run (view, errorHandler); }
+    /// <inheritdoc cref="IApplication.Run(Toplevel, Func{Exception, bool})"/>
+    public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = null) => ApplicationImpl.Instance.Run (view, errorHandler);
 
-    /// <summary>Adds a timeout to the application.</summary>
-    /// <remarks>
-    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
-    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
-    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
-    /// </remarks>
-    public static object? AddTimeout (TimeSpan time, Func<bool> callback) { return ApplicationImpl.Instance.AddTimeout (time, callback); }
+    /// <inheritdoc cref="IApplication.AddTimeout"/>
+    public static object? AddTimeout (TimeSpan time, Func<bool> callback) => ApplicationImpl.Instance.AddTimeout (time, callback);
 
-    /// <summary>Removes a previously scheduled timeout</summary>
-    /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
-    /// Returns
-    /// <see langword="true"/>
-    /// if the timeout is successfully removed; otherwise,
-    /// <see langword="false"/>
-    /// .
-    /// This method also returns
-    /// <see langword="false"/>
-    /// if the timeout is not found.
-    public static bool RemoveTimeout (object token) { return ApplicationImpl.Instance.RemoveTimeout (token); }
-
-    /// <summary>Runs <paramref name="action"/> on the thread that is processing events</summary>
-    /// <param name="action">the action to be invoked on the main processing thread.</param>
-    public static void Invoke (Action action) { ApplicationImpl.Instance.Invoke (action); }
-
-    /// <summary>
-    ///     Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that
-    ///     need to be laid out (see <see cref="View.NeedsLayout"/>) will be laid out.
-    ///     Only Views that need to be drawn (see <see cref="View.NeedsDraw"/>) will be drawn.
-    /// </summary>
-    /// <param name="forceRedraw">
-    ///     If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and
-    ///     should only be overriden for testing.
-    /// </param>
-    public static void LayoutAndDraw (bool forceRedraw = false)
-    {
-        ApplicationImpl.Instance.LayoutAndDraw (forceRedraw);
-    }
+    /// <inheritdoc cref="IApplication.RemoveTimeout"/>
+    public static bool RemoveTimeout (object token) => ApplicationImpl.Instance.RemoveTimeout (token);
 
-    /// <summary>This event is raised on each iteration of the main loop.</summary>
-    /// <remarks>See also <see cref="Timeout"/></remarks>
-    public static event EventHandler<IterationEventArgs>? Iteration;
+    /// <inheritdoc cref="IApplication.TimedEvents"/>
+    /// 
+    public static ITimedEvents? TimedEvents => ApplicationImpl.Instance?.TimedEvents;
+    /// <inheritdoc cref="IApplication.Invoke"/>
+    public static void Invoke (Action action) => ApplicationImpl.Instance.Invoke (action);
 
-    /// <summary>
-    ///     Set to true to cause <see cref="End"/> to be called after the first iteration. Set to false (the default) to
-    ///     cause the application to continue running until Application.RequestStop () is called.
-    /// </summary>
-    public static bool EndAfterFirstIteration { get; set; }
+    /// <inheritdoc cref="IApplication.LayoutAndDraw"/>
+    public static void LayoutAndDraw (bool forceRedraw = false) => ApplicationImpl.Instance.LayoutAndDraw (forceRedraw);
 
-    /// <summary>Building block API: Runs the main loop for the created <see cref="Toplevel"/>.</summary>
-    /// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
-    public static void RunLoop (RunState state)
+    /// <inheritdoc cref="IApplication.StopAfterFirstIteration"/>
+    public static bool StopAfterFirstIteration
     {
-        ArgumentNullException.ThrowIfNull (state);
-        ObjectDisposedException.ThrowIf (state.Toplevel is null, "state");
-
-        var firstIteration = true;
+        get => ApplicationImpl.Instance.StopAfterFirstIteration;
+        set => ApplicationImpl.Instance.StopAfterFirstIteration = value;
+    }
 
-        for (state.Toplevel.Running = true; state.Toplevel?.Running == true;)
-        {
-            if (EndAfterFirstIteration && !firstIteration)
-            {
-                return;
-            }
+    /// <inheritdoc cref="IApplication.RequestStop(Toplevel)"/>
+    public static void RequestStop (Toplevel? top = null) => ApplicationImpl.Instance.RequestStop (top);
 
-            firstIteration = RunIteration (ref state, firstIteration);
-        }
+    /// <inheritdoc cref="IApplication.End"/>
+    public static void End (SessionToken sessionToken) => ApplicationImpl.Instance.End (sessionToken);
 
-        // Run one last iteration to consume any outstanding input events from Driver
-        // This is important for remaining OnKeyUp events.
-        RunIteration (ref state, firstIteration);
-    }
+    /// <inheritdoc cref="IApplication.RaiseIteration"/>
+    internal static void RaiseIteration () => ApplicationImpl.Instance.RaiseIteration ();
 
-    /// <summary>Run one application iteration.</summary>
-    /// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
-    /// <param name="firstIteration">
-    ///     Set to <see langword="true"/> if this is the first run loop iteration.
-    /// </param>
-    /// <returns><see langword="false"/> if at least one iteration happened.</returns>
-    public static bool RunIteration (ref RunState state, bool firstIteration = false)
+    /// <inheritdoc cref="IApplication.Iteration"/>
+    public static event EventHandler<IterationEventArgs>? Iteration
     {
-        ApplicationImpl appImpl = (ApplicationImpl)ApplicationImpl.Instance;
-        appImpl.Coordinator?.RunIteration ();
-
-        return false;
+        add => ApplicationImpl.Instance.Iteration += value;
+        remove => ApplicationImpl.Instance.Iteration -= value;
     }
 
-    /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
-    /// <param name="top">The <see cref="Toplevel"/> to stop.</param>
-    /// <remarks>
-    ///     <para>This will cause <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to return.</para>
-    ///     <para>
-    ///         Calling <see cref="RequestStop(Toplevel)"/> is equivalent to setting the
-    ///         <see cref="Toplevel.Running"/>
-    ///         property on the currently running <see cref="Toplevel"/> to false.
-    ///     </para>
-    /// </remarks>
-    public static void RequestStop (Toplevel? top = null) { ApplicationImpl.Instance.RequestStop (top); }
-
-    /// <summary>
-    ///     Building block API: completes the execution of a <see cref="Toplevel"/> that was started with
-    ///     <see cref="Begin(Toplevel)"/> .
-    /// </summary>
-    /// <param name="runState">The <see cref="RunState"/> returned by the <see cref="Begin(Toplevel)"/> method.</param>
-    public static void End (RunState runState)
+    /// <inheritdoc cref="IApplication.SessionBegun"/>
+    public static event EventHandler<SessionTokenEventArgs>? SessionBegun
     {
-        ArgumentNullException.ThrowIfNull (runState);
-
-        if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
-        {
-            ApplicationPopover.HideWithQuitCommand (visiblePopover);
-        }
-
-        runState.Toplevel.OnUnloaded ();
-
-        // End the RunState.Toplevel
-        // First, take it off the Toplevel Stack
-        if (TopLevels.TryPop (out Toplevel? topOfStack))
-        {
-            if (topOfStack != runState.Toplevel)
-            {
-                // If the top of the stack is not the RunState.Toplevel then
-                // this call to End is not balanced with the call to Begin that started the RunState
-                throw new ArgumentException ("End must be balanced with calls to Begin");
-            }
-        }
-
-        // Notify that it is closing
-        runState.Toplevel?.OnClosed (runState.Toplevel);
-
-        if (TopLevels.TryPeek (out Toplevel? newTop))
-        {
-            Top = newTop;
-            Top?.SetNeedsDraw ();
-        }
-
-        if (runState.Toplevel is { HasFocus: true })
-        {
-            runState.Toplevel.HasFocus = false;
-        }
-
-        if (Top is { HasFocus: false })
-        {
-            Top.SetFocus ();
-        }
-
-        CachedRunStateToplevel = runState.Toplevel;
-
-        runState.Toplevel = null;
-        runState.Dispose ();
-
-        LayoutAndDraw (true);
+        add => ApplicationImpl.Instance.SessionBegun += value;
+        remove => ApplicationImpl.Instance.SessionBegun -= value;
     }
-    internal static void RaiseIteration ()
+
+    /// <inheritdoc cref="IApplication.SessionEnded"/>
+    public static event EventHandler<ToplevelEventArgs>? SessionEnded
     {
-        Iteration?.Invoke (null, new ());
+        add => ApplicationImpl.Instance.SessionEnded += value;
+        remove => ApplicationImpl.Instance.SessionEnded -= value;
     }
 }

+ 8 - 35
Terminal.Gui/App/Application.Screen.cs

@@ -4,50 +4,23 @@ namespace Terminal.Gui.App;
 
 public static partial class Application // Screen related stuff; intended to hide Driver details
 {
-    /// <summary>
-    ///     Gets or sets the size of the screen. By default, this is the size of the screen as reported by the <see cref="IConsoleDriver"/>.
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    ///     If the <see cref="IConsoleDriver"/> has not been initialized, this will return a default size of 2048x2048; useful for unit tests.
-    /// </para>
-    /// </remarks>
+    /// <inheritdoc cref="IApplication.Screen"/>
+
     public static Rectangle Screen
     {
         get => ApplicationImpl.Instance.Screen;
         set => ApplicationImpl.Instance.Screen = value;
     }
 
-    /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
-    public static event EventHandler<EventArgs<Rectangle>>? ScreenChanged;
-
-    /// <summary>
-    ///     Called when the application's size has changed. Sets the size of all <see cref="Toplevel"/>s and fires the
-    ///     <see cref="ScreenChanged"/> event.
-    /// </summary>
-    /// <param name="screen">The new screen size and position.</param>
-    public static void RaiseScreenChangedEvent (Rectangle screen)
+    /// <inheritdoc cref="IApplication.ScreenChanged"/>
+    public static event EventHandler<EventArgs<Rectangle>>? ScreenChanged
     {
-        Screen = new (Point.Empty, screen.Size);
-
-        ScreenChanged?.Invoke (ApplicationImpl.Instance, new (screen));
-
-        foreach (Toplevel t in TopLevels)
-        {
-            t.OnSizeChanging (new (screen.Size));
-            t.SetNeedsLayout ();
-        }
-
-        LayoutAndDraw (true);
+        add => ApplicationImpl.Instance.ScreenChanged += value;
+        remove => ApplicationImpl.Instance.ScreenChanged -= value;
     }
 
-    /// <summary>
-    ///     Gets or sets whether the screen will be cleared, and all Views redrawn, during the next Application iteration.
-    /// </summary>
-    /// <remarks>
-    ///     This is typical set to true when a View's <see cref="View.Frame"/> changes and that view has no
-    ///     SuperView (e.g. when <see cref="Application.Top"/> is moved or resized.
-    /// </remarks>
+    /// <inheritdoc cref="IApplication.ClearScreenNextIteration"/>
+
     internal static bool ClearScreenNextIteration
     {
         get => ApplicationImpl.Instance.ClearScreenNextIteration;

+ 2 - 12
Terminal.Gui/App/Application.Toplevel.cs

@@ -5,10 +5,8 @@ namespace Terminal.Gui.App;
 
 public static partial class Application // Toplevel handling
 {
-    // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What
-
-    /// <summary>Holds the stack of TopLevel views.</summary>
-    internal static ConcurrentStack<Toplevel> TopLevels => ApplicationImpl.Instance.TopLevels;
+    /// <inheritdoc cref="IApplication.TopLevels"/>
+    public static ConcurrentStack<Toplevel> TopLevels => ApplicationImpl.Instance.TopLevels;
 
     /// <summary>The <see cref="Toplevel"/> that is currently active.</summary>
     /// <value>The top.</value>
@@ -17,12 +15,4 @@ public static partial class Application // Toplevel handling
         get => ApplicationImpl.Instance.Top;
         internal set => ApplicationImpl.Instance.Top = value;
     }
-
-    internal static Toplevel? CachedRunStateToplevel
-    {
-        get => ApplicationImpl.Instance.CachedRunStateToplevel;
-        private set => ApplicationImpl.Instance.CachedRunStateToplevel = value;
-    }
-
-
 }

+ 4 - 4
Terminal.Gui/App/Application.cd

@@ -36,19 +36,19 @@
       <FileName>App\MainLoopSyncContext.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.App.RunState" Collapsed="true" BaseTypeListCollapsed="true">
+  <Class Name="Terminal.Gui.App.SessionToken" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="15.25" Y="4" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAACACAgAAAAAAAAAAAAAAAAACQAAAAAAAAAA=</HashCode>
-      <FileName>App\RunState.cs</FileName>
+      <FileName>App\SessionToken.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" Collapsed="true" />
   </Class>
-  <Class Name="Terminal.Gui.App.RunStateEventArgs" Collapsed="true">
+  <Class Name="Terminal.Gui.App.SessionTokenEventArgs" Collapsed="true">
     <Position X="17" Y="4" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=</HashCode>
-      <FileName>App\RunStateEventArgs.cs</FileName>
+      <FileName>App\SessionTokenEventArgs.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.App.Timeout" Collapsed="true">

+ 7 - 111
Terminal.Gui/App/Application.cs

@@ -39,18 +39,6 @@ namespace Terminal.Gui.App;
 /// <remarks></remarks>
 public static partial class Application
 {
-    /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
-    public static List<CultureInfo>? SupportedCultures { get; private set; } = GetSupportedCultures ();
-
-
-    /// <summary>
-    /// <para>
-    /// Handles recurring events. These are invoked on the main UI thread - allowing for
-    /// safe updates to <see cref="View"/> instances.
-    /// </para>
-    /// </summary>
-    public static ITimedEvents? TimedEvents => ApplicationImpl.Instance?.TimedEvents;
-
     /// <summary>
     /// Maximum number of iterations of the main loop (and hence draws)
     /// to allow to occur per second. Defaults to <see cref="DefaultMaximumIterationsPerSecond"/>> which is a 40ms sleep
@@ -71,7 +59,7 @@ public static partial class Application
     /// <returns>A string representation of the Application </returns>
     public new static string ToString ()
     {
-        IConsoleDriver? driver = Driver;
+        IDriver? driver = Driver;
 
         if (driver is null)
         {
@@ -82,11 +70,11 @@ public static partial class Application
     }
 
     /// <summary>
-    ///     Gets a string representation of the Application rendered by the provided <see cref="IConsoleDriver"/>.
+    ///     Gets a string representation of the Application rendered by the provided <see cref="IDriver"/>.
     /// </summary>
     /// <param name="driver">The driver to use to render the contents.</param>
     /// <returns>A string representation of the Application </returns>
-    public static string ToString (IConsoleDriver? driver)
+    public static string ToString (IDriver? driver)
     {
         if (driver is null)
         {
@@ -129,6 +117,10 @@ public static partial class Application
         return sb.ToString ();
     }
 
+    /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
+    public static List<CultureInfo>? SupportedCultures { get; private set; } = GetSupportedCultures ();
+
+
     internal static List<CultureInfo> GetAvailableCulturesFromEmbeddedResources ()
     {
         ResourceManager rm = new (typeof (Strings));
@@ -171,100 +163,4 @@ public static partial class Application
         // It's called from a self-contained single-file and get available cultures from the embedded resources strings.
         return GetAvailableCulturesFromEmbeddedResources ();
     }
-
-    // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
-    // Encapsulate all setting of initial state for Application; Having
-    // this in a function like this ensures we don't make mistakes in
-    // guaranteeing that the state of this singleton is deterministic when Init
-    // starts running and after Shutdown returns.
-    internal static void ResetState (bool ignoreDisposed = false)
-    {
-        // Shutdown is the bookend for Init. As such it needs to clean up all resources
-        // Init created. Apps that do any threading will need to code defensively for this.
-        // e.g. see Issue #537
-        foreach (Toplevel? t in TopLevels)
-        {
-            t!.Running = false;
-        }
-
-        if (Popover?.GetActivePopover () is View popover)
-        {
-            // This forcefully closes the popover; invoking Command.Quit would be more graceful
-            // but since this is shutdown, doing this is ok.
-            popover.Visible = false;
-        }
-
-        Popover?.Dispose ();
-        Popover = null;
-
-        TopLevels.Clear ();
-#if DEBUG_IDISPOSABLE
-
-        // Don't dispose the Top. It's up to caller dispose it
-        if (View.EnableDebugIDisposableAsserts && !ignoreDisposed && Top is { })
-        {
-            Debug.Assert (Top.WasDisposed, $"Title = {Top.Title}, Id = {Top.Id}");
-
-            // If End wasn't called _cachedRunStateToplevel may be null
-            if (CachedRunStateToplevel is { })
-            {
-                Debug.Assert (CachedRunStateToplevel.WasDisposed);
-                Debug.Assert (CachedRunStateToplevel == Top);
-            }
-        }
-#endif
-        Top = null;
-        CachedRunStateToplevel = null;
-
-        MainThreadId = -1;
-        Iteration = null;
-        EndAfterFirstIteration = false;
-        ClearScreenNextIteration = false;
-
-        // Driver stuff
-        if (Driver is { })
-        {
-            UnsubscribeDriverEvents ();
-            Driver?.End ();
-            Driver = null;
-        }
-
-        // Reset Screen to null so it will be recalculated on next access
-        // Note: ApplicationImpl.Shutdown() also calls ResetScreen() before calling this method
-        // to avoid potential circular reference issues. Calling it twice is harmless.
-        if (ApplicationImpl.Instance is ApplicationImpl impl)
-        {
-            impl.ResetScreen ();
-        }
-
-        // Don't reset ForceDriver; it needs to be set before Init is called.
-        //ForceDriver = string.Empty;
-        //Force16Colors = false;
-        _forceFakeConsole = false;
-
-        // Run State stuff
-        NotifyNewRunState = null;
-        NotifyStopRunState = null;
-        // Mouse and Keyboard will be lazy-initialized in ApplicationImpl on next access
-        Initialized = false;
-
-        // Mouse
-        // Do not clear _lastMousePosition; Popovers require it to stay set with
-        // last mouse pos.
-        //_lastMousePosition = null;
-        CachedViewsUnderMouse.Clear ();
-        ResetMouseState ();
-
-        // Keyboard events and bindings are now managed by the Keyboard instance
-
-        ScreenChanged = null;
-
-        Navigation = null;
-
-        // Reset synchronization context to allow the user to run async/await,
-        // as the main loop has been ended, the synchronization context from
-        // gui.cs does no longer process any callbacks. See #1084 for more details:
-        // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
-        SynchronizationContext.SetSynchronizationContext (null);
-    }
 }

+ 176 - 0
Terminal.Gui/App/ApplicationImpl.Driver.cs

@@ -0,0 +1,176 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.App;
+
+public partial class ApplicationImpl
+{
+    /// <inheritdoc/>
+    public IDriver? Driver { get; set; }
+
+    /// <inheritdoc/>
+    public bool Force16Colors { get; set; }
+
+    /// <inheritdoc/>
+    public string ForceDriver { get; set; } = string.Empty;
+
+    /// <inheritdoc/>
+    public List<SixelToRender> Sixel { get; } = new ();
+
+    /// <summary>
+    ///     Creates the appropriate <see cref="IDriver"/> based on platform and driverName.
+    /// </summary>
+    /// <param name="driverName"></param>
+    /// <returns></returns>
+    /// <exception cref="Exception"></exception>
+    /// <exception cref="InvalidOperationException"></exception>
+    private void CreateDriver (string? driverName)
+    {
+        PlatformID p = Environment.OSVersion.Platform;
+
+        // Check component factory type first - this takes precedence over driverName
+        bool factoryIsWindows = _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
+        bool factoryIsDotNet = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
+        bool factoryIsUnix = _componentFactory is IComponentFactory<char>;
+        bool factoryIsFake = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
+
+        // Then check driverName
+        bool nameIsWindows = driverName?.Contains ("win", StringComparison.OrdinalIgnoreCase) ?? false;
+        bool nameIsDotNet = driverName?.Contains ("dotnet", StringComparison.OrdinalIgnoreCase) ?? false;
+        bool nameIsUnix = driverName?.Contains ("unix", StringComparison.OrdinalIgnoreCase) ?? false;
+        bool nameIsFake = driverName?.Contains ("fake", StringComparison.OrdinalIgnoreCase) ?? false;
+
+        // Decide which driver to use - component factory type takes priority
+        if (factoryIsFake || (!factoryIsWindows && !factoryIsDotNet && !factoryIsUnix && nameIsFake))
+        {
+            Coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
+            _driverName = "fake";
+        }
+        else if (factoryIsWindows || (!factoryIsDotNet && !factoryIsUnix && nameIsWindows))
+        {
+            Coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
+            _driverName = "windows";
+        }
+        else if (factoryIsDotNet || (!factoryIsWindows && !factoryIsUnix && nameIsDotNet))
+        {
+            Coordinator = CreateSubcomponents (() => new NetComponentFactory ());
+            _driverName = "dotnet";
+        }
+        else if (factoryIsUnix || (!factoryIsWindows && !factoryIsDotNet && nameIsUnix))
+        {
+            Coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
+            _driverName = "unix";
+        }
+        else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        {
+            Coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
+            _driverName = "windows";
+        }
+        else if (p == PlatformID.Unix)
+        {
+            Coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
+            _driverName = "unix";
+        }
+        else
+        {
+            Logging.Information($"Falling back to dotnet driver.");
+            Coordinator = CreateSubcomponents (() => new NetComponentFactory ());
+            _driverName = "dotnet";
+        }
+
+        Logging.Trace ($"Created Subcomponents: {Coordinator}");
+
+        Coordinator.StartInputTaskAsync ().Wait ();
+
+        if (Driver == null)
+        {
+            throw new ("Driver was null even after booting MainLoopCoordinator");
+        }
+    }
+
+    private readonly IComponentFactory? _componentFactory;
+
+    /// <summary>
+    ///     INTERNAL: Gets or sets the main loop coordinator that orchestrates the application's event processing,
+    ///     input handling, and rendering pipeline.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         The <see cref="IMainLoopCoordinator"/> is the central component responsible for:
+    ///         <list type="bullet">
+    ///             <item>Managing the platform-specific input thread that reads from the console</item>
+    ///             <item>Coordinating the main application loop via <see cref="IMainLoopCoordinator.RunIteration"/></item>
+    ///             <item>Processing queued input events and translating them to Terminal.Gui events</item>
+    ///             <item>Managing the <see cref="ApplicationMainLoop{TInputRecord}"/> that handles rendering</item>
+    ///             <item>Executing scheduled timeouts and callbacks via <see cref="ITimedEvents"/></item>
+    ///         </list>
+    ///     </para>
+    ///     <para>
+    ///         The coordinator is created in <see cref="CreateDriver"/> based on the selected driver
+    ///         (Windows, Unix, .NET, or Fake) and is started by calling
+    ///         <see cref="IMainLoopCoordinator.StartInputTaskAsync"/>.
+    ///     </para>
+    /// </remarks>
+    internal IMainLoopCoordinator? Coordinator { get; private set; }
+
+    /// <summary>
+    ///     INTERNAL: Creates a <see cref="MainLoopCoordinator{TInputRecord}"/> with the appropriate component factory
+    ///     for the specified input record type.
+    /// </summary>
+    /// <typeparam name="TInputRecord">
+    ///     Platform-specific input type: <see cref="ConsoleKeyInfo"/> (.NET/Fake),
+    ///     <see cref="WindowsConsole.InputRecord"/> (Windows), or <see cref="char"/> (Unix).
+    /// </typeparam>
+    /// <param name="fallbackFactory">
+    ///     Factory function to create the component factory if <see cref="_componentFactory"/>
+    ///     is not of type <see cref="IComponentFactory{TInputRecord}"/>.
+    /// </param>
+    /// <returns>
+    ///     A <see cref="MainLoopCoordinator{TInputRecord}"/> configured with the input queue,
+    ///     main loop, timed events, and selected component factory.
+    /// </returns>
+    private IMainLoopCoordinator CreateSubcomponents<TInputRecord> (Func<IComponentFactory<TInputRecord>> fallbackFactory) where TInputRecord : struct
+    {
+        ConcurrentQueue<TInputRecord> inputQueue = new ();
+        ApplicationMainLoop<TInputRecord> loop = new ();
+
+        IComponentFactory<TInputRecord> cf;
+
+        if (_componentFactory is IComponentFactory<TInputRecord> typedFactory)
+        {
+            cf = typedFactory;
+        }
+        else
+        {
+            cf = fallbackFactory ();
+        }
+
+        return new MainLoopCoordinator<TInputRecord> (_timedEvents, inputQueue, loop, cf);
+    }
+
+    internal void SubscribeDriverEvents ()
+    {
+        ArgumentNullException.ThrowIfNull (Driver);
+
+        Driver.SizeChanged += Driver_SizeChanged;
+        Driver.KeyDown += Driver_KeyDown;
+        Driver.KeyUp += Driver_KeyUp;
+        Driver.MouseEvent += Driver_MouseEvent;
+    }
+
+    internal void UnsubscribeDriverEvents ()
+    {
+        ArgumentNullException.ThrowIfNull (Driver);
+
+        Driver.SizeChanged -= Driver_SizeChanged;
+        Driver.KeyDown -= Driver_KeyDown;
+        Driver.KeyUp -= Driver_KeyUp;
+        Driver.MouseEvent -= Driver_MouseEvent;
+    }
+
+    private void Driver_KeyDown (object? sender, Key e) { Keyboard?.RaiseKeyDownEvent (e); }
+
+    private void Driver_KeyUp (object? sender, Key e) { Keyboard?.RaiseKeyUpEvent (e); }
+
+    private void Driver_MouseEvent (object? sender, MouseEventArgs e) { Mouse?.RaiseMouseEvent (e); }
+}

+ 251 - 0
Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

@@ -0,0 +1,251 @@
+#nullable enable
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Terminal.Gui.App;
+
+public partial class ApplicationImpl
+{
+    /// <inheritdoc/>
+    public bool Initialized { get; set; }
+
+    /// <inheritdoc/>
+    public event EventHandler<EventArgs<bool>>? InitializedChanged;
+
+    /// <inheritdoc/>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public void Init (IDriver? driver = null, string? driverName = null)
+    {
+        if (Initialized)
+        {
+            Logging.Error ("Init called multiple times without shutdown, aborting.");
+
+            throw new InvalidOperationException ("Init called multiple times without Shutdown");
+        }
+
+        if (!string.IsNullOrWhiteSpace (driverName))
+        {
+            _driverName = driverName;
+        }
+
+        if (string.IsNullOrWhiteSpace (_driverName))
+        {
+            _driverName = ForceDriver;
+        }
+
+        Debug.Assert (Navigation is null);
+        Navigation = new ();
+
+        Debug.Assert (Popover is null);
+        Popover = new ();
+
+        // Preserve existing keyboard settings if they exist
+        bool hasExistingKeyboard = _keyboard is { };
+        Key existingQuitKey = _keyboard?.QuitKey ?? Key.Esc;
+        Key existingArrangeKey = _keyboard?.ArrangeKey ?? Key.F5.WithCtrl;
+        Key existingNextTabKey = _keyboard?.NextTabKey ?? Key.Tab;
+        Key existingPrevTabKey = _keyboard?.PrevTabKey ?? Key.Tab.WithShift;
+        Key existingNextTabGroupKey = _keyboard?.NextTabGroupKey ?? Key.F6;
+        Key existingPrevTabGroupKey = _keyboard?.PrevTabGroupKey ?? Key.F6.WithShift;
+
+        // Reset keyboard to ensure fresh state with default bindings
+        _keyboard = new KeyboardImpl { Application = this };
+
+        // Restore previously set keys if they existed and were different from defaults
+        if (hasExistingKeyboard)
+        {
+            _keyboard.QuitKey = existingQuitKey;
+            _keyboard.ArrangeKey = existingArrangeKey;
+            _keyboard.NextTabKey = existingNextTabKey;
+            _keyboard.PrevTabKey = existingPrevTabKey;
+            _keyboard.NextTabGroupKey = existingNextTabGroupKey;
+            _keyboard.PrevTabGroupKey = existingPrevTabGroupKey;
+        }
+
+        CreateDriver (driverName ?? _driverName);
+        Screen = Driver!.Screen;
+        Initialized = true;
+
+        RaiseInitializedChanged (this, new (true));
+        SubscribeDriverEvents ();
+
+        SynchronizationContext.SetSynchronizationContext (new ());
+        MainThreadId = Thread.CurrentThread.ManagedThreadId;
+    }
+
+    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    public void Shutdown ()
+    {
+        // Stop the coordinator if running
+        Coordinator?.Stop ();
+
+        // Capture state before cleanup
+        bool wasInitialized = Initialized;
+
+#if DEBUG
+
+        // Check that all Application events have no remaining subscribers BEFORE clearing them
+        // Only check if we were actually initialized
+        if (wasInitialized)
+        {
+            AssertNoEventSubscribers (nameof (Iteration), Iteration);
+            AssertNoEventSubscribers (nameof (SessionBegun), SessionBegun);
+            AssertNoEventSubscribers (nameof (SessionEnded), SessionEnded);
+            AssertNoEventSubscribers (nameof (ScreenChanged), ScreenChanged);
+
+            //AssertNoEventSubscribers (nameof (InitializedChanged), InitializedChanged);
+        }
+#endif
+
+        // Clean up all application state (including sync context)
+        // ResetState handles the case where Initialized is false
+        ResetState ();
+
+        // Configuration manager diagnostics
+        ConfigurationManager.PrintJsonErrors ();
+
+        // Raise the initialized changed event to notify shutdown
+        if (wasInitialized)
+        {
+            bool init = Initialized; // Will be false after ResetState
+            RaiseInitializedChanged (this, new (in init));
+        }
+
+        // Clear the event to prevent memory leaks
+        InitializedChanged = null;
+
+        // Create a new lazy instance for potential future Init
+        _lazyInstance = new (() => new ApplicationImpl ());
+    }
+
+#if DEBUG
+    /// <summary>
+    ///     DEBUG ONLY: Asserts that an event has no remaining subscribers.
+    /// </summary>
+    /// <param name="eventName">The name of the event for diagnostic purposes.</param>
+    /// <param name="eventDelegate">The event delegate to check.</param>
+    private static void AssertNoEventSubscribers (string eventName, Delegate? eventDelegate)
+    {
+        if (eventDelegate is null)
+        {
+            return;
+        }
+
+        Delegate [] subscribers = eventDelegate.GetInvocationList ();
+
+        if (subscribers.Length > 0)
+        {
+            string subscriberInfo = string.Join (
+                                                 ", ",
+                                                 subscribers.Select (d => $"{d.Method.DeclaringType?.Name}.{d.Method.Name}"
+                                                                    )
+                                                );
+
+            Debug.Fail (
+                        $"Application.{eventName} has {subscribers.Length} remaining subscriber(s) after Shutdown: {subscriberInfo}"
+                       );
+        }
+    }
+#endif
+
+    /// <inheritdoc/>
+    public void ResetState (bool ignoreDisposed = false)
+    {
+        // Shutdown is the bookend for Init. As such it needs to clean up all resources
+        // Init created. Apps that do any threading will need to code defensively for this.
+        // e.g. see Issue #537
+
+        // === 1. Stop all running toplevels ===
+        foreach (Toplevel? t in TopLevels)
+        {
+            t!.Running = false;
+        }
+
+        // === 2. Close and dispose popover ===
+        if (Popover?.GetActivePopover () is View popover)
+        {
+            // This forcefully closes the popover; invoking Command.Quit would be more graceful
+            // but since this is shutdown, doing this is ok.
+            popover.Visible = false;
+        }
+
+        Popover?.Dispose ();
+        Popover = null;
+
+        // === 3. Clean up toplevels ===
+        TopLevels.Clear ();
+
+#if DEBUG_IDISPOSABLE
+
+        // Don't dispose the Top. It's up to caller dispose it
+        if (View.EnableDebugIDisposableAsserts && !ignoreDisposed && Top is { })
+        {
+            Debug.Assert (Top.WasDisposed, $"Title = {Top.Title}, Id = {Top.Id}");
+
+            // If End wasn't called _CachedSessionTokenToplevel may be null
+            if (CachedSessionTokenToplevel is { })
+            {
+                Debug.Assert (CachedSessionTokenToplevel.WasDisposed);
+                Debug.Assert (CachedSessionTokenToplevel == Top);
+            }
+        }
+#endif
+
+        Top = null;
+        CachedSessionTokenToplevel = null;
+
+        // === 4. Clean up driver ===
+        if (Driver is { })
+        {
+            UnsubscribeDriverEvents ();
+            Driver?.End ();
+            Driver = null;
+        }
+
+        // Reset screen
+        ResetScreen ();
+        _screen = null;
+
+        // === 5. Clear run state ===
+        Iteration = null;
+        SessionBegun = null;
+        SessionEnded = null;
+        StopAfterFirstIteration = false;
+        ClearScreenNextIteration = false;
+
+        // === 6. Reset input systems ===
+        // Mouse and Keyboard will be lazy-initialized on next access
+        _mouse = null;
+        _keyboard = null;
+        Mouse.ResetState ();
+
+        // === 7. Clear navigation and screen state ===
+        ScreenChanged = null;
+        Navigation = null;
+
+        // === 8. Reset initialization state ===
+        Initialized = false;
+        MainThreadId = null;
+
+        // === 9. Clear graphics ===
+        Sixel.Clear ();
+
+        // === 10. Reset synchronization context ===
+        // IMPORTANT: Always reset sync context, even if not initialized
+        // This ensures cleanup works correctly even if Shutdown is called without Init
+        // Reset synchronization context to allow the user to run async/await,
+        // as the main loop has been ended, the synchronization context from
+        // gui.cs does no longer process any callbacks. See #1084 for more details:
+        // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
+        SynchronizationContext.SetSynchronizationContext (null);
+
+        // Note: ForceDriver and Force16Colors are NOT reset; 
+        // they need to persist across Init/Shutdown cycles
+    }
+
+    /// <summary>
+    ///     Raises the <see cref="InitializedChanged"/> event.
+    /// </summary>
+    internal void RaiseInitializedChanged (object sender, EventArgs<bool> e) { InitializedChanged?.Invoke (sender, e); }
+}

+ 347 - 0
Terminal.Gui/App/ApplicationImpl.Run.cs

@@ -0,0 +1,347 @@
+#nullable enable
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Terminal.Gui.App;
+
+public partial class ApplicationImpl
+{
+    /// <summary>
+    ///     INTERNAL: Gets or sets the managed thread ID of the application's main UI thread, which is set during
+    ///     <see cref="Init"/> and used to determine if code is executing on the main thread.
+    /// </summary>
+    /// <value>
+    ///     The managed thread ID of the main UI thread, or <see langword="null"/> if the application is not initialized.
+    /// </value>
+    internal int? MainThreadId { get; set; }
+
+    #region Begin->Run->Stop->End
+
+    /// <inheritdoc/>
+    public event EventHandler<SessionTokenEventArgs>? SessionBegun;
+
+    /// <inheritdoc/>
+    public event EventHandler<ToplevelEventArgs>? SessionEnded;
+
+    /// <inheritdoc/>
+    public SessionToken Begin (Toplevel toplevel)
+    {
+        ArgumentNullException.ThrowIfNull (toplevel);
+
+        // Ensure the mouse is ungrabbed.
+        if (Mouse.MouseGrabView is { })
+        {
+            Mouse.UngrabMouse ();
+        }
+
+        var rs = new SessionToken (toplevel);
+
+#if DEBUG_IDISPOSABLE
+        if (View.EnableDebugIDisposableAsserts && Top is { } && toplevel != Top && !TopLevels.Contains (Top))
+        {
+            // This assertion confirm if the Top was already disposed
+            Debug.Assert (Top.WasDisposed);
+            Debug.Assert (Top == CachedSessionTokenToplevel);
+        }
+#endif
+
+        lock (TopLevels)
+        {
+            if (Top is { } && toplevel != Top && !TopLevels.Contains (Top))
+            {
+                // If Top was already disposed and isn't on the Toplevels Stack,
+                // clean it up here if is the same as _CachedSessionTokenToplevel
+                if (Top == CachedSessionTokenToplevel)
+                {
+                    Top = null;
+                }
+                else
+                {
+                    // Probably this will never hit
+                    throw new ObjectDisposedException (Top.GetType ().FullName);
+                }
+            }
+
+            // BUGBUG: We should not depend on `Id` internally.
+            // BUGBUG: It is super unclear what this code does anyway.
+            if (string.IsNullOrEmpty (toplevel.Id))
+            {
+                var count = 1;
+                var id = (TopLevels.Count + count).ToString ();
+
+                while (TopLevels.Count > 0 && TopLevels.FirstOrDefault (x => x.Id == id) is { })
+                {
+                    count++;
+                    id = (TopLevels.Count + count).ToString ();
+                }
+
+                toplevel.Id = (TopLevels.Count + count).ToString ();
+
+                TopLevels.Push (toplevel);
+            }
+            else
+            {
+                Toplevel? dup = TopLevels.FirstOrDefault (x => x.Id == toplevel.Id);
+
+                if (dup is null)
+                {
+                    TopLevels.Push (toplevel);
+                }
+            }
+        }
+
+        if (Top is null)
+        {
+            Top = toplevel;
+        }
+
+        if ((Top?.Modal == false && toplevel.Modal)
+            || (Top?.Modal == false && !toplevel.Modal)
+            || (Top?.Modal == true && toplevel.Modal))
+        {
+            if (toplevel.Visible)
+            {
+                if (Top is { HasFocus: true })
+                {
+                    Top.HasFocus = false;
+                }
+
+                // Force leave events for any entered views in the old Top
+                if (Mouse.GetLastMousePosition () is { })
+                {
+                    Mouse.RaiseMouseEnterLeaveEvents (Mouse.GetLastMousePosition ()!.Value, new ());
+                }
+
+                Top?.OnDeactivate (toplevel);
+                Toplevel previousTop = Top!;
+
+                Top = toplevel;
+                Top.OnActivate (previousTop);
+            }
+        }
+
+        // View implements ISupportInitializeNotification which is derived from ISupportInitialize
+        if (!toplevel.IsInitialized)
+        {
+            toplevel.BeginInit ();
+            toplevel.EndInit (); // Calls Layout
+        }
+
+        // Try to set initial focus to any TabStop
+        if (!toplevel.HasFocus)
+        {
+            toplevel.SetFocus ();
+        }
+
+        toplevel.OnLoaded ();
+
+        Instance.LayoutAndDraw (true);
+
+        if (PositionCursor ())
+        {
+            Driver?.UpdateCursor ();
+        }
+
+        SessionBegun?.Invoke (this, new (rs));
+
+        return rs;
+    }
+
+    /// <inheritdoc/>
+    public bool StopAfterFirstIteration { get; set; }
+
+    /// <inheritdoc/>
+    public event EventHandler<IterationEventArgs>? Iteration;
+
+    /// <inheritdoc/>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public Toplevel Run (Func<Exception, bool>? errorHandler = null, string? driver = null) { return Run<Toplevel> (errorHandler, driver); }
+
+    /// <inheritdoc/>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public TView Run<TView> (Func<Exception, bool>? errorHandler = null, string? driver = null)
+        where TView : Toplevel, new ()
+    {
+        if (!Initialized)
+        {
+            // Init() has NOT been called. Auto-initialize as per interface contract.
+            Init (null, driver);
+        }
+
+        TView top = new ();
+        Run (top, errorHandler);
+
+        return top;
+    }
+
+
+    /// <inheritdoc/>
+    public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
+    {
+        Logging.Information ($"Run '{view}'");
+        ArgumentNullException.ThrowIfNull (view);
+
+        if (!Initialized)
+        {
+            throw new NotInitializedException (nameof (Run));
+        }
+
+        if (Driver == null)
+        {
+            throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view");
+        }
+
+        Top = view;
+
+        SessionToken rs = Application.Begin (view);
+
+        Top.Running = true;
+
+        var firstIteration = true;
+
+        while (TopLevels.TryPeek (out Toplevel? found) && found == view && view.Running)
+        {
+            if (Coordinator is null)
+            {
+                throw new ($"{nameof (IMainLoopCoordinator)} inexplicably became null during Run");
+            }
+
+            Coordinator.RunIteration ();
+
+            if (StopAfterFirstIteration && firstIteration)
+            {
+                Logging.Information ("Run - Stopping after first iteration as requested");
+                view.RequestStop ();
+            }
+
+            firstIteration = false;
+        }
+
+        Logging.Information ("Run - Calling End");
+        Application.End (rs);
+    }
+
+    /// <inheritdoc/>
+    public void End (SessionToken sessionToken)
+    {
+        ArgumentNullException.ThrowIfNull (sessionToken);
+
+        if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
+        {
+            ApplicationPopover.HideWithQuitCommand (visiblePopover);
+        }
+
+        sessionToken.Toplevel.OnUnloaded ();
+
+        // End the Session
+        // First, take it off the Toplevel Stack
+        if (TopLevels.TryPop (out Toplevel? topOfStack))
+        {
+            if (topOfStack != sessionToken.Toplevel)
+            {
+                // If the top of the stack is not the SessionToken.Toplevel then
+                // this call to End is not balanced with the call to Begin that started the Session
+                throw new ArgumentException ("End must be balanced with calls to Begin");
+            }
+        }
+
+        // Notify that it is closing
+        sessionToken.Toplevel?.OnClosed (sessionToken.Toplevel);
+
+        if (TopLevels.TryPeek (out Toplevel? newTop))
+        {
+            Top = newTop;
+            Top?.SetNeedsDraw ();
+        }
+
+        if (sessionToken.Toplevel is { HasFocus: true })
+        {
+            sessionToken.Toplevel.HasFocus = false;
+        }
+
+        if (Top is { HasFocus: false })
+        {
+            Top.SetFocus ();
+        }
+
+        CachedSessionTokenToplevel = sessionToken.Toplevel;
+
+        sessionToken.Toplevel = null;
+        sessionToken.Dispose ();
+
+        // BUGBUG: Why layout and draw here? This causes the screen to be cleared!
+        //LayoutAndDraw (true);
+
+        SessionEnded?.Invoke (this, new (CachedSessionTokenToplevel));
+    }
+
+    /// <inheritdoc/>
+    public void RequestStop () { RequestStop (null); }
+
+    /// <inheritdoc/>
+    public void RequestStop (Toplevel? top)
+    {
+        Logging.Trace ($"Top: '{(top is { } ? top : "null")}'");
+
+        top ??= Top;
+
+        if (top == null)
+        {
+            return;
+        }
+
+        ToplevelClosingEventArgs ev = new (top);
+        top.OnClosing (ev);
+
+        if (ev.Cancel)
+        {
+            return;
+        }
+
+        top.Running = false;
+    }
+
+    /// <inheritdoc/>
+    public void RaiseIteration () { Iteration?.Invoke (null, new ()); }
+
+    #endregion Begin->Run->Stop->End
+
+    #region Timeouts and Invoke
+
+    private readonly ITimedEvents _timedEvents = new TimedEvents ();
+
+    /// <inheritdoc/>
+    public ITimedEvents? TimedEvents => _timedEvents;
+
+    /// <inheritdoc/>
+    public object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.Add (time, callback); }
+
+    /// <inheritdoc/>
+    public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); }
+
+    /// <inheritdoc/>
+    public void Invoke (Action action)
+    {
+        // If we are already on the main UI thread
+        if (Top is { Running: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId)
+        {
+            action ();
+
+            return;
+        }
+
+        _timedEvents.Add (
+                          TimeSpan.Zero,
+                          () =>
+                          {
+                              action ();
+
+                              return false;
+                          }
+                         );
+    }
+
+    #endregion Timeouts and Invoke
+}

+ 177 - 0
Terminal.Gui/App/ApplicationImpl.Screen.cs

@@ -0,0 +1,177 @@
+#nullable enable
+
+namespace Terminal.Gui.App;
+
+public partial class ApplicationImpl
+{
+    /// <inheritdoc/>
+    public event EventHandler<EventArgs<Rectangle>>? ScreenChanged;
+
+    private readonly object _lockScreen = new ();
+    private Rectangle? _screen;
+
+    /// <inheritdoc/>
+    public Rectangle Screen
+    {
+        get
+        {
+            lock (_lockScreen)
+            {
+                if (_screen == null)
+                {
+                    _screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
+                }
+
+                return _screen.Value;
+            }
+        }
+        set
+        {
+            if (value is { } && (value.X != 0 || value.Y != 0))
+            {
+                throw new NotImplementedException ("Screen locations other than 0, 0 are not yet supported");
+            }
+
+            lock (_lockScreen)
+            {
+                _screen = value;
+            }
+        }
+    }
+
+    /// <inheritdoc/>
+    public bool ClearScreenNextIteration { get; set; }
+
+    /// <inheritdoc/>
+    public bool PositionCursor ()
+    {
+        // Find the most focused view and position the cursor there.
+        View? mostFocused = Navigation?.GetFocused ();
+
+        // If the view is not visible or enabled, don't position the cursor
+        if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
+        {
+            var current = CursorVisibility.Invisible;
+            Driver?.GetCursorVisibility (out current);
+
+            if (current != CursorVisibility.Invisible)
+            {
+                Driver?.SetCursorVisibility (CursorVisibility.Invisible);
+            }
+
+            return false;
+        }
+
+        // If the view is not visible within it's superview, don't position the cursor
+        Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty });
+
+        Rectangle superViewViewport =
+            mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver!.Screen;
+
+        if (!superViewViewport.IntersectsWith (mostFocusedViewport))
+        {
+            return false;
+        }
+
+        Point? cursor = mostFocused.PositionCursor ();
+
+        Driver!.GetCursorVisibility (out CursorVisibility currentCursorVisibility);
+
+        if (cursor is { })
+        {
+            // Convert cursor to screen coords
+            cursor = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = cursor.Value }).Location;
+
+            // If the cursor is not in a visible location in the SuperView, hide it
+            if (!superViewViewport.Contains (cursor.Value))
+            {
+                if (currentCursorVisibility != CursorVisibility.Invisible)
+                {
+                    Driver.SetCursorVisibility (CursorVisibility.Invisible);
+                }
+
+                return false;
+            }
+
+            // Show it
+            if (currentCursorVisibility == CursorVisibility.Invisible)
+            {
+                Driver.SetCursorVisibility (mostFocused.CursorVisibility);
+            }
+
+            return true;
+        }
+
+        if (currentCursorVisibility != CursorVisibility.Invisible)
+        {
+            Driver.SetCursorVisibility (CursorVisibility.Invisible);
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     INTERNAL: Resets the Screen field to null so it will be recalculated on next access.
+    /// </summary>
+    private void ResetScreen ()
+    {
+        lock (_lockScreen)
+        {
+            _screen = null;
+        }
+    }
+
+    /// <summary>
+    ///     INTERNAL: Called when the application's size has changed. Sets the size of all <see cref="Toplevel"/>s and fires
+    ///     the
+    ///     <see cref="ScreenChanged"/> event.
+    /// </summary>
+    /// <param name="screen">The new screen size and position.</param>
+    private void RaiseScreenChangedEvent (Rectangle screen)
+    {
+        Screen = new (Point.Empty, screen.Size);
+
+        ScreenChanged?.Invoke (this, new (screen));
+
+        foreach (Toplevel t in TopLevels)
+        {
+            t.OnSizeChanging (new (screen.Size));
+            t.SetNeedsLayout ();
+        }
+
+        LayoutAndDraw (true);
+    }
+
+    private void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { RaiseScreenChangedEvent (new (new (0, 0), e.Size!.Value)); }
+
+    /// <inheritdoc/>
+    public void LayoutAndDraw (bool forceRedraw = false)
+    {
+        List<View> tops = [.. TopLevels];
+
+        if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
+        {
+            visiblePopover.SetNeedsDraw ();
+            visiblePopover.SetNeedsLayout ();
+            tops.Insert (0, visiblePopover);
+        }
+
+        bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Screen.Size);
+
+        if (ClearScreenNextIteration)
+        {
+            forceRedraw = true;
+            ClearScreenNextIteration = false;
+        }
+
+        if (forceRedraw)
+        {
+            Driver?.ClearContents ();
+        }
+
+        View.SetClipToScreen ();
+        View.Draw (tops, neededLayout || forceRedraw);
+        View.SetClipToScreen ();
+        Driver?.Refresh ();
+    }
+}

+ 43 - 488
Terminal.Gui/App/ApplicationImpl.cs

@@ -1,54 +1,55 @@
 #nullable enable
 using System.Collections.Concurrent;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using Microsoft.Extensions.Logging;
-using Terminal.Gui.Drivers;
 
 namespace Terminal.Gui.App;
 
 /// <summary>
-/// Implementation of core <see cref="Application"/> methods using the modern
-/// main loop architecture with component factories for different platforms.
+///     Implementation of core <see cref="Application"/> methods using the modern
+///     main loop architecture with component factories for different platforms.
 /// </summary>
-public class ApplicationImpl : IApplication
+public partial class ApplicationImpl : IApplication
 {
-    private readonly IComponentFactory? _componentFactory;
-    private IMainLoopCoordinator? _coordinator;
-    private string? _driverName;
-    private readonly ITimedEvents _timedEvents = new TimedEvents ();
-    private IConsoleDriver? _driver;
-    private bool _initialized;
-    private ApplicationPopover? _popover;
-    private ApplicationNavigation? _navigation;
-    private Toplevel? _top;
-    private readonly ConcurrentStack<Toplevel> _topLevels = new ();
-    private int _mainThreadId = -1;
-    private bool _force16Colors;
-    private string _forceDriver = string.Empty;
-    private readonly List<SixelToRender> _sixel = new ();
-    private readonly object _lockScreen = new ();
-    private Rectangle? _screen;
-    private bool _clearScreenNextIteration;
+    /// <summary>
+    ///     Creates a new instance of the Application backend.
+    /// </summary>
+    public ApplicationImpl () { }
+
+    /// <summary>
+    ///     INTERNAL: Creates a new instance of the Application backend.
+    /// </summary>
+    /// <param name="componentFactory"></param>
+    internal ApplicationImpl (IComponentFactory componentFactory) { _componentFactory = componentFactory; }
+
+    #region Singleton
 
     // Private static readonly Lazy instance of Application
     private static Lazy<IApplication> _lazyInstance = new (() => new ApplicationImpl ());
 
     /// <summary>
-    /// Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
-    /// Change to your own implementation by using <see cref="ChangeInstance"/> (before init).
+    ///     Change the singleton implementation, should not be called except before application
+    ///     startup. This method lets you provide alternative implementations of core static gateway
+    ///     methods of <see cref="Application"/>.
+    /// </summary>
+    /// <param name="newApplication"></param>
+    public static void ChangeInstance (IApplication? newApplication) { _lazyInstance = new (newApplication!); }
+
+    /// <summary>
+    ///     Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
+    ///     Change to your own implementation by using <see cref="ChangeInstance"/> (before init).
     /// </summary>
     public static IApplication Instance => _lazyInstance.Value;
 
-    /// <inheritdoc/>
-    public ITimedEvents? TimedEvents => _timedEvents;
+    #endregion Singleton
+
+    private string? _driverName;
 
-    internal IMainLoopCoordinator? Coordinator => _coordinator;
+
+    #region Input
 
     private IMouse? _mouse;
 
     /// <summary>
-    /// Handles mouse event state and processing.
+    ///     Handles mouse event state and processing.
     /// </summary>
     public IMouse Mouse
     {
@@ -58,6 +59,7 @@ public class ApplicationImpl : IApplication
             {
                 _mouse = new MouseImpl { Application = this };
             }
+
             return _mouse;
         }
         set => _mouse = value ?? throw new ArgumentNullException (nameof (value));
@@ -66,7 +68,7 @@ public class ApplicationImpl : IApplication
     private IKeyboard? _keyboard;
 
     /// <summary>
-    /// Handles keyboard input and key bindings at the Application level
+    ///     Handles keyboard input and key bindings at the Application level
     /// </summary>
     public IKeyboard Keyboard
     {
@@ -76,479 +78,32 @@ public class ApplicationImpl : IApplication
             {
                 _keyboard = new KeyboardImpl { Application = this };
             }
+
             return _keyboard;
         }
         set => _keyboard = value ?? throw new ArgumentNullException (nameof (value));
     }
 
-    /// <inheritdoc/>
-    public IConsoleDriver? Driver
-    {
-        get => _driver;
-        set => _driver = value;
-    }
+    #endregion Input
 
-    /// <inheritdoc/>
-    public bool Initialized
-    {
-        get => _initialized;
-        set => _initialized = value;
-    }
+    #region View Management
 
     /// <inheritdoc/>
-    public bool Force16Colors
-    {
-        get => _force16Colors;
-        set => _force16Colors = value;
-    }
+    public ApplicationPopover? Popover { get; set; }
 
     /// <inheritdoc/>
-    public string ForceDriver
-    {
-        get => _forceDriver;
-        set => _forceDriver = value;
-    }
+    public ApplicationNavigation? Navigation { get; set; }
 
     /// <inheritdoc/>
-    public List<SixelToRender> Sixel => _sixel;
+    public Toplevel? Top { get; set; }
 
-    /// <inheritdoc/>
-    public Rectangle Screen
-    {
-        get
-        {
-            lock (_lockScreen)
-            {
-                if (_screen == null)
-                {
-                    _screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
-                }
-
-                return _screen.Value;
-            }
-        }
-        set
-        {
-            if (value is { } && (value.X != 0 || value.Y != 0))
-            {
-                throw new NotImplementedException ($"Screen locations other than 0, 0 are not yet supported");
-            }
-
-            lock (_lockScreen)
-            {
-                _screen = value;
-            }
-        }
-    }
+    // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What
 
     /// <inheritdoc/>
-    public bool ClearScreenNextIteration
-    {
-        get => _clearScreenNextIteration;
-        set => _clearScreenNextIteration = value;
-    }
-
-    /// <inheritdoc/>
-    public ApplicationPopover? Popover
-    {
-        get => _popover;
-        set => _popover = value;
-    }
+    public ConcurrentStack<Toplevel> TopLevels { get; } = new ();
 
     /// <inheritdoc/>
-    public ApplicationNavigation? Navigation
-    {
-        get => _navigation;
-        set => _navigation = value;
-    }
-
-    /// <inheritdoc/>
-    public Toplevel? Top
-    {
-        get => _top;
-        set => _top = value;
-    }
+    public Toplevel? CachedSessionTokenToplevel { get; set; }
 
-    /// <inheritdoc/>
-    public ConcurrentStack<Toplevel> TopLevels => _topLevels;
-
-    // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`.
-    // This variable is set in `End` in this case so that `Begin` correctly sets `Top`.
-    /// <inheritdoc />
-    public Toplevel? CachedRunStateToplevel { get; set; }
-
-    /// <summary>
-    /// Gets or sets the main thread ID for the application.
-    /// </summary>
-    internal int MainThreadId
-    {
-        get => _mainThreadId;
-        set => _mainThreadId = value;
-    }
-
-    /// <inheritdoc/>
-    public void RequestStop () => RequestStop (null);
-
-    /// <summary>
-    /// Creates a new instance of the Application backend.
-    /// </summary>
-    public ApplicationImpl ()
-    {
-    }
-
-    internal ApplicationImpl (IComponentFactory componentFactory)
-    {
-        _componentFactory = componentFactory;
-    }
-
-    /// <summary>
-    /// Change the singleton implementation, should not be called except before application
-    /// startup. This method lets you provide alternative implementations of core static gateway
-    /// methods of <see cref="Application"/>.
-    /// </summary>
-    /// <param name="newApplication"></param>
-    public static void ChangeInstance (IApplication newApplication)
-    {
-        _lazyInstance = new Lazy<IApplication> (newApplication);
-    }
-
-    /// <inheritdoc/>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    public void Init (IConsoleDriver? driver = null, string? driverName = null)
-    {
-        if (_initialized)
-        {
-            Logging.Logger.LogError ("Init called multiple times without shutdown, aborting.");
-
-            throw new InvalidOperationException ("Init called multiple times without Shutdown");
-        }
-
-        if (!string.IsNullOrWhiteSpace (driverName))
-        {
-            _driverName = driverName;
-        }
-
-        if (string.IsNullOrWhiteSpace (_driverName))
-        {
-            _driverName = ForceDriver;
-        }
-
-        Debug.Assert (_navigation is null);
-        _navigation = new ();
-
-        Debug.Assert (_popover is null);
-        _popover = new ();
-
-        // Preserve existing keyboard settings if they exist
-        bool hasExistingKeyboard = _keyboard is not null;
-        Key existingQuitKey = _keyboard?.QuitKey ?? Key.Esc;
-        Key existingArrangeKey = _keyboard?.ArrangeKey ?? Key.F5.WithCtrl;
-        Key existingNextTabKey = _keyboard?.NextTabKey ?? Key.Tab;
-        Key existingPrevTabKey = _keyboard?.PrevTabKey ?? Key.Tab.WithShift;
-        Key existingNextTabGroupKey = _keyboard?.NextTabGroupKey ?? Key.F6;
-        Key existingPrevTabGroupKey = _keyboard?.PrevTabGroupKey ?? Key.F6.WithShift;
-
-        // Reset keyboard to ensure fresh state with default bindings
-        _keyboard = new KeyboardImpl { Application = this };
-
-        // Restore previously set keys if they existed and were different from defaults
-        if (hasExistingKeyboard)
-        {
-            _keyboard.QuitKey = existingQuitKey;
-            _keyboard.ArrangeKey = existingArrangeKey;
-            _keyboard.NextTabKey = existingNextTabKey;
-            _keyboard.PrevTabKey = existingPrevTabKey;
-            _keyboard.NextTabGroupKey = existingNextTabGroupKey;
-            _keyboard.PrevTabGroupKey = existingPrevTabGroupKey;
-        }
-
-        CreateDriver (driverName ?? _driverName);
-        Screen = Driver!.Screen;
-        _initialized = true;
-
-        Application.OnInitializedChanged (this, new (true));
-        Application.SubscribeDriverEvents ();
-
-        SynchronizationContext.SetSynchronizationContext (new ());
-        _mainThreadId = Thread.CurrentThread.ManagedThreadId;
-    }
-
-    private void CreateDriver (string? driverName)
-    {
-        PlatformID p = Environment.OSVersion.Platform;
-
-        // Check component factory type first - this takes precedence over driverName
-        bool factoryIsWindows = _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
-        bool factoryIsDotNet = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
-        bool factoryIsUnix = _componentFactory is IComponentFactory<char>;
-        bool factoryIsFake = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
-
-        // Then check driverName
-        bool nameIsWindows = driverName?.Contains ("win", StringComparison.OrdinalIgnoreCase) ?? false;
-        bool nameIsDotNet = (driverName?.Contains ("dotnet", StringComparison.OrdinalIgnoreCase) ?? false);
-        bool nameIsUnix = driverName?.Contains ("unix", StringComparison.OrdinalIgnoreCase) ?? false;
-        bool nameIsFake = driverName?.Contains ("fake", StringComparison.OrdinalIgnoreCase) ?? false;
-
-        // Decide which driver to use - component factory type takes priority
-        if (factoryIsFake || (!factoryIsWindows && !factoryIsDotNet && !factoryIsUnix && nameIsFake))
-        {
-            FakeConsoleOutput fakeOutput = new ();
-            fakeOutput.SetConsoleSize (80, 25);
-
-            _coordinator = CreateSubcomponents (() => new FakeComponentFactory (null, fakeOutput));
-        }
-        else if (factoryIsWindows || (!factoryIsDotNet && !factoryIsUnix && nameIsWindows))
-        {
-            _coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
-        }
-        else if (factoryIsDotNet || (!factoryIsWindows && !factoryIsUnix && nameIsDotNet))
-        {
-            _coordinator = CreateSubcomponents (() => new NetComponentFactory ());
-        }
-        else if (factoryIsUnix || (!factoryIsWindows && !factoryIsDotNet && nameIsUnix))
-        {
-            _coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
-        }
-        else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
-        {
-            _coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
-        }
-        else
-        {
-            _coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
-        }
-
-        _coordinator.StartAsync ().Wait ();
-
-        if (_driver == null)
-        {
-            throw new ("Driver was null even after booting MainLoopCoordinator");
-        }
-    }
-
-    private IMainLoopCoordinator CreateSubcomponents<T> (Func<IComponentFactory<T>> fallbackFactory)
-    {
-        ConcurrentQueue<T> inputBuffer = new ();
-        ApplicationMainLoop<T> loop = new ();
-
-        IComponentFactory<T> cf;
-
-        if (_componentFactory is IComponentFactory<T> typedFactory)
-        {
-            cf = typedFactory;
-        }
-        else
-        {
-            cf = fallbackFactory ();
-        }
-
-        return new MainLoopCoordinator<T> (_timedEvents, inputBuffer, loop, cf);
-    }
-
-    /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
-    /// </summary>
-    /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    public Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
-
-    /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
-    /// </summary>
-    /// <param name="errorHandler"></param>
-    /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
-    ///     be used. Must be <see langword="null"/> if <see cref="Init"/> has already been called.
-    /// </param>
-    /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-        where T : Toplevel, new()
-    {
-        if (!_initialized)
-        {
-            // Init() has NOT been called. Auto-initialize as per interface contract.
-            Init (driver, null);
-        }
-
-        T top = new ();
-        Run (top, errorHandler);
-        return top;
-    }
-
-    /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
-    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
-    /// <param name="errorHandler">Handler for any unhandled exceptions.</param>
-    public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
-    {
-        Logging.Information ($"Run '{view}'");
-        ArgumentNullException.ThrowIfNull (view);
-
-        if (!_initialized)
-        {
-            throw new NotInitializedException (nameof (Run));
-        }
-
-        if (_driver == null)
-        {
-            throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view");
-        }
-
-        _top = view;
-
-        RunState rs = Application.Begin (view);
-
-        _top.Running = true;
-
-        while (_topLevels.TryPeek (out Toplevel? found) && found == view && view.Running)
-        {
-            if (_coordinator is null)
-            {
-                throw new ($"{nameof (IMainLoopCoordinator)} inexplicably became null during Run");
-            }
-
-            _coordinator.RunIteration ();
-        }
-
-        Logging.Information ($"Run - Calling End");
-        Application.End (rs);
-    }
-
-    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
-    public void Shutdown ()
-    {
-        _coordinator?.Stop ();
-
-        bool wasInitialized = _initialized;
-
-        // Reset Screen before calling Application.ResetState to avoid circular reference
-        ResetScreen ();
-
-        // Call ResetState FIRST so it can properly dispose Popover and other resources
-        // that are accessed via Application.* static properties that now delegate to instance fields
-        Application.ResetState ();
-        ConfigurationManager.PrintJsonErrors ();
-
-        // Clear instance fields after ResetState has disposed everything
-        _driver = null;
-        _mouse = null;
-        _keyboard = null;
-        _initialized = false;
-        _navigation = null;
-        _popover = null;
-        CachedRunStateToplevel = null;
-        _top = null;
-        _topLevels.Clear ();
-        _mainThreadId = -1;
-        _screen = null;
-        _clearScreenNextIteration = false;
-        _sixel.Clear ();
-        // Don't reset ForceDriver and Force16Colors; they need to be set before Init is called
-
-        if (wasInitialized)
-        {
-            bool init = _initialized; // Will be false after clearing fields above
-            Application.OnInitializedChanged (this, new (in init));
-        }
-
-        _lazyInstance = new (() => new ApplicationImpl ());
-    }
-
-    /// <inheritdoc />
-    public void RequestStop (Toplevel? top)
-    {
-        Logging.Logger.LogInformation ($"RequestStop '{(top is { } ? top : "null")}'");
-
-        top ??= _top;
-
-        if (top == null)
-        {
-            return;
-        }
-
-        ToplevelClosingEventArgs ev = new (top);
-        top.OnClosing (ev);
-
-        if (ev.Cancel)
-        {
-            return;
-        }
-
-        top.Running = false;
-    }
-
-    /// <inheritdoc />
-    public void Invoke (Action action)
-    {
-        // If we are already on the main UI thread
-        if (Top is { Running: true } && _mainThreadId == Thread.CurrentThread.ManagedThreadId)
-        {
-            action ();
-            return;
-        }
-
-        _timedEvents.Add (TimeSpan.Zero,
-                              () =>
-                              {
-                                  action ();
-                                  return false;
-                              }
-                             );
-    }
-
-    /// <inheritdoc />
-    public bool IsLegacy => false;
-
-    /// <inheritdoc />
-    public object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.Add (time, callback); }
-
-    /// <inheritdoc />
-    public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); }
-
-    /// <inheritdoc />
-    public void LayoutAndDraw (bool forceRedraw = false)
-    {
-        List<View> tops = [.. _topLevels];
-
-        if (_popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
-        {
-            visiblePopover.SetNeedsDraw ();
-            visiblePopover.SetNeedsLayout ();
-            tops.Insert (0, visiblePopover);
-        }
-
-        bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Screen.Size);
-
-        if (ClearScreenNextIteration)
-        {
-            forceRedraw = true;
-            ClearScreenNextIteration = false;
-        }
-
-        if (forceRedraw)
-        {
-            _driver?.ClearContents ();
-        }
-
-        View.SetClipToScreen ();
-        View.Draw (tops, neededLayout || forceRedraw);
-        View.SetClipToScreen ();
-        _driver?.Refresh ();
-    }
-
-    /// <summary>
-    /// Resets the Screen field to null so it will be recalculated on next access.
-    /// </summary>
-    internal void ResetScreen ()
-    {
-        lock (_lockScreen)
-        {
-            _screen = null;
-        }
-    }
+    #endregion View Management
 }

+ 2 - 2
Terminal.Gui/App/Clipboard/ClipboardBase.cs

@@ -103,7 +103,7 @@ public abstract class ClipboardBase : IClipboard
     }
 
     /// <summary>
-    ///     Returns the contents of the OS clipboard if possible. Implemented by <see cref="IConsoleDriver"/>-specific
+    ///     Returns the contents of the OS clipboard if possible. Implemented by <see cref="IDriver"/>-specific
     ///     subclasses.
     /// </summary>
     /// <returns>The contents of the OS clipboard if successful.</returns>
@@ -111,7 +111,7 @@ public abstract class ClipboardBase : IClipboard
     protected abstract string GetClipboardDataImpl ();
 
     /// <summary>
-    ///     Pastes the <paramref name="text"/> to the OS clipboard if possible. Implemented by <see cref="IConsoleDriver"/>
+    ///     Pastes the <paramref name="text"/> to the OS clipboard if possible. Implemented by <see cref="IDriver"/>
     ///     -specific subclasses.
     /// </summary>
     /// <param name="text">The text to paste to the OS clipboard.</param>

+ 1 - 1
Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs

@@ -5,7 +5,7 @@ namespace Terminal.Gui.App;
 
 /// <summary>
 ///     Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
-///     UnixDriver, but also used in Unit tests which is why it is in IConsoleDriver.cs.
+///     UnixDriver, but also used in Unit tests which is why it is in IDriver.cs.
 /// </summary>
 internal static class ClipboardProcessRunner
 {

+ 440 - 163
Terminal.Gui/App/IApplication.cs

@@ -1,4 +1,5 @@
 #nullable enable
+using System.Collections.Concurrent;
 using System.Diagnostics.CodeAnalysis;
 
 namespace Terminal.Gui.App;
@@ -9,257 +10,533 @@ namespace Terminal.Gui.App;
 /// </summary>
 public interface IApplication
 {
-    /// <summary>Adds a timeout to the application.</summary>
-    /// <remarks>
-    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
-    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
-    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
-    /// </remarks>
-    object AddTimeout (TimeSpan time, Func<bool> callback);
+    #region Keyboard
 
     /// <summary>
-    /// Handles keyboard input and key bindings at the Application level.
+    ///     Handles keyboard input and key bindings at the Application level.
     /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Provides access to keyboard state, key bindings, and keyboard event handling. Set during <see cref="Init"/>.
+    ///     </para>
+    /// </remarks>
     IKeyboard Keyboard { get; set; }
 
+    #endregion Keyboard
+
+    #region Mouse
+
     /// <summary>
     ///     Handles mouse event state and processing.
     /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Provides access to mouse state, mouse grabbing, and mouse event handling. Set during <see cref="Init"/>.
+    ///     </para>
+    /// </remarks>
     IMouse Mouse { get; set; }
 
-    /// <summary>Gets or sets the console driver being used.</summary>
-    IConsoleDriver? Driver { get; set; }
+    #endregion Mouse
+
+    #region Initialization and Shutdown
+
+    /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
+    /// <param name="driver">
+    ///     The <see cref="IDriver"/> to use. If neither <paramref name="driver"/> or
+    ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
+    /// </param>
+    /// <param name="driverName">
+    ///     The short name (e.g. "dotnet", "windows", "unix", or "fake") of the
+    ///     <see cref="IDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
+    ///     specified the default driver for the platform will be used.
+    /// </param>
+    /// <remarks>
+    ///     <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
+    ///     <para>
+    ///         This function loads the right <see cref="IDriver"/> for the platform, creates a main loop coordinator,
+    ///         initializes keyboard and mouse handlers, and subscribes to driver events.
+    ///     </para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after
+    ///         <see cref="Run{T}"/> has returned) to ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         The <see cref="Run{T}"/> function combines <see cref="Init(IDriver,string)"/> and
+    ///         <see cref="Run(Toplevel, Func{Exception, bool})"/> into a single call. An application can use
+    ///         <see cref="Run{T}"/> without explicitly calling <see cref="Init(IDriver,string)"/>.
+    ///     </para>
+    /// </remarks>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public void Init (IDriver? driver = null, string? driverName = null);
+
+    /// <summary>
+    ///     This event is raised after the <see cref="Init"/> and <see cref="Shutdown"/> methods have been called.
+    /// </summary>
+    /// <remarks>
+    ///     Intended to support unit tests that need to know when the application has been initialized.
+    /// </remarks>
+    public event EventHandler<EventArgs<bool>>? InitializedChanged;
 
     /// <summary>Gets or sets whether the application has been initialized.</summary>
     bool Initialized { get; set; }
 
+    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    /// <remarks>
+    ///     Shutdown must be called for every call to <see cref="Init"/> or
+    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
+    ///     up (Disposed) and terminal settings are restored.
+    /// </remarks>
+    public void Shutdown ();
+
     /// <summary>
-    ///     Gets or sets whether <see cref="Driver"/> will be forced to output only the 16 colors defined in
-    ///     <see cref="ColorName16"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be output
-    ///     as long as the selected <see cref="IConsoleDriver"/> supports TrueColor.
+    ///     Resets the state of this instance.
     /// </summary>
-    bool Force16Colors { get; set; }
+    /// <param name="ignoreDisposed">If true, ignores disposed state checks during reset.</param>
+    /// <remarks>
+    ///     <para>
+    ///         Encapsulates all setting of initial state for Application; having this in a function like this ensures we
+    ///         don't make mistakes in guaranteeing that the state of this singleton is deterministic when <see cref="Init"/>
+    ///         starts running and after <see cref="Shutdown"/> returns.
+    ///     </para>
+    ///     <para>
+    ///         IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
+    ///     </para>
+    /// </remarks>
+    public void ResetState (bool ignoreDisposed = false);
+
+    #endregion Initialization and Shutdown
+
+    #region Begin->Run->Iteration->Stop->End
 
     /// <summary>
-    ///     Forces the use of the specified driver (one of "fake", "dotnet", "windows", or "unix"). If not
-    ///     specified, the driver is selected based on the platform.
+    ///     Building block API: Creates a <see cref="SessionToken"/> and prepares the provided <see cref="Toplevel"/> for
+    ///     execution. Not usually called directly by applications. Use <see cref="Run(Toplevel, Func{Exception, bool})"/>
+    ///     instead.
     /// </summary>
-    string ForceDriver { get; set; }
+    /// <returns>
+    ///     The <see cref="SessionToken"/> that needs to be passed to the <see cref="End(SessionToken)"/> method upon
+    ///     completion.
+    /// </returns>
+    /// <param name="toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
+    /// <remarks>
+    ///     <para>
+    ///         This method prepares the provided <see cref="Toplevel"/> for running. It adds this to the
+    ///         list of <see cref="Toplevel"/>s, lays out the SubViews, focuses the first element, and draws the
+    ///         <see cref="Toplevel"/> on the screen. This is usually followed by starting the main loop, and then the
+    ///         <see cref="End(SessionToken)"/> method upon termination which will undo these changes.
+    ///     </para>
+    ///     <para>
+    ///         Raises the <see cref="SessionBegun"/> event before returning.
+    ///     </para>
+    /// </remarks>
+    public SessionToken Begin (Toplevel toplevel);
 
     /// <summary>
-    /// Collection of sixel images to write out to screen when updating.
-    /// Only add to this collection if you are sure terminal supports sixel format.
+    ///     Runs a new Session creating a <see cref="Toplevel"/> and calling <see cref="Begin(Toplevel)"/>. When the session is
+    ///     stopped, <see cref="End(SessionToken)"/> will be called.
     /// </summary>
-    List<SixelToRender> Sixel { get; }
+    /// <param name="errorHandler">Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
+    /// <param name="driver">
+    ///     The driver name. If not specified the default driver for the platform will be used. Must be
+    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
+    /// </param>
+    /// <returns>The created <see cref="Toplevel"/>. The caller is responsible for disposing this object.</returns>
+    /// <remarks>
+    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         The caller is responsible for disposing the object returned by this method.
+    ///     </para>
+    /// </remarks>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public Toplevel Run (Func<Exception, bool>? errorHandler = null, string? driver = null);
 
     /// <summary>
-    ///     Gets or sets the size of the screen. By default, this is the size of the screen as reported by the <see cref="IConsoleDriver"/>.
+    ///     Runs a new Session creating a <see cref="Toplevel"/>-derived object of type <typeparamref name="TView"/>
+    ///     and calling <see cref="Run(Toplevel, Func{Exception, bool})"/>. When the session is stopped,
+    ///     <see cref="End(SessionToken)"/> will be called.
     /// </summary>
-    Rectangle Screen { get; set; }
+    /// <typeparam name="TView">The type of <see cref="Toplevel"/> to create and run.</typeparam>
+    /// <param name="errorHandler">Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
+    /// <param name="driver">
+    ///     The driver name. If not specified the default driver for the platform will be used. Must be
+    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
+    /// </param>
+    /// <returns>The created <typeparamref name="TView"/> object. The caller is responsible for disposing this object.</returns>
+    /// <remarks>
+    ///     <para>
+    ///         This method is used to start processing events for the main application, but it is also used to run other
+    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
+    ///     </para>
+    ///     <para>
+    ///         To make <see cref="Run(Toplevel, Func{Exception, bool})"/> stop execution, call
+    ///         <see cref="RequestStop()"/> or <see cref="RequestStop(Toplevel)"/>.
+    ///     </para>
+    ///     <para>
+    ///         Calling <see cref="Run(Toplevel, Func{Exception, bool})"/> is equivalent to calling
+    ///         <see cref="Begin(Toplevel)"/>, followed by starting the main loop, and then calling
+    ///         <see cref="End(SessionToken)"/>.
+    ///     </para>
+    ///     <para>
+    ///         When using <see cref="Run{T}"/> or <see cref="Run(Func{Exception, bool}, string)"/>,
+    ///         <see cref="Init"/> will be called automatically.
+    ///     </para>
+    ///     <para>
+    ///         In RELEASE builds: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
+    ///         rethrown. Otherwise, <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
+    ///         returns <see langword="true"/> the main loop will resume; otherwise this method will exit.
+    ///     </para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         In RELEASE builds: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
+    ///         rethrown. Otherwise, <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
+    ///         returns <see langword="true"/> the main loop will resume; otherwise this method will exit.
+    ///     </para>
+    ///     <para>
+    ///         The caller is responsible for disposing the object returned by this method.
+    ///     </para>
+    /// </remarks>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public TView Run<TView> (Func<Exception, bool>? errorHandler = null, string? driver = null)
+        where TView : Toplevel, new ();
 
     /// <summary>
-    ///     Gets or sets whether the screen will be cleared, and all Views redrawn, during the next Application iteration.
+    ///     Runs a new Session using the provided <see cref="Toplevel"/> view and calling
+    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    ///     When the session is stopped, <see cref="End(SessionToken)"/> will be called..
     /// </summary>
-    bool ClearScreenNextIteration { get; set; }
-
-    /// <summary>Gets or sets the popover manager.</summary>
-    ApplicationPopover? Popover { get; set; }
+    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
+    /// <param name="errorHandler">Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
+    /// <remarks>
+    ///     <para>
+    ///         This method is used to start processing events for the main application, but it is also used to run other
+    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
+    ///     </para>
+    ///     <para>
+    ///         To make <see cref="Run(Toplevel, Func{Exception, bool})"/> stop execution, call
+    ///         <see cref="RequestStop()"/> or <see cref="RequestStop(Toplevel)"/>.
+    ///     </para>
+    ///     <para>
+    ///         Calling <see cref="Run(Toplevel, Func{Exception, bool})"/> is equivalent to calling
+    ///         <see cref="Begin(Toplevel)"/>, followed by starting the main loop, and then calling
+    ///         <see cref="End(SessionToken)"/>.
+    ///     </para>
+    ///     <para>
+    ///         When using <see cref="Run{T}"/> or <see cref="Run(Func{Exception, bool}, string)"/>,
+    ///         <see cref="Init"/> will be called automatically.
+    ///     </para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         In RELEASE builds: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
+    ///         rethrown. Otherwise, <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
+    ///         returns <see langword="true"/> the main loop will resume; otherwise this method will exit.
+    ///     </para>
+    ///     <para>
+    ///         The caller is responsible for disposing the object returned by this method.
+    ///     </para>
+    /// </remarks>
+    public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null);
 
-    /// <summary>Gets or sets the navigation manager.</summary>
-    ApplicationNavigation? Navigation { get; set; }
+    /// <summary>
+    ///     Raises the <see cref="Iteration"/> event.
+    /// </summary>
+    /// <remarks>
+    ///     This is called once per main loop iteration, before processing input, timeouts, or rendering.
+    /// </remarks>
+    public void RaiseIteration ();
 
-    /// <summary>Gets the currently active Toplevel.</summary>
-    Toplevel? Top { get; set; }
+    /// <summary>This event is raised on each iteration of the main loop.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This event is raised before input processing, timeout callbacks, and rendering occur each iteration.
+    ///     </para>
+    ///     <para>See also <see cref="AddTimeout"/> and <see cref="TimedEvents"/>.</para>
+    /// </remarks>
+    public event EventHandler<IterationEventArgs>? Iteration;
 
-    /// <summary>Gets the stack of all Toplevels.</summary>
-    System.Collections.Concurrent.ConcurrentStack<Toplevel> TopLevels { get; }
+    /// <summary>Runs <paramref name="action"/> on the main UI loop thread.</summary>
+    /// <param name="action">The action to be invoked on the main processing thread.</param>
+    /// <remarks>
+    ///     <para>
+    ///         If called from the main thread, the action is executed immediately. Otherwise, it is queued via
+    ///         <see cref="AddTimeout"/> with <see cref="TimeSpan.Zero"/> and will be executed on the next main loop
+    ///         iteration.
+    ///     </para>
+    /// </remarks>
+    void Invoke (Action action);
 
     /// <summary>
-    /// Caches the Toplevel associated with the current RunState.
+    ///     Building block API: Ends a Session and completes the execution of a <see cref="Toplevel"/> that was started with
+    ///     <see cref="Begin(Toplevel)"/>. Not usually called directly by applications.
+    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>
+    ///     will automatically call this method when the session is stopped.
     /// </summary>
-    Toplevel? CachedRunStateToplevel { get; set; }
+    /// <param name="sessionToken">The <see cref="SessionToken"/> returned by the <see cref="Begin(Toplevel)"/> method.</param>
+    /// <remarks>
+    ///     <para>
+    ///         This method removes the <see cref="Toplevel"/> from the stack, raises the <see cref="SessionEnded"/>
+    ///         event, and disposes the <paramref name="sessionToken"/>.
+    ///     </para>
+    /// </remarks>
+    public void End (SessionToken sessionToken);
 
-    /// <summary>Requests that the application stop running.</summary>
+    /// <summary>Requests that the currently running Session stop. The Session will stop after the current iteration completes.</summary>
+    /// <remarks>
+    ///     <para>This will cause <see cref="Run(Toplevel, Func{Exception, bool})"/> to return.</para>
+    ///     <para>
+    ///         This is equivalent to calling <see cref="RequestStop(Toplevel)"/> with <see cref="Top"/> as the parameter.
+    ///     </para>
+    /// </remarks>
     void RequestStop ();
 
+    /// <summary>Requests that the currently running Session stop. The Session will stop after the current iteration completes.</summary>
+    /// <param name="top">
+    ///     The <see cref="Toplevel"/> to stop. If <see langword="null"/>, stops the currently running <see cref="Top"/>.
+    /// </param>
+    /// <remarks>
+    ///     <para>This will cause <see cref="Run(Toplevel, Func{Exception, bool})"/> to return.</para>
+    ///     <para>
+    ///         Calling <see cref="RequestStop(Toplevel)"/> is equivalent to setting the <see cref="Toplevel.Running"/>
+    ///         property on the specified <see cref="Toplevel"/> to <see langword="false"/>.
+    ///     </para>
+    /// </remarks>
+    void RequestStop (Toplevel? top);
+
     /// <summary>
-    ///     Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that
-    ///     need to be laid out (see <see cref="View.NeedsLayout"/>) will be laid out.
-    ///     Only Views that need to be drawn (see <see cref="View.NeedsDraw"/>) will be drawn.
+    ///     Set to <see langword="true"/> to cause the session to stop running after first iteration.
     /// </summary>
-    /// <param name="forceRedraw">
-    ///     If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and
-    ///     should only be overriden for testing.
-    /// </param>
-    public void LayoutAndDraw (bool forceRedraw = false);
+    /// <remarks>
+    ///     <para>
+    ///         Used primarily for unit testing. When <see langword="true"/>, <see cref="End"/> will be called
+    ///         automatically after the first main loop iteration.
+    ///     </para>
+    /// </remarks>
+    bool StopAfterFirstIteration { get; set; }
 
-    /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
-    /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
-    /// <para>
-    ///     This function loads the right <see cref="IConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
-    ///     assigns it to <see cref="Application.Top"/>
-    /// </para>
-    /// <para>
-    ///     <see cref="Shutdown"/> must be called when the application is closing (typically after
-    ///     <see cref="Run{T}"/> has returned) to ensure resources are cleaned up and
-    ///     terminal settings
-    ///     restored.
-    /// </para>
-    /// <para>
-    ///     The <see cref="Run{T}"/> function combines
-    ///     <see cref="Init(IConsoleDriver,string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
-    ///     into a single
-    ///     call. An application cam use <see cref="Run{T}"/> without explicitly calling
-    ///     <see cref="Init(IConsoleDriver,string)"/>.
-    /// </para>
-    /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or
-    ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
-    /// </param>
-    /// <param name="driverName">
-    ///     The driver name (e.g. "dotnet", "windows", "fake", or "unix") of the
-    ///     <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
-    ///     specified the default driver for the platform will be used.
-    /// </param>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    public void Init (IConsoleDriver? driver = null, string? driverName = null);
+    /// <summary>
+    ///     Raised when <see cref="Begin(Toplevel)"/> has been called and has created a new <see cref="SessionToken"/>.
+    /// </summary>
+    /// <remarks>
+    ///     If <see cref="StopAfterFirstIteration"/> is <see langword="true"/>, callers to <see cref="Begin(Toplevel)"/>
+    ///     must also subscribe to <see cref="SessionEnded"/> and manually dispose of the <see cref="SessionToken"/> token
+    ///     when the application is done.
+    /// </remarks>
+    public event EventHandler<SessionTokenEventArgs>? SessionBegun;
 
-    /// <summary>Runs <paramref name="action"/> on the main UI loop thread</summary>
-    /// <param name="action">the action to be invoked on the main processing thread.</param>
-    void Invoke (Action action);
+    /// <summary>
+    ///     Raised when <see cref="End(SessionToken)"/> was called and the session is stopping. The event args contain a
+    ///     reference to the <see cref="Toplevel"/>
+    ///     that was active during the session. This can be used to ensure the Toplevel is disposed of properly.
+    /// </summary>
+    /// <remarks>
+    ///     If <see cref="StopAfterFirstIteration"/> is <see langword="true"/>, callers to <see cref="Begin(Toplevel)"/>
+    ///     must also subscribe to <see cref="SessionEnded"/> and manually dispose of the <see cref="SessionToken"/> token
+    ///     when the application is done.
+    /// </remarks>
+    public event EventHandler<ToplevelEventArgs>? SessionEnded;
+
+    #endregion Begin->Run->Iteration->Stop->End
+
+    #region Toplevel Management
+
+    /// <summary>Gets or sets the current Toplevel.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This is set by <see cref="Begin(Toplevel)"/> and cleared by <see cref="End(SessionToken)"/>.
+    ///     </para>
+    /// </remarks>
+    Toplevel? Top { get; set; }
+
+    /// <summary>Gets the stack of all Toplevels.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Toplevels are added to this stack by <see cref="Begin(Toplevel)"/> and removed by
+    ///         <see cref="End(SessionToken)"/>.
+    ///     </para>
+    /// </remarks>
+    ConcurrentStack<Toplevel> TopLevels { get; }
 
     /// <summary>
-    ///     <see langword="true"/> if implementation is 'old'. <see langword="false"/> if implementation
-    ///     is cutting edge.
+    ///     Caches the Toplevel associated with the current Session.
     /// </summary>
-    bool IsLegacy { get; }
+    /// <remarks>
+    ///     Used internally to optimize Toplevel state transitions.
+    /// </remarks>
+    Toplevel? CachedSessionTokenToplevel { get; set; }
 
-    /// <summary>Removes a previously scheduled timeout</summary>
-    /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
-    /// <returns>
-    ///     <see langword="true"/>
-    ///     if the timeout is successfully removed; otherwise,
-    ///     <see langword="false"/>
-    ///     .
-    ///     This method also returns
-    ///     <see langword="false"/>
-    ///     if the timeout is not found.
-    /// </returns>
-    bool RemoveTimeout (object token);
+    #endregion Toplevel Management
+
+    #region Screen and Driver
 
-    /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
-    /// <param name="top">The <see cref="Toplevel"/> to stop.</param>
+    /// <summary>Gets or sets the console driver being used.</summary>
     /// <remarks>
-    ///     <para>This will cause <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to return.</para>
     ///     <para>
-    ///         Calling <see cref="RequestStop(Toplevel)"/> is equivalent to setting the <see cref="Toplevel.Running"/>
-    ///         property on the currently running <see cref="Toplevel"/> to false.
+    ///         Set by <see cref="Init"/> based on the driver parameter or platform default.
     ///     </para>
     /// </remarks>
-    void RequestStop (Toplevel? top);
+    IDriver? Driver { get; set; }
 
     /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    ///     Gets or sets whether <see cref="Driver"/> will be forced to output only the 16 colors defined in
+    ///     <see cref="ColorName16"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be
+    ///     output as long as the selected <see cref="IDriver"/> supports TrueColor.
+    /// </summary>
+    bool Force16Colors { get; set; }
+
+    /// <summary>
+    ///     Forces the use of the specified driver (one of "fake", "dotnet", "windows", or "unix"). If not
+    ///     specified, the driver is selected based on the platform.
+    /// </summary>
+    string ForceDriver { get; set; }
+
+    /// <summary>
+    ///     Gets or sets the size of the screen. By default, this is the size of the screen as reported by the
+    ///     <see cref="IDriver"/>.
     /// </summary>
     /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
     ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
-    ///         ensure resources are cleaned up and terminal settings restored.
+    ///         If the <see cref="IDriver"/> has not been initialized, this will return a default size of 2048x2048; useful
+    ///         for unit tests.
     ///     </para>
+    /// </remarks>
+    Rectangle Screen { get; set; }
+
+    /// <summary>Raised when the terminal's size changed. The new size of the terminal is provided.</summary>
+    /// <remarks>
     ///     <para>
-    ///         The caller is responsible for disposing the object returned by this method.
+    ///         This event is raised when the driver detects a screen size change. The event provides the new screen
+    ///         rectangle.
     ///     </para>
     /// </remarks>
-    /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    public Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null);
+    public event EventHandler<EventArgs<Rectangle>>? ScreenChanged;
 
     /// <summary>
-    ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
-    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    ///     Gets or sets whether the screen will be cleared, and all Views redrawn, during the next Application iteration.
     /// </summary>
     /// <remarks>
-    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
     ///     <para>
-    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
-    ///         ensure resources are cleaned up and terminal settings restored.
+    ///         This is typically set to <see langword="true"/> when a View's <see cref="View.Frame"/> changes and that view
+    ///         has no SuperView (e.g. when <see cref="Top"/> is moved or resized).
     ///     </para>
     ///     <para>
-    ///         The caller is responsible for disposing the object returned by this method.
+    ///         Automatically reset to <see langword="false"/> after <see cref="LayoutAndDraw"/> processes it.
     ///     </para>
     /// </remarks>
-    /// <param name="errorHandler"></param>
-    /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
-    ///     be used. Must be
-    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
-    /// </param>
-    /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
-    [RequiresUnreferencedCode ("AOT")]
-    [RequiresDynamicCode ("AOT")]
-    public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-        where T : Toplevel, new();
+    bool ClearScreenNextIteration { get; set; }
 
-    /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
+    /// <summary>
+    ///     Collection of sixel images to write out to screen when updating.
+    ///     Only add to this collection if you are sure terminal supports sixel format.
+    /// </summary>
+    List<SixelToRender> Sixel { get; }
+
+    #endregion Screen and Driver
+
+    #region Layout and Drawing
+
+    /// <summary>
+    ///     Causes any Toplevels that need layout to be laid out, then draws any Toplevels that need display. Only Views
+    ///     that need to be laid out (see <see cref="View.NeedsLayout"/>) will be laid out. Only Views that need to be drawn
+    ///     (see <see cref="View.NeedsDraw"/>) will be drawn.
+    /// </summary>
+    /// <param name="forceRedraw">
+    ///     If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and
+    ///     should only be overridden for testing.
+    /// </param>
     /// <remarks>
     ///     <para>
-    ///         This method is used to start processing events for the main application, but it is also used to run other
-    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
+    ///         This method is called automatically each main loop iteration when any views need layout or drawing.
     ///     </para>
     ///     <para>
-    ///         To make a <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> stop execution, call
-    ///         <see cref="Application.RequestStop"/>.
-    ///     </para>
-    ///     <para>
-    ///         Calling <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
-    ///         <see cref="Application.Begin(Toplevel)"/>, followed by <see cref="Application.RunLoop(RunState)"/>, and then
-    ///         calling
-    ///         <see cref="Application.End(RunState)"/>.
+    ///         If <see cref="ClearScreenNextIteration"/> is <see langword="true"/>, the screen will be cleared before
+    ///         drawing and the flag will be reset to <see langword="false"/>.
     ///     </para>
+    /// </remarks>
+    public void LayoutAndDraw (bool forceRedraw = false);
+
+    /// <summary>
+    ///     Calls <see cref="View.PositionCursor"/> on the most focused view.
+    /// </summary>
+    /// <remarks>
+    ///     <para>Does nothing if there is no most focused view.</para>
     ///     <para>
-    ///         Alternatively, to have a program control the main loop and process events manually, call
-    ///         <see cref="Application.Begin(Toplevel)"/> to set things up manually and then repeatedly call
-    ///         <see cref="Application.RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="Application.RunLoop(RunState)"/> method will only process any pending events, timers handlers and
-    ///         then
-    ///         return control immediately.
+    ///         If the most focused view is not visible within its superview, the cursor will be hidden.
     ///     </para>
+    /// </remarks>
+    /// <returns><see langword="true"/> if a view positioned the cursor and the position is visible.</returns>
+    public bool PositionCursor ();
+
+    #endregion Layout and Drawing
+
+    #region Navigation and Popover
+
+    /// <summary>Gets or sets the popover manager.</summary>
+    /// <remarks>
     ///     <para>
-    ///         When using <see cref="Run{T}"/> or
-    ///         <see cref="Run(System.Func{System.Exception,bool},IConsoleDriver)"/>
-    ///         <see cref="Init"/> will be called automatically.
+    ///         Manages application-level popover views. Initialized during <see cref="Init"/>.
     ///     </para>
+    /// </remarks>
+    ApplicationPopover? Popover { get; set; }
+
+    /// <summary>Gets or sets the navigation manager.</summary>
+    /// <remarks>
     ///     <para>
-    ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
-    ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
-    ///         returns <see langword="true"/> the <see cref="Application.RunLoop(RunState)"/> will resume; otherwise this
-    ///         method will
-    ///         exit.
+    ///         Manages focus navigation and tracking of the most focused view. Initialized during <see cref="Init"/>.
     ///     </para>
     /// </remarks>
-    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
-    /// <param name="errorHandler">
-    ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
-    ///     rethrows when null).
-    /// </param>
-    public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null);
+    ApplicationNavigation? Navigation { get; set; }
 
-    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    #endregion Navigation and Popover
+
+    #region Timeouts
+
+    /// <summary>Adds a timeout to the application.</summary>
+    /// <param name="time">The time span to wait before invoking the callback.</param>
+    /// <param name="callback">
+    ///     The callback to invoke. If it returns <see langword="true"/>, the timeout will be reset and repeat. If it
+    ///     returns <see langword="false"/>, the timeout will stop and be removed.
+    /// </param>
+    /// <returns>
+    ///     A token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
+    /// </returns>
     /// <remarks>
-    ///     Shutdown must be called for every call to <see cref="Init"/> or
-    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
-    ///     up (Disposed)
-    ///     and terminal settings are restored.
+    ///     <para>
+    ///         When the time specified passes, the callback will be invoked on the main UI thread.
+    ///     </para>
     /// </remarks>
-    public void Shutdown ();
+    object AddTimeout (TimeSpan time, Func<bool> callback);
+
+    /// <summary>Removes a previously scheduled timeout.</summary>
+    /// <param name="token">The token returned by <see cref="AddTimeout"/>.</param>
+    /// <returns>
+    ///     <see langword="true"/> if the timeout is successfully removed; otherwise, <see langword="false"/>.
+    ///     This method also returns <see langword="false"/> if the timeout is not found.
+    /// </returns>
+    bool RemoveTimeout (object token);
 
     /// <summary>
     ///     Handles recurring events. These are invoked on the main UI thread - allowing for
     ///     safe updates to <see cref="View"/> instances.
     /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Provides low-level access to the timeout management system. Most applications should use
+    ///         <see cref="AddTimeout"/> and <see cref="RemoveTimeout"/> instead.
+    ///     </para>
+    /// </remarks>
     ITimedEvents? TimedEvents { get; }
+
+    #endregion Timeouts
 }

+ 1 - 1
Terminal.Gui/App/IterationEventArgs.cs

@@ -1,5 +1,5 @@
 namespace Terminal.Gui.App;
 
-/// <summary>Event arguments for the <see cref="Application.Iteration"/> event.</summary>
+/// <summary>Event arguments for the <see cref="IApplication.Iteration"/> event.</summary>
 public class IterationEventArgs : EventArgs
 { }

+ 2 - 2
Terminal.Gui/App/Keyboard/IKeyboard.cs

@@ -17,7 +17,7 @@ public interface IKeyboard
     IApplication? Application { get; set; }
 
     /// <summary>
-    ///     Called when the user presses a key (by the <see cref="IConsoleDriver"/>). Raises the cancelable
+    ///     Called when the user presses a key (by the <see cref="IDriver"/>). Raises the cancelable
     ///     <see cref="KeyDown"/> event, then calls <see cref="View.NewKeyDownEvent"/> on all top level views, and finally
     ///     if the key was not handled, invokes any Application-scoped <see cref="KeyBindings"/>.
     /// </summary>
@@ -27,7 +27,7 @@ public interface IKeyboard
     bool RaiseKeyDownEvent (Key key);
 
     /// <summary>
-    ///     Called when the user releases a key (by the <see cref="IConsoleDriver"/>). Raises the cancelable
+    ///     Called when the user releases a key (by the <see cref="IDriver"/>). Raises the cancelable
     ///     <see cref="KeyUp"/>
     ///     event
     ///     then calls <see cref="View.NewKeyUpEvent"/> on all top level views. Called after <see cref="RaiseKeyDownEvent"/>.

+ 4 - 1
Terminal.Gui/App/Keyboard/KeyboardImpl.cs

@@ -1,4 +1,6 @@
 #nullable enable
+using System.Diagnostics;
+
 namespace Terminal.Gui.App;
 
 /// <summary>
@@ -114,7 +116,8 @@ internal class KeyboardImpl : IKeyboard
     /// <inheritdoc/>
     public bool RaiseKeyDownEvent (Key key)
     {
-        Logging.Debug ($"{key}");
+        //ebug.Assert (App.Application.MainThreadId == Thread.CurrentThread.ManagedThreadId);
+        //Logging.Debug ($"{key}");
 
         // TODO: Add a way to ignore certain keys, esp for debugging.
         //#if DEBUG

+ 35 - 39
Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs

@@ -1,7 +1,8 @@
 #nullable enable
-using Terminal.Gui.Drivers;
+using System;
 using System.Collections.Concurrent;
 using System.Diagnostics;
+using Terminal.Gui.Drivers;
 
 namespace Terminal.Gui.App;
 
@@ -19,15 +20,15 @@ namespace Terminal.Gui.App;
 ///         <item>Throttling iterations to respect <see cref="Application.MaximumIterationsPerSecond"/></item>
 ///     </list>
 /// </remarks>
-/// <typeparam name="T">Type of raw input events, e.g. <see cref="ConsoleKeyInfo"/> for .NET driver</typeparam>
-public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
+/// <typeparam name="TInputRecord">Type of raw input events, e.g. <see cref="ConsoleKeyInfo"/> for .NET driver</typeparam>
+public class ApplicationMainLoop<TInputRecord> : IApplicationMainLoop<TInputRecord> where TInputRecord : struct
 {
     private ITimedEvents? _timedEvents;
-    private ConcurrentQueue<T>? _inputBuffer;
+    private ConcurrentQueue<TInputRecord>? _inputQueue;
     private IInputProcessor? _inputProcessor;
-    private IConsoleOutput? _out;
+    private IOutput? _output;
     private AnsiRequestScheduler? _ansiRequestScheduler;
-    private IConsoleSizeMonitor? _consoleSizeMonitor;
+    private ISizeMonitor? _sizeMonitor;
 
     /// <inheritdoc/>
     public ITimedEvents TimedEvents
@@ -40,13 +41,13 @@ public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
 
     /// <summary>
     ///     The input events thread-safe collection. This is populated on separate
-    ///     thread by a <see cref="IConsoleInput{T}"/>. Is drained as part of each
-    ///     <see cref="Iteration"/>
+    ///     thread by a <see cref="IInput{T}"/>. Is drained as part of each
+    ///     <see cref="Iteration"/> on the main loop thread.
     /// </summary>
-    public ConcurrentQueue<T> InputBuffer
+    public ConcurrentQueue<TInputRecord> InputQueue
     {
-        get => _inputBuffer ?? throw new NotInitializedException (nameof (InputBuffer));
-        private set => _inputBuffer = value;
+        get => _inputQueue ?? throw new NotInitializedException (nameof (InputQueue));
+        private set => _inputQueue = value;
     }
 
     /// <inheritdoc/>
@@ -57,13 +58,13 @@ public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
     }
 
     /// <inheritdoc/>
-    public IOutputBuffer OutputBuffer { get; } = new OutputBuffer ();
+    public IOutputBuffer OutputBuffer { get; } = new OutputBufferImpl ();
 
     /// <inheritdoc/>
-    public IConsoleOutput Out
+    public IOutput Output
     {
-        get => _out ?? throw new NotInitializedException (nameof (Out));
-        private set => _out = value;
+        get => _output ?? throw new NotInitializedException (nameof (Output));
+        private set => _output = value;
     }
 
     /// <inheritdoc/>
@@ -74,10 +75,10 @@ public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
     }
 
     /// <inheritdoc/>
-    public IConsoleSizeMonitor ConsoleSizeMonitor
+    public ISizeMonitor SizeMonitor
     {
-        get => _consoleSizeMonitor ?? throw new NotInitializedException (nameof (ConsoleSizeMonitor));
-        private set => _consoleSizeMonitor = value;
+        get => _sizeMonitor ?? throw new NotInitializedException (nameof (SizeMonitor));
+        private set => _sizeMonitor = value;
     }
 
     /// <summary>
@@ -85,12 +86,6 @@ public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
     /// </summary>
     public IToplevelTransitionManager ToplevelTransitionManager = new ToplevelTransitionManager ();
 
-    /// <summary>
-    ///     Determines how to get the current system type, adjust
-    ///     in unit tests to simulate specific timings.
-    /// </summary>
-    public Func<DateTime> Now { get; set; } = () => DateTime.Now;
-
     /// <summary>
     ///     Initializes the class with the provided subcomponents
     /// </summary>
@@ -101,34 +96,34 @@ public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
     /// <param name="componentFactory"></param>
     public void Initialize (
         ITimedEvents timedEvents,
-        ConcurrentQueue<T> inputBuffer,
+        ConcurrentQueue<TInputRecord> inputBuffer,
         IInputProcessor inputProcessor,
-        IConsoleOutput consoleOutput,
-        IComponentFactory<T> componentFactory
+        IOutput consoleOutput,
+        IComponentFactory<TInputRecord> componentFactory
     )
     {
-        InputBuffer = inputBuffer;
-        Out = consoleOutput;
+        InputQueue = inputBuffer;
+        Output = consoleOutput;
         InputProcessor = inputProcessor;
 
         TimedEvents = timedEvents;
         AnsiRequestScheduler = new (InputProcessor.GetParser ());
 
-        ConsoleSizeMonitor = componentFactory.CreateConsoleSizeMonitor (Out, OutputBuffer);
+        OutputBuffer.SetSize (consoleOutput.GetSize ().Width, consoleOutput.GetSize ().Height);
+        SizeMonitor = componentFactory.CreateSizeMonitor (Output, OutputBuffer);
     }
 
     /// <inheritdoc/>
     public void Iteration ()
     {
-
         Application.RaiseIteration ();
 
-        DateTime dt = Now ();
+        DateTime dt = DateTime.Now;
         int timeAllowed = 1000 / Math.Max(1,(int)Application.MaximumIterationsPerSecond);
 
         IterationImpl ();
 
-        TimeSpan took = Now () - dt;
+        TimeSpan took = DateTime.Now - dt;
         TimeSpan sleepFor = TimeSpan.FromMilliseconds (timeAllowed) - took;
 
         Logging.TotalIterationMetric.Record (took.Milliseconds);
@@ -141,6 +136,7 @@ public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
 
     internal void IterationImpl ()
     {
+        // Pull any input events from the input queue and process them
         InputProcessor.ProcessQueue ();
 
         ToplevelTransitionManager.RaiseReadyEventIfNeeded ();
@@ -152,7 +148,7 @@ public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
                                      || AnySubViewsNeedDrawn (Application.Top)
                                      || (Application.Mouse.MouseGrabView != null && AnySubViewsNeedDrawn (Application.Mouse.MouseGrabView));
 
-            bool sizeChanged = ConsoleSizeMonitor.Poll ();
+            bool sizeChanged = SizeMonitor.Poll ();
 
             if (needsDrawOrLayout || sizeChanged)
             {
@@ -160,9 +156,9 @@ public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
 
                 Application.LayoutAndDraw (true);
 
-                Out.Write (OutputBuffer);
+                Output.Write (OutputBuffer);
 
-                Out.SetCursorVisibility (CursorVisibility.Default);
+                Output.SetCursorVisibility (CursorVisibility.Default);
             }
 
             SetCursor ();
@@ -191,12 +187,12 @@ public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
             // Translate to screen coordinates
             to = mostFocused.ViewportToScreen (to.Value);
 
-            Out.SetCursorPosition (to.Value.X, to.Value.Y);
-            Out.SetCursorVisibility (mostFocused.CursorVisibility);
+            Output.SetCursorPosition (to.Value.X, to.Value.Y);
+            Output.SetCursorVisibility (mostFocused.CursorVisibility);
         }
         else
         {
-            Out.SetCursorVisibility (CursorVisibility.Invisible);
+            Output.SetCursorVisibility (CursorVisibility.Invisible);
         }
     }
 

+ 54 - 19
Terminal.Gui/App/MainLoop/IApplicationMainLoop.cs

@@ -15,27 +15,27 @@ namespace Terminal.Gui.App;
 ///         <item>Rendering UI updates to the console</item>
 ///     </list>
 /// </remarks>
-/// <typeparam name="T">Type of raw input events processed by the loop, e.g. <see cref="ConsoleKeyInfo"/> for cross-platform .NET driver</typeparam>
-public interface IApplicationMainLoop<T> : IDisposable
+/// <typeparam name="TInputRecord">Type of raw input events processed by the loop, e.g. <see cref="ConsoleKeyInfo"/> for cross-platform .NET driver</typeparam>
+public interface IApplicationMainLoop<TInputRecord> : IDisposable where TInputRecord : struct
 {
     /// <summary>
-    ///     Gets the class responsible for servicing user timeouts
+    ///     Gets the <see cref="ITimedEvents"/> implementation that manages user-defined timeouts and periodic events.
     /// </summary>
     public ITimedEvents TimedEvents { get; }
 
     /// <summary>
-    ///     Gets the class responsible for writing final rendered output to the console
+    ///     Gets the <see cref="IOutputBuffer"/> representing the desired screen state for console rendering.
     /// </summary>
     public IOutputBuffer OutputBuffer { get; }
 
     /// <summary>
-    ///     Class for writing output to the console.
+    ///     Gets the <see cref="IOutput"/> implementation responsible for rendering the <see cref="OutputBuffer"/> to the console using platform specific methods.
     /// </summary>
-    public IConsoleOutput Out { get; }
+    public IOutput Output { get; }
 
     /// <summary>
-    ///     Gets the class responsible for processing buffered console input and translating
-    ///     it into events on the UI thread.
+    ///     Gets <see cref="InputProcessor"/> implementation that processes the mouse and keyboard input populated by <see cref="IInput{TInputRecord}"/>
+    ///     implementations on the input thread and translating to events on the UI thread.
     /// </summary>
     public IInputProcessor InputProcessor { get; }
 
@@ -46,24 +46,59 @@ public interface IApplicationMainLoop<T> : IDisposable
     public AnsiRequestScheduler AnsiRequestScheduler { get; }
 
     /// <summary>
-    ///     Gets the class responsible for determining the current console size
+    ///     Gets the <see cref="ISizeMonitor"/> implementation that tracks terminal size changes.
     /// </summary>
-    public IConsoleSizeMonitor ConsoleSizeMonitor { get; }
+    public ISizeMonitor SizeMonitor { get; }
 
     /// <summary>
-    ///     Initializes the loop with a buffer from which data can be read
+    ///     Initializes the main loop with its required dependencies.
     /// </summary>
-    /// <param name="timedEvents"></param>
-    /// <param name="inputBuffer"></param>
-    /// <param name="inputProcessor"></param>
-    /// <param name="consoleOutput"></param>
-    /// <param name="componentFactory"></param>
+    /// <param name="timedEvents">
+    ///     The <see cref="ITimedEvents"/> implementation for managing user-defined timeouts and periodic callbacks
+    ///     (e.g., <see cref="Application.AddTimeout"/>).
+    /// </param>
+    /// <param name="inputQueue">
+    ///     The thread-safe queue containing raw input events populated by <see cref="IInput{TInputRecord}"/> on
+    ///     the input thread. This queue is drained by <see cref="InputProcessor"/> during each <see cref="Iteration"/>.
+    /// </param>
+    /// <param name="inputProcessor">
+    ///     The <see cref="IInputProcessor"/> that translates raw input records (e.g., <see cref="ConsoleKeyInfo"/>) 
+    ///     into Terminal.Gui events (<see cref="Key"/>, <see cref="MouseEventArgs"/>) and raises them on the main UI thread.
+    /// </param>
+    /// <param name="output">
+    ///     The <see cref="IOutput"/> implementation responsible for rendering the <see cref="OutputBuffer"/> to the
+    ///     console using platform-specific methods (e.g., Win32 APIs, ANSI escape sequences).
+    /// </param>
+    /// <param name="componentFactory">
+    ///     The factory for creating driver-specific components. Used here to create the <see cref="ISizeMonitor"/>
+    ///     that tracks terminal size changes.
+    /// </param>
+    /// <remarks>
+    ///     <para>
+    ///         This method is called by <see cref="MainLoopCoordinator{TInputRecord}"/> during application startup
+    ///         to wire up all the components needed for the main loop to function. It must be called before
+    ///         <see cref="Iteration"/> can be invoked.
+    ///     </para>
+    ///     <para>
+    ///         <b>Initialization order:</b>
+    ///     </para>
+    ///     <list type="number">
+    ///         <item>Store references to <paramref name="timedEvents"/>, <paramref name="inputQueue"/>, 
+    ///               <paramref name="inputProcessor"/>, and <paramref name="output"/></item>
+    ///         <item>Create <see cref="AnsiRequestScheduler"/> for managing ANSI requests/responses</item>
+    ///         <item>Initialize <see cref="OutputBuffer"/> size to match current console dimensions</item>
+    ///         <item>Create <see cref="ISizeMonitor"/> using the <paramref name="componentFactory"/></item>
+    ///     </list>
+    ///     <para>
+    ///         After initialization, the main loop is ready to process events via <see cref="Iteration"/>.
+    ///     </para>
+    /// </remarks>
     void Initialize (
         ITimedEvents timedEvents,
-        ConcurrentQueue<T> inputBuffer,
+        ConcurrentQueue<TInputRecord> inputQueue,
         IInputProcessor inputProcessor,
-        IConsoleOutput consoleOutput,
-        IComponentFactory<T> componentFactory
+        IOutput output,
+        IComponentFactory<TInputRecord> componentFactory
     );
 
     /// <summary>

+ 1 - 2
Terminal.Gui/App/MainLoop/IMainLoopCoordinator.cs

@@ -8,7 +8,6 @@
 ///     <list type="bullet">
 ///         <item>Starting the asynchronous input reading thread</item>
 ///         <item>Initializing the main UI loop on the application thread</item>
-///         <item>Building the <see cref="IConsoleDriver"/> facade</item>
 ///         <item>Coordinating clean shutdown of both threads</item>
 ///     </list>
 /// </remarks>
@@ -26,7 +25,7 @@ public interface IMainLoopCoordinator
     ///     </list>
     /// </remarks>
     /// <returns>A task that completes when initialization is done</returns>
-    public Task StartAsync ();
+    public Task StartInputTaskAsync ();
 
     /// <summary>
     ///     Stops the input thread and performs cleanup.

+ 97 - 80
Terminal.Gui/App/MainLoop/MainLoopCoordinator.cs

@@ -1,6 +1,4 @@
 using System.Collections.Concurrent;
-using Terminal.Gui.Drivers;
-using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui.App;
 
@@ -15,50 +13,52 @@ namespace Terminal.Gui.App;
 ///     </para>
 ///     <para>This class is designed to be managed by <see cref="ApplicationImpl"/></para>
 /// </summary>
-/// <typeparam name="T">Type of raw input events, e.g. <see cref="ConsoleKeyInfo"/> for .NET driver</typeparam>
-internal class MainLoopCoordinator<T> : IMainLoopCoordinator
+/// <typeparam name="TInputRecord">Type of raw input events, e.g. <see cref="ConsoleKeyInfo"/> for .NET driver</typeparam>
+internal class MainLoopCoordinator<TInputRecord> : IMainLoopCoordinator where TInputRecord : struct
 {
-    private readonly ConcurrentQueue<T> _inputBuffer;
-    private readonly IInputProcessor _inputProcessor;
-    private readonly IApplicationMainLoop<T> _loop;
-    private readonly IComponentFactory<T> _componentFactory;
-    private readonly CancellationTokenSource _tokenSource = new ();
-    private IConsoleInput<T> _input;
-    private IConsoleOutput _output;
-    private readonly object _oLockInitialization = new ();
-    private ConsoleDriverFacade<T> _facade;
-    private Task _inputTask;
-    private readonly ITimedEvents _timedEvents;
-
-    private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
-
     /// <summary>
     ///     Creates a new coordinator that will manage the main UI loop and input thread.
     /// </summary>
     /// <param name="timedEvents">Handles scheduling and execution of user timeout callbacks</param>
-    /// <param name="inputBuffer">Thread-safe queue for buffering raw console input</param>
+    /// <param name="inputQueue">Thread-safe queue for buffering raw console input</param>
     /// <param name="loop">The main application loop instance</param>
     /// <param name="componentFactory">Factory for creating driver-specific components (input, output, etc.)</param>
     public MainLoopCoordinator (
         ITimedEvents timedEvents,
-        ConcurrentQueue<T> inputBuffer,
-        IApplicationMainLoop<T> loop,
-        IComponentFactory<T> componentFactory
+        ConcurrentQueue<TInputRecord> inputQueue,
+        IApplicationMainLoop<TInputRecord> loop,
+        IComponentFactory<TInputRecord> componentFactory
     )
     {
         _timedEvents = timedEvents;
-        _inputBuffer = inputBuffer;
-        _inputProcessor = componentFactory.CreateInputProcessor (_inputBuffer);
+        _inputQueue = inputQueue;
+        _inputProcessor = componentFactory.CreateInputProcessor (_inputQueue);
         _loop = loop;
         _componentFactory = componentFactory;
     }
 
+    private readonly IApplicationMainLoop<TInputRecord> _loop;
+    private readonly IComponentFactory<TInputRecord> _componentFactory;
+    private readonly CancellationTokenSource _runCancellationTokenSource = new ();
+    private readonly ConcurrentQueue<TInputRecord> _inputQueue;
+    private readonly IInputProcessor _inputProcessor;
+    private readonly object _oLockInitialization = new ();
+    private readonly ITimedEvents _timedEvents;
+
+    private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
+    private IInput<TInputRecord> _input;
+    private Task _inputTask;
+    private IOutput _output;
+    private DriverImpl _driver;
+
+    private bool _stopCalled;
+
     /// <summary>
     ///     Starts the input loop thread in separate task (returning immediately).
     /// </summary>
-    public async Task StartAsync ()
+    public async Task StartInputTaskAsync ()
     {
-        Logging.Logger.LogInformation ("Main Loop Coordinator booting...");
+        Logging.Trace ("Booting... ()");
 
         _inputTask = Task.Run (RunInput);
 
@@ -80,100 +80,117 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
                 throw _inputTask.Exception;
             }
 
-            Logging.Logger.LogCritical("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)");
+            Logging.Critical ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)");
         }
 
-        Logging.Logger.LogInformation ("Main Loop Coordinator booting complete");
+        Logging.Trace ("Booting complete");
     }
 
-    private void RunInput ()
+    /// <inheritdoc/>
+    public void RunIteration ()
     {
-        try
-        {
-            lock (_oLockInitialization)
-            {
-                // Instance must be constructed on the thread in which it is used.
-                _input = _componentFactory.CreateInput ();
-                _input.Initialize (_inputBuffer);
-
-                BuildFacadeIfPossible ();
-            }
-
-            try
-            {
-                _input.Run (_tokenSource.Token);
-            }
-            catch (OperationCanceledException)
-            { }
-
-            _input.Dispose ();
-        }
-        catch (Exception e)
+        lock (_oLockInitialization)
         {
-            Logging.Logger.LogCritical (e, "Input loop crashed");
-
-            throw;
+            _loop.Iteration ();
         }
+    }
 
+    /// <inheritdoc/>
+    public void Stop ()
+    {
+        // Ignore repeated calls to Stop - happens if user spams Application.Shutdown().
         if (_stopCalled)
         {
-            Logging.Logger.LogInformation ("Input loop exited cleanly");
-        }
-        else
-        {
-            Logging.Logger.LogCritical ("Input loop exited early (stop not called)");
+            return;
         }
-    }
 
-    /// <inheritdoc/>
-    public void RunIteration () { _loop.Iteration (); }
+        _stopCalled = true;
+
+        _runCancellationTokenSource.Cancel ();
+        _output.Dispose ();
+
+        // Wait for input infinite loop to exit
+        _inputTask.Wait ();
+    }
 
     private void BootMainLoop ()
     {
+        //Logging.Trace ($"_inputProcessor: {_inputProcessor}, _output: {_output}, _componentFactory: {_componentFactory}");
+
         lock (_oLockInitialization)
         {
             // Instance must be constructed on the thread in which it is used.
             _output = _componentFactory.CreateOutput ();
-            _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output,_componentFactory);
+            _loop.Initialize (_timedEvents, _inputQueue, _inputProcessor, _output, _componentFactory);
 
-            BuildFacadeIfPossible ();
+            BuildDriverIfPossible ();
         }
     }
 
-    private void BuildFacadeIfPossible ()
+    private void BuildDriverIfPossible ()
     {
+
         if (_input != null && _output != null)
         {
-            _facade = new (
+            _driver = new (
                            _inputProcessor,
                            _loop.OutputBuffer,
                            _output,
                            _loop.AnsiRequestScheduler,
-                           _loop.ConsoleSizeMonitor);
+                           _loop.SizeMonitor);
 
-            Application.Driver = _facade;
+            Application.Driver = _driver;
 
             _startupSemaphore.Release ();
+            Logging.Trace ($"Driver: _input: {_input}, _output: {_output}");
         }
     }
 
-    private bool _stopCalled;
-
-    /// <inheritdoc/>
-    public void Stop ()
+    /// <summary>
+    ///     INTERNAL: Runs the IInput read loop on a new thread called the "Input Thread".
+    /// </summary>
+    private void RunInput ()
     {
-        // Ignore repeated calls to Stop - happens if user spams Application.Shutdown().
-        if (_stopCalled)
+        try
         {
-            return;
-        }
+            lock (_oLockInitialization)
+            {
+                // Instance must be constructed on the thread in which it is used.
+                _input = _componentFactory.CreateInput ();
+                _input.Initialize (_inputQueue);
 
-        _stopCalled = true;
+                // Wire up InputImpl reference for ITestableInput support
+                if (_inputProcessor is InputProcessorImpl<TInputRecord> impl)
+                {
+                    impl.InputImpl = _input;
+                }
 
-        _tokenSource.Cancel ();
-        _output.Dispose ();
+                BuildDriverIfPossible ();
+            }
 
-        // Wait for input infinite loop to exit
-        _inputTask.Wait ();
+            try
+            {
+                _input.Run (_runCancellationTokenSource.Token);
+            }
+            catch (OperationCanceledException)
+            { }
+
+            _input.Dispose ();
+        }
+        catch (Exception e)
+        {
+            Logging.Critical ($"Input loop crashed: {e}");
+
+            throw;
+        }
+
+        if (_stopCalled)
+        {
+            Logging.Information ("Input loop exited cleanly");
+        }
+        else
+        {
+            Logging.Critical ("Input loop exited early (stop not called)");
+        }
     }
 }

+ 2 - 0
Terminal.Gui/App/Mouse/MouseImpl.cs

@@ -1,5 +1,6 @@
 #nullable enable
 using System.ComponentModel;
+using System.Diagnostics;
 
 namespace Terminal.Gui.App;
 
@@ -55,6 +56,7 @@ internal class MouseImpl : IMouse
     /// <inheritdoc/>
     public void RaiseMouseEvent (MouseEventArgs mouseEvent)
     {
+        //Debug.Assert (App.Application.MainThreadId == Thread.CurrentThread.ManagedThreadId);
         if (Application?.Initialized is true)
         {
             // LastMousePosition is only set if the application is initialized.

+ 0 - 12
Terminal.Gui/App/RunStateEventArgs.cs

@@ -1,12 +0,0 @@
-namespace Terminal.Gui.App;
-
-/// <summary>Event arguments for events about <see cref="RunState"/></summary>
-public class RunStateEventArgs : EventArgs
-{
-    /// <summary>Creates a new instance of the <see cref="RunStateEventArgs"/> class</summary>
-    /// <param name="state"></param>
-    public RunStateEventArgs (RunState state) { State = state; }
-
-    /// <summary>The state being reported on by the event</summary>
-    public RunState State { get; }
-}

+ 19 - 19
Terminal.Gui/App/RunState.cs → Terminal.Gui/App/SessionToken.cs

@@ -2,22 +2,22 @@
 
 namespace Terminal.Gui.App;
 
-/// <summary>The execution state for a <see cref="Toplevel"/> view.</summary>
-public class RunState : IDisposable
+/// <summary>Defines a session token for a running <see cref="Toplevel"/>.</summary>
+public class SessionToken : IDisposable
 {
-    /// <summary>Initializes a new <see cref="RunState"/> class.</summary>
+    /// <summary>Initializes a new <see cref="SessionToken"/> class.</summary>
     /// <param name="view"></param>
-    public RunState (Toplevel view) { Toplevel = view; }
+    public SessionToken (Toplevel view) { Toplevel = view; }
 
-    /// <summary>The <see cref="Toplevel"/> belonging to this <see cref="RunState"/>.</summary>
+    /// <summary>The <see cref="Toplevel"/> belonging to this <see cref="SessionToken"/>.</summary>
     public Toplevel Toplevel { get; internal set; }
 
-    /// <summary>Releases all resource used by the <see cref="RunState"/> object.</summary>
-    /// <remarks>Call <see cref="Dispose()"/> when you are finished using the <see cref="RunState"/>.</remarks>
+    /// <summary>Releases all resource used by the <see cref="SessionToken"/> object.</summary>
+    /// <remarks>Call <see cref="Dispose()"/> when you are finished using the <see cref="SessionToken"/>.</remarks>
     /// <remarks>
-    ///     <see cref="Dispose()"/> method leaves the <see cref="RunState"/> in an unusable state. After calling
-    ///     <see cref="Dispose()"/>, you must release all references to the <see cref="RunState"/> so the garbage collector can
-    ///     reclaim the memory that the <see cref="RunState"/> was occupying.
+    ///     <see cref="Dispose()"/> method leaves the <see cref="SessionToken"/> in an unusable state. After calling
+    ///     <see cref="Dispose()"/>, you must release all references to the <see cref="SessionToken"/> so the garbage collector can
+    ///     reclaim the memory that the <see cref="SessionToken"/> was occupying.
     /// </remarks>
     public void Dispose ()
     {
@@ -28,7 +28,7 @@ public class RunState : IDisposable
 #endif
     }
 
-    /// <summary>Releases all resource used by the <see cref="RunState"/> object.</summary>
+    /// <summary>Releases all resource used by the <see cref="SessionToken"/> object.</summary>
     /// <param name="disposing">If set to <see langword="true"/> we are disposing and should dispose held objects.</param>
     protected virtual void Dispose (bool disposing)
     {
@@ -38,7 +38,7 @@ public class RunState : IDisposable
             // But that is not correct becaue `Begin` didn't create the TopLevel, `Init` did; thus
             // disposing should be done by `Shutdown`, not `End`.
             throw new InvalidOperationException (
-                                                 "Toplevel must be null before calling Application.RunState.Dispose"
+                                                 "Toplevel must be null before calling Application.SessionToken.Dispose"
                                                 );
         }
     }
@@ -46,29 +46,29 @@ public class RunState : IDisposable
 #if DEBUG_IDISPOSABLE
 #pragma warning disable CS0419 // Ambiguous reference in cref attribute
     /// <summary>
-    ///     Gets whether <see cref="RunState.Dispose"/> was called on this RunState or not.
+    ///     Gets whether <see cref="SessionToken.Dispose"/> was called on this SessionToken or not.
     ///     For debug purposes to verify objects are being disposed properly.
     ///     Only valid when DEBUG_IDISPOSABLE is defined.
     /// </summary>
     public bool WasDisposed { get; private set; }
 
     /// <summary>
-    ///     Gets the number of times <see cref="RunState.Dispose"/> was called on this object.
+    ///     Gets the number of times <see cref="SessionToken.Dispose"/> was called on this object.
     ///     For debug purposes to verify objects are being disposed properly.
     ///     Only valid when DEBUG_IDISPOSABLE is defined.
     /// </summary>
     public int DisposedCount { get; private set; } = 0;
 
     /// <summary>
-    ///     Gets the list of RunState objects that have been created and not yet disposed.
-    ///     Note, this is a static property and will affect all RunState objects.
+    ///     Gets the list of SessionToken objects that have been created and not yet disposed.
+    ///     Note, this is a static property and will affect all SessionToken objects.
     ///     For debug purposes to verify objects are being disposed properly.
     ///     Only valid when DEBUG_IDISPOSABLE is defined.
     /// </summary>
-    public static ConcurrentBag<RunState> Instances { get; private set; } = [];
+    public static ConcurrentBag<SessionToken> Instances { get; private set; } = [];
 
-    /// <summary>Creates a new RunState object.</summary>
-    public RunState ()
+    /// <summary>Creates a new SessionToken object.</summary>
+    public SessionToken ()
     {
         Instances.Add (this);
     }

+ 12 - 0
Terminal.Gui/App/SessionTokenEventArgs.cs

@@ -0,0 +1,12 @@
+namespace Terminal.Gui.App;
+
+/// <summary>Event arguments for events about <see cref="SessionToken"/></summary>
+public class SessionTokenEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="SessionTokenEventArgs"/> class</summary>
+    /// <param name="state"></param>
+    public SessionTokenEventArgs (SessionToken state) { State = state; }
+
+    /// <summary>The state being reported on by the event</summary>
+    public SessionToken State { get; }
+}

+ 1 - 1
Terminal.Gui/App/Timeout/ITimedEvents.cs

@@ -23,7 +23,7 @@ public interface ITimedEvents
 
     /// <summary>
     ///     Invoked when a new timeout is added. To be used in the case when
-    ///     <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
+    ///     <see cref="IApplication.StopAfterFirstIteration"/> is <see langword="true"/>.
     /// </summary>
     event EventHandler<TimeoutEventArgs>? Added;
 

+ 1 - 1
Terminal.Gui/Drawing/Cell.cs

@@ -5,7 +5,7 @@ namespace Terminal.Gui.Drawing;
 
 /// <summary>
 ///     Represents a single row/column in a Terminal.Gui rendering surface (e.g. <see cref="LineCanvas"/> and
-///     <see cref="IConsoleDriver"/>).
+///     <see cref="IDriver"/>).
 /// </summary>
 public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default)
 {

+ 3 - 3
Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs

@@ -402,7 +402,7 @@ public class LineCanvas : IDisposable
         // TODO: Add other resolvers
     };
 
-    private Cell? GetCellForIntersects (IConsoleDriver? driver, ReadOnlySpan<IntersectionDefinition> intersects)
+    private Cell? GetCellForIntersects (IDriver? driver, ReadOnlySpan<IntersectionDefinition> intersects)
     {
         if (intersects.IsEmpty)
         {
@@ -422,7 +422,7 @@ public class LineCanvas : IDisposable
         return cell;
     }
 
-    private Rune? GetRuneForIntersects (IConsoleDriver? driver, ReadOnlySpan<IntersectionDefinition> intersects)
+    private Rune? GetRuneForIntersects (IDriver? driver, ReadOnlySpan<IntersectionDefinition> intersects)
     {
         if (intersects.IsEmpty)
         {
@@ -769,7 +769,7 @@ public class LineCanvas : IDisposable
         internal Rune _thickV;
         protected IntersectionRuneResolver () { SetGlyphs (); }
 
-        public Rune? GetRuneForIntersects (IConsoleDriver? driver, ReadOnlySpan<IntersectionDefinition> intersects)
+        public Rune? GetRuneForIntersects (IDriver? driver, ReadOnlySpan<IntersectionDefinition> intersects)
         {
             // Note that there aren't any glyphs for intersections of double lines with heavy lines
 

+ 1 - 1
Terminal.Gui/Drawing/Ruler.cs

@@ -24,7 +24,7 @@ internal class Ruler
     /// <param name="location">The location to start drawing the ruler, in screen-relative coordinates.</param>
     /// <param name="start">The start value of the ruler.</param>
     /// <param name="driver">Optional Driver. If not provided, driver will be used.</param>
-    public void Draw (Point location, int start = 0, IConsoleDriver? driver = null)
+    public void Draw (Point location, int start = 0, IDriver? driver = null)
     {
         if (start < 0)
         {

+ 1 - 1
Terminal.Gui/Drawing/Sixel/SixelToRender.cs

@@ -2,7 +2,7 @@
 
 /// <summary>
 ///     Describes a request to render a given <see cref="SixelData"/> at a given <see cref="ScreenPosition"/>.
-///     Requires that the terminal and <see cref="IConsoleDriver"/> both support sixel.
+///     Requires that the terminal and <see cref="IDriver"/> both support sixel.
 /// </summary>
 public class SixelToRender
 {

+ 1 - 1
Terminal.Gui/Drawing/Thickness.cs

@@ -90,7 +90,7 @@ public record struct Thickness
     /// <param name="label">The diagnostics label to draw on the bottom of the <see cref="Bottom"/>.</param>
     /// <param name="driver">Optional driver. If not specified, <see cref="Application.Driver"/> will be used.</param>
     /// <returns>The inner rectangle remaining to be drawn.</returns>
-    public Rectangle Draw (Rectangle rect, ViewDiagnosticFlags diagnosticFlags = ViewDiagnosticFlags.Off, string? label = null, IConsoleDriver? driver = null)
+    public Rectangle Draw (Rectangle rect, ViewDiagnosticFlags diagnosticFlags = ViewDiagnosticFlags.Off, string? label = null, IDriver? driver = null)
     {
         if (rect.Size.Width < 1 || rect.Size.Height < 1)
         {

+ 1 - 1
Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequenceRequest.cs

@@ -22,7 +22,7 @@ public class AnsiEscapeSequenceRequest : AnsiEscapeSequence
 
 
     /// <summary>
-    ///     Sends the <see cref="AnsiEscapeSequence.Request"/> to the raw output stream of the current <see cref="ConsoleDriver"/>.
+    ///     Sends the <see cref="AnsiEscapeSequence.Request"/> to the raw output stream of the current <see cref="IDriver"/>.
     ///     Only call this method from the main UI thread. You should use <see cref="AnsiRequestScheduler"/> if
     ///     sending many requests.
     /// </summary>

+ 16 - 17
Terminal.Gui/Drivers/AnsiHandling/AnsiResponseParser.cs

@@ -141,6 +141,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
 
             bool isEscape = currentChar == ESCAPE;
 
+            // Logging.Trace($"Processing character '{currentChar}' (isEscape: {isEscape})");
             switch (State)
             {
                 case AnsiResponseParserState.Normal:
@@ -261,7 +262,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
                 {
                     _heldContent.ClearHeld ();
 
-                    Logging.Trace ($"AnsiResponseParser last minute swallowed '{cur}'");
+                    //Logging.Trace ($"AnsiResponseParser last minute swallowed '{cur}'");
                 }
             }
         }
@@ -342,7 +343,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
                 {
                     _heldContent.ClearHeld ();
 
-                    Logging.Trace ($"AnsiResponseParser swallowed '{cur}'");
+                    //Logging.Trace ($"AnsiResponseParser swallowed '{cur}'");
 
                     // Do not send back to input stream
                     return false;
@@ -403,7 +404,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
 
         if (matchingResponse?.Response != null)
         {
-            Logging.Trace ($"AnsiResponseParser processed '{cur}'");
+            //Logging.Trace ($"AnsiResponseParser processed '{cur}'");
 
             if (invokeCallback)
             {
@@ -479,16 +480,14 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
     }
 }
 
-internal class AnsiResponseParser<T> : AnsiResponseParserBase
+internal class AnsiResponseParser<TInputRecord> () : AnsiResponseParserBase (new GenericHeld<TInputRecord> ())
 {
-    public AnsiResponseParser () : base (new GenericHeld<T> ()) { }
-
     /// <inheritdoc cref="AnsiResponseParser.UnknownResponseHandler"/>
-    public Func<IEnumerable<Tuple<char, T>>, bool> UnexpectedResponseHandler { get; set; } = _ => false;
+    public Func<IEnumerable<Tuple<char, TInputRecord>>, bool> UnexpectedResponseHandler { get; set; } = _ => false;
 
-    public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
+    public IEnumerable<Tuple<char, TInputRecord>> ProcessInput (params Tuple<char, TInputRecord> [] input)
     {
-        List<Tuple<char, T>> output = new ();
+        List<Tuple<char, TInputRecord>> output = [];
 
         ProcessInputBase (
                           i => input [i].Item1,
@@ -499,22 +498,22 @@ internal class AnsiResponseParser<T> : AnsiResponseParserBase
         return output;
     }
 
-    private void AppendOutput (List<Tuple<char, T>> output, object c)
+    private void AppendOutput (List<Tuple<char, TInputRecord>> output, object c)
     {
-        Tuple<char, T> tuple = (Tuple<char, T>)c;
+        Tuple<char, TInputRecord> tuple = (Tuple<char, TInputRecord>)c;
 
-        Logging.Trace ($"AnsiResponseParser releasing '{tuple.Item1}'");
+        //Logging.Trace ($"AnsiResponseParser releasing '{tuple.Item1}'");
         output.Add (tuple);
     }
 
-    public Tuple<char, T> [] Release ()
+    public Tuple<char, TInputRecord> [] Release ()
     {
         // Lock in case Release is called from different Thread from parse
         lock (_lockState)
         {
             TryLastMinuteSequences ();
 
-            Tuple<char, T> [] result = HeldToEnumerable ().ToArray ();
+            Tuple<char, TInputRecord> [] result = HeldToEnumerable ().ToArray ();
 
             ResetState ();
 
@@ -522,7 +521,7 @@ internal class AnsiResponseParser<T> : AnsiResponseParserBase
         }
     }
 
-    private IEnumerable<Tuple<char, T>> HeldToEnumerable () { return (IEnumerable<Tuple<char, T>>)_heldContent.HeldToObjects (); }
+    private IEnumerable<Tuple<char, TInputRecord>> HeldToEnumerable () { return (IEnumerable<Tuple<char, TInputRecord>>)_heldContent.HeldToObjects (); }
 
     /// <summary>
     ///     'Overload' for specifying an expectation that requires the metadata as well as characters. Has
@@ -532,7 +531,7 @@ internal class AnsiResponseParser<T> : AnsiResponseParserBase
     /// <param name="response"></param>
     /// <param name="abandoned"></param>
     /// <param name="persistent"></param>
-    public void ExpectResponseT (string? terminator, Action<IEnumerable<Tuple<char, T>>> response, Action? abandoned, bool persistent)
+    public void ExpectResponseT (string? terminator, Action<IEnumerable<Tuple<char, TInputRecord>>> response, Action? abandoned, bool persistent)
     {
         lock (_lockExpectedResponses)
         {
@@ -581,7 +580,7 @@ internal class AnsiResponseParser () : AnsiResponseParserBase (new StringHeld ()
 
     private void AppendOutput (StringBuilder output, char c)
     {
-        Logging.Trace ($"AnsiResponseParser releasing '{c}'");
+       // Logging.Trace ($"AnsiResponseParser releasing '{c}'");
         output.Append (c);
     }
 

+ 4 - 4
Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqUtils.cs

@@ -295,11 +295,11 @@ public static class EscSeqUtils
 
                 break;
             default:
-                uint ck = ConsoleKeyMapping.MapKeyCodeToConsoleKey ((KeyCode)consoleKeyInfo.KeyChar, out bool isConsoleKey);
+                //uint ck = ConsoleKeyMapping.MapKeyCodeToConsoleKey ((KeyCode)consoleKeyInfo.KeyChar, out bool isConsoleKey);
 
-                if (isConsoleKey)
-                {
-                    key = (ConsoleKey)ck;
+                //if (isConsoleKey)
+            {
+                key = consoleKeyInfo.Key;// (ConsoleKey)ck;
                 }
 
                 newConsoleKeyInfo = new (

+ 5 - 5
Terminal.Gui/Drivers/AnsiHandling/GenericHeld.cs

@@ -2,12 +2,12 @@
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-///     Implementation of <see cref="IHeld"/> for <see cref="AnsiResponseParser{T}"/>
+///     Implementation of <see cref="IHeld"/> for <see cref="AnsiResponseParser{TInputRecord}"/>
 /// </summary>
-/// <typeparam name="T"></typeparam>
-internal class GenericHeld<T> : IHeld
+/// <typeparam name="TInputRecord"></typeparam>
+internal class GenericHeld<TInputRecord> : IHeld
 {
-    private readonly List<Tuple<char, T>> held = new ();
+    private readonly List<Tuple<char, TInputRecord>> held = [];
 
     public void ClearHeld () { held.Clear (); }
 
@@ -15,7 +15,7 @@ internal class GenericHeld<T> : IHeld
 
     public IEnumerable<object> HeldToObjects () { return held; }
 
-    public void AddToHeld (object o) { held.Add ((Tuple<char, T>)o); }
+    public void AddToHeld (object o) { held.Add ((Tuple<char, TInputRecord>)o); }
 
     /// <inheritdoc/>
     public int Length => held.Count;

+ 0 - 26
Terminal.Gui/Drivers/ComponentFactory.cs

@@ -1,26 +0,0 @@
-#nullable enable
-using System.Collections.Concurrent;
-
-namespace Terminal.Gui.Drivers;
-
-/// <summary>
-/// Abstract base class implementation of <see cref="IComponentFactory{T}"/>
-/// </summary>
-/// <typeparam name="T"></typeparam>
-public abstract class ComponentFactory<T> : IComponentFactory<T>
-{
-    /// <inheritdoc />
-    public abstract IConsoleInput<T> CreateInput ();
-
-    /// <inheritdoc />
-    public abstract IInputProcessor CreateInputProcessor (ConcurrentQueue<T> inputBuffer);
-
-    /// <inheritdoc />
-    public virtual IConsoleSizeMonitor CreateConsoleSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer)
-    {
-        return new ConsoleSizeMonitor (consoleOutput, outputBuffer);
-    }
-
-    /// <inheritdoc />
-    public abstract IConsoleOutput CreateOutput ();
-}

+ 25 - 0
Terminal.Gui/Drivers/ComponentFactoryImpl.cs

@@ -0,0 +1,25 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+/// Abstract base class implementation of <see cref="IComponentFactory{TInputRecord}"/> that provides a default implementation of <see cref="CreateSizeMonitor"/>.</summary>
+/// <typeparam name="TInputRecord">The platform specific keyboard input type (e.g. <see cref="ConsoleKeyInfo"/> or <see cref="WindowsConsole.InputRecord"/></typeparam>
+public abstract class ComponentFactoryImpl<TInputRecord> : IComponentFactory<TInputRecord> where TInputRecord : struct
+{
+    /// <inheritdoc />
+    public abstract IInput<TInputRecord> CreateInput ();
+
+    /// <inheritdoc />
+    public abstract IInputProcessor CreateInputProcessor (ConcurrentQueue<TInputRecord> inputBuffer);
+
+    /// <inheritdoc />
+    public virtual ISizeMonitor CreateSizeMonitor (IOutput consoleOutput, IOutputBuffer outputBuffer)
+    {
+        return new SizeMonitorImpl (consoleOutput);
+    }
+
+    /// <inheritdoc />
+    public abstract IOutput CreateOutput ();
+}

+ 0 - 753
Terminal.Gui/Drivers/ConsoleDriver.cs

@@ -1,753 +0,0 @@
-#nullable enable
-
-using System.Diagnostics;
-
-namespace Terminal.Gui.Drivers;
-
-/// <summary>Base class for Terminal.Gui IConsoleDriver implementations.</summary>
-/// <remarks>
-///     There are currently four implementations:
-/// - DotNetDriver that uses the .NET Console API and works on all platforms
-/// - UnixDriver optimized for Unix and Mac.
-/// - WindowsDriver optimized for Windows.
-/// - FakeDriver for unit testing.
-/// </remarks>
-public abstract class ConsoleDriver : IConsoleDriver
-{
-    /// <summary>
-    ///     Set this to true in any unit tests that attempt to test drivers other than FakeDriver.
-    ///     <code>
-    ///  public ColorTests ()
-    ///  {
-    ///    ConsoleDriver.RunningUnitTests = true;
-    ///  }
-    /// </code>
-    /// </summary>
-    internal static bool RunningUnitTests { get; set; }
-
-    /// <summary>Get the operating system clipboard.</summary>
-    public IClipboard? Clipboard { get; internal set; }
-
-    /// <summary>Returns the name of the driver and relevant library version information.</summary>
-    /// <returns></returns>
-    public virtual string GetVersionInfo () { return GetType ().Name; }
-
-    #region ANSI Esc Sequence Handling
-
-    // QUESTION: This appears to be an API to help in debugging. It's only implemented in UnixDriver and WindowsDriver.
-    // QUESTION: Can it be factored such that it does not contaminate the ConsoleDriver API?
-    /// <summary>
-    ///     Provide proper writing to send escape sequence recognized by the <see cref="ConsoleDriver"/>.
-    /// </summary>
-    /// <param name="ansi"></param>
-    public abstract void WriteRaw (string ansi);
-
-    #endregion ANSI Esc Sequence Handling
-
-    #region Screen and Contents
-
-
-    /// <summary>
-    /// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
-    /// </summary>
-    public TimeSpan EscTimeout { get; } = TimeSpan.FromMilliseconds (50);
-
-    // As performance is a concern, we keep track of the dirty lines and only refresh those.
-    // This is in addition to the dirty flag on each cell.
-    internal bool []? _dirtyLines;
-
-    // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application?
-    /// <summary>Gets the location and size of the terminal screen.</summary>
-    public Rectangle Screen => new (0, 0, Cols, Rows);
-
-    /// <summary>
-    /// Sets the screen size for testing purposes. Only supported by FakeDriver.
-    /// <see cref="Screen"/> is the source of truth for screen dimensions.
-    /// <see cref="Cols"/> and <see cref="Rows"/> are read-only and derived from <see cref="Screen"/>.
-    /// </summary>
-    /// <param name="width">The new width in columns.</param>
-    /// <param name="height">The new height in rows.</param>
-    /// <exception cref="NotSupportedException">Thrown when called on non-FakeDriver instances.</exception>
-    public virtual void SetScreenSize (int width, int height)
-    {
-        throw new NotSupportedException ("SetScreenSize is only supported by FakeDriver for test scenarios.");
-    }
-
-    private Region? _clip;
-
-    /// <summary>
-    ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject
-    ///     to.
-    /// </summary>
-    /// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
-    public Region? Clip
-    {
-        get => _clip;
-        set
-        {
-            if (_clip == value)
-            {
-                return;
-            }
-
-            _clip = value;
-
-            // Don't ever let Clip be bigger than Screen
-            if (_clip is { })
-            {
-                _clip.Intersect (Screen);
-            }
-        }
-    }
-
-    /// <summary>
-    ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
-    ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
-    /// </summary>
-    public int Col { get; private set; }
-
-    /// <summary>The number of columns visible in the terminal.</summary>
-    public virtual int Cols
-    {
-        get => _cols;
-        set
-        {
-            _cols = value;
-            ClearContents ();
-        }
-    }
-
-    /// <summary>
-    ///     The contents of the application output. The driver outputs this buffer to the terminal when
-    ///     <see cref="UpdateScreen"/> is called.
-    ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
-    /// </summary>
-    public Cell [,]? Contents { get; set; }
-
-    /// <summary>The leftmost column in the terminal.</summary>
-    public virtual int Left { get; set; } = 0;
-
-    /// <summary>Tests if the specified rune is supported by the driver.</summary>
-    /// <param name="rune"></param>
-    /// <returns>
-    ///     <see langword="true"/> if the rune can be properly presented; <see langword="false"/> if the driver does not
-    ///     support displaying this rune.
-    /// </returns>
-    public virtual bool IsRuneSupported (Rune rune) { return Rune.IsValid (rune.Value); }
-
-    /// <summary>Tests whether the specified coordinate are valid for drawing.</summary>
-    /// <param name="col">The column.</param>
-    /// <param name="row">The row.</param>
-    /// <returns>
-    ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of <see cref="Clip"/>.
-    ///     <see langword="true"/> otherwise.
-    /// </returns>
-    public bool IsValidLocation (int col, int row) { return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip!.Contains (col, row); }
-
-    /// <summary>
-    ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
-    ///     Used by <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
-    /// </summary>
-    /// <remarks>
-    ///     <para>This does not move the cursor on the screen, it only updates the internal state of the driver.</para>
-    ///     <para>
-    ///         If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="Cols"/> and
-    ///         <see cref="Rows"/>, the method still sets those properties.
-    ///     </para>
-    /// </remarks>
-    /// <param name="col">Column to move to.</param>
-    /// <param name="row">Row to move to.</param>
-    public virtual void Move (int col, int row)
-    {
-        //Debug.Assert (col >= 0 && row >= 0 && col < Contents.GetLength(1) && row < Contents.GetLength(0));
-        Col = col;
-        Row = row;
-    }
-
-    /// <summary>
-    ///     Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
-    ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
-    /// </summary>
-    public int Row { get; private set; }
-
-    /// <summary>The number of rows visible in the terminal.</summary>
-    public virtual int Rows
-    {
-        get => _rows;
-        set
-        {
-            _rows = value;
-            ClearContents ();
-        }
-    }
-
-    /// <summary>The topmost row in the terminal.</summary>
-    public virtual int Top { get; set; } = 0;
-
-    /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         When the method returns, <see cref="Col"/> will be incremented by the number of columns
-    ///         <paramref name="rune"/> required, even if the new column value is outside of the <see cref="Clip"/> or screen
-    ///         dimensions defined by <see cref="Cols"/>.
-    ///     </para>
-    ///     <para>
-    ///         If <paramref name="rune"/> requires more than one column, and <see cref="Col"/> plus the number of columns
-    ///         needed exceeds the <see cref="Clip"/> or screen dimensions, the default Unicode replacement character (U+FFFD)
-    ///         will be added instead.
-    ///     </para>
-    /// </remarks>
-    /// <param name="rune">Rune to add.</param>
-    public void AddRune (Rune rune)
-    {
-        int runeWidth = -1;
-        bool validLocation = IsValidLocation (rune, Col, Row);
-
-        if (Contents is null)
-        {
-            return;
-        }
-
-        Rectangle clipRect = Clip!.GetBounds ();
-
-        if (validLocation)
-        {
-            rune = rune.MakePrintable ();
-            runeWidth = rune.GetColumns ();
-
-            lock (Contents)
-            {
-                if (runeWidth == 0 && rune.IsCombiningMark ())
-                {
-                    // AtlasEngine does not support NON-NORMALIZED combining marks in a way
-                    // compatible with the driver architecture. Any CMs (except in the first col)
-                    // are correctly combined with the base char, but are ALSO treated as 1 column
-                    // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
-                    // 
-                    // Until this is addressed (see Issue #), we do our best by
-                    // a) Attempting to normalize any CM with the base char to it's left
-                    // b) Ignoring any CMs that don't normalize
-                    if (Col > 0)
-                    {
-                        if (Contents [Row, Col - 1].CombiningMarks.Count > 0)
-                        {
-                            // Just add this mark to the list
-                            Contents [Row, Col - 1].AddCombiningMark (rune);
-
-                            // Ignore. Don't move to next column (let the driver figure out what to do).
-                        }
-                        else
-                        {
-                            // Attempt to normalize the cell to our left combined with this mark
-                            string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
-
-                            // Normalize to Form C (Canonical Composition)
-                            string normalized = combined.Normalize (NormalizationForm.FormC);
-
-                            if (normalized.Length == 1)
-                            {
-                                // It normalized! We can just set the Cell to the left with the
-                                // normalized codepoint
-                                Contents [Row, Col - 1].Rune = (Rune)normalized [0];
-
-                                // Ignore. Don't move to next column because we're already there
-                            }
-                            else
-                            {
-                                // It didn't normalize. Add it to the Cell to left's CM list
-                                Contents [Row, Col - 1].AddCombiningMark (rune);
-
-                                // Ignore. Don't move to next column (let the driver figure out what to do).
-                            }
-                        }
-
-                        Contents [Row, Col - 1].Attribute = CurrentAttribute;
-                        Contents [Row, Col - 1].IsDirty = true;
-                    }
-                    else
-                    {
-                        // Most drivers will render a combining mark at col 0 as the mark
-                        Contents [Row, Col].Rune = rune;
-                        Contents [Row, Col].Attribute = CurrentAttribute;
-                        Contents [Row, Col].IsDirty = true;
-                        Col++;
-                    }
-                }
-                else
-                {
-                    Contents [Row, Col].Attribute = CurrentAttribute;
-                    Contents [Row, Col].IsDirty = true;
-
-                    if (Col > 0)
-                    {
-                        // Check if cell to left has a wide glyph
-                        if (Contents [Row, Col - 1].Rune.GetColumns () > 1)
-                        {
-                            // Invalidate cell to left
-                            Contents [Row, Col - 1].Rune = (Rune)'\0';
-                            Contents [Row, Col - 1].IsDirty = true;
-                        }
-                    }
-
-                    if (runeWidth < 1)
-                    {
-                        Contents [Row, Col].Rune = Rune.ReplacementChar;
-                    }
-                    else if (runeWidth == 1)
-                    {
-                        Contents [Row, Col].Rune = rune;
-
-                        if (Col < clipRect.Right - 1)
-                        {
-                            Contents [Row, Col + 1].IsDirty = true;
-                        }
-                    }
-                    else if (runeWidth == 2)
-                    {
-                        if (!Clip.Contains (Col + 1, Row))
-                        {
-                            // We're at the right edge of the clip, so we can't display a wide character.
-                            // TODO: Figure out if it is better to show a replacement character or ' '
-                            Contents [Row, Col].Rune = Rune.ReplacementChar;
-                        }
-                        else if (!Clip.Contains (Col, Row))
-                        {
-                            // Our 1st column is outside the clip, so we can't display a wide character.
-                            Contents [Row, Col + 1].Rune = Rune.ReplacementChar;
-                        }
-                        else
-                        {
-                            Contents [Row, Col].Rune = rune;
-
-                            if (Col < clipRect.Right - 1)
-                            {
-                                // Invalidate cell to right so that it doesn't get drawn
-                                // TODO: Figure out if it is better to show a replacement character or ' '
-                                Contents [Row, Col + 1].Rune = (Rune)'\0';
-                                Contents [Row, Col + 1].IsDirty = true;
-                            }
-                        }
-                    }
-                    else
-                    {
-                        // This is a non-spacing character, so we don't need to do anything
-                        Contents [Row, Col].Rune = (Rune)' ';
-                        Contents [Row, Col].IsDirty = false;
-                    }
-
-                    _dirtyLines! [Row] = true;
-                }
-            }
-        }
-
-        if (runeWidth is < 0 or > 0)
-        {
-            Col++;
-        }
-
-        if (runeWidth > 1)
-        {
-            Debug.Assert (runeWidth <= 2);
-
-            if (validLocation && Col < clipRect.Right)
-            {
-                lock (Contents!)
-                {
-                    // This is a double-width character, and we are not at the end of the line.
-                    // Col now points to the second column of the character. Ensure it doesn't
-                    // Get rendered.
-                    Contents [Row, Col].IsDirty = false;
-                    Contents [Row, Col].Attribute = CurrentAttribute;
-
-                    // TODO: Determine if we should wipe this out (for now now)
-                    //Contents [Row, Col].Rune = (Rune)' ';
-                }
-            }
-
-            Col++;
-        }
-    }
-
-    /// <summary>
-    ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
-    ///     convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
-    /// </summary>
-    /// <param name="c">Character to add.</param>
-    public void AddRune (char c) { AddRune (new Rune (c)); }
-
-    /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         When the method returns, <see cref="Col"/> will be incremented by the number of columns
-    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="Clip"/> or screen
-    ///         dimensions defined by <see cref="Cols"/>.
-    ///     </para>
-    ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
-    /// </remarks>
-    /// <param name="str">String.</param>
-    public void AddStr (string str)
-    {
-        List<Rune> runes = str.EnumerateRunes ().ToList ();
-
-        for (var i = 0; i < runes.Count; i++)
-        {
-            AddRune (runes [i]);
-        }
-    }
-
-    /// <summary>Fills the specified rectangle with the specified rune, using <see cref="CurrentAttribute"/></summary>
-    /// <remarks>
-    ///     The value of <see cref="Clip"/> is honored. Any parts of the rectangle not in the clip will not be drawn.
-    /// </remarks>
-    /// <param name="rect">The Screen-relative rectangle.</param>
-    /// <param name="rune">The Rune used to fill the rectangle</param>
-    public void FillRect (Rectangle rect, Rune rune = default)
-    {
-        // BUGBUG: This should be a method on Region
-        rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen);
-        lock (Contents!)
-        {
-            for (int r = rect.Y; r < rect.Y + rect.Height; r++)
-            {
-                for (int c = rect.X; c < rect.X + rect.Width; c++)
-                {
-                    if (!IsValidLocation (rune, c, r))
-                    {
-                        continue;
-                    }
-                    Contents [r, c] = new Cell
-                    {
-                        Rune = rune != default ? rune : (Rune)' ',
-                        Attribute = CurrentAttribute, IsDirty = true
-                    };
-                    _dirtyLines! [r] = true;
-                }
-            }
-        }
-    }
-
-    /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>
-    public void ClearContents ()
-    {
-        Contents = new Cell [Rows, Cols];
-
-        //CONCURRENCY: Unsynchronized access to Clip isn't safe.
-        // TODO: ClearContents should not clear the clip; it should only clear the contents. Move clearing it elsewhere.
-        Clip = new (Screen);
-
-        _dirtyLines = new bool [Rows];
-
-        lock (Contents)
-        {
-            for (var row = 0; row < Rows; row++)
-            {
-                for (var c = 0; c < Cols; c++)
-                {
-                    Contents [row, c] = new ()
-                    {
-                        Rune = (Rune)' ',
-                        Attribute = new Attribute (Color.White, Color.Black),
-                        IsDirty = true
-                    };
-                }
-
-                _dirtyLines [row] = true;
-            }
-        }
-
-        ClearedContents?.Invoke (this, EventArgs.Empty);
-    }
-
-    /// <summary>
-    ///     Raised each time <see cref="ClearContents"/> is called. For benchmarking.
-    /// </summary>
-    public event EventHandler<EventArgs>? ClearedContents;
-
-    /// <summary>
-    /// Sets <see cref="Contents"/> as dirty for situations where views
-    /// don't need layout and redrawing, but just refresh the screen.
-    /// </summary>
-    protected void SetContentsAsDirty ()
-    {
-        lock (Contents!)
-        {
-            for (var row = 0; row < Rows; row++)
-            {
-                for (var c = 0; c < Cols; c++)
-                {
-                    Contents [row, c].IsDirty = true;
-                }
-
-                _dirtyLines! [row] = true;
-            }
-        }
-    }
-
-    /// <summary>
-    ///     Fills the specified rectangle with the specified <see langword="char"/>. This method is a convenience method
-    ///     that calls <see cref="FillRect(Rectangle, Rune)"/>.
-    /// </summary>
-    /// <param name="rect"></param>
-    /// <param name="c"></param>
-    public void FillRect (Rectangle rect, char c) { FillRect (rect, new Rune (c)); }
-
-    #endregion Screen and Contents
-
-    #region Cursor Handling
-
-    /// <summary>Gets the terminal cursor visibility.</summary>
-    /// <param name="visibility">The current <see cref="CursorVisibility"/></param>
-    /// <returns><see langword="true"/> upon success</returns>
-    public abstract bool GetCursorVisibility (out CursorVisibility visibility);
-
-    /// <summary>Tests whether the specified coordinate are valid for drawing the specified Rune.</summary>
-    /// <param name="rune">Used to determine if one or two columns are required.</param>
-    /// <param name="col">The column.</param>
-    /// <param name="row">The row.</param>
-    /// <returns>
-    ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of <see cref="Clip"/>.
-    ///     <see langword="true"/> otherwise.
-    /// </returns>
-    public bool IsValidLocation (Rune rune, int col, int row)
-    {
-        if (rune.GetColumns () < 2)
-        {
-            return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip!.Contains (col, row);
-        }
-        else
-        {
-
-            return Clip!.Contains (col, row) || Clip!.Contains (col + 1, row);
-        }
-    }
-
-    /// <summary>
-    /// Called when the terminal screen changes (size, position, etc.). Fires the <see cref="SizeChanged"/> event.
-    /// <see cref="Screen"/> reflects the source of truth for screen dimensions.
-    /// <see cref="Cols"/> and <see cref="Rows"/> are derived from <see cref="Screen"/> and are read-only.
-    /// </summary>
-    /// <param name="args">Event arguments containing the new screen size.</param>
-    public void OnSizeChanged (SizeChangedEventArgs args) 
-    { 
-        SizeChanged?.Invoke (this, args);
-    }
-
-
-    /// <summary>Updates the screen to reflect all the changes that have been done to the display buffer</summary>
-    public void Refresh ()
-    {
-        bool updated = UpdateScreen ();
-        UpdateCursor ();
-
-        Refreshed?.Invoke (this, new EventArgs<bool> (in updated));
-    }
-
-    /// <summary>
-    ///     Raised each time <see cref="Refresh"/> is called. For benchmarking.
-    /// </summary>
-    public event EventHandler<EventArgs<bool>>? Refreshed;
-
-    /// <summary>Sets the terminal cursor visibility.</summary>
-    /// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
-    /// <returns><see langword="true"/> upon success</returns>
-    public abstract bool SetCursorVisibility (CursorVisibility visibility);
-
-    /// <summary>
-    /// The event fired when the screen changes (size, position, etc.).
-    /// <see cref="Screen"/> is the source of truth for screen dimensions.
-    /// <see cref="Cols"/> and <see cref="Rows"/> are read-only and derived from <see cref="Screen"/>.
-    /// </summary>
-    public event EventHandler<SizeChangedEventArgs>? SizeChanged;
-
-    #endregion Cursor Handling
-
-    /// <summary>Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.</summary>
-    /// <remarks>This is only implemented in the Unix driver.</remarks>
-    public abstract void Suspend ();
-
-    /// <summary>Sets the position of the terminal cursor to <see cref="Col"/> and <see cref="Row"/>.</summary>
-    public abstract void UpdateCursor ();
-
-    /// <summary>Redraws the physical screen with the contents that have been queued up via any of the printing commands.</summary>
-    /// <returns><see langword="true"/> if any updates to the screen were made.</returns>
-    public abstract bool UpdateScreen ();
-
-    #region Setup & Teardown
-
-    /// <summary>Initializes the driver</summary>
-    public abstract void Init ();
-
-    /// <summary>Ends the execution of the console driver.</summary>
-    public abstract void End ();
-
-    #endregion
-
-    #region Color Handling
-
-    /// <summary>Gets whether the <see cref="IConsoleDriver"/> supports TrueColor output.</summary>
-    public virtual bool SupportsTrueColor => true;
-
-    // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
-    // BUGBUG: Application.Force16Colors should be bool? so if SupportsTrueColor and Application.Force16Colors == false, this doesn't override
-    /// <summary>
-    ///     Gets or sets whether the <see cref="IConsoleDriver"/> should use 16 colors instead of the default TrueColors.
-    ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         Will be forced to <see langword="true"/> if <see cref="IConsoleDriver.SupportsTrueColor"/> is
-    ///         <see langword="false"/>, indicating that the <see cref="IConsoleDriver"/> cannot support TrueColor.
-    ///     </para>
-    /// </remarks>
-    public virtual bool Force16Colors
-    {
-        get => Application.Force16Colors || !SupportsTrueColor;
-        set => Application.Force16Colors = value || !SupportsTrueColor;
-    }
-
-    private int _cols;
-    private int _rows;
-
-    /// <summary>
-    ///     The <see cref="Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/>
-    ///     call.
-    /// </summary>
-    public Attribute CurrentAttribute { get; set; }
-
-    /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
-    /// <remarks>Implementations should call <c>base.SetAttribute(c)</c>.</remarks>
-    /// <param name="c">C.</param>
-    public Attribute SetAttribute (Attribute c)
-    {
-        Attribute prevAttribute = CurrentAttribute;
-        CurrentAttribute = c;
-
-        return prevAttribute;
-    }
-
-    /// <summary>Gets the current <see cref="Attribute"/>.</summary>
-    /// <returns>The current attribute.</returns>
-    public Attribute GetAttribute () { return CurrentAttribute; }
-    
-    #endregion Color Handling
-
-    #region Mouse Handling
-
-    /// <summary>Event fired when a mouse event occurs.</summary>
-    public event EventHandler<MouseEventArgs>? MouseEvent;
-
-    /// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
-    /// <param name="a"></param>
-    public void OnMouseEvent (MouseEventArgs a)
-    {
-        // Ensure ScreenPosition is set
-        a.ScreenPosition = a.Position;
-
-        MouseEvent?.Invoke (this, a);
-    }
-
-    #endregion Mouse Handling
-
-    #region Keyboard Handling
-
-    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
-    public event EventHandler<Key>? KeyDown;
-
-    /// <summary>
-    ///     Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
-    ///     <see cref="OnKeyUp"/>.
-    /// </summary>
-    /// <param name="a"></param>
-    public void OnKeyDown (Key a) { KeyDown?.Invoke (this, a); }
-
-    /// <summary>Event fired when a key is released.</summary>
-    /// <remarks>
-    ///     Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is
-    ///     complete.
-    /// </remarks>
-    public event EventHandler<Key>? KeyUp;
-
-    /// <summary>Called when a key is released. Fires the <see cref="KeyUp"/> event.</summary>
-    /// <remarks>
-    ///     Drivers that do not support key release events will call this method after <see cref="OnKeyDown"/> processing
-    ///     is complete.
-    /// </remarks>
-    /// <param name="a"></param>
-    public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
-
-    internal char _highSurrogate = '\0';
-
-    internal bool IsValidInput (KeyCode keyCode, out KeyCode result)
-    {
-        result = keyCode;
-
-        if (char.IsHighSurrogate ((char)keyCode))
-        {
-            _highSurrogate = (char)keyCode;
-
-            return false;
-        }
-
-        if (_highSurrogate > 0 && char.IsLowSurrogate ((char)keyCode))
-        {
-            result = (KeyCode)new Rune (_highSurrogate, (char)keyCode).Value;
-
-            if ((keyCode & KeyCode.AltMask) != 0)
-            {
-                result |= KeyCode.AltMask;
-            }
-
-            if ((keyCode & KeyCode.CtrlMask) != 0)
-            {
-                result |= KeyCode.CtrlMask;
-            }
-
-            if ((keyCode & KeyCode.ShiftMask) != 0)
-            {
-                result |= KeyCode.ShiftMask;
-            }
-
-            _highSurrogate = '\0';
-
-            return true;
-        }
-
-        if (char.IsSurrogate ((char)keyCode))
-        {
-            return false;
-        }
-
-        if (_highSurrogate > 0)
-        {
-            _highSurrogate = '\0';
-        }
-
-        return true;
-    }
-
-    #endregion
-
-    private AnsiRequestScheduler? _scheduler;
-
-    /// <summary>
-    /// Queues the given <paramref name="request"/> for execution
-    /// </summary>
-    /// <param name="request"></param>
-    public void QueueAnsiRequest (AnsiEscapeSequenceRequest request)
-    {
-        GetRequestScheduler ().SendOrSchedule (request);
-    }
-
-    internal abstract IAnsiResponseParser GetParser ();
-
-    /// <summary>
-    ///     Gets the <see cref="AnsiRequestScheduler"/> for this <see cref="ConsoleDriver"/>.
-    /// </summary>
-    /// <returns></returns>
-    public AnsiRequestScheduler GetRequestScheduler ()
-    {
-        // Lazy initialization because GetParser is virtual
-        return _scheduler ??= new (GetParser ());
-    }
-
-}

+ 0 - 79
Terminal.Gui/Drivers/ConsoleInput.cs

@@ -1,79 +0,0 @@
-#nullable enable
-using System.Collections.Concurrent;
-
-namespace Terminal.Gui.Drivers;
-
-/// <summary>
-///     Base class for reading console input in perpetual loop
-/// </summary>
-/// <typeparam name="T"></typeparam>
-public abstract class ConsoleInput<T> : IConsoleInput<T>
-{
-    private ConcurrentQueue<T>? _inputBuffer;
-
-    /// <summary>
-    ///     Determines how to get the current system type, adjust
-    ///     in unit tests to simulate specific timings.
-    /// </summary>
-    public Func<DateTime> Now { get; set; } = () => DateTime.Now;
-
-    /// <inheritdoc/>
-    public virtual void Dispose () { }
-
-    /// <inheritdoc/>
-    public void Initialize (ConcurrentQueue<T> inputBuffer) { _inputBuffer = inputBuffer; }
-
-    /// <inheritdoc/>
-    public void Run (CancellationToken token)
-    {
-        try
-        {
-            if (_inputBuffer == null)
-            {
-                throw new ("Cannot run input before Initialization");
-            }
-
-            do
-            {
-                DateTime dt = Now ();
-
-                while (Peek ())
-                {
-                    foreach (T r in Read ())
-                    {
-                        _inputBuffer.Enqueue (r);
-                    }
-                }
-
-                TimeSpan took = Now () - dt;
-                TimeSpan sleepFor = TimeSpan.FromMilliseconds (20) - took;
-
-                Logging.DrainInputStream.Record (took.Milliseconds);
-
-                if (sleepFor.Milliseconds > 0)
-                {
-                    Task.Delay (sleepFor, token).Wait (token);
-                }
-
-                token.ThrowIfCancellationRequested ();
-            }
-            while (!token.IsCancellationRequested);
-        }
-        catch (OperationCanceledException)
-        { }
-    }
-
-    /// <summary>
-    ///     When implemented in a derived class, returns true if there is data available
-    ///     to read from console.
-    /// </summary>
-    /// <returns></returns>
-    protected abstract bool Peek ();
-
-    /// <summary>
-    ///     Returns the available data without blocking, called when <see cref="Peek"/>
-    ///     returns <see langword="true"/>.
-    /// </summary>
-    /// <returns></returns>
-    protected abstract IEnumerable<T> Read ();
-}

+ 95 - 0
Terminal.Gui/Drivers/ConsoleKeyInfoExtensions.cs

@@ -0,0 +1,95 @@
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+///     Extension methods for <see cref="ConsoleKeyInfo"/>.
+/// </summary>
+public static class ConsoleKeyInfoExtensions
+{
+    /// <summary>
+    ///     Returns a string representation of the <see cref="ConsoleKeyInfo"/> suitable for debugging and logging.
+    /// </summary>
+    /// <param name="consoleKeyInfo">The ConsoleKeyInfo to convert to string.</param>
+    /// <returns>A formatted string showing the key, character, and modifiers.</returns>
+    /// <remarks>
+    ///     <para>
+    ///         Examples:
+    ///         <list type="bullet">
+    ///             <item><c>Key: A ('a')</c> - lowercase 'a' pressed</item>
+    ///             <item><c>Key: A ('A'), Modifiers: Shift</c> - uppercase 'A' pressed</item>
+    ///             <item><c>Key: A (\0), Modifiers: Control</c> - Ctrl+A (no printable char)</item>
+    ///             <item><c>Key: Enter (0x000D)</c> - Enter key (carriage return)</item>
+    ///             <item><c>Key: F5 (\0)</c> - F5 function key</item>
+    ///             <item><c>Key: D2 ('@'), Modifiers: Shift</c> - Shift+2 on US keyboard</item>
+    ///             <item><c>Key: None ('é')</c> - Accented character</item>
+    ///             <item><c>Key: CursorUp (\0), Modifiers: Shift | Control</c> - Ctrl+Shift+Up Arrow</item>
+    ///         </list>
+    ///     </para>
+    /// </remarks>
+    public static string ToString (this ConsoleKeyInfo consoleKeyInfo)
+    {
+        var sb = new StringBuilder ();
+
+        // Always show the ConsoleKey enum value
+        sb.Append ("Key: ");
+        sb.Append (consoleKeyInfo.Key);
+
+        // Show the character if it's printable, otherwise show hex representation
+        sb.Append (" (");
+
+        if (consoleKeyInfo.KeyChar >= 32 && consoleKeyInfo.KeyChar <= 126) // Printable ASCII range
+        {
+            sb.Append ('\'');
+            sb.Append (consoleKeyInfo.KeyChar);
+            sb.Append ('\'');
+        }
+        else if (consoleKeyInfo.KeyChar == 0)
+        {
+            sb.Append ("\\0");
+        }
+        else
+        {
+            // Show special characters or non-printable as hex
+            sb.Append ("0x");
+            sb.Append (((int)consoleKeyInfo.KeyChar).ToString ("X4"));
+        }
+
+        sb.Append (')');
+
+        // Show modifiers if any are set
+        if (consoleKeyInfo.Modifiers != 0)
+        {
+            sb.Append (", Modifiers: ");
+
+            var needsSeparator = false;
+
+            if ((consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
+            {
+                sb.Append ("Shift");
+                needsSeparator = true;
+            }
+
+            if ((consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0)
+            {
+                if (needsSeparator)
+                {
+                    sb.Append (" | ");
+                }
+
+                sb.Append ("Alt");
+                needsSeparator = true;
+            }
+
+            if ((consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0)
+            {
+                if (needsSeparator)
+                {
+                    sb.Append (" | ");
+                }
+
+                sb.Append ("Control");
+            }
+        }
+
+        return sb.ToString ();
+    }
+}

+ 118 - 2315
Terminal.Gui/Drivers/ConsoleKeyMapping.cs

@@ -1,271 +1,34 @@
-using System.Globalization;
-using System.Runtime.InteropServices;
+namespace Terminal.Gui.Drivers;
 
-namespace Terminal.Gui.Drivers;
-
-// QUESTION: This class combines Windows specific code with cross-platform code. Should this be split into two classes?
-/// <summary>Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.</summary>
+/// <summary>Helper class to handle mapping between <see cref="KeyCode"/> and <see cref="ConsoleKeyInfo"/>.</summary>
 public static class ConsoleKeyMapping
 {
-#if !WT_ISSUE_8871_FIXED // https://github.com/microsoft/terminal/issues/8871
-    /// <summary>
-    ///     Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a
-    ///     virtual-key code.
-    /// </summary>
-    /// <param name="vk"></param>
-    /// <param name="uMapType">
-    ///     If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an
-    ///     un-shifted character value in the low order word of the return value.
-    /// </param>
-    /// <param name="dwhkl"></param>
-    /// <returns>
-    ///     An un-shifted character value in the low order word of the return value. Dead keys (diacritics) are indicated
-    ///     by setting the top bit of the return value. If there is no translation, the function returns 0. See Remarks.
-    /// </returns>
-    [DllImport ("user32.dll", EntryPoint = "MapVirtualKeyExW", CharSet = CharSet.Unicode)]
-    private static extern uint MapVirtualKeyEx (VK vk, uint uMapType, nint dwhkl);
-
-    /// <summary>Retrieves the active input locale identifier (formerly called the keyboard layout).</summary>
-    /// <param name="idThread">0 for current thread</param>
-    /// <returns>
-    ///     The return value is the input locale identifier for the thread. The low word contains a Language Identifier
-    ///     for the input language and the high word contains a device handle to the physical layout of the keyboard.
-    /// </returns>
-    [DllImport ("user32.dll", EntryPoint = "GetKeyboardLayout", CharSet = CharSet.Unicode)]
-    private static extern nint GetKeyboardLayout (nint idThread);
-
-    //[DllImport ("user32.dll", EntryPoint = "GetKeyboardLayoutNameW", CharSet = CharSet.Unicode)]
-    //extern static uint GetKeyboardLayoutName (uint idThread);
-    [DllImport ("user32.dll")]
-    private static extern nint GetForegroundWindow ();
-
-    [DllImport ("user32.dll")]
-    private static extern nint GetWindowThreadProcessId (nint hWnd, nint ProcessId);
-
-    /// <summary>
-    ///     Translates the specified virtual-key code and keyboard state to the corresponding Unicode character or
-    ///     characters using the Win32 API MapVirtualKey.
-    /// </summary>
-    /// <param name="vk"></param>
-    /// <returns>
-    ///     An un-shifted character value in the low order word of the return value. Dead keys (diacritics) are indicated
-    ///     by setting the top bit of the return value. If there is no translation, the function returns 0.
-    /// </returns>
-    public static uint MapVKtoChar (VK vk)
-    {
-        if (Environment.OSVersion.Platform != PlatformID.Win32NT)
-        {
-            return 0;
-        }
-
-        nint tid = GetWindowThreadProcessId (GetForegroundWindow (), 0);
-        nint hkl = GetKeyboardLayout (tid);
-
-        return MapVirtualKeyEx (vk, 2, hkl);
-    }
-#else
     /// <summary>
-    /// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a virtual-key code.
+    ///     Gets a <see cref="ConsoleKeyInfo"/> from a <see cref="KeyCode"/>.
     /// </summary>
-    /// <param name="vk"></param>
-    /// <param name="uMapType">
-    /// If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an unshifted
-    /// character value in the low order word of the return value. 
-    /// </param>
-    /// <returns>An unshifted character value in the low order word of the return value. Dead keys (diacritics)
-    /// are indicated by setting the top bit of the return value. If there is no translation,
-    /// the function returns 0. See Remarks.</returns>
-    [DllImport ("user32.dll", EntryPoint = "MapVirtualKeyW", CharSet = CharSet.Unicode)]
-    extern static uint MapVirtualKey (VK vk, uint uMapType = 2);
-
-    uint MapVKtoChar (VK vk) => MapVirtualKeyToCharEx (vk);
-#endif
-    /// <summary>
-    ///     Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling
-    ///     thread.
-    /// </summary>
-    /// <param name="pwszKLID"></param>
-    /// <returns></returns>
-    [DllImport ("user32.dll")]
-    private static extern bool GetKeyboardLayoutName ([Out] StringBuilder pwszKLID);
-
-    /// <summary>
-    ///     Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling
-    ///     thread.
-    /// </summary>
-    /// <returns></returns>
-    public static string GetKeyboardLayoutName ()
-    {
-        if (Environment.OSVersion.Platform != PlatformID.Win32NT)
-        {
-            return "none";
-        }
-
-        var klidSB = new StringBuilder ();
-        GetKeyboardLayoutName (klidSB);
-
-        return klidSB.ToString ();
-    }
-
-    private class ScanCodeMapping : IEquatable<ScanCodeMapping>
-    {
-        public readonly ConsoleModifiers Modifiers;
-        public readonly uint ScanCode;
-        public readonly uint UnicodeChar;
-        public readonly VK VirtualKey;
-
-        public ScanCodeMapping (uint scanCode, VK virtualKey, ConsoleModifiers modifiers, uint unicodeChar)
-        {
-            ScanCode = scanCode;
-            VirtualKey = virtualKey;
-            Modifiers = modifiers;
-            UnicodeChar = unicodeChar;
-        }
-
-        public bool Equals (ScanCodeMapping other)
-        {
-            return ScanCode.Equals (other.ScanCode)
-                   && VirtualKey.Equals (other.VirtualKey)
-                   && Modifiers.Equals (other.Modifiers)
-                   && UnicodeChar.Equals (other.UnicodeChar);
-        }
-    }
-
-    private static ConsoleModifiers GetModifiers (ConsoleModifiers modifiers)
-    {
-        if (modifiers.HasFlag (ConsoleModifiers.Shift)
-            && !modifiers.HasFlag (ConsoleModifiers.Alt)
-            && !modifiers.HasFlag (ConsoleModifiers.Control))
-        {
-            return ConsoleModifiers.Shift;
-        }
-
-        if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control))
-        {
-            return modifiers;
-        }
-
-        return 0;
-    }
-
-    private static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers)
-    {
-        switch (propName)
-        {
-            case "UnicodeChar":
-                ScanCodeMapping sCode =
-                    _scanCodes.FirstOrDefault (e => e.UnicodeChar == keyValue && e.Modifiers == modifiers);
-
-                if (sCode is null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control))
-                {
-                    return _scanCodes.FirstOrDefault (e => e.UnicodeChar == keyValue && e.Modifiers == 0);
-                }
-
-                return sCode;
-            case "VirtualKey":
-                sCode = _scanCodes.FirstOrDefault (e => e.VirtualKey == (VK)keyValue && e.Modifiers == modifiers);
-
-                if (sCode is null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control))
-                {
-                    return _scanCodes.FirstOrDefault (e => e.VirtualKey == (VK)keyValue && e.Modifiers == 0);
-                }
-
-                return sCode;
-        }
-
-        return null;
-    }
-
-    // BUGBUG: This API is not correct. It is only used by WindowsDriver in VKPacket scenarios
-    /// <summary>Get the scan code from a <see cref="ConsoleKeyInfo"/>.</summary>
-    /// <param name="consoleKeyInfo">The console key info.</param>
-    /// <returns>The value if apply.</returns>
-    public static uint GetScanCodeFromConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-    {
-        ConsoleModifiers mod = GetModifiers (consoleKeyInfo.Modifiers);
-        ScanCodeMapping scode = GetScanCode ("VirtualKey", (uint)consoleKeyInfo.Key, mod);
-
-        if (scode is { })
-        {
-            return scode.ScanCode;
-        }
-
-        return 0;
-    }
-
-    // BUGBUG: This API is not correct. It is only used by FakeDriver and VkeyPacketSimulator
-    /// <summary>Gets the <see cref="ConsoleKeyInfo"/> from the provided <see cref="KeyCode"/>.</summary>
-    /// <param name="key">The key code.</param>
-    /// <returns>The console key info.</returns>
+    /// <param name="key">The key code to convert.</param>
+    /// <returns>A ConsoleKeyInfo representing the key.</returns>
+    /// <remarks>
+    ///     This method is primarily used for test simulation via <see cref="IKeyConverter{T}.ToKeyInfo"/>.
+    ///     It produces a keyboard-layout-agnostic "best effort" ConsoleKeyInfo suitable for testing.
+    ///     For shifted characters (e.g., Shift+2), the character returned is US keyboard layout (Shift+2 = '@').
+    ///     This is acceptable for test simulation but may not match the user's actual keyboard layout.
+    /// </remarks>
     public static ConsoleKeyInfo GetConsoleKeyInfoFromKeyCode (KeyCode key)
     {
         ConsoleModifiers modifiers = MapToConsoleModifiers (key);
-        uint keyValue = MapKeyCodeToConsoleKey (key, out bool isConsoleKey);
-
-        if (isConsoleKey)
-        {
-            ConsoleModifiers mod = GetModifiers (modifiers);
-            ScanCodeMapping scode = GetScanCode ("VirtualKey", keyValue, mod);
-
-            if (scode is { })
-            {
-                return new ConsoleKeyInfo (
-                                           (char)scode.UnicodeChar,
-                                           (ConsoleKey)scode.VirtualKey,
-                                           modifiers.HasFlag (ConsoleModifiers.Shift),
-                                           modifiers.HasFlag (ConsoleModifiers.Alt),
-                                           modifiers.HasFlag (ConsoleModifiers.Control)
-                                          );
-            }
-        }
-        else
-        {
-            uint keyChar = GetKeyCharFromUnicodeChar (keyValue, modifiers, out uint consoleKey, out _, isConsoleKey);
-
-            if (consoleKey != 0)
-            {
-                return new ConsoleKeyInfo (
-                                           (char)keyChar,
-                                           (ConsoleKey)consoleKey,
-                                           modifiers.HasFlag (ConsoleModifiers.Shift),
-                                           modifiers.HasFlag (ConsoleModifiers.Alt),
-                                           modifiers.HasFlag (ConsoleModifiers.Control)
-                                          );
-            }
-        }
-
-        return new ConsoleKeyInfo (
-                                   (char)keyValue,
-                                   ConsoleKey.None,
-                                   modifiers.HasFlag (ConsoleModifiers.Shift),
-                                   modifiers.HasFlag (ConsoleModifiers.Alt),
-                                   modifiers.HasFlag (ConsoleModifiers.Control)
-                                  );
-    }
-
-    /// <summary>Map existing <see cref="KeyCode"/> modifiers to <see cref="ConsoleModifiers"/>.</summary>
-    /// <param name="key">The key code.</param>
-    /// <returns>The console modifiers.</returns>
-    public static ConsoleModifiers MapToConsoleModifiers (KeyCode key)
-    {
-        var modifiers = new ConsoleModifiers ();
-
-        if (key.HasFlag (KeyCode.ShiftMask) || char.IsUpper ((char)key))
-        {
-            modifiers |= ConsoleModifiers.Shift;
-        }
-
-        if (key.HasFlag (KeyCode.AltMask))
-        {
-            modifiers |= ConsoleModifiers.Alt;
-        }
-
-        if (key.HasFlag (KeyCode.CtrlMask))
-        {
-            modifiers |= ConsoleModifiers.Control;
-        }
-
-        return modifiers;
+        KeyCode keyWithoutModifiers = key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask;
+
+        // Map to ConsoleKey enum
+        (ConsoleKey consoleKey, char keyChar) = MapToConsoleKeyAndChar (keyWithoutModifiers, modifiers);
+
+        return new (
+                    keyChar,
+                    consoleKey,
+                    modifiers.HasFlag (ConsoleModifiers.Shift),
+                    modifiers.HasFlag (ConsoleModifiers.Alt),
+                    modifiers.HasFlag (ConsoleModifiers.Control)
+                   );
     }
 
     /// <summary>Gets <see cref="ConsoleModifiers"/> from <see cref="bool"/> modifiers.</summary>
@@ -295,450 +58,6 @@ public static class ConsoleKeyMapping
         return modifiers;
     }
 
-    /// <summary>
-    ///     Get the <see cref="ConsoleKeyInfo"/> from a unicode character and modifiers (e.g. (Key)'a' and
-    ///     (Key)Key.CtrlMask).
-    /// </summary>
-    /// <param name="keyValue">The key as a unicode codepoint.</param>
-    /// <param name="modifiers">The modifier keys.</param>
-    /// <param name="scanCode">The resulting scan code.</param>
-    /// <returns>The <see cref="ConsoleKeyInfo"/>.</returns>
-    private static ConsoleKeyInfo GetConsoleKeyInfoFromKeyChar (
-        uint keyValue,
-        ConsoleModifiers modifiers,
-        out uint scanCode
-    )
-    {
-        scanCode = 0;
-
-        if (keyValue == 0)
-        {
-            return new ConsoleKeyInfo (
-                                       (char)keyValue,
-                                       ConsoleKey.None,
-                                       modifiers.HasFlag (ConsoleModifiers.Shift),
-                                       modifiers.HasFlag (ConsoleModifiers.Alt),
-                                       modifiers.HasFlag (ConsoleModifiers.Control)
-                                      );
-        }
-
-        uint outputChar = keyValue;
-        uint consoleKey;
-
-        if (keyValue > byte.MaxValue)
-        {
-            ScanCodeMapping sCode = _scanCodes.FirstOrDefault (e => e.UnicodeChar == keyValue);
-
-            if (sCode is null)
-            {
-                consoleKey = (byte)(keyValue & byte.MaxValue);
-                sCode = _scanCodes.FirstOrDefault (e => e.VirtualKey == (VK)consoleKey);
-
-                if (sCode is null)
-                {
-                    consoleKey = 0;
-                    outputChar = keyValue;
-                }
-                else
-                {
-                    outputChar = (char)(keyValue >> 8);
-                }
-            }
-            else
-            {
-                consoleKey = (byte)sCode.VirtualKey;
-                outputChar = keyValue;
-            }
-        }
-        else
-        {
-            consoleKey = (byte)keyValue;
-            outputChar = '\0';
-        }
-
-        return new ConsoleKeyInfo (
-                                   (char)outputChar,
-                                   (ConsoleKey)consoleKey,
-                                   modifiers.HasFlag (ConsoleModifiers.Shift),
-                                   modifiers.HasFlag (ConsoleModifiers.Alt),
-                                   modifiers.HasFlag (ConsoleModifiers.Control)
-                                  );
-    }
-
-    // Used only by unit tests
-    internal static uint GetKeyChar (uint keyValue, ConsoleModifiers modifiers)
-    {
-        if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'A' and <= 'Z')
-        {
-            return keyValue - 32;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue is >= 'A' and <= 'Z')
-        {
-            return keyValue + 32;
-        }
-
-        if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'À' and <= 'Ý')
-        {
-            return keyValue - 32;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue is >= 'À' and <= 'Ý')
-        {
-            return keyValue + 32;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '0')
-        {
-            return keyValue + 13;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue - 13 is '0')
-        {
-            return keyValue - 13;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is >= '1' and <= '9' and not '7')
-        {
-            return keyValue - 16;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue + 16 is >= '1' and <= '9' and not '7')
-        {
-            return keyValue + 16;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '7')
-        {
-            return keyValue - 8;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue + 8 is '7')
-        {
-            return keyValue + 8;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\'')
-        {
-            return keyValue + 24;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue - 24 is '\'')
-        {
-            return keyValue - 24;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '«')
-        {
-            return keyValue + 16;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue - 16 is '«')
-        {
-            return keyValue - 16;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\\')
-        {
-            return keyValue + 32;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue - 32 is '\\')
-        {
-            return keyValue - 32;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '+')
-        {
-            return keyValue - 1;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue + 1 is '+')
-        {
-            return keyValue + 1;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '´')
-        {
-            return keyValue - 84;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue + 84 is '´')
-        {
-            return keyValue + 84;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is 'º')
-        {
-            return keyValue - 16;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue + 16 is 'º')
-        {
-            return keyValue + 16;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '~')
-        {
-            return keyValue - 32;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue + 32 is '~')
-        {
-            return keyValue + 32;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '<')
-        {
-            return keyValue + 2;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue - 2 is '<')
-        {
-            return keyValue - 2;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is ',')
-        {
-            return keyValue + 15;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue - 15 is ',')
-        {
-            return keyValue - 15;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '.')
-        {
-            return keyValue + 12;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue - 12 is '.')
-        {
-            return keyValue - 12;
-        }
-
-        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '-')
-        {
-            return keyValue + 50;
-        }
-
-        if (modifiers == ConsoleModifiers.None && keyValue - 50 is '-')
-        {
-            return keyValue - 50;
-        }
-
-        return keyValue;
-    }
-
-    /// <summary>
-    ///     Get the output character from the <see cref="GetConsoleKeyInfoFromKeyCode"/>, with the correct
-    ///     <see cref="ConsoleKey"/> and the scan code used on Windows.
-    /// </summary>
-    /// <param name="unicodeChar">The unicode character.</param>
-    /// <param name="modifiers">The modifiers keys.</param>
-    /// <param name="consoleKey">The resulting console key.</param>
-    /// <param name="scanCode">The resulting scan code.</param>
-    /// <param name="isConsoleKey">Indicates if the <paramref name="unicodeChar"/> is a <see cref="ConsoleKey"/>.</param>
-    /// <returns>The output character or the <paramref name="consoleKey"/>.</returns>
-    /// <remarks>This is only used by the <see cref="GetConsoleKeyInfoFromKeyCode"/> and by unit tests.</remarks>
-    internal static uint GetKeyCharFromUnicodeChar (
-        uint unicodeChar,
-        ConsoleModifiers modifiers,
-        out uint consoleKey,
-        out uint scanCode,
-        bool isConsoleKey = false
-    )
-    {
-        uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar;
-        uint keyChar = decodedChar;
-        consoleKey = 0;
-        ConsoleModifiers mod = GetModifiers (modifiers);
-        scanCode = 0;
-        ScanCodeMapping scode = null;
-
-        if (unicodeChar != 0 && unicodeChar >> 8 != 0xff && isConsoleKey)
-        {
-            scode = GetScanCode ("VirtualKey", decodedChar, mod);
-        }
-
-        if (isConsoleKey && scode is { })
-        {
-            consoleKey = (uint)scode.VirtualKey;
-            keyChar = scode.UnicodeChar;
-            scanCode = scode.ScanCode;
-        }
-
-        if (scode is null)
-        {
-            scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null;
-
-            if (scode is { })
-            {
-                consoleKey = (uint)scode.VirtualKey;
-                keyChar = scode.UnicodeChar;
-                scanCode = scode.ScanCode;
-            }
-        }
-
-        if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar))
-        {
-            string stFormD = ((char)decodedChar).ToString ().Normalize (NormalizationForm.FormD);
-
-            for (var i = 0; i < stFormD.Length; i++)
-            {
-                UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]);
-
-                if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter)
-                {
-                    char ck = char.ToUpper (stFormD [i]);
-                    consoleKey = (uint)(ck > 0 && ck <= 255 ? char.ToUpper (stFormD [i]) : 0);
-                    scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0);
-
-                    if (scode is { })
-                    {
-                        scanCode = scode.ScanCode;
-                    }
-                }
-            }
-        }
-
-        if (keyChar < 255 && consoleKey == 0 && scanCode == 0)
-        {
-            scode = GetScanCode ("VirtualKey", keyChar, mod);
-
-            if (scode is { })
-            {
-                consoleKey = (uint)scode.VirtualKey;
-                keyChar = scode.UnicodeChar;
-                scanCode = scode.ScanCode;
-            }
-        }
-
-        return keyChar;
-    }
-
-    /// <summary>Maps a unicode character (e.g. (Key)'a') to a uint representing a <see cref="ConsoleKey"/>.</summary>
-    /// <param name="keyValue">The key value.</param>
-    /// <param name="isConsoleKey">
-    ///     Indicates if the <paramref name="keyValue"/> is a <see cref="ConsoleKey"/>.
-    ///     <see langword="true"/> means the return value is in the ConsoleKey enum. <see langword="false"/> means the return
-    ///     value can be mapped to a valid unicode character.
-    /// </param>
-    /// <returns>The <see cref="ConsoleKey"/> or the <paramref name="keyValue"/>.</returns>
-    /// <remarks>This is only used by the <see cref="GetConsoleKeyInfoFromKeyCode"/> and by unit tests.</remarks>
-    internal static uint MapKeyCodeToConsoleKey (KeyCode keyValue, out bool isConsoleKey)
-    {
-        isConsoleKey = true;
-        keyValue = keyValue & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask;
-
-        switch (keyValue)
-        {
-            case KeyCode.Enter:
-                return (uint)ConsoleKey.Enter;
-            case KeyCode.CursorUp:
-                return (uint)ConsoleKey.UpArrow;
-            case KeyCode.CursorDown:
-                return (uint)ConsoleKey.DownArrow;
-            case KeyCode.CursorLeft:
-                return (uint)ConsoleKey.LeftArrow;
-            case KeyCode.CursorRight:
-                return (uint)ConsoleKey.RightArrow;
-            case KeyCode.PageUp:
-                return (uint)ConsoleKey.PageUp;
-            case KeyCode.PageDown:
-                return (uint)ConsoleKey.PageDown;
-            case KeyCode.Home:
-                return (uint)ConsoleKey.Home;
-            case KeyCode.End:
-                return (uint)ConsoleKey.End;
-            case KeyCode.Insert:
-                return (uint)ConsoleKey.Insert;
-            case KeyCode.Delete:
-                return (uint)ConsoleKey.Delete;
-            case KeyCode.F1:
-                return (uint)ConsoleKey.F1;
-            case KeyCode.F2:
-                return (uint)ConsoleKey.F2;
-            case KeyCode.F3:
-                return (uint)ConsoleKey.F3;
-            case KeyCode.F4:
-                return (uint)ConsoleKey.F4;
-            case KeyCode.F5:
-                return (uint)ConsoleKey.F5;
-            case KeyCode.F6:
-                return (uint)ConsoleKey.F6;
-            case KeyCode.F7:
-                return (uint)ConsoleKey.F7;
-            case KeyCode.F8:
-                return (uint)ConsoleKey.F8;
-            case KeyCode.F9:
-                return (uint)ConsoleKey.F9;
-            case KeyCode.F10:
-                return (uint)ConsoleKey.F10;
-            case KeyCode.F11:
-                return (uint)ConsoleKey.F11;
-            case KeyCode.F12:
-                return (uint)ConsoleKey.F12;
-            case KeyCode.F13:
-                return (uint)ConsoleKey.F13;
-            case KeyCode.F14:
-                return (uint)ConsoleKey.F14;
-            case KeyCode.F15:
-                return (uint)ConsoleKey.F15;
-            case KeyCode.F16:
-                return (uint)ConsoleKey.F16;
-            case KeyCode.F17:
-                return (uint)ConsoleKey.F17;
-            case KeyCode.F18:
-                return (uint)ConsoleKey.F18;
-            case KeyCode.F19:
-                return (uint)ConsoleKey.F19;
-            case KeyCode.F20:
-                return (uint)ConsoleKey.F20;
-            case KeyCode.F21:
-                return (uint)ConsoleKey.F21;
-            case KeyCode.F22:
-                return (uint)ConsoleKey.F22;
-            case KeyCode.F23:
-                return (uint)ConsoleKey.F23;
-            case KeyCode.F24:
-                return (uint)ConsoleKey.F24;
-            case KeyCode.Tab | KeyCode.ShiftMask:
-                return (uint)ConsoleKey.Tab;
-            case KeyCode.Space:
-                return (uint)ConsoleKey.Spacebar;
-            default:
-                uint c = (char)keyValue;
-
-                if (c is >= (char)ConsoleKey.A and <= (char)ConsoleKey.Z)
-                {
-                    return c;
-                }
-
-                if ((c - 32) is >= (char)ConsoleKey.A and <= (char)ConsoleKey.Z)
-                {
-                    return (c - 32);
-                }
-
-                if (Enum.IsDefined (typeof (ConsoleKey), keyValue.ToString ()))
-                {
-                    return (uint)keyValue;
-                }
-
-                // DEL
-                if ((uint)keyValue == 127)
-                {
-                    return (uint)ConsoleKey.Backspace;
-                }
-                break;
-        }
-
-        isConsoleKey = false;
-
-        return (uint)keyValue;
-    }
-
     /// <summary>Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.</summary>
     /// <param name="consoleKeyInfo">The console key.</param>
     /// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKeyInfo"/>.</returns>
@@ -922,6 +241,34 @@ public static class ConsoleKeyMapping
         return keyCode;
     }
 
+    /// <summary>Map existing <see cref="KeyCode"/> modifiers to <see cref="ConsoleModifiers"/>.</summary>
+    /// <param name="key">The key code.</param>
+    /// <returns>The console modifiers.</returns>
+    public static ConsoleModifiers MapToConsoleModifiers (KeyCode key)
+    {
+        var modifiers = new ConsoleModifiers ();
+
+        // BUGFIX: Only set Shift if ShiftMask is explicitly set.
+        // KeyCode.A-Z (65-90) represent UNSHIFTED keys, even though their numeric values
+        // match uppercase ASCII characters. Do NOT check char.IsUpper!
+        if (key.HasFlag (KeyCode.ShiftMask))
+        {
+            modifiers |= ConsoleModifiers.Shift;
+        }
+
+        if (key.HasFlag (KeyCode.AltMask))
+        {
+            modifiers |= ConsoleModifiers.Alt;
+        }
+
+        if (key.HasFlag (KeyCode.CtrlMask))
+        {
+            modifiers |= ConsoleModifiers.Control;
+        }
+
+        return modifiers;
+    }
+
     /// <summary>Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.</summary>
     /// <param name="modifiers">The console modifiers.</param>
     /// <param name="key">The key code.</param>
@@ -948,1636 +295,92 @@ public static class ConsoleKeyMapping
         return keyMod != KeyCode.Null ? keyMod | key : key;
     }
 
-    /// <summary>Generated from winuser.h. See https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes</summary>
-    public enum VK : ushort
+    /// <summary>
+    ///     Maps a KeyCode to its corresponding ConsoleKey and character representation.
+    /// </summary>
+    private static (ConsoleKey consoleKey, char keyChar) MapToConsoleKeyAndChar (KeyCode key, ConsoleModifiers modifiers)
     {
-        /// <summary>Left mouse button.</summary>
-        LBUTTON = 0x01,
-
-        /// <summary>Right mouse button.</summary>
-        RBUTTON = 0x02,
-
-        /// <summary>Control-break processing.</summary>
-        CANCEL = 0x03,
-
-        /// <summary>Middle mouse button (three-button mouse).</summary>
-        MBUTTON = 0x04,
-
-        /// <summary>X1 mouse button.</summary>
-        XBUTTON1 = 0x05,
-
-        /// <summary>X2 mouse button.</summary>
-        XBUTTON2 = 0x06,
-
-        /// <summary>BACKSPACE key.</summary>
-        BACK = 0x08,
-
-        /// <summary>TAB key.</summary>
-        TAB = 0x09,
-
-        /// <summary>CLEAR key.</summary>
-        CLEAR = 0x0C,
-
-        /// <summary>ENTER key.</summary>
-        RETURN = 0x0D,
-
-        /// <summary>SHIFT key.</summary>
-        SHIFT = 0x10,
-
-        /// <summary>CTRL key.</summary>
-        CONTROL = 0x11,
-
-        /// <summary>ALT key.</summary>
-        MENU = 0x12,
-
-        /// <summary>PAUSE key.</summary>
-        PAUSE = 0x13,
-
-        /// <summary>CAPS LOCK key.</summary>
-        CAPITAL = 0x14,
-
-        /// <summary>IME Kana mode.</summary>
-        KANA = 0x15,
-
-        /// <summary>IME Hangul mode.</summary>
-        HANGUL = 0x15,
-
-        /// <summary>IME Junja mode.</summary>
-        JUNJA = 0x17,
-
-        /// <summary>IME final mode.</summary>
-        FINAL = 0x18,
-
-        /// <summary>IME Hanja mode.</summary>
-        HANJA = 0x19,
-
-        /// <summary>IME Kanji mode.</summary>
-        KANJI = 0x19,
-
-        /// <summary>ESC key.</summary>
-        ESCAPE = 0x1B,
-
-        /// <summary>IME convert.</summary>
-        CONVERT = 0x1C,
-
-        /// <summary>IME nonconvert.</summary>
-        NONCONVERT = 0x1D,
-
-        /// <summary>IME accept.</summary>
-        ACCEPT = 0x1E,
-
-        /// <summary>IME mode change request.</summary>
-        MODECHANGE = 0x1F,
-
-        /// <summary>SPACEBAR.</summary>
-        SPACE = 0x20,
-
-        /// <summary>PAGE UP key.</summary>
-        PRIOR = 0x21,
-
-        /// <summary>PAGE DOWN key.</summary>
-        NEXT = 0x22,
-
-        /// <summary>END key.</summary>
-        END = 0x23,
-
-        /// <summary>HOME key.</summary>
-        HOME = 0x24,
-
-        /// <summary>LEFT ARROW key.</summary>
-        LEFT = 0x25,
-
-        /// <summary>UP ARROW key.</summary>
-        UP = 0x26,
-
-        /// <summary>RIGHT ARROW key.</summary>
-        RIGHT = 0x27,
-
-        /// <summary>DOWN ARROW key.</summary>
-        DOWN = 0x28,
-
-        /// <summary>SELECT key.</summary>
-        SELECT = 0x29,
-
-        /// <summary>PRINT key.</summary>
-        PRINT = 0x2A,
-
-        /// <summary>EXECUTE key</summary>
-        EXECUTE = 0x2B,
-
-        /// <summary>PRINT SCREEN key</summary>
-        SNAPSHOT = 0x2C,
-
-        /// <summary>INS key</summary>
-        INSERT = 0x2D,
-
-        /// <summary>DEL key</summary>
-        DELETE = 0x2E,
-
-        /// <summary>HELP key</summary>
-        HELP = 0x2F,
-
-        /// <summary>Left Windows key (Natural keyboard)</summary>
-        LWIN = 0x5B,
-
-        /// <summary>Right Windows key (Natural keyboard)</summary>
-        RWIN = 0x5C,
-
-        /// <summary>Applications key (Natural keyboard)</summary>
-        APPS = 0x5D,
-
-        /// <summary>Computer Sleep key</summary>
-        SLEEP = 0x5F,
-
-        /// <summary>Numeric keypad 0 key</summary>
-        NUMPAD0 = 0x60,
-
-        /// <summary>Numeric keypad 1 key</summary>
-        NUMPAD1 = 0x61,
-
-        /// <summary>Numeric keypad 2 key</summary>
-        NUMPAD2 = 0x62,
-
-        /// <summary>Numeric keypad 3 key</summary>
-        NUMPAD3 = 0x63,
-
-        /// <summary>Numeric keypad 4 key</summary>
-        NUMPAD4 = 0x64,
-
-        /// <summary>Numeric keypad 5 key</summary>
-        NUMPAD5 = 0x65,
-
-        /// <summary>Numeric keypad 6 key</summary>
-        NUMPAD6 = 0x66,
-
-        /// <summary>Numeric keypad 7 key</summary>
-        NUMPAD7 = 0x67,
-
-        /// <summary>Numeric keypad 8 key</summary>
-        NUMPAD8 = 0x68,
-
-        /// <summary>Numeric keypad 9 key</summary>
-        NUMPAD9 = 0x69,
+        var keyValue = (uint)key;
 
-        /// <summary>Multiply key</summary>
-        MULTIPLY = 0x6A,
-
-        /// <summary>Add key</summary>
-        ADD = 0x6B,
-
-        /// <summary>Separator key</summary>
-        SEPARATOR = 0x6C,
-
-        /// <summary>Subtract key</summary>
-        SUBTRACT = 0x6D,
-
-        /// <summary>Decimal key</summary>
-        DECIMAL = 0x6E,
-
-        /// <summary>Divide key</summary>
-        DIVIDE = 0x6F,
-
-        /// <summary>F1 key</summary>
-        F1 = 0x70,
-
-        /// <summary>F2 key</summary>
-        F2 = 0x71,
-
-        /// <summary>F3 key</summary>
-        F3 = 0x72,
-
-        /// <summary>F4 key</summary>
-        F4 = 0x73,
-
-        /// <summary>F5 key</summary>
-        F5 = 0x74,
-
-        /// <summary>F6 key</summary>
-        F6 = 0x75,
-
-        /// <summary>F7 key</summary>
-        F7 = 0x76,
-
-        /// <summary>F8 key</summary>
-        F8 = 0x77,
-
-        /// <summary>F9 key</summary>
-        F9 = 0x78,
-
-        /// <summary>F10 key</summary>
-        F10 = 0x79,
-
-        /// <summary>F11 key</summary>
-        F11 = 0x7A,
-
-        /// <summary>F12 key</summary>
-        F12 = 0x7B,
-
-        /// <summary>F13 key</summary>
-        F13 = 0x7C,
-
-        /// <summary>F14 key</summary>
-        F14 = 0x7D,
-
-        /// <summary>F15 key</summary>
-        F15 = 0x7E,
-
-        /// <summary>F16 key</summary>
-        F16 = 0x7F,
-
-        /// <summary>F17 key</summary>
-        F17 = 0x80,
-
-        /// <summary>F18 key</summary>
-        F18 = 0x81,
-
-        /// <summary>F19 key</summary>
-        F19 = 0x82,
-
-        /// <summary>F20 key</summary>
-        F20 = 0x83,
-
-        /// <summary>F21 key</summary>
-        F21 = 0x84,
-
-        /// <summary>F22 key</summary>
-        F22 = 0x85,
-
-        /// <summary>F23 key</summary>
-        F23 = 0x86,
-
-        /// <summary>F24 key</summary>
-        F24 = 0x87,
-
-        /// <summary>NUM LOCK key</summary>
-        NUMLOCK = 0x90,
-
-        /// <summary>SCROLL LOCK key</summary>
-        SCROLL = 0x91,
-
-        /// <summary>NEC PC-9800 kbd definition: '=' key on numpad</summary>
-        OEM_NEC_EQUAL = 0x92,
-
-        /// <summary>Fujitsu/OASYS kbd definition: 'Dictionary' key</summary>
-        OEM_FJ_JISHO = 0x92,
-
-        /// <summary>Fujitsu/OASYS kbd definition: 'Unregister word' key</summary>
-        OEM_FJ_MASSHOU = 0x93,
-
-        /// <summary>Fujitsu/OASYS kbd definition: 'Register word' key</summary>
-        OEM_FJ_TOUROKU = 0x94,
-
-        /// <summary>Fujitsu/OASYS kbd definition: 'Left OYAYUBI' key</summary>
-        OEM_FJ_LOYA = 0x95,
-
-        /// <summary>Fujitsu/OASYS kbd definition: 'Right OYAYUBI' key</summary>
-        OEM_FJ_ROYA = 0x96,
-
-        /// <summary>Left SHIFT key</summary>
-        LSHIFT = 0xA0,
-
-        /// <summary>Right SHIFT key</summary>
-        RSHIFT = 0xA1,
-
-        /// <summary>Left CONTROL key</summary>
-        LCONTROL = 0xA2,
-
-        /// <summary>Right CONTROL key</summary>
-        RCONTROL = 0xA3,
-
-        /// <summary>Left MENU key (Left Alt key)</summary>
-        LMENU = 0xA4,
-
-        /// <summary>Right MENU key (Right Alt key)</summary>
-        RMENU = 0xA5,
-
-        /// <summary>Browser Back key</summary>
-        BROWSER_BACK = 0xA6,
-
-        /// <summary>Browser Forward key</summary>
-        BROWSER_FORWARD = 0xA7,
-
-        /// <summary>Browser Refresh key</summary>
-        BROWSER_REFRESH = 0xA8,
-
-        /// <summary>Browser Stop key</summary>
-        BROWSER_STOP = 0xA9,
-
-        /// <summary>Browser Search key</summary>
-        BROWSER_SEARCH = 0xAA,
-
-        /// <summary>Browser Favorites key</summary>
-        BROWSER_FAVORITES = 0xAB,
-
-        /// <summary>Browser Home key</summary>
-        BROWSER_HOME = 0xAC,
-
-        /// <summary>Volume Mute key</summary>
-        VOLUME_MUTE = 0xAD,
-
-        /// <summary>Volume Down key</summary>
-        VOLUME_DOWN = 0xAE,
-
-        /// <summary>Volume Up key</summary>
-        VOLUME_UP = 0xAF,
-
-        /// <summary>Next Track key</summary>
-        MEDIA_NEXT_TRACK = 0xB0,
-
-        /// <summary>Previous Track key</summary>
-        MEDIA_PREV_TRACK = 0xB1,
-
-        /// <summary>Stop Media key</summary>
-        MEDIA_STOP = 0xB2,
-
-        /// <summary>Play/Pause Media key</summary>
-        MEDIA_PLAY_PAUSE = 0xB3,
-
-        /// <summary>Start Mail key</summary>
-        LAUNCH_MAIL = 0xB4,
-
-        /// <summary>Select Media key</summary>
-        LAUNCH_MEDIA_SELECT = 0xB5,
-
-        /// <summary>Start Application 1 key</summary>
-        LAUNCH_APP1 = 0xB6,
-
-        /// <summary>Start Application 2 key</summary>
-        LAUNCH_APP2 = 0xB7,
-
-        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key</summary>
-        OEM_1 = 0xBA,
-
-        /// <summary>For any country/region, the '+' key</summary>
-        OEM_PLUS = 0xBB,
-
-        /// <summary>For any country/region, the ',' key</summary>
-        OEM_COMMA = 0xBC,
-
-        /// <summary>For any country/region, the '-' key</summary>
-        OEM_MINUS = 0xBD,
-
-        /// <summary>For any country/region, the '.' key</summary>
-        OEM_PERIOD = 0xBE,
-
-        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key</summary>
-        OEM_2 = 0xBF,
-
-        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key</summary>
-        OEM_3 = 0xC0,
-
-        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key</summary>
-        OEM_4 = 0xDB,
-
-        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key</summary>
-        OEM_5 = 0xDC,
-
-        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key</summary>
-        OEM_6 = 0xDD,
-
-        /// <summary>
-        ///     Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the
-        ///     'single-quote/double-quote' key
-        /// </summary>
-        OEM_7 = 0xDE,
-
-        /// <summary>Used for miscellaneous characters; it can vary by keyboard.</summary>
-        OEM_8 = 0xDF,
-
-        /// <summary>'AX' key on Japanese AX kbd</summary>
-        OEM_AX = 0xE1,
-
-        /// <summary>Either the angle bracket key or the backslash key on the RT 102-key keyboard</summary>
-        OEM_102 = 0xE2,
-
-        /// <summary>Help key on ICO</summary>
-        ICO_HELP = 0xE3,
-
-        /// <summary>00 key on ICO</summary>
-        ICO_00 = 0xE4,
-
-        /// <summary>Process key</summary>
-        PROCESSKEY = 0xE5,
-
-        /// <summary>Clear key on ICO</summary>
-        ICO_CLEAR = 0xE6,
-
-        /// <summary>Packet key to be used to pass Unicode characters as if they were keystrokes</summary>
-        PACKET = 0xE7,
-
-        /// <summary>Reset key</summary>
-        OEM_RESET = 0xE9,
-
-        /// <summary>Jump key</summary>
-        OEM_JUMP = 0xEA,
-
-        /// <summary>PA1 key</summary>
-        OEM_PA1 = 0xEB,
-
-        /// <summary>PA2 key</summary>
-        OEM_PA2 = 0xEC,
-
-        /// <summary>PA3 key</summary>
-        OEM_PA3 = 0xED,
-
-        /// <summary>WsCtrl key</summary>
-        OEM_WSCTRL = 0xEE,
-
-        /// <summary>CuSel key</summary>
-        OEM_CUSEL = 0xEF,
-
-        /// <summary>Attn key</summary>
-        OEM_ATTN = 0xF0,
-
-        /// <summary>Finish key</summary>
-        OEM_FINISH = 0xF1,
-
-        /// <summary>Copy key</summary>
-        OEM_COPY = 0xF2,
-
-        /// <summary>Auto key</summary>
-        OEM_AUTO = 0xF3,
-
-        /// <summary>Enlw key</summary>
-        OEM_ENLW = 0xF4,
-
-        /// <summary>BackTab key</summary>
-        OEM_BACKTAB = 0xF5,
-
-        /// <summary>Attn key</summary>
-        ATTN = 0xF6,
-
-        /// <summary>CrSel key</summary>
-        CRSEL = 0xF7,
+        // Check if this is a special key (value > MaxCodePoint means it's offset by MaxCodePoint)
+        if (keyValue > (uint)KeyCode.MaxCodePoint)
+        {
+            var specialKey = (ConsoleKey)(keyValue - (uint)KeyCode.MaxCodePoint);
 
-        /// <summary>ExSel key</summary>
-        EXSEL = 0xF8,
+            // Special keys don't have printable characters
+            char specialChar = specialKey switch
+                               {
+                                   ConsoleKey.Enter => '\r',
+                                   ConsoleKey.Tab => '\t',
+                                   ConsoleKey.Escape => '\u001B',
+                                   ConsoleKey.Backspace => '\b',
+                                   ConsoleKey.Spacebar => ' ',
+                                   _ => '\0' // Function keys, arrows, etc. have no character
+                               };
 
-        /// <summary>Erase EOF key</summary>
-        EREOF = 0xF9,
+            return (specialKey, specialChar);
+        }
 
-        /// <summary>Play key</summary>
-        PLAY = 0xFA,
+        // Handle letter keys (A-Z)
+        if (keyValue >= (uint)KeyCode.A && keyValue <= (uint)KeyCode.Z)
+        {
+            var letterKey = (ConsoleKey)keyValue;
+            var letterChar = (char)('a' + (keyValue - (uint)KeyCode.A));
 
-        /// <summary>Zoom key</summary>
-        ZOOM = 0xFB,
+            if (modifiers.HasFlag (ConsoleModifiers.Shift))
+            {
+                letterChar = char.ToUpper (letterChar);
+            }
 
-        /// <summary>Reserved</summary>
-        NONAME = 0xFC,
+            return (letterKey, letterChar);
+        }
 
-        /// <summary>PA1 key</summary>
-        PA1 = 0xFD,
+        // Handle number keys (D0-D9) with US keyboard layout
+        if (keyValue >= (uint)KeyCode.D0 && keyValue <= (uint)KeyCode.D9)
+        {
+            var numberKey = (ConsoleKey)keyValue;
+            char numberChar;
 
-        /// <summary>Clear key</summary>
-        OEM_CLEAR = 0xFE
-    }
+            if (modifiers.HasFlag (ConsoleModifiers.Shift))
+            {
+                // US keyboard layout: Shift+0-9 produces )!@#$%^&*(
+                numberChar = ")!@#$%^&*(" [(int)(keyValue - (uint)KeyCode.D0)];
+            }
+            else
+            {
+                numberChar = (char)('0' + (keyValue - (uint)KeyCode.D0));
+            }
 
-    // BUGBUG: This database makes no sense. It is not possible to map a VK code to a character without knowing the keyboard layout
-    //         It should be deleted.
-    private static readonly HashSet<ScanCodeMapping> _scanCodes = new ()
-    {
-        new ScanCodeMapping (
-                             1,
-                             VK.ESCAPE,
-                             0,
-                             '\u001B'
-                            ), // Escape
-        new ScanCodeMapping (
-                             1,
-                             VK.ESCAPE,
-                             ConsoleModifiers.Shift,
-                             '\u001B'
-                            ),
-        new ScanCodeMapping (
-                             2,
-                             (VK)'1',
-                             0,
-                             '1'
-                            ), // D1
-        new ScanCodeMapping (
-                             2,
-                             (VK)'1',
-                             ConsoleModifiers.Shift,
-                             '!'
-                            ),
-        new ScanCodeMapping (
-                             3,
-                             (VK)'2',
-                             0,
-                             '2'
-                            ), // D2
-        new ScanCodeMapping (
-                             3,
-                             (VK)'2',
-                             ConsoleModifiers.Shift,
-                             '\"'
-                            ), // BUGBUG: This is true for Portuguese keyboard, but not ENG (@) or DEU (")
-        new ScanCodeMapping (
-                             3,
-                             (VK)'2',
-                             ConsoleModifiers.Alt
-                             | ConsoleModifiers.Control,
-                             '@'
-                            ),
-        new ScanCodeMapping (
-                             4,
-                             (VK)'3',
-                             0,
-                             '3'
-                            ), // D3
-        new ScanCodeMapping (
-                             4,
-                             (VK)'3',
-                             ConsoleModifiers.Shift,
-                             '#'
-                            ),
-        new ScanCodeMapping (
-                             4,
-                             (VK)'3',
-                             ConsoleModifiers.Alt
-                             | ConsoleModifiers.Control,
-                             '£'
-                            ),
-        new ScanCodeMapping (
-                             5,
-                             (VK)'4',
-                             0,
-                             '4'
-                            ), // D4
-        new ScanCodeMapping (
-                             5,
-                             (VK)'4',
-                             ConsoleModifiers.Shift,
-                             '$'
-                            ),
-        new ScanCodeMapping (
-                             5,
-                             (VK)'4',
-                             ConsoleModifiers.Alt
-                             | ConsoleModifiers.Control,
-                             '§'
-                            ),
-        new ScanCodeMapping (
-                             6,
-                             (VK)'5',
-                             0,
-                             '5'
-                            ), // D5
-        new ScanCodeMapping (
-                             6,
-                             (VK)'5',
-                             ConsoleModifiers.Shift,
-                             '%'
-                            ),
-        new ScanCodeMapping (
-                             6,
-                             (VK)'5',
-                             ConsoleModifiers.Alt
-                             | ConsoleModifiers.Control,
-                             '€'
-                            ),
-        new ScanCodeMapping (
-                             7,
-                             (VK)'6',
-                             0,
-                             '6'
-                            ), // D6
-        new ScanCodeMapping (
-                             7,
-                             (VK)'6',
-                             ConsoleModifiers.Shift,
-                             '&'
-                            ),
-        new ScanCodeMapping (
-                             8,
-                             (VK)'7',
-                             0,
-                             '7'
-                            ), // D7
-        new ScanCodeMapping (
-                             8,
-                             (VK)'7',
-                             ConsoleModifiers.Shift,
-                             '/'
-                            ),
-        new ScanCodeMapping (
-                             8,
-                             (VK)'7',
-                             ConsoleModifiers.Alt
-                             | ConsoleModifiers.Control,
-                             '{'
-                            ),
-        new ScanCodeMapping (
-                             9,
-                             (VK)'8',
-                             0,
-                             '8'
-                            ), // D8
-        new ScanCodeMapping (
-                             9,
-                             (VK)'8',
-                             ConsoleModifiers.Shift,
-                             '('
-                            ),
-        new ScanCodeMapping (
-                             9,
-                             (VK)'8',
-                             ConsoleModifiers.Alt
-                             | ConsoleModifiers.Control,
-                             '['
-                            ),
-        new ScanCodeMapping (
-                             10,
-                             (VK)'9',
-                             0,
-                             '9'
-                            ), // D9
-        new ScanCodeMapping (
-                             10,
-                             (VK)'9',
-                             ConsoleModifiers.Shift,
-                             ')'
-                            ),
-        new ScanCodeMapping (
-                             10,
-                             (VK)'9',
-                             ConsoleModifiers.Alt
-                             | ConsoleModifiers.Control,
-                             ']'
-                            ),
-        new ScanCodeMapping (
-                             11,
-                             (VK)'0',
-                             0,
-                             '0'
-                            ), // D0
-        new ScanCodeMapping (
-                             11,
-                             (VK)'0',
-                             ConsoleModifiers.Shift,
-                             '='
-                            ),
-        new ScanCodeMapping (
-                             11,
-                             (VK)'0',
-                             ConsoleModifiers.Alt
-                             | ConsoleModifiers.Control,
-                             '}'
-                            ),
-        new ScanCodeMapping (
-                             12,
-                             VK.OEM_4,
-                             0,
-                             '\''
-                            ), // Oem4
-        new ScanCodeMapping (
-                             12,
-                             VK.OEM_4,
-                             ConsoleModifiers.Shift,
-                             '?'
-                            ),
-        new ScanCodeMapping (
-                             13,
-                             VK.OEM_6,
-                             0,
-                             '+'
-                            ), // Oem6
-        new ScanCodeMapping (
-                             13,
-                             VK.OEM_6,
-                             ConsoleModifiers.Shift,
-                             '*'
-                            ),
-        new ScanCodeMapping (
-                             14,
-                             VK.BACK,
-                             0,
-                             '\u0008'
-                            ), // Backspace
-        new ScanCodeMapping (
-                             14,
-                             VK.BACK,
-                             ConsoleModifiers.Shift,
-                             '\u0008'
-                            ),
-        new ScanCodeMapping (
-                             15,
-                             VK.TAB,
-                             0,
-                             '\u0009'
-                            ), // Tab
-        new ScanCodeMapping (
-                             15,
-                             VK.TAB,
-                             ConsoleModifiers.Shift,
-                             '\u000F'
-                            ),
-        new ScanCodeMapping (
-                             16,
-                             (VK)'Q',
-                             0,
-                             'q'
-                            ), // Q
-        new ScanCodeMapping (
-                             16,
-                             (VK)'Q',
-                             ConsoleModifiers.Shift,
-                             'Q'
-                            ),
-        new ScanCodeMapping (
-                             17,
-                             (VK)'W',
-                             0,
-                             'w'
-                            ), // W
-        new ScanCodeMapping (
-                             17,
-                             (VK)'W',
-                             ConsoleModifiers.Shift,
-                             'W'
-                            ),
-        new ScanCodeMapping (
-                             18,
-                             (VK)'E',
-                             0,
-                             'e'
-                            ), // E
-        new ScanCodeMapping (
-                             18,
-                             (VK)'E',
-                             ConsoleModifiers.Shift,
-                             'E'
-                            ),
-        new ScanCodeMapping (
-                             19,
-                             (VK)'R',
-                             0,
-                             'r'
-                            ), // R
-        new ScanCodeMapping (
-                             19,
-                             (VK)'R',
-                             ConsoleModifiers.Shift,
-                             'R'
-                            ),
-        new ScanCodeMapping (
-                             20,
-                             (VK)'T',
-                             0,
-                             't'
-                            ), // T
-        new ScanCodeMapping (
-                             20,
-                             (VK)'T',
-                             ConsoleModifiers.Shift,
-                             'T'
-                            ),
-        new ScanCodeMapping (
-                             21,
-                             (VK)'Y',
-                             0,
-                             'y'
-                            ), // Y
-        new ScanCodeMapping (
-                             21,
-                             (VK)'Y',
-                             ConsoleModifiers.Shift,
-                             'Y'
-                            ),
-        new ScanCodeMapping (
-                             22,
-                             (VK)'U',
-                             0,
-                             'u'
-                            ), // U
-        new ScanCodeMapping (
-                             22,
-                             (VK)'U',
-                             ConsoleModifiers.Shift,
-                             'U'
-                            ),
-        new ScanCodeMapping (
-                             23,
-                             (VK)'I',
-                             0,
-                             'i'
-                            ), // I
-        new ScanCodeMapping (
-                             23,
-                             (VK)'I',
-                             ConsoleModifiers.Shift,
-                             'I'
-                            ),
-        new ScanCodeMapping (
-                             24,
-                             (VK)'O',
-                             0,
-                             'o'
-                            ), // O
-        new ScanCodeMapping (
-                             24,
-                             (VK)'O',
-                             ConsoleModifiers.Shift,
-                             'O'
-                            ),
-        new ScanCodeMapping (
-                             25,
-                             (VK)'P',
-                             0,
-                             'p'
-                            ), // P
-        new ScanCodeMapping (
-                             25,
-                             (VK)'P',
-                             ConsoleModifiers.Shift,
-                             'P'
-                            ),
-        new ScanCodeMapping (
-                             26,
-                             VK.OEM_PLUS,
-                             0,
-                             '+'
-                            ), // OemPlus
-        new ScanCodeMapping (
-                             26,
-                             VK.OEM_PLUS,
-                             ConsoleModifiers.Shift,
-                             '*'
-                            ),
-        new ScanCodeMapping (
-                             26,
-                             VK.OEM_PLUS,
-                             ConsoleModifiers.Alt
-                             | ConsoleModifiers.Control,
-                             '¨'
-                            ),
-        new ScanCodeMapping (
-                             27,
-                             VK.OEM_1,
-                             0,
-                             '´'
-                            ), // Oem1
-        new ScanCodeMapping (
-                             27,
-                             VK.OEM_1,
-                             ConsoleModifiers.Shift,
-                             '`'
-                            ),
-        new ScanCodeMapping (
-                             28,
-                             VK.RETURN,
-                             0,
-                             '\u000D'
-                            ), // Enter
-        new ScanCodeMapping (
-                             28,
-                             VK.RETURN,
-                             ConsoleModifiers.Shift,
-                             '\u000D'
-                            ),
-        new ScanCodeMapping (
-                             29,
-                             VK.CONTROL,
-                             0,
-                             '\0'
-                            ), // Control
-        new ScanCodeMapping (
-                             29,
-                             VK.CONTROL,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             30,
-                             (VK)'A',
-                             0,
-                             'a'
-                            ), // A
-        new ScanCodeMapping (
-                             30,
-                             (VK)'A',
-                             ConsoleModifiers.Shift,
-                             'A'
-                            ),
-        new ScanCodeMapping (
-                             31,
-                             (VK)'S',
-                             0,
-                             's'
-                            ), // S
-        new ScanCodeMapping (
-                             31,
-                             (VK)'S',
-                             ConsoleModifiers.Shift,
-                             'S'
-                            ),
-        new ScanCodeMapping (
-                             32,
-                             (VK)'D',
-                             0,
-                             'd'
-                            ), // D
-        new ScanCodeMapping (
-                             32,
-                             (VK)'D',
-                             ConsoleModifiers.Shift,
-                             'D'
-                            ),
-        new ScanCodeMapping (
-                             33,
-                             (VK)'F',
-                             0,
-                             'f'
-                            ), // F
-        new ScanCodeMapping (
-                             33,
-                             (VK)'F',
-                             ConsoleModifiers.Shift,
-                             'F'
-                            ),
-        new ScanCodeMapping (
-                             34,
-                             (VK)'G',
-                             0,
-                             'g'
-                            ), // G
-        new ScanCodeMapping (
-                             34,
-                             (VK)'G',
-                             ConsoleModifiers.Shift,
-                             'G'
-                            ),
-        new ScanCodeMapping (
-                             35,
-                             (VK)'H',
-                             0,
-                             'h'
-                            ), // H
-        new ScanCodeMapping (
-                             35,
-                             (VK)'H',
-                             ConsoleModifiers.Shift,
-                             'H'
-                            ),
-        new ScanCodeMapping (
-                             36,
-                             (VK)'J',
-                             0,
-                             'j'
-                            ), // J
-        new ScanCodeMapping (
-                             36,
-                             (VK)'J',
-                             ConsoleModifiers.Shift,
-                             'J'
-                            ),
-        new ScanCodeMapping (
-                             37,
-                             (VK)'K',
-                             0,
-                             'k'
-                            ), // K
-        new ScanCodeMapping (
-                             37,
-                             (VK)'K',
-                             ConsoleModifiers.Shift,
-                             'K'
-                            ),
-        new ScanCodeMapping (
-                             38,
-                             (VK)'L',
-                             0,
-                             'l'
-                            ), // L
-        new ScanCodeMapping (
-                             38,
-                             (VK)'L',
-                             ConsoleModifiers.Shift,
-                             'L'
-                            ),
-        new ScanCodeMapping (
-                             39,
-                             VK.OEM_3,
-                             0,
-                             '`'
-                            ), // Oem3 (Backtick/Grave)
-        new ScanCodeMapping (
-                             39,
-                             VK.OEM_3,
-                             ConsoleModifiers.Shift,
-                             '~'
-                            ),
-        new ScanCodeMapping (
-                             40,
-                             VK.OEM_7,
-                             0,
-                             '\''
-                            ), // Oem7 (Single Quote)
-        new ScanCodeMapping (
-                             40,
-                             VK.OEM_7,
-                             ConsoleModifiers.Shift,
-                             '\"'
-                            ),
-        new ScanCodeMapping (
-                             41,
-                             VK.OEM_5,
-                             0,
-                             '\\'
-                            ), // Oem5 (Backslash)
-        new ScanCodeMapping (
-                             41,
-                             VK.OEM_5,
-                             ConsoleModifiers.Shift,
-                             '|'
-                            ),
-        new ScanCodeMapping (
-                             42,
-                             VK.LSHIFT,
-                             0,
-                             '\0'
-                            ), // Left Shift
-        new ScanCodeMapping (
-                             42,
-                             VK.LSHIFT,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             43,
-                             VK.OEM_2,
-                             0,
-                             '/'
-                            ), // Oem2 (Forward Slash)
-        new ScanCodeMapping (
-                             43,
-                             VK.OEM_2,
-                             ConsoleModifiers.Shift,
-                             '?'
-                            ),
-        new ScanCodeMapping (
-                             44,
-                             (VK)'Z',
-                             0,
-                             'z'
-                            ), // Z
-        new ScanCodeMapping (
-                             44,
-                             (VK)'Z',
-                             ConsoleModifiers.Shift,
-                             'Z'
-                            ),
-        new ScanCodeMapping (
-                             45,
-                             (VK)'X',
-                             0,
-                             'x'
-                            ), // X
-        new ScanCodeMapping (
-                             45,
-                             (VK)'X',
-                             ConsoleModifiers.Shift,
-                             'X'
-                            ),
-        new ScanCodeMapping (
-                             46,
-                             (VK)'C',
-                             0,
-                             'c'
-                            ), // C
-        new ScanCodeMapping (
-                             46,
-                             (VK)'C',
-                             ConsoleModifiers.Shift,
-                             'C'
-                            ),
-        new ScanCodeMapping (
-                             47,
-                             (VK)'V',
-                             0,
-                             'v'
-                            ), // V
-        new ScanCodeMapping (
-                             47,
-                             (VK)'V',
-                             ConsoleModifiers.Shift,
-                             'V'
-                            ),
-        new ScanCodeMapping (
-                             48,
-                             (VK)'B',
-                             0,
-                             'b'
-                            ), // B
-        new ScanCodeMapping (
-                             48,
-                             (VK)'B',
-                             ConsoleModifiers.Shift,
-                             'B'
-                            ),
-        new ScanCodeMapping (
-                             49,
-                             (VK)'N',
-                             0,
-                             'n'
-                            ), // N
-        new ScanCodeMapping (
-                             49,
-                             (VK)'N',
-                             ConsoleModifiers.Shift,
-                             'N'
-                            ),
-        new ScanCodeMapping (
-                             50,
-                             (VK)'M',
-                             0,
-                             'm'
-                            ), // M
-        new ScanCodeMapping (
-                             50,
-                             (VK)'M',
-                             ConsoleModifiers.Shift,
-                             'M'
-                            ),
-        new ScanCodeMapping (
-                             51,
-                             VK.OEM_COMMA,
-                             0,
-                             ','
-                            ), // OemComma
-        new ScanCodeMapping (
-                             51,
-                             VK.OEM_COMMA,
-                             ConsoleModifiers.Shift,
-                             '<'
-                            ),
-        new ScanCodeMapping (
-                             52,
-                             VK.OEM_PERIOD,
-                             0,
-                             '.'
-                            ), // OemPeriod
-        new ScanCodeMapping (
-                             52,
-                             VK.OEM_PERIOD,
-                             ConsoleModifiers.Shift,
-                             '>'
-                            ),
-        new ScanCodeMapping (
-                             53,
-                             VK.OEM_MINUS,
-                             0,
-                             '-'
-                            ), // OemMinus
-        new ScanCodeMapping (
-                             53,
-                             VK.OEM_MINUS,
-                             ConsoleModifiers.Shift,
-                             '_'
-                            ),
-        new ScanCodeMapping (
-                             54,
-                             VK.RSHIFT,
-                             0,
-                             '\0'
-                            ), // Right Shift
-        new ScanCodeMapping (
-                             54,
-                             VK.RSHIFT,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             55,
-                             VK.PRINT,
-                             0,
-                             '\0'
-                            ), // Print Screen
-        new ScanCodeMapping (
-                             55,
-                             VK.PRINT,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             56,
-                             VK.LMENU,
-                             0,
-                             '\0'
-                            ), // Alt
-        new ScanCodeMapping (
-                             56,
-                             VK.LMENU,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             57,
-                             VK.SPACE,
-                             0,
-                             ' '
-                            ), // Spacebar
-        new ScanCodeMapping (
-                             57,
-                             VK.SPACE,
-                             ConsoleModifiers.Shift,
-                             ' '
-                            ),
-        new ScanCodeMapping (
-                             58,
-                             VK.CAPITAL,
-                             0,
-                             '\0'
-                            ), // Caps Lock
-        new ScanCodeMapping (
-                             58,
-                             VK.CAPITAL,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             59,
-                             VK.F1,
-                             0,
-                             '\0'
-                            ), // F1
-        new ScanCodeMapping (
-                             59,
-                             VK.F1,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             60,
-                             VK.F2,
-                             0,
-                             '\0'
-                            ), // F2
-        new ScanCodeMapping (
-                             60,
-                             VK.F2,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             61,
-                             VK.F3,
-                             0,
-                             '\0'
-                            ), // F3
-        new ScanCodeMapping (
-                             61,
-                             VK.F3,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             62,
-                             VK.F4,
-                             0,
-                             '\0'
-                            ), // F4
-        new ScanCodeMapping (
-                             62,
-                             VK.F4,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             63,
-                             VK.F5,
-                             0,
-                             '\0'
-                            ), // F5
-        new ScanCodeMapping (
-                             63,
-                             VK.F5,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             64,
-                             VK.F6,
-                             0,
-                             '\0'
-                            ), // F6
-        new ScanCodeMapping (
-                             64,
-                             VK.F6,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             65,
-                             VK.F7,
-                             0,
-                             '\0'
-                            ), // F7
-        new ScanCodeMapping (
-                             65,
-                             VK.F7,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             66,
-                             VK.F8,
-                             0,
-                             '\0'
-                            ), // F8
-        new ScanCodeMapping (
-                             66,
-                             VK.F8,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             67,
-                             VK.F9,
-                             0,
-                             '\0'
-                            ), // F9
-        new ScanCodeMapping (
-                             67,
-                             VK.F9,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             68,
-                             VK.F10,
-                             0,
-                             '\0'
-                            ), // F10
-        new ScanCodeMapping (
-                             68,
-                             VK.F10,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             69,
-                             VK.NUMLOCK,
-                             0,
-                             '\0'
-                            ), // Num Lock
-        new ScanCodeMapping (
-                             69,
-                             VK.NUMLOCK,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             70,
-                             VK.SCROLL,
-                             0,
-                             '\0'
-                            ), // Scroll Lock
-        new ScanCodeMapping (
-                             70,
-                             VK.SCROLL,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             71,
-                             VK.HOME,
-                             0,
-                             '\0'
-                            ), // Home
-        new ScanCodeMapping (
-                             71,
-                             VK.HOME,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             72,
-                             VK.UP,
-                             0,
-                             '\0'
-                            ), // Up Arrow
-        new ScanCodeMapping (
-                             72,
-                             VK.UP,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             73,
-                             VK.PRIOR,
-                             0,
-                             '\0'
-                            ), // Page Up
-        new ScanCodeMapping (
-                             73,
-                             VK.PRIOR,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             74,
-                             VK.SUBTRACT,
-                             0,
-                             '-'
-                            ), // Subtract (Num Pad '-')
-        new ScanCodeMapping (
-                             74,
-                             VK.SUBTRACT,
-                             ConsoleModifiers.Shift,
-                             '-'
-                            ),
-        new ScanCodeMapping (
-                             75,
-                             VK.LEFT,
-                             0,
-                             '\0'
-                            ), // Left Arrow
-        new ScanCodeMapping (
-                             75,
-                             VK.LEFT,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             76,
-                             VK.CLEAR,
-                             0,
-                             '\0'
-                            ), // Center key (Num Pad 5 with Num Lock off)
-        new ScanCodeMapping (
-                             76,
-                             VK.CLEAR,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             77,
-                             VK.RIGHT,
-                             0,
-                             '\0'
-                            ), // Right Arrow
-        new ScanCodeMapping (
-                             77,
-                             VK.RIGHT,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             78,
-                             VK.ADD,
-                             0,
-                             '+'
-                            ), // Add (Num Pad '+')
-        new ScanCodeMapping (
-                             78,
-                             VK.ADD,
-                             ConsoleModifiers.Shift,
-                             '+'
-                            ),
-        new ScanCodeMapping (
-                             79,
-                             VK.END,
-                             0,
-                             '\0'
-                            ), // End
-        new ScanCodeMapping (
-                             79,
-                             VK.END,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             80,
-                             VK.DOWN,
-                             0,
-                             '\0'
-                            ), // Down Arrow
-        new ScanCodeMapping (
-                             80,
-                             VK.DOWN,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             81,
-                             VK.NEXT,
-                             0,
-                             '\0'
-                            ), // Page Down
-        new ScanCodeMapping (
-                             81,
-                             VK.NEXT,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             82,
-                             VK.INSERT,
-                             0,
-                             '\0'
-                            ), // Insert
-        new ScanCodeMapping (
-                             82,
-                             VK.INSERT,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             83,
-                             VK.DELETE,
-                             0,
-                             '\0'
-                            ), // Delete
-        new ScanCodeMapping (
-                             83,
-                             VK.DELETE,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             86,
-                             VK.OEM_102,
-                             0,
-                             '<'
-                            ), // OEM 102 (Typically '<' or '|' key next to Left Shift)
-        new ScanCodeMapping (
-                             86,
-                             VK.OEM_102,
-                             ConsoleModifiers.Shift,
-                             '>'
-                            ),
-        new ScanCodeMapping (
-                             87,
-                             VK.F11,
-                             0,
-                             '\0'
-                            ), // F11
-        new ScanCodeMapping (
-                             87,
-                             VK.F11,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            ),
-        new ScanCodeMapping (
-                             88,
-                             VK.F12,
-                             0,
-                             '\0'
-                            ), // F12
-        new ScanCodeMapping (
-                             88,
-                             VK.F12,
-                             ConsoleModifiers.Shift,
-                             '\0'
-                            )
-    };
-
-    /// <summary>Decode a <see cref="ConsoleKeyInfo"/> that is using <see cref="ConsoleKey.Packet"/>.</summary>
-    /// <param name="consoleKeyInfo">The console key info.</param>
-    /// <returns>The decoded <see cref="ConsoleKeyInfo"/> or the <paramref name="consoleKeyInfo"/>.</returns>
-    /// <remarks>
-    ///     If it's a <see cref="ConsoleKey.Packet"/> the <see cref="ConsoleKeyInfo.KeyChar"/> may be a
-    ///     <see cref="ConsoleKeyInfo.Key"/> or a <see cref="ConsoleKeyInfo.KeyChar"/> value.
-    /// </remarks>
-    public static ConsoleKeyInfo DecodeVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-    {
-        if (consoleKeyInfo.Key != ConsoleKey.Packet)
-        {
-            return consoleKeyInfo;
+            return (numberKey, numberChar);
         }
 
-        return GetConsoleKeyInfoFromKeyChar (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
-    }
+        // Handle other standard keys
+        var standardKey = (ConsoleKey)keyValue;
 
-    /// <summary>
-    ///     Encode the <see cref="ConsoleKeyInfo.KeyChar"/> with the <see cref="ConsoleKeyInfo.Key"/> if the first a byte
-    ///     length, otherwise only the KeyChar is considered and searched on the database.
-    /// </summary>
-    /// <param name="consoleKeyInfo">The console key info.</param>
-    /// <returns>The encoded KeyChar with the Key if both can be shifted, otherwise only the KeyChar.</returns>
-    /// <remarks>This is useful to use with the <see cref="ConsoleKey.Packet"/>.</remarks>
-    public static char EncodeKeyCharForVKPacket (ConsoleKeyInfo consoleKeyInfo)
-    {
-        char keyChar = consoleKeyInfo.KeyChar;
-        ConsoleKey consoleKey = consoleKeyInfo.Key;
-
-        if (keyChar != 0 && consoleKeyInfo.KeyChar < byte.MaxValue && consoleKey == ConsoleKey.None)
+        if (Enum.IsDefined (typeof (ConsoleKey), (int)keyValue))
         {
-            // try to get the ConsoleKey
-            ScanCodeMapping scode = _scanCodes.FirstOrDefault (e => e.UnicodeChar == keyChar);
+            char standardChar = standardKey switch
+                                {
+                                    ConsoleKey.Enter => '\r',
+                                    ConsoleKey.Tab => '\t',
+                                    ConsoleKey.Escape => '\u001B',
+                                    ConsoleKey.Backspace => '\b',
+                                    ConsoleKey.Spacebar => ' ',
+                                    ConsoleKey.Clear => '\0',
+                                    _ when keyValue <= 0x1F => '\0', // Control characters
+                                    _ => (char)keyValue
+                                };
 
-            if (scode is { })
-            {
-                consoleKey = (ConsoleKey)scode.VirtualKey;
-            }
+            return (standardKey, standardChar);
         }
 
-        if (keyChar < byte.MaxValue && consoleKey != ConsoleKey.None)
+        // For printable Unicode characters, return character with ConsoleKey.None
+        if (keyValue <= 0x10FFFF && !char.IsControl ((char)keyValue))
         {
-            keyChar = (char)((consoleKeyInfo.KeyChar << 8) | (byte)consoleKey);
+            return (ConsoleKey.None, (char)keyValue);
         }
 
-        return keyChar;
+        // Fallback
+        return (ConsoleKey.None, (char)keyValue);
     }
 }

+ 4 - 1
Terminal.Gui/Drivers/DotNetDriver/INetInput.cs

@@ -1,4 +1,7 @@
 namespace Terminal.Gui.Drivers;
 
-internal interface INetInput : IConsoleInput<ConsoleKeyInfo>
+/// <summary>
+///     Wraps IConsoleInput for .NET console input events (ConsoleKeyInfo). Needed to support Mocking in tests.
+/// </summary>
+internal interface INetInput : IInput<ConsoleKeyInfo>
 { }

+ 8 - 17
Terminal.Gui/Drivers/DotNetDriver/NetComponentFactory.cs

@@ -4,26 +4,17 @@ using System.Collections.Concurrent;
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-/// <see cref="IComponentFactory{T}"/> implementation for native csharp console I/O i.e. dotnet.
-/// This factory creates instances of internal classes <see cref="NetInput"/>, <see cref="NetOutput"/> etc.
+///     <see cref="IComponentFactory{T}"/> implementation for native csharp console I/O i.e. dotnet.
+///     This factory creates instances of internal classes <see cref="NetInput"/>, <see cref="NetOutput"/> etc.
 /// </summary>
-public class NetComponentFactory : ComponentFactory<ConsoleKeyInfo>
+public class NetComponentFactory : ComponentFactoryImpl<ConsoleKeyInfo>
 {
     /// <inheritdoc/>
-    public override IConsoleInput<ConsoleKeyInfo> CreateInput ()
-    {
-        return new NetInput ();
-    }
+    public override IInput<ConsoleKeyInfo> CreateInput () { return new NetInput (); }
 
-    /// <inheritdoc />
-    public override IConsoleOutput CreateOutput ()
-    {
-        return new NetOutput ();
-    }
+    /// <inheritdoc/>
+    public override IInputProcessor CreateInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) { return new NetInputProcessor (inputBuffer); }
 
-    /// <inheritdoc />
-    public override IInputProcessor CreateInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer)
-    {
-        return new NetInputProcessor (inputBuffer);
-    }
+    /// <inheritdoc/>
+    public override IOutput CreateOutput () { return new NetOutput (); }
 }

+ 81 - 51
Terminal.Gui/Drivers/DotNetDriver/NetInput.cs

@@ -3,12 +3,12 @@
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-///     Console input implementation that uses native dotnet methods e.g. <see cref="System.Console"/>.
+///     <see cref="IInput{TInputRecord}"/> implementation that uses native dotnet methods e.g. <see cref="System.Console"/>.
+///     The <see cref="Peek"/> and <see cref="Read"/> methods are executed
+///     on the input thread created by <see cref="MainLoopCoordinator{TInputRecord}.StartInputTaskAsync"/>.
 /// </summary>
-public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
+public class NetInput : InputImpl<ConsoleKeyInfo>, ITestableInput<ConsoleKeyInfo>, IDisposable
 {
-    private readonly NetWinVTConsole _adjustConsole;
-
     /// <summary>
     ///     Creates a new instance of the class. Implicitly sends
     ///     console mode settings that enable virtual input (mouse
@@ -16,12 +16,7 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
     /// </summary>
     public NetInput ()
     {
-        Logging.Logger.LogInformation ($"Creating {nameof (NetInput)}");
-
-        if (ConsoleDriver.RunningUnitTests)
-        {
-            return;
-        }
+        Logging.Information ($"Creating {nameof (NetInput)}");
 
         PlatformID p = Environment.OSVersion.Platform;
 
@@ -34,75 +29,110 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
             catch (ApplicationException ex)
             {
                 // Likely running as a unit test, or in a non-interactive session.
-                Logging.Logger.LogCritical (
-                                            ex,
-                                            "NetWinVTConsole could not be constructed i.e. could not configure terminal modes. May indicate running in non-interactive session e.g. unit testing CI");
+                Logging.Critical ($"NetWinVTConsole could not configure terminal modes. May indicate running in non-interactive session: {ex}");
+
+                return;
             }
         }
 
-        //Enable alternative screen buffer.
-        Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+        try
+        {
+            //Enable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
 
-        //Set cursor key to application.
-        Console.Out.Write (EscSeqUtils.CSI_HideCursor);
+            //Set cursor key to application.
+            Console.Out.Write (EscSeqUtils.CSI_HideCursor);
 
-        Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
-        Console.TreatControlCAsInput = true;
+            Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+            Console.TreatControlCAsInput = true;
+        }
+        catch
+        {
+            // Swallow any exceptions during initialization for unit tests
+        }
     }
 
+    private readonly NetWinVTConsole _adjustConsole;
+
     /// <inheritdoc/>
-    protected override bool Peek ()
+    public override void Dispose ()
     {
-        if (ConsoleDriver.RunningUnitTests)
+        base.Dispose ();
+
+        try
         {
-            return false;
-        }
+            // Disable mouse events first
+            Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+
+            //Disable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+
+            //Set cursor key to cursor.
+            Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
 
-        return Console.KeyAvailable;
+            _adjustConsole?.Cleanup ();
+
+            // Flush any pending input so no stray events appear
+            FlushConsoleInput ();
+        }
+        catch
+        {
+            // Swallow any exceptions during Dispose for unit tests
+        }
     }
 
+    /// <inheritdoc />
+    public void AddInput (ConsoleKeyInfo input) { throw new NotImplementedException (); }
+
     /// <inheritdoc/>
-    protected override IEnumerable<ConsoleKeyInfo> Read ()
+    public override bool Peek ()
     {
-        while (Console.KeyAvailable)
+        try
         {
-            yield return Console.ReadKey (true);
+            return Console.KeyAvailable;
+        }
+        catch
+        {
+            return false;
         }
     }
 
-    private void FlushConsoleInput ()
+    /// <inheritdoc/>
+    public override IEnumerable<ConsoleKeyInfo> Read ()
     {
-        if (!ConsoleDriver.RunningUnitTests)
+        while (true)
         {
-            while (Console.KeyAvailable)
+            ConsoleKeyInfo keyInfo = default;
+
+            try
+            {
+                if (!Console.KeyAvailable)
+                {
+                    break;
+                }
+
+                keyInfo = Console.ReadKey (true);
+            }
+            catch (InvalidOperationException)
+            {
+                // Not connected to a terminal (GitHub Actions, redirected input, etc.)
+                yield break;
+            }
+            catch (IOException)
             {
-                Console.ReadKey (intercept: true);
+                // I/O error reading from console
+                yield break;
             }
+
+            yield return keyInfo;
         }
     }
 
-    /// <inheritdoc/>
-    public override void Dispose ()
+    private void FlushConsoleInput ()
     {
-        base.Dispose ();
-
-        if (ConsoleDriver.RunningUnitTests)
+        while (Console.KeyAvailable)
         {
-            return;
+            Console.ReadKey (true);
         }
-
-        // Disable mouse events first
-        Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
-
-        //Disable alternative screen buffer.
-        Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
-
-        //Set cursor key to cursor.
-        Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
-
-        _adjustConsole?.Cleanup ();
-
-        // Flush any pending input so no stray events appear
-        FlushConsoleInput ();
     }
 }

+ 3 - 45
Terminal.Gui/Drivers/DotNetDriver/NetInputProcessor.cs

@@ -5,20 +5,8 @@ namespace Terminal.Gui.Drivers;
 /// <summary>
 ///     Input processor for <see cref="NetInput"/>, deals in <see cref="ConsoleKeyInfo"/> stream
 /// </summary>
-public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
+public class NetInputProcessor : InputProcessorImpl<ConsoleKeyInfo>
 {
-#pragma warning disable CA2211
-    /// <summary>
-    ///     Set to true to generate code in <see cref="Logging"/> (verbose only) for test cases in NetInputProcessorTests.
-    ///     <remarks>
-    ///         This makes the task of capturing user/language/terminal specific keyboard issues easier to
-    ///         diagnose. By turning this on and searching logs user can send us exactly the input codes that are released
-    ///         to input stream.
-    ///     </remarks>
-    /// </summary>
-    public static bool GenerateTestCasesForKeyPresses = false;
-#pragma warning restore CA2211
-
     /// <inheritdoc/>
     public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ())
     {
@@ -26,41 +14,11 @@ public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
     }
 
     /// <inheritdoc/>
-    protected override void Process (ConsoleKeyInfo consoleKeyInfo)
+    protected override void Process (ConsoleKeyInfo input)
     {
-        // For building test cases
-        if (GenerateTestCasesForKeyPresses)
-        {
-            Logging.Trace (FormatConsoleKeyInfoForTestCase (consoleKeyInfo));
-        }
-
-        foreach (Tuple<char, ConsoleKeyInfo> released in Parser.ProcessInput (Tuple.Create (consoleKeyInfo.KeyChar, consoleKeyInfo)))
+        foreach (Tuple<char, ConsoleKeyInfo> released in Parser.ProcessInput (Tuple.Create (input.KeyChar, input)))
         {
             ProcessAfterParsing (released.Item2);
         }
     }
-
-    /// <inheritdoc/>
-    protected override void ProcessAfterParsing (ConsoleKeyInfo input)
-    {
-        var key = KeyConverter.ToKey (input);
-
-        // If the key is not valid, we don't want to raise any events.
-        if (IsValidInput (key, out key))
-        {
-            OnKeyDown (key);
-            OnKeyUp (key);
-        }
-    }
-
-    /* For building test cases */
-    private static string FormatConsoleKeyInfoForTestCase (ConsoleKeyInfo input)
-    {
-        string charLiteral = input.KeyChar == '\0' ? @"'\0'" : $"'{input.KeyChar}'";
-
-        return $"new ConsoleKeyInfo({charLiteral}, ConsoleKey.{input.Key}, "
-               + $"{input.Modifiers.HasFlag (ConsoleModifiers.Shift).ToString ().ToLower ()}, "
-               + $"{input.Modifiers.HasFlag (ConsoleModifiers.Alt).ToString ().ToLower ()}, "
-               + $"{input.Modifiers.HasFlag (ConsoleModifiers.Control).ToString ().ToLower ()}),";
-    }
 }

+ 4 - 2
Terminal.Gui/Drivers/DotNetDriver/NetKeyConverter.cs

@@ -1,5 +1,4 @@
-
-namespace Terminal.Gui.Drivers;
+namespace Terminal.Gui.Drivers;
 
 /// <summary>
 ///     <see cref="IKeyConverter{T}"/> capable of converting the
@@ -23,4 +22,7 @@ internal class NetKeyConverter : IKeyConverter<ConsoleKeyInfo>
 
         return EscSeqUtils.MapKey (adjustedInput);
     }
+
+    /// <inheritdoc/>
+    public ConsoleKeyInfo ToKeyInfo (Key key) { return ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key.KeyCode); }
 }

+ 57 - 20
Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs

@@ -3,10 +3,10 @@
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-///     Implementation of <see cref="IConsoleOutput"/> that uses native dotnet
+///     Implementation of <see cref="IOutput"/> that uses native dotnet
 ///     methods e.g. <see cref="System.Console"/>
 /// </summary>
-public class NetOutput : OutputBase, IConsoleOutput
+public class NetOutput : OutputBase, IOutput
 {
     private readonly bool _isWinPlatform;
 
@@ -15,9 +15,16 @@ public class NetOutput : OutputBase, IConsoleOutput
     /// </summary>
     public NetOutput ()
     {
-        Logging.Logger.LogInformation ($"Creating {nameof (NetOutput)}");
+        Logging.Information ($"Creating {nameof (NetOutput)}");
 
-        Console.OutputEncoding = Encoding.UTF8;
+        try
+        {
+            Console.OutputEncoding = Encoding.UTF8;
+        }
+        catch
+        {
+            // ignore for unit tests
+        }
 
         PlatformID p = Environment.OSVersion.Platform;
 
@@ -30,20 +37,36 @@ public class NetOutput : OutputBase, IConsoleOutput
     /// <inheritdoc/>
     public void Write (ReadOnlySpan<char> text)
     {
-        Console.Out.Write (text);
+        try
+        {
+            Console.Out.Write (text);
+        }
+        catch (IOException)
+        {
+            // Not connected to a terminal; do nothing
+        }
     }
 
 
     /// <inheritdoc/>
     public Size GetSize ()
     {
-        if (ConsoleDriver.RunningUnitTests)
+        try
         {
-            // For unit tests, we return a default size.
-            return Size.Empty;
+            Size size = new (Console.WindowWidth, Console.WindowHeight);
+            return size.IsEmpty ? new (80, 25) : size;
         }
+        catch (IOException)
+        {
+            // Not connected to a terminal; return a default size
+            return new (80, 25);
+        }
+    }
 
-        return new (Console.WindowWidth, Console.WindowHeight);
+    /// <inheritdoc />
+    public Point GetCursorPosition ()
+    {
+        return _lastCursorPosition ?? Point.Empty;
     }
 
     /// <inheritdoc/>
@@ -88,7 +111,14 @@ public class NetOutput : OutputBase, IConsoleOutput
     /// <inheritdoc />
     protected override void Write (StringBuilder output)
     {
-        Console.Out.Write (output);
+        try
+        {
+            Console.Out.Write (output);
+        }
+        catch (IOException)
+        {
+            // Not connected to a terminal; do nothing
+        }
     }
 
     /// <inheritdoc />
@@ -103,7 +133,7 @@ public class NetOutput : OutputBase, IConsoleOutput
 
         if (_isWinPlatform)
         {
-            // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
+            // Could happen that the windows is still resizing and the col is bigger than Console.WindowWidth.
             try
             {
                 Console.SetCursorPosition (col, row);
@@ -131,23 +161,30 @@ public class NetOutput : OutputBase, IConsoleOutput
 
     private EscSeqUtils.DECSCUSR_Style? _currentDecscusrStyle;
 
-    /// <inheritdoc cref="IConsoleOutput.SetCursorVisibility"/>
+    /// <inheritdoc cref="IOutput.SetCursorVisibility"/>
     public override void SetCursorVisibility (CursorVisibility visibility)
     {
-        if (visibility != CursorVisibility.Invisible)
+        try
         {
-            if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))
+            if (visibility != CursorVisibility.Invisible)
             {
-                _currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF);
+                if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))
+                {
+                    _currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF);
 
-                Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle));
-            }
+                    Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle));
+                }
 
-            Write (EscSeqUtils.CSI_ShowCursor);
+                Write (EscSeqUtils.CSI_ShowCursor);
+            }
+            else
+            {
+                Write (EscSeqUtils.CSI_HideCursor);
+            }
         }
-        else
+        catch
         {
-            Write (EscSeqUtils.CSI_HideCursor);
+            // Ignore any exceptions
         }
     }
 }

+ 1 - 1
Terminal.Gui/Drivers/DotNetDriver/NetWinVTConsole.cs

@@ -79,7 +79,7 @@ internal class NetWinVTConsole
     {
         if (!FlushConsoleInputBuffer (_inputHandle))
         {
-            throw new ApplicationException ($"Failed to flush input buffer, error code: {GetLastError ()}.");
+            throw new ApplicationException ($"Failed to flush input queue, error code: {GetLastError ()}.");
         }
 
         if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode))

+ 166 - 102
Terminal.Gui/Drivers/ConsoleDriverFacade.cs → Terminal.Gui/Drivers/DriverImpl.cs

@@ -3,69 +3,108 @@ using System.Runtime.InteropServices;
 
 namespace Terminal.Gui.Drivers;
 
-internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
+/// <summary>
+///     Provides the main implementation of the driver abstraction layer for Terminal.Gui.
+///     This implementation of <see cref="IDriver"/> coordinates the interaction between input processing, output
+///     rendering,
+///     screen size monitoring, and ANSI escape sequence handling.
+/// </summary>
+/// <remarks>
+///     <para>
+///         <see cref="DriverImpl"/> implements <see cref="IDriver"/>,
+///         serving as the central coordination point for console I/O operations. It delegates functionality
+///         to specialized components:
+///     </para>
+///     <list type="bullet">
+///         <item><see cref="IInputProcessor"/> - Processes keyboard and mouse input</item>
+///         <item><see cref="IOutputBuffer"/> - Manages the screen buffer state</item>
+///         <item><see cref="IOutput"/> - Handles actual console output rendering</item>
+///         <item><see cref="AnsiRequestScheduler"/> - Manages ANSI escape sequence requests</item>
+///         <item><see cref="ISizeMonitor"/> - Monitors terminal size changes</item>
+///     </list>
+///     <para>
+///         This class is internal and should not be used directly by application code.
+///         Applications interact with drivers through the <see cref="Application"/> class.
+///     </para>
+/// </remarks>
+internal class DriverImpl : IDriver
 {
-    private readonly IConsoleOutput _output;
-    private readonly IOutputBuffer _outputBuffer;
+    private readonly IOutput _output;
     private readonly AnsiRequestScheduler _ansiRequestScheduler;
     private CursorVisibility _lastCursor = CursorVisibility.Default;
 
     /// <summary>
-    /// The event fired when the screen changes (size, position, etc.).
+    ///     Initializes a new instance of the <see cref="DriverImpl"/> class.
     /// </summary>
-    public event EventHandler<SizeChangedEventArgs>? SizeChanged;
-
-    public IInputProcessor InputProcessor { get; }
-    public IOutputBuffer OutputBuffer => _outputBuffer;
-
-    public IConsoleSizeMonitor ConsoleSizeMonitor { get; }
-
-
-    public ConsoleDriverFacade (
+    /// <param name="inputProcessor">The input processor for handling keyboard and mouse events.</param>
+    /// <param name="outputBuffer">The output buffer for managing screen state.</param>
+    /// <param name="output">The output interface for rendering to the console.</param>
+    /// <param name="ansiRequestScheduler">The scheduler for managing ANSI escape sequence requests.</param>
+    /// <param name="sizeMonitor">The monitor for tracking terminal size changes.</param>
+    public DriverImpl (
         IInputProcessor inputProcessor,
         IOutputBuffer outputBuffer,
-        IConsoleOutput output,
+        IOutput output,
         AnsiRequestScheduler ansiRequestScheduler,
-        IConsoleSizeMonitor sizeMonitor
+        ISizeMonitor sizeMonitor
     )
     {
         InputProcessor = inputProcessor;
         _output = output;
-        _outputBuffer = outputBuffer;
+        OutputBuffer = outputBuffer;
         _ansiRequestScheduler = ansiRequestScheduler;
 
         InputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e);
         InputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
+
         InputProcessor.MouseEvent += (s, e) =>
                                      {
                                          //Logging.Logger.LogTrace ($"Mouse {e.Flags} at x={e.ScreenPosition.X} y={e.ScreenPosition.Y}");
                                          MouseEvent?.Invoke (s, e);
                                      };
 
-        ConsoleSizeMonitor = sizeMonitor;
+        SizeMonitor = sizeMonitor;
+
         sizeMonitor.SizeChanged += (_, e) =>
-        {
-            SetScreenSize(e.Size!.Value.Width, e.Size.Value.Height);
-            //SizeChanged?.Invoke (this, e);
-        };
+                                   {
+                                       SetScreenSize (e.Size!.Value.Width, e.Size.Value.Height);
+
+                                       //SizeChanged?.Invoke (this, e);
+                                   };
 
         CreateClipboard ();
     }
 
+    /// <summary>
+    ///     The event fired when the screen changes (size, position, etc.).
+    /// </summary>
+    public event EventHandler<SizeChangedEventArgs>? SizeChanged;
+
+    /// <inheritdoc/>
+    public IInputProcessor InputProcessor { get; }
+
+    /// <inheritdoc/>
+    public IOutputBuffer OutputBuffer { get; }
+
+    /// <inheritdoc/>
+    public ISizeMonitor SizeMonitor { get; }
+
+
     private void CreateClipboard ()
     {
-        if (FakeDriver.FakeBehaviors.UseFakeClipboard)
+        if (InputProcessor.DriverName is { } && InputProcessor.DriverName.Contains ("fake"))
         {
-            Clipboard = new FakeClipboard (
-                FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException,
-                FakeDriver.FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse);
+            if (Clipboard is null)
+            {
+                Clipboard = new FakeClipboard ();
+            }
 
             return;
         }
 
         PlatformID p = Environment.OSVersion.Platform;
 
-        if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        if (p is PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows)
         {
             Clipboard = new WindowsClipboard ();
         }
@@ -77,10 +116,8 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
         {
             Clipboard = new WSLClipboard ();
         }
-        else
-        {
-            Clipboard = new FakeClipboard ();
-        }
+
+        // Clipboard is set to FakeClipboard at initialization
     }
 
     /// <summary>Gets the location and size of the terminal screen.</summary>
@@ -88,27 +125,27 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     {
         get
         {
-            if (ConsoleDriver.RunningUnitTests && _output is WindowsOutput or NetOutput)
-            {
-                // In unit tests, we don't have a real output, so we return an empty rectangle.
-                return Rectangle.Empty;
-            }
+            //if (Application.RunningUnitTests && _output is WindowsConsoleOutput or NetOutput)
+            //{
+            //    // In unit tests, we don't have a real output, so we return an empty rectangle.
+            //    return Rectangle.Empty;
+            //}
 
-            return new (0, 0, _outputBuffer.Cols, _outputBuffer.Rows);
+            return new (0, 0, OutputBuffer.Cols, OutputBuffer.Rows);
         }
     }
 
     /// <summary>
-    /// Sets the screen size for testing purposes. Only supported by FakeDriver.
+    ///     Sets the screen size for testing purposes. Only supported by FakeDriver.
     /// </summary>
     /// <param name="width">The new width in columns.</param>
     /// <param name="height">The new height in rows.</param>
     /// <exception cref="NotSupportedException">Thrown when called on non-FakeDriver instances.</exception>
     public virtual void SetScreenSize (int width, int height)
     {
-        _outputBuffer.SetSize (width, height);
+        OutputBuffer.SetSize (width, height);
         _output.SetSize (width, height);
-        SizeChanged?.Invoke(this, new (new (width, height)));
+        SizeChanged?.Invoke (this, new (new (width, height)));
     }
 
     /// <summary>
@@ -118,24 +155,24 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     /// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
     public Region? Clip
     {
-        get => _outputBuffer.Clip;
-        set => _outputBuffer.Clip = value;
+        get => OutputBuffer.Clip;
+        set => OutputBuffer.Clip = value;
     }
 
     /// <summary>Get the operating system clipboard.</summary>
-    public IClipboard Clipboard { get; private set; } = new FakeClipboard ();
+    public IClipboard? Clipboard { get; private set; } = new FakeClipboard ();
 
     /// <summary>
     ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
     ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
     /// </summary>
-    public int Col => _outputBuffer.Col;
+    public int Col => OutputBuffer.Col;
 
     /// <summary>The number of columns visible in the terminal.</summary>
     public int Cols
     {
-        get => _outputBuffer.Cols;
-        set => _outputBuffer.Cols = value;
+        get => OutputBuffer.Cols;
+        set => OutputBuffer.Cols = value;
     }
 
     /// <summary>
@@ -144,51 +181,51 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     /// </summary>
     public Cell [,]? Contents
     {
-        get => _outputBuffer.Contents;
-        set => _outputBuffer.Contents = value;
+        get => OutputBuffer.Contents;
+        set => OutputBuffer.Contents = value;
     }
 
     /// <summary>The leftmost column in the terminal.</summary>
     public int Left
     {
-        get => _outputBuffer.Left;
-        set => _outputBuffer.Left = value;
+        get => OutputBuffer.Left;
+        set => OutputBuffer.Left = value;
     }
 
     /// <summary>
     ///     Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
     ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
     /// </summary>
-    public int Row => _outputBuffer.Row;
+    public int Row => OutputBuffer.Row;
 
     /// <summary>The number of rows visible in the terminal.</summary>
     public int Rows
     {
-        get => _outputBuffer.Rows;
-        set => _outputBuffer.Rows = value;
+        get => OutputBuffer.Rows;
+        set => OutputBuffer.Rows = value;
     }
 
     /// <summary>The topmost row in the terminal.</summary>
     public int Top
     {
-        get => _outputBuffer.Top;
-        set => _outputBuffer.Top = value;
+        get => OutputBuffer.Top;
+        set => OutputBuffer.Top = value;
     }
 
     // TODO: Probably not everyone right?
 
-    /// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
+    /// <summary>Gets whether the <see cref="IDriver"/> supports TrueColor output.</summary>
     public bool SupportsTrueColor => true;
 
     // TODO: Currently ignored
     /// <summary>
-    ///     Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors.
+    ///     Gets or sets whether the <see cref="IDriver"/> should use 16 colors instead of the default TrueColors.
     ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
     /// </summary>
     /// <remarks>
     ///     <para>
-    ///         Will be forced to <see langword="true"/> if <see cref="ConsoleDriver.SupportsTrueColor"/> is
-    ///         <see langword="false"/>, indicating that the <see cref="ConsoleDriver"/> cannot support TrueColor.
+    ///         Will be forced to <see langword="true"/> if <see cref="IDriver.SupportsTrueColor"/> is
+    ///         <see langword="false"/>, indicating that the <see cref="IDriver"/> cannot support TrueColor.
     ///     </para>
     /// </remarks>
     public bool Force16Colors
@@ -198,85 +235,86 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     }
 
     /// <summary>
-    ///     The <see cref="System.Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/>
+    ///     The <see cref="System.Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or
+    ///     <see cref="AddStr"/>
     ///     call.
     /// </summary>
     public Attribute CurrentAttribute
     {
-        get => _outputBuffer.CurrentAttribute;
-        set => _outputBuffer.CurrentAttribute = value;
+        get => OutputBuffer.CurrentAttribute;
+        set => OutputBuffer.CurrentAttribute = value;
     }
 
     /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
     /// <remarks>
     ///     <para>
-    ///         When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
+    ///         When the method returns, <see cref="IDriver.Col"/> will be incremented by the number of columns
     ///         <paramref name="rune"/> required, even if the new column value is outside of the
-    ///         <see cref="ConsoleDriver.Clip"/> or screen
-    ///         dimensions defined by <see cref="ConsoleDriver.Cols"/>.
+    ///         <see cref="IDriver.Clip"/> or screen
+    ///         dimensions defined by <see cref="IDriver.Cols"/>.
     ///     </para>
     ///     <para>
-    ///         If <paramref name="rune"/> requires more than one column, and <see cref="ConsoleDriver.Col"/> plus the number
+    ///         If <paramref name="rune"/> requires more than one column, and <see cref="IDriver.Col"/> plus the number
     ///         of columns
-    ///         needed exceeds the <see cref="ConsoleDriver.Clip"/> or screen dimensions, the default Unicode replacement
+    ///         needed exceeds the <see cref="IDriver.Clip"/> or screen dimensions, the default Unicode replacement
     ///         character (U+FFFD)
     ///         will be added instead.
     ///     </para>
     /// </remarks>
     /// <param name="rune">Rune to add.</param>
-    public void AddRune (Rune rune) { _outputBuffer.AddRune (rune); }
+    public void AddRune (Rune rune) { OutputBuffer.AddRune (rune); }
 
     /// <summary>
     ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
-    ///     convenience method that calls <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> with the <see cref="Rune"/>
+    ///     convenience method that calls <see cref="IDriver.AddRune(System.Text.Rune)"/> with the <see cref="Rune"/>
     ///     constructor.
     /// </summary>
     /// <param name="c">Character to add.</param>
-    public void AddRune (char c) { _outputBuffer.AddRune (c); }
+    public void AddRune (char c) { OutputBuffer.AddRune (c); }
 
     /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
     /// <remarks>
     ///     <para>
-    ///         When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
-    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="ConsoleDriver.Clip"/>
+    ///         When the method returns, <see cref="IDriver.Col"/> will be incremented by the number of columns
+    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="IDriver.Clip"/>
     ///         or screen
-    ///         dimensions defined by <see cref="ConsoleDriver.Cols"/>.
+    ///         dimensions defined by <see cref="IDriver.Cols"/>.
     ///     </para>
     ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
     /// </remarks>
     /// <param name="str">String.</param>
-    public void AddStr (string str) { _outputBuffer.AddStr (str); }
+    public void AddStr (string str) { OutputBuffer.AddStr (str); }
 
-    /// <summary>Clears the <see cref="ConsoleDriver.Contents"/> of the driver.</summary>
+    /// <summary>Clears the <see cref="IDriver.Contents"/> of the driver.</summary>
     public void ClearContents ()
     {
-        _outputBuffer.ClearContents ();
+        OutputBuffer.ClearContents ();
         ClearedContents?.Invoke (this, new MouseEventArgs ());
     }
 
     /// <summary>
-    ///     Raised each time <see cref="ConsoleDriver.ClearContents"/> is called. For benchmarking.
+    ///     Raised each time <see cref="IDriver.ClearContents"/> is called. For benchmarking.
     /// </summary>
     public event EventHandler<EventArgs>? ClearedContents;
 
     /// <summary>
-    ///     Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/>
+    ///     Fills the specified rectangle with the specified rune, using <see cref="IDriver.CurrentAttribute"/>
     /// </summary>
     /// <remarks>
-    ///     The value of <see cref="ConsoleDriver.Clip"/> is honored. Any parts of the rectangle not in the clip will not be
+    ///     The value of <see cref="IDriver.Clip"/> is honored. Any parts of the rectangle not in the clip will not be
     ///     drawn.
     /// </remarks>
     /// <param name="rect">The Screen-relative rectangle.</param>
     /// <param name="rune">The Rune used to fill the rectangle</param>
-    public void FillRect (Rectangle rect, Rune rune = default) { _outputBuffer.FillRect (rect, rune); }
+    public void FillRect (Rectangle rect, Rune rune = default) { OutputBuffer.FillRect (rect, rune); }
 
     /// <summary>
     ///     Fills the specified rectangle with the specified <see langword="char"/>. This method is a convenience method
-    ///     that calls <see cref="ConsoleDriver.FillRect(System.Drawing.Rectangle,System.Text.Rune)"/>.
+    ///     that calls <see cref="IDriver.FillRect(System.Drawing.Rectangle,System.Text.Rune)"/>.
     /// </summary>
     /// <param name="rect"></param>
     /// <param name="c"></param>
-    public void FillRect (Rectangle rect, char c) { _outputBuffer.FillRect (rect, c); }
+    public void FillRect (Rectangle rect, char c) { OutputBuffer.FillRect (rect, c); }
 
     /// <inheritdoc/>
     public virtual string GetVersionInfo ()
@@ -300,28 +338,28 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     /// <param name="row">The row.</param>
     /// <returns>
     ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of
-    ///     <see cref="ConsoleDriver.Clip"/>.
+    ///     <see cref="IDriver.Clip"/>.
     ///     <see langword="true"/> otherwise.
     /// </returns>
-    public bool IsValidLocation (Rune rune, int col, int row) { return _outputBuffer.IsValidLocation (rune, col, row); }
+    public bool IsValidLocation (Rune rune, int col, int row) { return OutputBuffer.IsValidLocation (rune, col, row); }
 
     /// <summary>
-    ///     Updates <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/> to the specified column and row in
-    ///     <see cref="ConsoleDriver.Contents"/>.
-    ///     Used by <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> and <see cref="ConsoleDriver.AddStr"/> to determine
+    ///     Updates <see cref="IDriver.Col"/> and <see cref="IDriver.Row"/> to the specified column and row in
+    ///     <see cref="IDriver.Contents"/>.
+    ///     Used by <see cref="IDriver.AddRune(System.Text.Rune)"/> and <see cref="IDriver.AddStr"/> to determine
     ///     where to add content.
     /// </summary>
     /// <remarks>
     ///     <para>This does not move the cursor on the screen, it only updates the internal state of the driver.</para>
     ///     <para>
-    ///         If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="ConsoleDriver.Cols"/>
+    ///         If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="IDriver.Cols"/>
     ///         and
-    ///         <see cref="ConsoleDriver.Rows"/>, the method still sets those properties.
+    ///         <see cref="IDriver.Rows"/>, the method still sets those properties.
     ///     </para>
     /// </remarks>
     /// <param name="col">Column to move to.</param>
     /// <param name="row">Row to move to.</param>
-    public void Move (int col, int row) { _outputBuffer.Move (col, row); }
+    public void Move (int col, int row) { OutputBuffer.Move (col, row); }
 
     // TODO: Probably part of output
 
@@ -347,6 +385,8 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     /// <inheritdoc/>
     public void Suspend ()
     {
+        // BUGBUG: This is all platform-specific and should not be implemented here.
+        // BUGBUG: This needs to be in each platform's driver implementation.
         if (Environment.OSVersion.Platform != PlatformID.Unix)
         {
             return;
@@ -354,7 +394,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
 
         Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
 
-        if (!ConsoleDriver.RunningUnitTests)
+        try
         {
             Console.ResetColor ();
             Console.Clear ();
@@ -369,16 +409,21 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
 
             //Enable alternative screen buffer.
             Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
-
-            Application.LayoutAndDraw ();
         }
+        catch (Exception ex)
+        {
+            Logging.Error ($"Error suspending terminal: {ex.Message}");
+        }
+
+        Application.LayoutAndDraw ();
+
 
         Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
     }
 
     /// <summary>
-    ///     Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and
-    ///     <see cref="ConsoleDriver.Row"/>.
+    ///     Sets the position of the terminal cursor to <see cref="IDriver.Col"/> and
+    ///     <see cref="IDriver.Row"/>.
     /// </summary>
     public void UpdateCursor () { _output.SetCursorPosition (Col, Row); }
 
@@ -397,22 +442,22 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     /// <returns>The previously set Attribute.</returns>
     public Attribute SetAttribute (Attribute newAttribute)
     {
-        Attribute currentAttribute = _outputBuffer.CurrentAttribute;
-        _outputBuffer.CurrentAttribute = newAttribute;
+        Attribute currentAttribute = OutputBuffer.CurrentAttribute;
+        OutputBuffer.CurrentAttribute = newAttribute;
 
         return currentAttribute;
     }
 
     /// <summary>Gets the current <see cref="Attribute"/>.</summary>
     /// <returns>The current attribute.</returns>
-    public Attribute GetAttribute () { return _outputBuffer.CurrentAttribute; }
+    public Attribute GetAttribute () { return OutputBuffer.CurrentAttribute; }
 
-    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="ConsoleDriver.KeyUp"/>.</summary>
+    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="IDriver.KeyUp"/>.</summary>
     public event EventHandler<Key>? KeyDown;
 
     /// <summary>Event fired when a key is released.</summary>
     /// <remarks>
-    ///     Drivers that do not support key release events will fire this event after <see cref="ConsoleDriver.KeyDown"/>
+    ///     Drivers that do not support key release events will fire this event after <see cref="IDriver.KeyDown"/>
     ///     processing is
     ///     complete.
     /// </remarks>
@@ -422,17 +467,31 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     public event EventHandler<MouseEventArgs>? MouseEvent;
 
     /// <summary>
-    ///     Provide proper writing to send escape sequence recognized by the <see cref="ConsoleDriver"/>.
+    ///     Provide proper writing to send escape sequence recognized by the <see cref="IDriver"/>.
     /// </summary>
     /// <param name="ansi"></param>
     public void WriteRaw (string ansi) { _output.Write (ansi); }
 
+    /// <inheritdoc/>
+    public void EnqueueKeyEvent (Key key)
+    {
+        InputProcessor.EnqueueKeyDownEvent (key);
+    }
+
     /// <summary>
-    ///     Queues the given <paramref name="request"/> for execution
+    ///     Queues the specified ANSI escape sequence request for execution.
     /// </summary>
-    /// <param name="request"></param>
+    /// <param name="request">The ANSI request to queue.</param>
+    /// <remarks>
+    ///     The request is sent immediately if possible, or queued for later execution
+    ///     by the <see cref="AnsiRequestScheduler"/> to prevent overwhelming the console.
+    /// </remarks>
     public void QueueAnsiRequest (AnsiEscapeSequenceRequest request) { _ansiRequestScheduler.SendOrSchedule (request); }
 
+    /// <summary>
+    ///     Gets the <see cref="AnsiRequestScheduler"/> instance used by this driver.
+    /// </summary>
+    /// <returns>The ANSI request scheduler.</returns>
     public AnsiRequestScheduler GetRequestScheduler () { return _ansiRequestScheduler; }
 
     /// <inheritdoc/>
@@ -440,4 +499,9 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     {
         // No need we will always draw when dirty
     }
+
+    public string? GetName ()
+    {
+        return InputProcessor.DriverName?.ToLowerInvariant ();
+    }
 }

+ 23 - 23
Terminal.Gui/Drivers/FakeDriver/FakeComponentFactory.cs

@@ -4,47 +4,47 @@ using System.Collections.Concurrent;
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-/// <see cref="IComponentFactory{T}"/> implementation for fake/mock console I/O used in unit tests.
-/// This factory creates instances that simulate console behavior without requiring a real terminal.
+///     <see cref="IComponentFactory{T}"/> implementation for fake/mock console I/O used in unit tests.
+///     This factory creates instances that simulate console behavior without requiring a real terminal.
 /// </summary>
-public class FakeComponentFactory : ComponentFactory<ConsoleKeyInfo>
+public class FakeComponentFactory : ComponentFactoryImpl<ConsoleKeyInfo>
 {
-    private readonly ConcurrentQueue<ConsoleKeyInfo>? _predefinedInput;
-    private readonly FakeConsoleOutput? _output;
+    private readonly FakeInput? _input;
+    private readonly IOutput? _output;
+    private readonly ISizeMonitor? _sizeMonitor;
 
     /// <summary>
-    /// Creates a new FakeComponentFactory with optional predefined input and output capture.
+    ///     Creates a new FakeComponentFactory with optional output capture.
     /// </summary>
-    /// <param name="predefinedInput">Optional queue of predefined input events to simulate.</param>
+    /// <param name="input"></param>
     /// <param name="output">Optional fake output to capture what would be written to console.</param>
-    public FakeComponentFactory (ConcurrentQueue<ConsoleKeyInfo>? predefinedInput = null, FakeConsoleOutput? output = null)
+    /// <param name="sizeMonitor"></param>
+    public FakeComponentFactory (FakeInput? input = null, IOutput? output = null, ISizeMonitor? sizeMonitor = null)
     {
-        _predefinedInput = predefinedInput;
+        _input = input;
         _output = output;
+        _sizeMonitor = sizeMonitor;
     }
 
+
     /// <inheritdoc/>
-    public override IConsoleInput<ConsoleKeyInfo> CreateInput ()
+    public override ISizeMonitor CreateSizeMonitor (IOutput consoleOutput, IOutputBuffer outputBuffer)
     {
-        return new FakeConsoleInput (_predefinedInput);
+        return _sizeMonitor ?? new SizeMonitorImpl (consoleOutput);
     }
 
-    /// <inheritdoc />
-    public override IConsoleOutput CreateOutput ()
+    /// <inheritdoc/>
+    public override IInput<ConsoleKeyInfo> CreateInput ()
     {
-        return _output ?? new FakeConsoleOutput ();
+        return _input ?? new FakeInput ();
     }
 
-    /// <inheritdoc />
-    public override IInputProcessor CreateInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer)
-    {
-        return new NetInputProcessor (inputBuffer);
-    }
+    /// <inheritdoc/>
+    public override IInputProcessor CreateInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) { return new FakeInputProcessor (inputBuffer); }
 
-    /// <inheritdoc />
-    public override IConsoleSizeMonitor CreateConsoleSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer)
+    /// <inheritdoc/>
+    public override IOutput CreateOutput ()
     {
-        outputBuffer.SetSize(consoleOutput.GetSize().Width, consoleOutput.GetSize().Height);
-        return new ConsoleSizeMonitor (consoleOutput, outputBuffer);
+        return _output ?? new FakeOutput ();
     }
 }

+ 0 - 1708
Terminal.Gui/Drivers/FakeDriver/FakeConsole.cs

@@ -1,1708 +0,0 @@
-//
-// FakeConsole.cs: A fake .NET Windows Console API implementation for unit tests.
-//
-
-namespace Terminal.Gui.Drivers;
-
-#pragma warning disable RCS1138 // Add summary to documentation comment.
-/// <summary></summary>
-public static class FakeConsole
-{
-#pragma warning restore RCS1138 // Add summary to documentation comment.
-
-    //
-    // Summary:
-    //	Gets or sets the width of the console window.
-    //
-    // Returns:
-    //	The width of the console window measured in columns.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight
-    //	property is less than or equal to 0.-or-The value of the System.Console.WindowHeight
-    //	property plus the value of the System.Console.WindowTop property is greater than
-    //	or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth
-    //	property or the value of the System.Console.WindowHeight property is greater
-    //	than the largest possible window width or height for the current screen resolution
-    //	and console font.
-    //
-    //	T:System.IO.IOException:
-    //	Error reading or writing information.
-#pragma warning disable RCS1138 // Add summary to documentation comment.
-
-    /// <summary>Specifies the initial console width.</summary>
-    public const int WIDTH = 0;
-
-    /// <summary>Specifies the initial console height.</summary>
-    public const int HEIGHT = 0;
-
-    /// <summary></summary>
-    public static int WindowWidth { get; set; } = WIDTH;
-
-    //
-    // Summary:
-    //	Gets a value that indicates whether output has been redirected from the standard
-    //	output stream.
-    //
-    // Returns:
-    //	true if output is redirected; otherwise, false.
-    /// <summary></summary>
-    public static bool IsOutputRedirected { get; }
-
-    //
-    // Summary:
-    //	Gets a value that indicates whether the error output stream has been redirected
-    //	from the standard error stream.
-    //
-    // Returns:
-    //	true if error output is redirected; otherwise, false.
-    /// <summary></summary>
-    public static bool IsErrorRedirected { get; }
-
-    //
-    // Summary:
-    //	Gets the standard input stream.
-    //
-    // Returns:
-    //	A System.IO.TextReader that represents the standard input stream.
-    /// <summary></summary>
-    public static TextReader In { get; }
-
-    //
-    // Summary:
-    //	Gets the standard output stream.
-    //
-    // Returns:
-    //	A System.IO.TextWriter that represents the standard output stream.
-    /// <summary></summary>
-    public static TextWriter Out { get; }
-
-    //
-    // Summary:
-    //	Gets the standard error output stream.
-    //
-    // Returns:
-    //	A System.IO.TextWriter that represents the standard error output stream.
-    /// <summary></summary>
-    public static TextWriter Error { get; }
-
-    //
-    // Summary:
-    //	Gets or sets the encoding the console uses to read input.
-    //
-    // Returns:
-    //	The encoding used to read console input.
-    //
-    // Exceptions:
-    //	T:System.ArgumentNullException:
-    //	The property value in a set operation is null.
-    //
-    //	T:System.IO.IOException:
-    //	An error occurred during the execution of this operation.
-    //
-    //	T:System.Security.SecurityException:
-    //	Your application does not have permission to perform this operation.
-    /// <summary></summary>
-    public static Encoding InputEncoding { get; set; }
-
-    //
-    // Summary:
-    //	Gets or sets the encoding the console uses to write output.
-    //
-    // Returns:
-    //	The encoding used to write console output.
-    //
-    // Exceptions:
-    //	T:System.ArgumentNullException:
-    //	The property value in a set operation is null.
-    //
-    //	T:System.IO.IOException:
-    //	An error occurred during the execution of this operation.
-    //
-    //	T:System.Security.SecurityException:
-    //	Your application does not have permission to perform this operation.
-    /// <summary></summary>
-    public static Encoding OutputEncoding { get; set; }
-
-    //
-    // Summary:
-    //	Gets or sets the background color of the console.
-    //
-    // Returns:
-    //	A value that specifies the background color of the console; that is, the color
-    //	that appears behind each character. The default is black.
-    //
-    // Exceptions:
-    //	T:System.ArgumentException:
-    //	The color specified in a set operation is not a valid member of System.ConsoleColor.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    private static readonly ConsoleColor _defaultBackgroundColor = ConsoleColor.Black;
-
-    /// <summary></summary>
-    public static ConsoleColor BackgroundColor { get; set; } = _defaultBackgroundColor;
-
-    //
-    // Summary:
-    //	Gets or sets the foreground color of the console.
-    //
-    // Returns:
-    //	A System.ConsoleColor that specifies the foreground color of the console; that
-    //	is, the color of each character that is displayed. The default is gray.
-    //
-    // Exceptions:
-    //	T:System.ArgumentException:
-    //	The color specified in a set operation is not a valid member of System.ConsoleColor.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    private static readonly ConsoleColor _defaultForegroundColor = ConsoleColor.Gray;
-
-    /// <summary></summary>
-    public static ConsoleColor ForegroundColor { get; set; } = _defaultForegroundColor;
-
-    //
-    // Summary:
-    //	Gets or sets the height of the buffer area.
-    //
-    // Returns:
-    //	The current height, in rows, of the buffer area.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	The value in a set operation is less than or equal to zero.-or- The value in
-    //	a set operation is greater than or equal to System.Int16.MaxValue.-or- The value
-    //	in a set operation is less than System.Console.WindowTop + System.Console.WindowHeight.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    public static int BufferHeight { get; set; } = HEIGHT;
-
-    //
-    // Summary:
-    //	Gets or sets the width of the buffer area.
-    //
-    // Returns:
-    //	The current width, in columns, of the buffer area.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	The value in a set operation is less than or equal to zero.-or- The value in
-    //	a set operation is greater than or equal to System.Int16.MaxValue.-or- The value
-    //	in a set operation is less than System.Console.WindowLeft + System.Console.WindowWidth.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    public static int BufferWidth { get; set; } = WIDTH;
-
-    //
-    // Summary:
-    //	Gets or sets the height of the console window area.
-    //
-    // Returns:
-    //	The height of the console window measured in rows.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight
-    //	property is less than or equal to 0.-or-The value of the System.Console.WindowHeight
-    //	property plus the value of the System.Console.WindowTop property is greater than
-    //	or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth
-    //	property or the value of the System.Console.WindowHeight property is greater
-    //	than the largest possible window width or height for the current screen resolution
-    //	and console font.
-    //
-    //	T:System.IO.IOException:
-    //	Error reading or writing information.
-    /// <summary></summary>
-    public static int WindowHeight { get; set; } = HEIGHT;
-
-    //
-    // Summary:
-    //	Gets or sets a value indicating whether the combination of the System.ConsoleModifiers.Control
-    //	modifier key and System.ConsoleKey.C console key (Ctrl+C) is treated as ordinary
-    //	input or as an interruption that is handled by the operating system.
-    //
-    // Returns:
-    //	true if Ctrl+C is treated as ordinary input; otherwise, false.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	Unable to get or set the input mode of the console input buffer.
-    /// <summary></summary>
-    public static bool TreatControlCAsInput { get; set; }
-
-    //
-    // Summary:
-    //	Gets the largest possible number of console window columns, based on the current
-    //	font and screen resolution.
-    //
-    // Returns:
-    //	The width of the largest possible console window measured in columns.
-    /// <summary></summary>
-    public static int LargestWindowWidth { get; }
-
-    //
-    // Summary:
-    //	Gets the largest possible number of console window rows, based on the current
-    //	font and screen resolution.
-    //
-    // Returns:
-    //	The height of the largest possible console window measured in rows.
-    /// <summary></summary>
-    public static int LargestWindowHeight { get; }
-
-    //
-    // Summary:
-    //	Gets or sets the leftmost position of the console window area relative to the
-    //	screen buffer.
-    //
-    // Returns:
-    //	The leftmost console window position measured in columns.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	In a set operation, the value to be assigned is less than zero.-or-As a result
-    //	of the assignment, System.Console.WindowLeft plus System.Console.WindowWidth
-    //	would exceed System.Console.BufferWidth.
-    //
-    //	T:System.IO.IOException:
-    //	Error reading or writing information.
-    /// <summary></summary>
-    public static int WindowLeft { get; set; }
-
-    //
-    // Summary:
-    //	Gets or sets the top position of the console window area relative to the screen
-    //	buffer.
-    //
-    // Returns:
-    //	The uppermost console window position measured in rows.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	In a set operation, the value to be assigned is less than zero.-or-As a result
-    //	of the assignment, System.Console.WindowTop plus System.Console.WindowHeight
-    //	would exceed System.Console.BufferHeight.
-    //
-    //	T:System.IO.IOException:
-    //	Error reading or writing information.
-    /// <summary></summary>
-    public static int WindowTop { get; set; }
-
-    //
-    // Summary:
-    //	Gets or sets the column position of the cursor within the buffer area.
-    //
-    // Returns:
-    //	The current position, in columns, of the cursor.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	The value in a set operation is less than zero.-or- The value in a set operation
-    //	is greater than or equal to System.Console.BufferWidth.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    public static int CursorLeft { get; set; }
-
-    //
-    // Summary:
-    //	Gets or sets the row position of the cursor within the buffer area.
-    //
-    // Returns:
-    //	The current position, in rows, of the cursor.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	The value in a set operation is less than zero.-or- The value in a set operation
-    //	is greater than or equal to System.Console.BufferHeight.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    public static int CursorTop { get; set; }
-
-    //
-    // Summary:
-    //	Gets or sets the height of the cursor within a character cell.
-    //
-    // Returns:
-    //	The size of the cursor expressed as a percentage of the height of a character
-    //	cell. The property value ranges from 1 to 100.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	The value specified in a set operation is less than 1 or greater than 100.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    public static int CursorSize { get; set; }
-
-    //
-    // Summary:
-    //	Gets or sets a value indicating whether the cursor is visible.
-    //
-    // Returns:
-    //	true if the cursor is visible; otherwise, false.
-    //
-    // Exceptions:
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    public static bool CursorVisible { get; set; }
-
-    //
-    // Summary:
-    //	Gets or sets the title to display in the console title bar.
-    //
-    // Returns:
-    //	The string to be displayed in the title bar of the console. The maximum length
-    //	of the title string is 24500 characters.
-    //
-    // Exceptions:
-    //	T:System.InvalidOperationException:
-    //	In a get operation, the retrieved title is longer than 24500 characters.
-    //
-    //	T:System.ArgumentOutOfRangeException:
-    //	In a set operation, the specified title is longer than 24500 characters.
-    //
-    //	T:System.ArgumentNullException:
-    //	In a set operation, the specified title is null.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    public static string Title { get; set; }
-
-    //
-    // Summary:
-    //	Gets a value indicating whether a key press is available in the input stream.
-    //
-    // Returns:
-    //	true if a key press is available; otherwise, false.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //
-    //	T:System.InvalidOperationException:
-    //	Standard input is redirected to a file instead of the keyboard.
-    /// <summary></summary>
-    public static bool KeyAvailable { get; }
-
-    //
-    // Summary:
-    //	Gets a value that indicates whether input has been redirected from the standard
-    //	input stream.
-    //
-    // Returns:
-    //	true if input is redirected; otherwise, false.
-    /// <summary></summary>
-    public static bool IsInputRedirected { get; }
-
-    //
-    // Summary:
-    //	Plays the sound of a beep through the console speaker.
-    //
-    // Exceptions:
-    //	T:System.Security.HostProtectionException:
-    //	This method was executed on a server, such as SQL Server, that does not permit
-    //	access to a user interface.
-    /// <summary></summary>
-    public static void Beep () { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Plays the sound of a beep of a specified frequency and duration through the console
-    //	speaker.
-    //
-    // Parameters:
-    //	frequency:
-    //	The frequency of the beep, ranging from 37 to 32767 hertz.
-    //
-    //	duration:
-    //	The duration of the beep measured in milliseconds.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	frequency is less than 37 or more than 32767 hertz.-or- duration is less than
-    //	or equal to zero.
-    //
-    //	T:System.Security.HostProtectionException:
-    //	This method was executed on a server, such as SQL Server, that does not permit
-    //	access to the console.
-    /// <summary></summary>
-    public static void Beep (int frequency, int duration) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Clears the console buffer and corresponding console window of display information.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    private static char [,] _buffer = new char [WindowWidth, WindowHeight];
-
-    /// <summary></summary>
-    public static void Clear ()
-    {
-        _buffer = new char [BufferWidth, BufferHeight];
-        SetCursorPosition (0, 0);
-    }
-
-    //
-    // Summary:
-    //	Copies a specified source area of the screen buffer to a specified destination
-    //	area.
-    //
-    // Parameters:
-    //	sourceLeft:
-    //	The leftmost column of the source area.
-    //
-    //	sourceTop:
-    //	The topmost row of the source area.
-    //
-    //	sourceWidth:
-    //	The number of columns in the source area.
-    //
-    //	sourceHeight:
-    //	The number of rows in the source area.
-    //
-    //	targetLeft:
-    //	The leftmost column of the destination area.
-    //
-    //	targetTop:
-    //	The topmost row of the destination area.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	One or more of the parameters is less than zero.-or- sourceLeft or targetLeft
-    //	is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop
-    //	is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight
-    //	is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth
-    //	is greater than or equal to System.Console.BufferWidth.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    public static void MoveBufferArea (
-        int sourceLeft,
-        int sourceTop,
-        int sourceWidth,
-        int sourceHeight,
-        int targetLeft,
-        int targetTop
-    )
-    {
-        throw new NotImplementedException ();
-    }
-
-    //
-    // Summary:
-    //	Copies a specified source area of the screen buffer to a specified destination
-    //	area.
-    //
-    // Parameters:
-    //	sourceLeft:
-    //	The leftmost column of the source area.
-    //
-    //	sourceTop:
-    //	The topmost row of the source area.
-    //
-    //	sourceWidth:
-    //	The number of columns in the source area.
-    //
-    //	sourceHeight:
-    //	The number of rows in the source area.
-    //
-    //	targetLeft:
-    //	The leftmost column of the destination area.
-    //
-    //	targetTop:
-    //	The topmost row of the destination area.
-    //
-    //	sourceChar:
-    //	The character used to fill the source area.
-    //
-    //	sourceForeColor:
-    //	The foreground color used to fill the source area.
-    //
-    //	sourceBackColor:
-    //	The background color used to fill the source area.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	One or more of the parameters is less than zero.-or- sourceLeft or targetLeft
-    //	is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop
-    //	is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight
-    //	is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth
-    //	is greater than or equal to System.Console.BufferWidth.
-    //
-    //	T:System.ArgumentException:
-    //	One or both of the color parameters is not a member of the System.ConsoleColor
-    //	enumeration.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //[SecuritySafeCritical]
-    /// <summary></summary>
-    public static void MoveBufferArea (
-        int sourceLeft,
-        int sourceTop,
-        int sourceWidth,
-        int sourceHeight,
-        int targetLeft,
-        int targetTop,
-        char sourceChar,
-        ConsoleColor sourceForeColor,
-        ConsoleColor sourceBackColor
-    )
-    {
-        throw new NotImplementedException ();
-    }
-
-    //
-    // Summary:
-    //	Acquires the standard error stream.
-    //
-    // Returns:
-    //	The standard error stream.
-    /// <summary></summary>
-    public static Stream OpenStandardError () { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Acquires the standard error stream, which is set to a specified buffer size.
-    //
-    // Parameters:
-    //	bufferSize:
-    //	The internal stream buffer size.
-    //
-    // Returns:
-    //	The standard error stream.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	bufferSize is less than or equal to zero.
-    /// <summary></summary>
-    public static Stream OpenStandardError (int bufferSize) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Acquires the standard input stream, which is set to a specified buffer size.
-    //
-    // Parameters:
-    //	bufferSize:
-    //	The internal stream buffer size.
-    //
-    // Returns:
-    //	The standard input stream.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	bufferSize is less than or equal to zero.
-    /// <summary></summary>
-    public static Stream OpenStandardInput (int bufferSize) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Acquires the standard input stream.
-    //
-    // Returns:
-    //	The standard input stream.
-    /// <summary></summary>
-    public static Stream OpenStandardInput () { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Acquires the standard output stream, which is set to a specified buffer size.
-    //
-    // Parameters:
-    //	bufferSize:
-    //	The internal stream buffer size.
-    //
-    // Returns:
-    //	The standard output stream.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	bufferSize is less than or equal to zero.
-    /// <summary></summary>
-    public static Stream OpenStandardOutput (int bufferSize) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Acquires the standard output stream.
-    //
-    // Returns:
-    //	The standard output stream.
-    /// <summary></summary>
-    public static Stream OpenStandardOutput () { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Reads the next character from the standard input stream.
-    //
-    // Returns:
-    //	The next character from the input stream, or negative one (-1) if there are currently
-    //	no more characters to be read.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    public static int Read () { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Obtains the next character or function key pressed by the user. The pressed key
-    //	is optionally displayed in the console window.
-    //
-    // Parameters:
-    //	intercept:
-    //	Determines whether to display the pressed key in the console window. true to
-    //	not display the pressed key; otherwise, false.
-    //
-    // Returns:
-    //	An object that describes the System.ConsoleKey constant and Unicode character,
-    //	if any, that correspond to the pressed console key. The System.ConsoleKeyInfo
-    //	object also describes, in a bitwise combination of System.ConsoleModifiers values,
-    //	whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously
-    //	with the console key.
-    //
-    // Exceptions:
-    //	T:System.InvalidOperationException:
-    //	The System.Console.In property is redirected from some stream other than the
-    //	console.
-    //[SecuritySafeCritical]
-
-    /// <summary>A stack of keypresses to return when ReadKey is called.</summary>
-    public static Stack<ConsoleKeyInfo> MockKeyPresses = new ();
-
-    /// <summary>Helper to push a <see cref="KeyCode"/> onto <see cref="MockKeyPresses"/>.</summary>
-    /// <param name="key"></param>
-    public static void PushMockKeyPress (KeyCode key)
-    {
-        MockKeyPresses.Push (
-                             new ConsoleKeyInfo (
-                                                 (char)(key
-                                                        & ~KeyCode.CtrlMask
-                                                        & ~KeyCode.ShiftMask
-                                                        & ~KeyCode.AltMask),
-                                                 ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key).Key,
-                                                 key.HasFlag (KeyCode.ShiftMask),
-                                                 key.HasFlag (KeyCode.AltMask),
-                                                 key.HasFlag (KeyCode.CtrlMask)
-                                                )
-                            );
-    }
-
-    //
-    // Summary:
-    //	Obtains the next character or function key pressed by the user. The pressed key
-    //	is displayed in the console window.
-    //
-    // Returns:
-    //	An object that describes the System.ConsoleKey constant and Unicode character,
-    //	if any, that correspond to the pressed console key. The System.ConsoleKeyInfo
-    //	object also describes, in a bitwise combination of System.ConsoleModifiers values,
-    //	whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously
-    //	with the console key.
-    //
-    // Exceptions:
-    //	T:System.InvalidOperationException:
-    //	The System.Console.In property is redirected from some stream other than the
-    //	console.
-    /// <summary></summary>
-    public static ConsoleKeyInfo ReadKey () { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Reads the next line of characters from the standard input stream.
-    //
-    // Returns:
-    //	The next line of characters from the input stream, or null if no more lines are
-    //	available.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //
-    //	T:System.OutOfMemoryException:
-    //	There is insufficient memory to allocate a buffer for the returned string.
-    //
-    //	T:System.ArgumentOutOfRangeException:
-    //	The number of characters in the next line of characters is greater than System.Int32.MaxValue.
-    /// <summary></summary>
-    public static string ReadLine () { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Sets the foreground and background console colors to their defaults.
-    //
-    // Exceptions:
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //[SecuritySafeCritical]
-    /// <summary></summary>
-    public static void ResetColor ()
-    {
-        BackgroundColor = _defaultBackgroundColor;
-        ForegroundColor = _defaultForegroundColor;
-    }
-
-    //
-    // Summary:
-    //	Sets the height and width of the screen buffer area to the specified values.
-    //
-    // Parameters:
-    //	width:
-    //	The width of the buffer area measured in columns.
-    //
-    //	height:
-    //	The height of the buffer area measured in rows.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	height or width is less than or equal to zero.-or- height or width is greater
-    //	than or equal to System.Int16.MaxValue.-or- width is less than System.Console.WindowLeft
-    //	+ System.Console.WindowWidth.-or- height is less than System.Console.WindowTop
-    //	+ System.Console.WindowHeight.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //[SecuritySafeCritical]
-    /// <summary></summary>
-    public static void SetBufferSize (int width, int height)
-    {
-        BufferWidth = width;
-        BufferHeight = height;
-        _buffer = new char [BufferWidth, BufferHeight];
-    }
-
-    //
-    // Summary:
-    //	Sets the position of the cursor.
-    //
-    // Parameters:
-    //	left:
-    //	The column position of the cursor. Columns are numbered from left to right starting
-    //	at 0.
-    //
-    //	top:
-    //	The row position of the cursor. Rows are numbered from top to bottom starting
-    //	at 0.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	left or top is less than zero.-or- left is greater than or equal to System.Console.BufferWidth.-or-
-    //	top is greater than or equal to System.Console.BufferHeight.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //[SecuritySafeCritical]
-    /// <summary></summary>
-    public static void SetCursorPosition (int left, int top)
-    {
-        CursorLeft = left;
-        CursorTop = top;
-        WindowLeft = Math.Max (Math.Min (left, BufferWidth - WindowWidth), 0);
-        WindowTop = Math.Max (Math.Min (top, BufferHeight - WindowHeight), 0);
-    }
-
-    //
-    // Summary:
-    //	Sets the System.Console.Error property to the specified System.IO.TextWriter
-    //	object.
-    //
-    // Parameters:
-    //	newError:
-    //	A stream that is the new standard error output.
-    //
-    // Exceptions:
-    //	T:System.ArgumentNullException:
-    //	newError is null.
-    //
-    //	T:System.Security.SecurityException:
-    //	The caller does not have the required permission.
-    //[SecuritySafeCritical]
-    /// <summary></summary>
-    public static void SetError (TextWriter newError) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Sets the System.Console.In property to the specified System.IO.TextReader object.
-    //
-    // Parameters:
-    //	newIn:
-    //	A stream that is the new standard input.
-    //
-    // Exceptions:
-    //	T:System.ArgumentNullException:
-    //	newIn is null.
-    //
-    //	T:System.Security.SecurityException:
-    //	The caller does not have the required permission.
-    //[SecuritySafeCritical]
-    /// <summary></summary>
-    public static void SetIn (TextReader newIn) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Sets the System.Console.Out property to the specified System.IO.TextWriter object.
-    //
-    // Parameters:
-    //	newOut:
-    //	A stream that is the new standard output.
-    //
-    // Exceptions:
-    //	T:System.ArgumentNullException:
-    //	newOut is null.
-    //
-    //	T:System.Security.SecurityException:
-    //	The caller does not have the required permission.
-    //[SecuritySafeCritical]
-    /// <summary></summary>
-    /// <param name="newOut"></param>
-    public static void SetOut (TextWriter newOut) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Sets the position of the console window relative to the screen buffer.
-    //
-    // Parameters:
-    //	left:
-    //	The column position of the upper left corner of the console window.
-    //
-    //	top:
-    //	The row position of the upper left corner of the console window.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	left or top is less than zero.-or- left + System.Console.WindowWidth is greater
-    //	than System.Console.BufferWidth.-or- top + System.Console.WindowHeight is greater
-    //	than System.Console.BufferHeight.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //[SecuritySafeCritical]
-    /// <summary></summary>
-    /// <param name="left"></param>
-    /// <param name="top"></param>
-    public static void SetWindowPosition (int left, int top)
-    {
-        WindowLeft = left;
-        WindowTop = top;
-    }
-
-    //
-    // Summary:
-    //	Sets the height and width of the console window to the specified values.
-    //
-    // Parameters:
-    //	width:
-    //	The width of the console window measured in columns.
-    //
-    //	height:
-    //	The height of the console window measured in rows.
-    //
-    // Exceptions:
-    //	T:System.ArgumentOutOfRangeException:
-    //	width or height is less than or equal to zero.-or- width plus System.Console.WindowLeft
-    //	or height plus System.Console.WindowTop is greater than or equal to System.Int16.MaxValue.
-    //	-or- width or height is greater than the largest possible window width or height
-    //	for the current screen resolution and console font.
-    //
-    //	T:System.Security.SecurityException:
-    //	The user does not have permission to perform this action.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //[SecuritySafeCritical]
-    /// <summary></summary>
-    /// <param name="width"></param>
-    /// <param name="height"></param>
-    public static void SetConsoleSize (int width, int height)
-    {
-        WindowWidth = width;
-        WindowHeight = height;
-    }
-
-    //
-    // Summary:
-    //	Writes the specified string value to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (string value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified object to the standard output
-    //	stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write, or null.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (object value)
-    {
-        if (value is Rune rune)
-        {
-            Write ((char)rune.Value);
-        }
-        else
-        {
-            throw new NotImplementedException ();
-        }
-    }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified 64-bit unsigned integer value
-    //	to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //[CLSCompliant (false)]
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (ulong value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified 64-bit signed integer value to
-    //	the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (long value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified objects to the standard output
-    //	stream using the specified format information.
-    //
-    // Parameters:
-    //	format:
-    //	A composite format string (see Remarks).
-    //
-    //	arg0:
-    //	The first object to write using format.
-    //
-    //	arg1:
-    //	The second object to write using format.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //
-    //	T:System.ArgumentNullException:
-    //	format is null.
-    //
-    //	T:System.FormatException:
-    //	The format specification in format is invalid.
-    /// <summary></summary>
-    /// <param name="format"></param>
-    /// <param name="arg0"></param>
-    /// <param name="arg1"></param>
-    public static void Write (string format, object arg0, object arg1) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified 32-bit signed integer value to
-    //	the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (int value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified object to the standard output
-    //	stream using the specified format information.
-    //
-    // Parameters:
-    //	format:
-    //	A composite format string (see Remarks).
-    //
-    //	arg0:
-    //	An object to write using format.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //
-    //	T:System.ArgumentNullException:
-    //	format is null.
-    //
-    //	T:System.FormatException:
-    //	The format specification in format is invalid.
-    /// <summary></summary>
-    /// <param name="format"></param>
-    /// <param name="arg0"></param>
-    public static void Write (string format, object arg0) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified 32-bit unsigned integer value
-    //	to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //[CLSCompliant (false)]
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (uint value) { throw new NotImplementedException (); }
-
-    //[CLSCompliant (false)]
-    /// <summary></summary>
-    /// <param name="format"></param>
-    /// <param name="arg0"></param>
-    /// <param name="arg1"></param>
-    /// <param name="arg2"></param>
-    /// <param name="arg3"></param>
-    public static void Write (string format, object arg0, object arg1, object arg2, object arg3) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified array of objects to the standard
-    //	output stream using the specified format information.
-    //
-    // Parameters:
-    //	format:
-    //	A composite format string (see Remarks).
-    //
-    //	arg:
-    //	An array of objects to write using format.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //
-    //	T:System.ArgumentNullException:
-    //	format or arg is null.
-    //
-    //	T:System.FormatException:
-    //	The format specification in format is invalid.
-    /// <summary></summary>
-    /// <param name="format"></param>
-    /// <param name="arg"></param>
-    public static void Write (string format, params object [] arg) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified Boolean value to the standard
-    //	output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (bool value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the specified Unicode character value to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (char value) { _buffer [CursorLeft, CursorTop] = value; }
-
-    //
-    // Summary:
-    //	Writes the specified array of Unicode characters to the standard output stream.
-    //
-    // Parameters:
-    //	buffer:
-    //	A Unicode character array.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="buffer"></param>
-    public static void Write (char [] buffer)
-    {
-        _buffer [CursorLeft, CursorTop] = (char)0;
-
-        foreach (char ch in buffer)
-        {
-            _buffer [CursorLeft, CursorTop] += ch;
-        }
-    }
-
-    //
-    // Summary:
-    //	Writes the specified subarray of Unicode characters to the standard output stream.
-    //
-    // Parameters:
-    //	buffer:
-    //	An array of Unicode characters.
-    //
-    //	index:
-    //	The starting position in buffer.
-    //
-    //	count:
-    //	The number of characters to write.
-    //
-    // Exceptions:
-    //	T:System.ArgumentNullException:
-    //	buffer is null.
-    //
-    //	T:System.ArgumentOutOfRangeException:
-    //	index or count is less than zero.
-    //
-    //	T:System.ArgumentException:
-    //	index plus count specify a position that is not within buffer.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="buffer"></param>
-    /// <param name="index"></param>
-    /// <param name="count"></param>
-    public static void Write (char [] buffer, int index, int count) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified objects to the standard output
-    //	stream using the specified format information.
-    //
-    // Parameters:
-    //	format:
-    //	A composite format string (see Remarks).
-    //
-    //	arg0:
-    //	The first object to write using format.
-    //
-    //	arg1:
-    //	The second object to write using format.
-    //
-    //	arg2:
-    //	The third object to write using format.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //
-    //	T:System.ArgumentNullException:
-    //	format is null.
-    //
-    //	T:System.FormatException:
-    //	The format specification in format is invalid.
-    /// <summary></summary>
-    /// <param name="format"></param>
-    /// <param name="arg0"></param>
-    /// <param name="arg1"></param>
-    /// <param name="arg2"></param>
-    public static void Write (string format, object arg0, object arg1, object arg2) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified System.Decimal value to the standard
-    //	output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (decimal value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified single-precision floating-point
-    //	value to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (float value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified double-precision floating-point
-    //	value to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void Write (double value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the current line terminator to the standard output stream.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    public static void WriteLine () { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified single-precision floating-point
-    //	value, followed by the current line terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (float value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified 32-bit signed integer value,
-    //	followed by the current line terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (int value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified 32-bit unsigned integer value,
-    //	followed by the current line terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //[CLSCompliant (false)]
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (uint value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified 64-bit signed integer value,
-    //	followed by the current line terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (long value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified 64-bit unsigned integer value,
-    //	followed by the current line terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //[CLSCompliant (false)]
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (ulong value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified object, followed by the current
-    //	line terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (object value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the specified string value, followed by the current line terminator, to
-    //	the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (string value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified object, followed by the current
-    //	line terminator, to the standard output stream using the specified format information.
-    //
-    // Parameters:
-    //	format:
-    //	A composite format string (see Remarks).
-    //
-    //	arg0:
-    //	An object to write using format.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //
-    //	T:System.ArgumentNullException:
-    //	format is null.
-    //
-    //	T:System.FormatException:
-    //	The format specification in format is invalid.
-    /// <summary></summary>
-    /// <param name="format"></param>
-    /// <param name="arg0"></param>
-    public static void WriteLine (string format, object arg0) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified objects, followed by the current
-    //	line terminator, to the standard output stream using the specified format information.
-    //
-    // Parameters:
-    //	format:
-    //	A composite format string (see Remarks).
-    //
-    //	arg0:
-    //	The first object to write using format.
-    //
-    //	arg1:
-    //	The second object to write using format.
-    //
-    //	arg2:
-    //	The third object to write using format.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //
-    //	T:System.ArgumentNullException:
-    //	format is null.
-    //
-    //	T:System.FormatException:
-    //	The format specification in format is invalid.
-    /// <summary></summary>
-    /// <param name="format"></param>
-    /// <param name="arg0"></param>
-    /// <param name="arg1"></param>
-    /// <param name="arg2"></param>
-    public static void WriteLine (string format, object arg0, object arg1, object arg2) { throw new NotImplementedException (); }
-
-    //[CLSCompliant (false)]
-    /// <summary></summary>
-    /// <param name="format"></param>
-    /// <param name="arg0"></param>
-    /// <param name="arg1"></param>
-    /// <param name="arg2"></param>
-    /// <param name="arg3"></param>
-    public static void WriteLine (string format, object arg0, object arg1, object arg2, object arg3) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified array of objects, followed by
-    //	the current line terminator, to the standard output stream using the specified
-    //	format information.
-    //
-    // Parameters:
-    //	format:
-    //	A composite format string (see Remarks).
-    //
-    //	arg:
-    //	An array of objects to write using format.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //
-    //	T:System.ArgumentNullException:
-    //	format or arg is null.
-    //
-    //	T:System.FormatException:
-    //	The format specification in format is invalid.
-    /// <summary></summary>
-    /// <param name="format"></param>
-    /// <param name="arg"></param>
-    public static void WriteLine (string format, params object [] arg) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the specified subarray of Unicode characters, followed by the current
-    //	line terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	buffer:
-    //	An array of Unicode characters.
-    //
-    //	index:
-    //	The starting position in buffer.
-    //
-    //	count:
-    //	The number of characters to write.
-    //
-    // Exceptions:
-    //	T:System.ArgumentNullException:
-    //	buffer is null.
-    //
-    //	T:System.ArgumentOutOfRangeException:
-    //	index or count is less than zero.
-    //
-    //	T:System.ArgumentException:
-    //	index plus count specify a position that is not within buffer.
-    //
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="buffer"></param>
-    /// <param name="index"></param>
-    /// <param name="count"></param>
-    public static void WriteLine (char [] buffer, int index, int count) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified System.Decimal value, followed
-    //	by the current line terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (decimal value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the specified array of Unicode characters, followed by the current line
-    //	terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	buffer:
-    //	A Unicode character array.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="buffer"></param>
-    public static void WriteLine (char [] buffer) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the specified Unicode character, followed by the current line terminator,
-    //	value to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (char value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified Boolean value, followed by the
-    //	current line terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (bool value) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified objects, followed by the current
-    //	line terminator, to the standard output stream using the specified format information.
-    //
-    // Parameters:
-    //	format:
-    //	A composite format string (see Remarks).
-    //
-    //	arg0:
-    //	The first object to write using format.
-    //
-    //	arg1:
-    //	The second object to write using format.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    //
-    //	T:System.ArgumentNullException:
-    //	format is null.
-    //
-    //	T:System.FormatException:
-    //	The format specification in format is invalid.
-    /// <summary></summary>
-    /// <param name="format"></param>
-    /// <param name="arg0"></param>
-    /// <param name="arg1"></param>
-    public static void WriteLine (string format, object arg0, object arg1) { throw new NotImplementedException (); }
-
-    //
-    // Summary:
-    //	Writes the text representation of the specified double-precision floating-point
-    //	value, followed by the current line terminator, to the standard output stream.
-    //
-    // Parameters:
-    //	value:
-    //	The value to write.
-    //
-    // Exceptions:
-    //	T:System.IO.IOException:
-    //	An I/O error occurred.
-    /// <summary></summary>
-    /// <param name="value"></param>
-    public static void WriteLine (double value) { throw new NotImplementedException (); }
-}

+ 0 - 42
Terminal.Gui/Drivers/FakeDriver/FakeConsoleInput.cs

@@ -1,42 +0,0 @@
-#nullable enable
-using System.Collections.Concurrent;
-
-namespace Terminal.Gui.Drivers;
-
-/// <summary>
-/// Fake console input for testing that can return predefined input or wait indefinitely.
-/// </summary>
-public class FakeConsoleInput : ConsoleInput<ConsoleKeyInfo>
-{
-    private readonly ConcurrentQueue<ConsoleKeyInfo>? _predefinedInput;
-
-    /// <summary>
-    /// Creates a new FakeConsoleInput with optional predefined input.
-    /// </summary>
-    /// <param name="predefinedInput">Optional queue of predefined input to return.</param>
-    public FakeConsoleInput (ConcurrentQueue<ConsoleKeyInfo>? predefinedInput = null)
-    {
-        _predefinedInput = predefinedInput;
-    }
-
-    /// <inheritdoc/>
-    protected override bool Peek ()
-    {
-        if (_predefinedInput != null && !_predefinedInput.IsEmpty)
-        {
-            return true;
-        }
-
-        // No input available
-        return false;
-    }
-
-    /// <inheritdoc/>
-    protected override IEnumerable<ConsoleKeyInfo> Read ()
-    {
-        if (_predefinedInput != null && _predefinedInput.TryDequeue (out ConsoleKeyInfo key))
-        {
-            yield return key;
-        }
-    }
-}

+ 0 - 379
Terminal.Gui/Drivers/FakeDriver/FakeDriver.cs

@@ -1,379 +0,0 @@
-#nullable enable
-
-//
-// FakeDriver.cs: A fake IConsoleDriver for unit tests. 
-//
-
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace Terminal.Gui.Drivers;
-
-/// <summary>Implements a mock IConsoleDriver for unit testing</summary>
-public class FakeDriver : ConsoleDriver
-{
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-
-    public class Behaviors
-    {
-        public Behaviors (
-            bool useFakeClipboard = false,
-            bool fakeClipboardAlwaysThrowsNotSupportedException = false,
-            bool fakeClipboardIsSupportedAlwaysTrue = false
-        )
-        {
-            UseFakeClipboard = useFakeClipboard;
-            FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
-            FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
-
-            // double check usage is correct
-            Debug.Assert (!useFakeClipboard && !fakeClipboardAlwaysThrowsNotSupportedException);
-            Debug.Assert (!useFakeClipboard && !fakeClipboardIsSupportedAlwaysTrue);
-        }
-
-        public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; }
-        public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; }
-        public bool UseFakeClipboard { get; internal set; }
-    }
-
-    public static Behaviors FakeBehaviors { get; } = new ();
-    public override bool SupportsTrueColor => false;
-
-    /// <inheritdoc/>
-    public override void WriteRaw (string ansi) { }
-
-    public FakeDriver ()
-    {
-        // FakeDriver implies UnitTests
-        RunningUnitTests = true;
-
-        //base.Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
-        //base.Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
-
-        if (FakeBehaviors.UseFakeClipboard)
-        {
-            Clipboard = new FakeClipboard (
-                                           FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException,
-                                           FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse
-                                          );
-        }
-        else
-        {
-            if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
-            {
-                Clipboard = new WindowsClipboard ();
-            }
-            else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
-            {
-                Clipboard = new MacOSXClipboard ();
-            }
-            else
-            {
-                if (PlatformDetection.IsWSLPlatform ())
-                {
-                    Clipboard = new WSLClipboard ();
-                }
-                else
-                {
-                    Clipboard = new UnixClipboard ();
-                }
-            }
-        }
-    }
-
-    public override void End ()
-    {
-        FakeConsole.ResetColor ();
-        FakeConsole.Clear ();
-    }
-
-    public override void Init ()
-    {
-        FakeConsole.MockKeyPresses.Clear ();
-
-        //Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
-        //Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
-        FakeConsole.Clear ();
-
-        SetScreenSize (80,25);
-        ResizeScreen ();
-        ClearContents ();
-        CurrentAttribute = new (Color.White, Color.Black);
-    }
-
-    public override bool UpdateScreen ()
-    {
-        var updated = false;
-
-        int savedRow = FakeConsole.CursorTop;
-        int savedCol = FakeConsole.CursorLeft;
-        bool savedCursorVisible = FakeConsole.CursorVisible;
-
-        var top = 0;
-        var left = 0;
-        int rows = Rows;
-        int cols = Cols;
-        var output = new StringBuilder ();
-        var redrawAttr = new Attribute ();
-        int lastCol = -1;
-
-        for (int row = top; row < rows; row++)
-        {
-            if (!_dirtyLines! [row])
-            {
-                continue;
-            }
-
-            updated = true;
-
-            FakeConsole.CursorTop = row;
-            FakeConsole.CursorLeft = 0;
-
-            _dirtyLines [row] = false;
-            output.Clear ();
-
-            for (int col = left; col < cols; col++)
-            {
-                lastCol = -1;
-                var outputWidth = 0;
-
-                for (; col < cols; col++)
-                {
-                    if (!Contents! [row, col].IsDirty)
-                    {
-                        if (output.Length > 0)
-                        {
-                            WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                        }
-                        else if (lastCol == -1)
-                        {
-                            lastCol = col;
-                        }
-
-                        if (lastCol + 1 < cols)
-                        {
-                            lastCol++;
-                        }
-
-                        continue;
-                    }
-
-                    if (lastCol == -1)
-                    {
-                        lastCol = col;
-                    }
-
-                    Attribute attr = Contents [row, col].Attribute!.Value;
-
-                    // Performance: Only send the escape sequence if the attribute has changed.
-                    if (attr != redrawAttr)
-                    {
-                        redrawAttr = attr;
-                        FakeConsole.ForegroundColor = (ConsoleColor)attr.Foreground.GetClosestNamedColor16 ();
-                        FakeConsole.BackgroundColor = (ConsoleColor)attr.Background.GetClosestNamedColor16 ();
-                    }
-
-                    outputWidth++;
-                    Rune rune = Contents [row, col].Rune;
-                    output.Append (rune.ToString ());
-
-                    if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
-                    {
-                        WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                        FakeConsole.CursorLeft--;
-                    }
-
-                    Contents [row, col].IsDirty = false;
-                }
-            }
-
-            if (output.Length > 0)
-            {
-                FakeConsole.CursorTop = row;
-                FakeConsole.CursorLeft = lastCol;
-
-                foreach (char c in output.ToString ())
-                {
-                    FakeConsole.Write (c);
-                }
-            }
-        }
-
-        FakeConsole.CursorTop = 0;
-        FakeConsole.CursorLeft = 0;
-
-        //SetCursorVisibility (savedVisibility);
-
-        void WriteToConsole (StringBuilder outputSb, ref int lastColumn, int row, ref int outputWidth)
-        {
-            FakeConsole.CursorTop = row;
-            FakeConsole.CursorLeft = lastColumn;
-
-            foreach (char c in outputSb.ToString ())
-            {
-                FakeConsole.Write (c);
-            }
-
-            outputSb.Clear ();
-            lastColumn += outputWidth;
-            outputWidth = 0;
-        }
-
-        FakeConsole.CursorTop = savedRow;
-        FakeConsole.CursorLeft = savedCol;
-        FakeConsole.CursorVisible = savedCursorVisible;
-
-        return updated;
-    }
-
-    private CursorVisibility _savedCursorVisibility;
-
-    /// <inheritdoc/>
-    public override bool GetCursorVisibility (out CursorVisibility visibility)
-    {
-        visibility = FakeConsole.CursorVisible
-                         ? CursorVisibility.Default
-                         : CursorVisibility.Invisible;
-
-        return FakeConsole.CursorVisible;
-    }
-
-    /// <inheritdoc/>
-    public override bool SetCursorVisibility (CursorVisibility visibility)
-    {
-        _savedCursorVisibility = visibility;
-
-        return FakeConsole.CursorVisible = visibility == CursorVisibility.Default;
-    }
-
-    private bool EnsureCursorVisibility ()
-    {
-        if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
-        {
-            GetCursorVisibility (out CursorVisibility cursorVisibility);
-            _savedCursorVisibility = cursorVisibility;
-            SetCursorVisibility (CursorVisibility.Invisible);
-
-            return false;
-        }
-
-        SetCursorVisibility (_savedCursorVisibility);
-
-        return FakeConsole.CursorVisible;
-    }
-
-    private readonly AnsiResponseParser _parser = new ();
-
-    /// <inheritdoc/>
-    internal override IAnsiResponseParser GetParser () { return _parser; }
-
-    /// <summary>
-    ///     Sets the screen size for testing purposes. Only available in FakeDriver.
-    ///     This method updates the driver's dimensions and triggers the ScreenChanged event.
-    /// </summary>
-    /// <param name="width">The new width in columns.</param>
-    /// <param name="height">The new height in rows.</param>
-    public override void SetScreenSize (int width, int height) { SetBufferSize (width, height); }
-
-    public void SetBufferSize (int width, int height)
-    {
-        FakeConsole.SetBufferSize (width, height);
-        Cols = width;
-        Rows = height;
-        SetConsoleSize (width, height);
-        ProcessResize ();
-    }
-
-    public void SetConsoleSize (int width, int height)
-    {
-        FakeConsole.SetConsoleSize (width, height);
-        FakeConsole.SetBufferSize (width, height);
-
-        if (width != Cols || height != Rows)
-        {
-            SetBufferSize (width, height);
-            Cols = width;
-            Rows = height;
-        }
-
-        ProcessResize ();
-    }
-
-    public void SetWindowPosition (int left, int top)
-    {
-        if (Left > 0 || Top > 0)
-        {
-            Left = 0;
-            Top = 0;
-        }
-
-        FakeConsole.SetWindowPosition (Left, Top);
-    }
-
-    private void ProcessResize ()
-    {
-        ResizeScreen ();
-        ClearContents ();
-        OnSizeChanged (new (new (Cols, Rows)));
-    }
-
-    public virtual void ResizeScreen ()
-    {
-        if (FakeConsole.WindowHeight > 0)
-        {
-            // Can raise an exception while it is still resizing.
-            try
-            {
-                FakeConsole.CursorTop = 0;
-                FakeConsole.CursorLeft = 0;
-                FakeConsole.WindowTop = 0;
-                FakeConsole.WindowLeft = 0;
-            }
-            catch (IOException)
-            {
-                return;
-            }
-            catch (ArgumentOutOfRangeException)
-            {
-                return;
-            }
-        }
-
-        // CONCURRENCY: Unsynchronized access to Clip is not safe.
-        Clip = new (Screen);
-    }
-
-    public override void UpdateCursor ()
-    {
-        if (!EnsureCursorVisibility ())
-        {
-            return;
-        }
-
-        // Prevents the exception to size changing during resizing.
-        try
-        {
-            // BUGBUG: Why is this using BufferWidth/Height and now Cols/Rows?
-            if (Col >= 0 && Col < FakeConsole.BufferWidth && Row >= 0 && Row < FakeConsole.BufferHeight)
-            {
-                FakeConsole.SetCursorPosition (Col, Row);
-            }
-        }
-        catch (IOException)
-        { }
-        catch (ArgumentOutOfRangeException)
-        { }
-    }
-
-    #region Not Implemented
-
-    public override void Suspend ()
-    {
-        //throw new NotImplementedException ();
-    }
-
-    #endregion
-
-
-#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
-}

+ 47 - 0
Terminal.Gui/Drivers/FakeDriver/FakeInput.cs

@@ -0,0 +1,47 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+///     <see cref="IInput{TInputRecord}"/> implementation that uses a fake input source for testing.
+///     The <see cref="Peek"/> and <see cref="Read"/> methods are executed
+///     on the input thread created by <see cref="MainLoopCoordinator{TInputRecord}.StartInputTaskAsync"/>.
+/// </summary>
+public class FakeInput : InputImpl<ConsoleKeyInfo>, ITestableInput<ConsoleKeyInfo>
+{
+    // Queue for storing injected input that will be returned by Peek/Read
+    private readonly ConcurrentQueue<ConsoleKeyInfo> _testInput = new ();
+
+    /// <summary>
+    ///     Creates a new FakeInput.
+    /// </summary>
+    public FakeInput ()
+    { }
+
+    /// <inheritdoc/>
+    public override bool Peek ()
+    {
+        // Will be called on the input thread.
+        return !_testInput.IsEmpty;
+    }
+
+    /// <inheritdoc/>
+    public override IEnumerable<ConsoleKeyInfo> Read ()
+    {
+        // Will be called on the input thread.
+        while (_testInput.TryDequeue (out ConsoleKeyInfo input))
+        {
+            yield return input;
+        }
+    }
+
+    /// <inheritdoc />
+    public void AddInput (ConsoleKeyInfo input)
+    {
+        //Logging.Trace ($"Enqueuing input: {input.Key}");
+
+        // Will be called on the main loop thread.
+        _testInput.Enqueue (input);
+    }
+}

+ 47 - 0
Terminal.Gui/Drivers/FakeDriver/FakeInputProcessor.cs

@@ -0,0 +1,47 @@
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+///     Input processor for <see cref="FakeInput"/>, deals in <see cref="ConsoleKeyInfo"/> stream
+/// </summary>
+public class FakeInputProcessor : InputProcessorImpl<ConsoleKeyInfo>
+{
+    /// <inheritdoc/>
+    public FakeInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ())
+    {
+        DriverName = "fake";
+    }
+
+    /// <inheritdoc/>
+    protected override void Process (ConsoleKeyInfo input)
+    {
+        Logging.Trace ($"input: {input.KeyChar}");
+
+        foreach (Tuple<char, ConsoleKeyInfo> released in Parser.ProcessInput (Tuple.Create (input.KeyChar, input)))
+        {
+            Logging.Trace($"released: {released.Item1}");
+            ProcessAfterParsing (released.Item2);
+        }
+    }
+
+    /// <inheritdoc />
+    public override void EnqueueMouseEvent (MouseEventArgs mouseEvent)
+    {
+        // FakeDriver uses ConsoleKeyInfo as its input record type, which cannot represent mouse events.
+
+        // If Application.Invoke is available (running in Application context), defer to next iteration
+        // to ensure proper timing - the event is raised after views are laid out.
+        // Otherwise (unit tests), raise immediately so tests can verify synchronously.
+        if (Application.MainThreadId is { })
+        {
+            // Application is running - use Invoke to defer to next iteration
+            Application.Invoke (() => RaiseMouseEvent (mouseEvent));
+        }
+        else
+        {
+            // Not in Application context (unit tests) - raise immediately
+            RaiseMouseEvent (mouseEvent);
+        }
+    }
+}

+ 39 - 14
Terminal.Gui/Drivers/FakeDriver/FakeConsoleOutput.cs → Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs

@@ -1,21 +1,43 @@
 #nullable enable
+using System;
+
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
 ///     Fake console output for testing that captures what would be written to the console.
 /// </summary>
-public class FakeConsoleOutput : OutputBase, IConsoleOutput
+public class FakeOutput : OutputBase, IOutput
 {
     private readonly StringBuilder _output = new ();
     private int _cursorLeft;
     private int _cursorTop;
     private Size _consoleSize = new (80, 25);
 
+    /// <summary>
+    /// 
+    /// </summary>
+    public FakeOutput ()
+    {
+        LastBuffer = new OutputBufferImpl ();
+        LastBuffer.SetSize (80, 25);
+    }
+
+    /// <summary>
+    ///     Gets or sets the last output buffer written.
+    /// </summary>
+    public IOutputBuffer? LastBuffer { get; set; }
+
     /// <summary>
     ///     Gets the captured output as a string.
     /// </summary>
     public string Output => _output.ToString ();
 
+    /// <inheritdoc />
+    public Point GetCursorPosition ()
+    {
+        return new (_cursorLeft, _cursorTop);
+    }
+
     /// <inheritdoc/>
     public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
 
@@ -34,23 +56,23 @@ public class FakeConsoleOutput : OutputBase, IConsoleOutput
         return true;
     }
 
-    /// <summary>
-    ///     Sets the fake window size.
-    /// </summary>
-    public void SetConsoleSize (int width, int height) { _consoleSize = new (width, height); }
-
-    /// <summary>
-    ///     Gets the current cursor position.
-    /// </summary>
-    public (int left, int top) GetCursorPosition () { return (_cursorLeft, _cursorTop); }
-
     /// <inheritdoc/>
     public Size GetSize () { return _consoleSize; }
 
     /// <inheritdoc/>
-    public void Write (ReadOnlySpan<char> text) { _output.Append (text); }
+    public void Write (ReadOnlySpan<char> text)
+    {
+        _output.Append (text);
+    }
 
-    /// <inheritdoc/>
+    /// <inheritdoc cref="IDriver"/>
+    public override void Write (IOutputBuffer buffer)
+    {
+        LastBuffer = buffer;
+        base.Write (buffer);
+    }
+
+    /// <inheritdoc cref="IDriver"/>
     public override void SetCursorVisibility (CursorVisibility visibility)
     {
         // Capture but don't act on it in fake output
@@ -70,5 +92,8 @@ public class FakeConsoleOutput : OutputBase, IConsoleOutput
     }
 
     /// <inheritdoc/>
-    protected override void Write (StringBuilder output) { _output.Append (output); }
+    protected override void Write (StringBuilder output)
+    {
+        _output.Append (output);
+    }
 }

+ 29 - 20
Terminal.Gui/Drivers/IComponentFactory.cs

@@ -1,51 +1,60 @@
 #nullable enable
 using System.Collections.Concurrent;
-using Terminal.Gui.App;
 
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-/// Base untyped interface for <see cref="IComponentFactory{T}"/> for methods that are not templated on low level
-/// console input type.
+///     Base untyped interface for <see cref="IComponentFactory{T}"/> for methods that are not templated on low level
+///     console input type.
 /// </summary>
 public interface IComponentFactory
 {
     /// <summary>
-    /// Create the <see cref="IConsoleOutput"/> class for the current driver implementation i.e. the class responsible for
-    /// rendering <see cref="IOutputBuffer"/> into the console.
+    ///     Create the <see cref="IOutput"/> class for the current driver implementation i.e. the class responsible for
+    ///     rendering <see cref="IOutputBuffer"/> into the console.
     /// </summary>
     /// <returns></returns>
-    IConsoleOutput CreateOutput ();
+    IOutput CreateOutput ();
 }
 
 /// <summary>
-/// Creates driver specific subcomponent classes (<see cref="IConsoleInput{T}"/>, <see cref="IInputProcessor"/> etc) for a
-/// <see cref="IMainLoopCoordinator"/>.
+///     Creates driver specific subcomponent classes (<see cref="IInput{TInputRecord}"/>, <see cref="IInputProcessor"/>
+///     etc) for a
+///     <see cref="IMainLoopCoordinator"/>.
 /// </summary>
-/// <typeparam name="T"></typeparam>
-public interface IComponentFactory<T> : IComponentFactory
+/// <typeparam name="TInputRecord">
+///     The platform specific console input type. Must be a value type (struct).
+///     Valid types are <see cref="ConsoleKeyInfo"/>, <see cref="WindowsConsole.InputRecord"/>, and <see cref="char"/>.
+/// </typeparam>
+public interface IComponentFactory<TInputRecord> : IComponentFactory
+    where TInputRecord : struct
 {
     /// <summary>
-    /// Create <see cref="IConsoleInput{T}"/> class for the current driver implementation i.e. the class responsible for reading
-    /// user input from the console.
+    ///     Create <see cref="IInput{T}"/> class for the current driver implementation i.e. the class responsible for reading
+    ///     user input from the console.
     /// </summary>
     /// <returns></returns>
-    IConsoleInput<T> CreateInput ();
+    IInput<TInputRecord> CreateInput ();
 
     /// <summary>
-    /// Creates the <see cref="InputProcessor{T}"/> class for the current driver implementation i.e. the class responsible for
-    /// translating raw console input into Terminal.Gui common event <see cref="Key"/> and <see cref="MouseEventArgs"/>.
+    ///     Creates the <see cref="InputProcessorImpl{T}"/> class for the current driver implementation i.e. the class
+    ///     responsible for
+    ///     translating raw console input into Terminal.Gui common event <see cref="Key"/> and <see cref="MouseEventArgs"/>.
     /// </summary>
-    /// <param name="inputBuffer"></param>
+    /// <param name="inputQueue">
+    ///     The input queue containing raw console input events, populated by <see cref="IInput{TInputRecord}"/>
+    ///     implementations on the input thread and
+    ///     read by <see cref="IInputProcessor"/> on the main loop thread.
+    /// </param>
     /// <returns></returns>
-    IInputProcessor CreateInputProcessor (ConcurrentQueue<T> inputBuffer);
+    IInputProcessor CreateInputProcessor (ConcurrentQueue<TInputRecord> inputQueue);
 
     /// <summary>
-    /// Creates <see cref="IConsoleSizeMonitor"/> class for the current driver implementation i.e. the class responsible for
-    /// reporting the current size of the terminal.
+    ///     Creates <see cref="ISizeMonitor"/> class for the current driver implementation i.e. the class responsible for
+    ///     reporting the current size of the terminal.
     /// </summary>
     /// <param name="consoleOutput"></param>
     /// <param name="outputBuffer"></param>
     /// <returns></returns>
-    IConsoleSizeMonitor CreateConsoleSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer);
+    ISizeMonitor CreateSizeMonitor (IOutput consoleOutput, IOutputBuffer outputBuffer);
 }

+ 0 - 26
Terminal.Gui/Drivers/IConsoleDriverFacade.cs

@@ -1,26 +0,0 @@
-#nullable enable
-namespace Terminal.Gui.Drivers;
-
-/// <summary>
-///     Interface for v2 driver abstraction layer
-/// </summary>
-public interface IConsoleDriverFacade
-{
-    /// <summary>
-    ///     Class responsible for processing native driver input objects
-    ///     e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
-    ///     and detecting and processing ansi escape sequences.
-    /// </summary>
-    IInputProcessor InputProcessor { get; }
-
-    /// <summary>
-    ///     Describes the desired screen state. Data source for <see cref="IConsoleOutput"/>.
-    /// </summary>
-    IOutputBuffer OutputBuffer { get; }
-
-    /// <summary>
-    ///     Interface for classes responsible for reporting the current
-    ///     size of the terminal window.
-    /// </summary>
-    IConsoleSizeMonitor ConsoleSizeMonitor { get; }
-}

+ 0 - 29
Terminal.Gui/Drivers/IConsoleInput.cs

@@ -1,29 +0,0 @@
-using System.Collections.Concurrent;
-
-namespace Terminal.Gui.Drivers;
-
-/// <summary>
-///     Interface for reading console input indefinitely -
-///     i.e. in an infinite loop. The class is responsible only
-///     for reading and storing the input in a thread safe input buffer
-///     which is then processed downstream e.g. on main UI thread.
-/// </summary>
-/// <typeparam name="T"></typeparam>
-public interface IConsoleInput<T> : IDisposable
-{
-    /// <summary>
-    ///     Initializes the input with a buffer into which to put data read
-    /// </summary>
-    /// <param name="inputBuffer"></param>
-    void Initialize (ConcurrentQueue<T> inputBuffer);
-
-    /// <summary>
-    ///     Runs in an infinite input loop.
-    /// </summary>
-    /// <param name="token"></param>
-    /// <exception cref="OperationCanceledException">
-    ///     Raised when token is
-    ///     cancelled. This is the only means of exiting the input.
-    /// </exception>
-    void Run (CancellationToken token);
-}

+ 61 - 31
Terminal.Gui/Drivers/IConsoleDriver.cs → Terminal.Gui/Drivers/IDriver.cs

@@ -2,13 +2,37 @@
 
 namespace Terminal.Gui.Drivers;
 
-/// <summary>Base interface for Terminal.Gui ConsoleDriver implementations.</summary>
+/// <summary>Base interface for Terminal.Gui Driver implementations.</summary>
 /// <remarks>
 ///     There are currently four implementations: UnixDriver, WindowsDriver, DotNetDriver, and FakeDriver
 /// </remarks>
-public interface IConsoleDriver
+public interface IDriver
 {
+    /// <summary>
+    ///     Gets the name of the driver implementation.
+    /// </summary>
+    string? GetName ();
+
+    /// <summary>
+    ///     Class responsible for processing native driver input objects
+    ///     e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
+    ///     and detecting and processing ansi escape sequences.
+    /// </summary>
+    IInputProcessor InputProcessor { get; }
+
+    /// <summary>
+    ///     Describes the desired screen state. Data source for <see cref="IOutput"/>.
+    /// </summary>
+    IOutputBuffer OutputBuffer { get; }
+
+    /// <summary>
+    ///     Interface for classes responsible for reporting the current
+    ///     size of the terminal window.
+    /// </summary>
+    ISizeMonitor SizeMonitor { get; }
+
     /// <summary>Get the operating system clipboard.</summary>
+    /// 
     IClipboard? Clipboard { get; }
 
     /// <summary>Gets the location and size of the terminal screen.</summary>
@@ -62,17 +86,17 @@ public interface IConsoleDriver
     /// <summary>The topmost row in the terminal.</summary>
     int Top { get; set; }
 
-    /// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
+    /// <summary>Gets whether the <see cref="IDriver"/> supports TrueColor output.</summary>
     bool SupportsTrueColor { get; }
 
     /// <summary>
-    ///     Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors.
+    ///     Gets or sets whether the <see cref="IDriver"/> should use 16 colors instead of the default TrueColors.
     ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
     /// </summary>
     /// <remarks>
     ///     <para>
-    ///         Will be forced to <see langword="true"/> if <see cref="ConsoleDriver.SupportsTrueColor"/> is
-    ///         <see langword="false"/>, indicating that the <see cref="ConsoleDriver"/> cannot support TrueColor.
+    ///         Will be forced to <see langword="true"/> if <see cref="IDriver.SupportsTrueColor"/> is
+    ///         <see langword="false"/>, indicating that the <see cref="IDriver"/> cannot support TrueColor.
     ///     </para>
     /// </remarks>
     bool Force16Colors { get; set; }
@@ -88,7 +112,7 @@ public interface IConsoleDriver
     string GetVersionInfo ();
 
     /// <summary>
-    ///     Provide proper writing to send escape sequence recognized by the <see cref="ConsoleDriver"/>.
+    ///     Provide proper writing to send escape sequence recognized by the <see cref="IDriver"/>.
     /// </summary>
     /// <param name="ansi"></param>
     void WriteRaw (string ansi);
@@ -107,23 +131,23 @@ public interface IConsoleDriver
     /// <param name="row">The row.</param>
     /// <returns>
     ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of
-    ///     <see cref="ConsoleDriver.Clip"/>.
+    ///     <see cref="IDriver.Clip"/>.
     ///     <see langword="true"/> otherwise.
     /// </returns>
     bool IsValidLocation (Rune rune, int col, int row);
 
     /// <summary>
-    ///     Updates <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/> to the specified column and row in
-    ///     <see cref="ConsoleDriver.Contents"/>.
-    ///     Used by <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> and <see cref="ConsoleDriver.AddStr"/> to determine
+    ///     Updates <see cref="IDriver.Col"/> and <see cref="IDriver.Row"/> to the specified column and row in
+    ///     <see cref="IDriver.Contents"/>.
+    ///     Used by <see cref="IDriver.AddRune(System.Text.Rune)"/> and <see cref="IDriver.AddStr"/> to determine
     ///     where to add content.
     /// </summary>
     /// <remarks>
     ///     <para>This does not move the cursor on the screen, it only updates the internal state of the driver.</para>
     ///     <para>
-    ///         If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="ConsoleDriver.Cols"/>
+    ///         If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="IDriver.Cols"/>
     ///         and
-    ///         <see cref="ConsoleDriver.Rows"/>, the method still sets those properties.
+    ///         <see cref="IDriver.Rows"/>, the method still sets those properties.
     ///     </para>
     /// </remarks>
     /// <param name="col">Column to move to.</param>
@@ -133,15 +157,15 @@ public interface IConsoleDriver
     /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
     /// <remarks>
     ///     <para>
-    ///         When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
+    ///         When the method returns, <see cref="IDriver.Col"/> will be incremented by the number of columns
     ///         <paramref name="rune"/> required, even if the new column value is outside of the
-    ///         <see cref="ConsoleDriver.Clip"/> or screen
-    ///         dimensions defined by <see cref="ConsoleDriver.Cols"/>.
+    ///         <see cref="IDriver.Clip"/> or screen
+    ///         dimensions defined by <see cref="IDriver.Cols"/>.
     ///     </para>
     ///     <para>
-    ///         If <paramref name="rune"/> requires more than one column, and <see cref="ConsoleDriver.Col"/> plus the number
+    ///         If <paramref name="rune"/> requires more than one column, and <see cref="IDriver.Col"/> plus the number
     ///         of columns
-    ///         needed exceeds the <see cref="ConsoleDriver.Clip"/> or screen dimensions, the default Unicode replacement
+    ///         needed exceeds the <see cref="IDriver.Clip"/> or screen dimensions, the default Unicode replacement
     ///         character (U+FFFD)
     ///         will be added instead.
     ///     </para>
@@ -151,7 +175,7 @@ public interface IConsoleDriver
 
     /// <summary>
     ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
-    ///     convenience method that calls <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> with the <see cref="Rune"/>
+    ///     convenience method that calls <see cref="IDriver.AddRune(System.Text.Rune)"/> with the <see cref="Rune"/>
     ///     constructor.
     /// </summary>
     /// <param name="c">Character to add.</param>
@@ -160,27 +184,27 @@ public interface IConsoleDriver
     /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
     /// <remarks>
     ///     <para>
-    ///         When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
-    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="ConsoleDriver.Clip"/>
+    ///         When the method returns, <see cref="IDriver.Col"/> will be incremented by the number of columns
+    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="IDriver.Clip"/>
     ///         or screen
-    ///         dimensions defined by <see cref="ConsoleDriver.Cols"/>.
+    ///         dimensions defined by <see cref="IDriver.Cols"/>.
     ///     </para>
     ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
     /// </remarks>
     /// <param name="str">String.</param>
     void AddStr (string str);
 
-    /// <summary>Clears the <see cref="ConsoleDriver.Contents"/> of the driver.</summary>
+    /// <summary>Clears the <see cref="IDriver.Contents"/> of the driver.</summary>
     void ClearContents ();
 
     /// <summary>
-    ///     Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/>
+    ///     Fills the specified rectangle with the specified rune, using <see cref="IDriver.CurrentAttribute"/>
     /// </summary>
     event EventHandler<EventArgs> ClearedContents;
 
-    /// <summary>Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/></summary>
+    /// <summary>Fills the specified rectangle with the specified rune, using <see cref="IDriver.CurrentAttribute"/></summary>
     /// <remarks>
-    ///     The value of <see cref="ConsoleDriver.Clip"/> is honored. Any parts of the rectangle not in the clip will not be
+    ///     The value of <see cref="IDriver.Clip"/> is honored. Any parts of the rectangle not in the clip will not be
     ///     drawn.
     /// </remarks>
     /// <param name="rect">The Screen-relative rectangle.</param>
@@ -189,7 +213,7 @@ public interface IConsoleDriver
 
     /// <summary>
     ///     Fills the specified rectangle with the specified <see langword="char"/>. This method is a convenience method
-    ///     that calls <see cref="ConsoleDriver.FillRect(System.Drawing.Rectangle,System.Text.Rune)"/>.
+    ///     that calls <see cref="IDriver.FillRect(System.Drawing.Rectangle,System.Text.Rune)"/>.
     /// </summary>
     /// <param name="rect"></param>
     /// <param name="c"></param>
@@ -220,8 +244,8 @@ public interface IConsoleDriver
     void Suspend ();
 
     /// <summary>
-    ///     Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and
-    ///     <see cref="ConsoleDriver.Row"/>.
+    ///     Sets the position of the terminal cursor to <see cref="IDriver.Col"/> and
+    ///     <see cref="IDriver.Row"/>.
     /// </summary>
     void UpdateCursor ();
 
@@ -243,17 +267,23 @@ public interface IConsoleDriver
     /// <summary>Event fired when a mouse event occurs.</summary>
     event EventHandler<MouseEventArgs>? MouseEvent;
 
-    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="ConsoleDriver.KeyUp"/>.</summary>
+    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="IDriver.KeyUp"/>.</summary>
     event EventHandler<Key>? KeyDown;
 
     /// <summary>Event fired when a key is released.</summary>
     /// <remarks>
-    ///     Drivers that do not support key release events will fire this event after <see cref="ConsoleDriver.KeyDown"/>
+    ///     Drivers that do not support key release events will fire this event after <see cref="IDriver.KeyDown"/>
     ///     processing is
     ///     complete.
     /// </remarks>
     event EventHandler<Key>? KeyUp;
 
+    /// <summary>
+    ///     Enqueues a key input event to the driver. For unit tests.
+    /// </summary>
+    /// <param name="key"></param>
+    void EnqueueKeyEvent (Key key);
+
     /// <summary>
     ///     Queues the given <paramref name="request"/> for execution
     /// </summary>

+ 172 - 0
Terminal.Gui/Drivers/IInput.cs

@@ -0,0 +1,172 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+///     Interface for reading console input in a perpetual loop on a dedicated input thread.
+/// </summary>
+/// <remarks>
+///     <para>
+///         Implementations run on a separate thread (started by
+///         <see cref="MainLoopCoordinator{TInputRecord}.StartInputTaskAsync"/>)
+///         and continuously read platform-specific input from the console, placing it into a thread-safe queue
+///         for processing by <see cref="IInputProcessor"/> on the main UI thread.
+///     </para>
+///     <para>
+///         <b>Architecture:</b>
+///     </para>
+///     <code>
+///         Input Thread:                    Main UI Thread:
+///         ┌─────────────────┐             ┌──────────────────────┐
+///         │ IInput.Run()    │             │ IInputProcessor      │
+///         │  ├─ Peek()      │             │  ├─ ProcessQueue()   │
+///         │  ├─ Read()      │──Enqueue──→ │  ├─ Process()        │
+///         │  └─ Enqueue     │             │  ├─ ToKey()          │
+///         └─────────────────┘             │  └─ Raise Events     │
+///                                         └──────────────────────┘
+///     </code>
+///     <para>
+///         <b>Lifecycle:</b>
+///     </para>
+///     <list type="number">
+///         <item><see cref="Initialize"/> - Set the shared input queue</item>
+///         <item><see cref="Run"/> - Start the perpetual read loop (blocks until cancelled)</item>
+///         <item>
+///             Loop calls <see cref="InputImpl{TInputRecord}.Peek"/> and <see cref="InputImpl{TInputRecord}.Read"/>
+///         </item>
+///         <item>Cancellation via `runCancellationToken` or <see cref="ExternalCancellationTokenSource"/></item>
+///     </list>
+///     <para>
+///         <b>Implementations:</b>
+///     </para>
+///     <list type="bullet">
+///         <item><see cref="WindowsInput"/> - Uses Windows Console API (<c>ReadConsoleInput</c>)</item>
+///         <item><see cref="NetInput"/> - Uses .NET <see cref="System.Console"/> API</item>
+///         <item><see cref="UnixInput"/> - Uses Unix terminal APIs</item>
+///         <item><see cref="FakeInput"/> - For testing, implements <see cref="ITestableInput{TInputRecord}"/></item>
+///     </list>
+///     <para>
+///         <b>Testing Support:</b> See <see cref="ITestableInput{TInputRecord}"/> for programmatic input injection
+///         in test scenarios.
+///     </para>
+/// </remarks>
+/// <typeparam name="TInputRecord">
+///     The platform-specific input record type:
+///     <list type="bullet">
+///         <item><see cref="ConsoleKeyInfo"/> - for .NET and Fake drivers</item>
+///         <item><see cref="WindowsConsole.InputRecord"/> - for Windows driver</item>
+///         <item><see cref="char"/> - for Unix driver</item>
+///     </list>
+/// </typeparam>
+public interface IInput<TInputRecord> : IDisposable
+{
+    /// <summary>
+    ///     Gets or sets an external cancellation token source that can stop the <see cref="Run"/> loop
+    ///     in addition to the `runCancellationToken` passed to <see cref="Run"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This property allows external code (e.g., test harnesses like <c>GuiTestContext</c>) to
+    ///         provide additional cancellation signals such as timeouts or hard-stop conditions.
+    ///     </para>
+    ///     <para>
+    ///         <b>Ownership:</b> The setter does NOT transfer ownership of the <see cref="CancellationTokenSource"/>.
+    ///         The creator is responsible for disposal. <see cref="IInput{TInputRecord}"/> implementations
+    ///         should NOT dispose this token source.
+    ///     </para>
+    ///     <para>
+    ///         <b>How it works:</b> <see cref="InputImpl{TInputRecord}.Run"/> creates a linked token that
+    ///         responds to BOTH the `runCancellationToken` AND this external token:
+    ///     </para>
+    ///     <code>
+    ///         var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(
+    ///             runCancellationToken,
+    ///             ExternalCancellationTokenSource.Token);
+    ///     </code>
+    /// </remarks>
+    /// <example>
+    ///     Test scenario with timeout:
+    ///     <code>
+    ///         var input = new FakeInput();
+    ///         input.ExternalCancellationTokenSource = new CancellationTokenSource(
+    ///             TimeSpan.FromSeconds(30)); // 30-second timeout
+    ///         
+    ///         // Run will stop if either:
+    ///         // 1. runCancellationToken is cancelled (normal shutdown)
+    ///         // 2. 30 seconds elapse (timeout)
+    ///         input.Run(normalCancellationToken);
+    ///     </code>
+    /// </example>
+    CancellationTokenSource? ExternalCancellationTokenSource { get; set; }
+
+    /// <summary>
+    ///     Initializes the input reader with the thread-safe queue where read input will be stored.
+    /// </summary>
+    /// <param name="inputQueue">
+    ///     The shared <see cref="ConcurrentQueue{T}"/> that both <see cref="Run"/> (producer)
+    ///     and <see cref="IInputProcessor"/> (consumer) use for passing input records between threads.
+    /// </param>
+    /// <remarks>
+    ///     <para>
+    ///         This queue is created by <see cref="Terminal.Gui.App.MainLoopCoordinator{TInputRecord}"/>
+    ///         and shared between the input thread and main UI thread.
+    ///     </para>
+    ///     <para>
+    ///         <b>Must be called before <see cref="Run"/>.</b> Calling <see cref="Run"/> without
+    ///         initialization will throw an exception.
+    ///     </para>
+    /// </remarks>
+    void Initialize (ConcurrentQueue<TInputRecord> inputQueue);
+
+    /// <summary>
+    ///     Runs the input loop, continuously reading input and placing it into the queue
+    ///     provided by <see cref="Initialize"/>.
+    /// </summary>
+    /// <param name="runCancellationToken">
+    ///     The primary cancellation token that stops the input loop. Provided by
+    ///     <see cref="Terminal.Gui.App.MainLoopCoordinator{TInputRecord}"/> and triggered
+    ///     during application shutdown.
+    /// </param>
+    /// <remarks>
+    ///     <para>
+    ///         <b>Threading:</b> This method runs on a dedicated input thread created by
+    ///         <see cref="MainLoopCoordinator{TInputRecord}.StartInputTaskAsync"/>. and blocks until
+    ///         cancellation is requested. It should never be called from the main UI thread.
+    ///     </para>
+    ///     <para>
+    ///         <b>Cancellation:</b> The loop stops when either <paramref name="runCancellationToken"/>
+    ///         or <see cref="ExternalCancellationTokenSource"/> (if set) is cancelled.
+    ///     </para>
+    ///     <para>
+    ///         <b>Base Implementation:</b> <see cref="InputImpl{TInputRecord}.Run"/> provides the
+    ///         standard loop logic:
+    ///     </para>
+    ///     <code>
+    ///         while (!cancelled)
+    ///         {
+    ///             while (Peek())  // Check for available input
+    ///             {
+    ///                 foreach (var input in Read())  // Read all available
+    ///                 {
+    ///                     inputQueue.Enqueue(input);  // Store for processing
+    ///                 }
+    ///             }
+    ///             Task.Delay(20ms);  // Throttle to ~50 polls/second
+    ///         }
+    ///     </code>
+    ///     <para>
+    ///         <b>Testing:</b> For <see cref="ITestableInput{TInputRecord}"/> implementations,
+    ///         test input injected via <see cref="ITestableInput{TInputRecord}.AddInput"/>
+    ///         flows through the same <c>Peek/Read</c> pipeline.
+    ///     </para>
+    /// </remarks>
+    /// <exception cref="OperationCanceledException">
+    ///     Thrown when <paramref name="runCancellationToken"/> or <see cref="ExternalCancellationTokenSource"/>
+    ///     is cancelled. This is the normal/expected means of exiting the input loop.
+    /// </exception>
+    /// <exception cref="InvalidOperationException">
+    ///     Thrown if <see cref="Initialize"/> was not called before <see cref="Run"/>.
+    /// </exception>
+    void Run (CancellationToken runCancellationToken);
+}

+ 58 - 39
Terminal.Gui/Drivers/IInputProcessor.cs

@@ -3,58 +3,22 @@
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-///     Interface for main loop class that will process the queued input buffer contents.
+///     Interface for main loop class that will process the queued input.
 ///     Is responsible for <see cref="ProcessQueue"/> and translating into common Terminal.Gui
 ///     events and data models.
 /// </summary>
 public interface IInputProcessor
 {
-    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
-    event EventHandler<Key>? KeyDown;
-
-    /// <summary>Event fired when a key is released.</summary>
-    /// <remarks>
-    ///     Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is
-    ///     complete.
-    /// </remarks>
-    event EventHandler<Key>? KeyUp;
-
-    /// <summary>Event fired when a terminal sequence read from input is not recognized and therefore ignored.</summary>
+    /// <summary>Event raised when a terminal sequence read from input is not recognized and therefore ignored.</summary>
     public event EventHandler<string>? AnsiSequenceSwallowed;
 
-    /// <summary>Event fired when a mouse event occurs.</summary>
-    event EventHandler<MouseEventArgs>? MouseEvent;
-
     /// <summary>
     /// Gets the name of the driver associated with this input processor.
     /// </summary>
     string? DriverName { get; init; }
 
     /// <summary>
-    ///     Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
-    ///     <see cref="OnKeyUp"/>.
-    /// </summary>
-    /// <param name="key">The key event data.</param>
-    void OnKeyDown (Key key);
-
-    /// <summary>
-    ///     Called when a key is released. Fires the <see cref="KeyUp"/> event.
-    /// </summary>
-    /// <remarks>
-    ///     Drivers that do not support key release events will call this method after <see cref="OnKeyDown"/> processing
-    ///     is complete.
-    /// </remarks>
-    /// <param name="key">The key event data.</param>
-    void OnKeyUp (Key key);
-
-    /// <summary>
-    ///     Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.
-    /// </summary>
-    /// <param name="mouseEventArgs">The mouse event data.</param>
-    void OnMouseEvent (MouseEventArgs mouseEventArgs);
-
-    /// <summary>
-    ///     Drains the input buffer, processing all available keystrokes
+    ///     Drains the input queue, processing all available keystrokes. To be called on the main loop thread.
     /// </summary>
     void ProcessQueue ();
 
@@ -74,4 +38,59 @@ public interface IInputProcessor
     ///     <see langword="false"/>.
     /// </returns>
     bool IsValidInput (Key key, out Key result);
+
+    /// <summary>
+    ///     Called when a key down event has been dequeued. Raises the <see cref="KeyDown"/> event. This is a precursor to
+    ///     <see cref="RaiseKeyUpEvent"/>.
+    /// </summary>
+    /// <param name="key">The key event data.</param>
+    void RaiseKeyDownEvent (Key key);
+
+    /// <summary>Event raised when a key down event has been dequeued. This is a precursor to <see cref="KeyUp"/>.</summary>
+    event EventHandler<Key>? KeyDown;
+
+    /// <summary>
+    ///     Adds a key up event to the input queue. For unit tests.
+    /// </summary>
+    /// <param name="key"></param>
+    void EnqueueKeyDownEvent (Key key);
+
+    /// <summary>
+    ///     Called when a key up event has been dequeued. Raises the <see cref="KeyUp"/> event.
+    /// </summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will call this method after <see cref="RaiseKeyDownEvent"/> processing
+    ///     is complete.
+    /// </remarks>
+    /// <param name="key">The key event data.</param>
+    void RaiseKeyUpEvent (Key key);
+
+    /// <summary>Event raised when a key up event has been dequeued.</summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is
+    ///     complete.
+    /// </remarks>
+    event EventHandler<Key>? KeyUp;
+
+    /// <summary>
+    ///     Adds a key up event to the input queue. For unit tests.
+    /// </summary>
+    /// <param name="key"></param>
+    void EnqueueKeyUpEvent (Key key);
+
+    /// <summary>
+    ///     Called when a mouse event has been dequeued. Raises the <see cref="MouseEvent"/> event.
+    /// </summary>
+    /// <param name="mouseEventArgs">The mouse event data.</param>
+    void RaiseMouseEvent (MouseEventArgs mouseEventArgs);
+
+    /// <summary>Event raised when a mouse event has been dequeued.</summary>
+    event EventHandler<MouseEventArgs>? MouseEvent;
+
+    /// <summary>
+    ///     Adds a mouse input event to the input queue. For unit tests.
+    /// </summary>
+    /// <param name="mouseEvent"></param>
+    void EnqueueMouseEvent (MouseEventArgs mouseEvent);
+
 }

+ 15 - 7
Terminal.Gui/Drivers/IKeyConverter.cs

@@ -2,18 +2,26 @@
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-///     Interface for subcomponent of a <see cref="InputProcessor{T}"/> which
+///     Interface for subcomponent of a <see cref="InputProcessorImpl{T}"/> which
 ///     can translate the raw console input type T (which typically varies by
 ///     driver) to the shared Terminal.Gui <see cref="Key"/> class.
 /// </summary>
-/// <typeparam name="T"></typeparam>
-public interface IKeyConverter<in T>
+/// <typeparam name="TInputRecord"></typeparam>
+public interface IKeyConverter<TInputRecord>
 {
     /// <summary>
-    ///     Converts the native keyboard class read from console into
-    ///     the shared <see cref="Key"/> class used by Terminal.Gui views.
+    ///     Converts the native keyboard info type into
+    ///     the <see cref="Key"/> class used by Terminal.Gui views.
     /// </summary>
-    /// <param name="value"></param>
+    /// <param name="keyInfo"></param>
     /// <returns></returns>
-    Key ToKey (T value);
+    Key ToKey (TInputRecord keyInfo);
+
+    /// <summary>
+    ///     Converts a <see cref="Key"/> into the native keyboard info type. Should be used for simulating
+    ///     key presses in unit tests.
+    /// </summary>
+    /// <param name="key"></param>
+    /// <returns></returns>
+    TInputRecord ToKeyInfo (Key key);
 }

+ 28 - 21
Terminal.Gui/Drivers/IConsoleOutput.cs → Terminal.Gui/Drivers/IOutput.cs

@@ -1,23 +1,16 @@
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-///     Interface for writing console output
+///     The low-level interface drivers implement to provide output capabilities; encapsulates platform-specific
+///     output functionality.
 /// </summary>
-public interface IConsoleOutput : IDisposable
+public interface IOutput : IDisposable
 {
     /// <summary>
-    ///     Writes the given text directly to the console. Use to send
-    ///     ansi escape codes etc. Regular screen output should use the
-    ///     <see cref="IOutputBuffer"/> overload.
-    /// </summary>
-    /// <param name="text"></param>
-    void Write (ReadOnlySpan<char> text);
-
-    /// <summary>
-    ///     Write the contents of the <paramref name="buffer"/> to the console
+    ///     Gets the current position of the console cursor.
     /// </summary>
-    /// <param name="buffer"></param>
-    void Write (IOutputBuffer buffer);
+    /// <returns></returns>
+    Point GetCursorPosition ();
 
     /// <summary>
     ///     Returns the current size of the console in rows/columns (i.e.
@@ -26,13 +19,6 @@ public interface IConsoleOutput : IDisposable
     /// <returns></returns>
     public Size GetSize ();
 
-    /// <summary>
-    ///     Updates the console cursor (the blinking underscore) to be hidden,
-    ///     visible etc.
-    /// </summary>
-    /// <param name="visibility"></param>
-    void SetCursorVisibility (CursorVisibility visibility);
-
     /// <summary>
     ///     Moves the console cursor to the given location.
     /// </summary>
@@ -41,9 +27,30 @@ public interface IConsoleOutput : IDisposable
     void SetCursorPosition (int col, int row);
 
     /// <summary>
-    ///     Sets the size of the console..
+    ///     Updates the console cursor (the blinking underscore) to be hidden,
+    ///     visible etc.
+    /// </summary>
+    /// <param name="visibility"></param>
+    void SetCursorVisibility (CursorVisibility visibility);
+
+    /// <summary>
+    ///     Sets the size of the console.
     /// </summary>
     /// <param name="width"></param>
     /// <param name="height"></param>
     void SetSize (int width, int height);
+
+    /// <summary>
+    ///     Writes the given text directly to the console. Use to send
+    ///     ansi escape codes etc. Regular screen output should use the
+    ///     <see cref="IOutputBuffer"/> overload.
+    /// </summary>
+    /// <param name="text"></param>
+    void Write (ReadOnlySpan<char> text);
+
+    /// <summary>
+    ///     Write the contents of the <paramref name="buffer"/> to the console
+    /// </summary>
+    /// <param name="buffer"></param>
+    void Write (IOutputBuffer buffer);
 }

+ 74 - 59
Terminal.Gui/Drivers/IOutputBuffer.cs

@@ -3,16 +3,44 @@
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-///     Describes the screen state that you want the console to be in.
-///     Is designed to be drawn to repeatedly then manifest into the console
-///     once at the end of iteration after all drawing is finalized.
+///     Represents the desired screen state for console rendering. This interface provides methods for building up
+///     visual content (text, attributes, fills) in a buffer that can be efficiently written to the terminal
+///     in a single operation at the end of each iteration. Final output is handled by <see cref="IOutput"/>.
 /// </summary>
+/// <remarks>
+///     <para>
+///         The <see cref="IOutputBuffer"/> acts as an intermediary between Terminal.Gui's high-level drawing operations
+///         and the low-level console output. Rather than writing directly to the console for each operation, views
+///         draw to this buffer during layout and rendering. The buffer is then flushed to the terminal by
+///         <see cref="IOutput"/> after all drawing is complete, minimizing flicker and improving performance.
+///     </para>
+///     <para>
+///         The buffer maintains a 2D array of <see cref="Cell"/> objects in <see cref="Contents"/>, where each cell
+///         represents a single character position on screen with its associated character, attributes, and dirty state.
+///         Drawing operations like <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> modify cells at the
+///         current cursor position (tracked by <see cref="Col"/> and <see cref="Row"/>), respecting any active
+///         <see cref="Clip"/> region.
+///     </para>
+/// </remarks>
 public interface IOutputBuffer
 {
+    /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
+    /// <param name="rune">Rune to add.</param>
+    void AddRune (Rune rune);
+
     /// <summary>
-    ///     The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
+    ///     Adds the specified character to the display at the current cursor position. This is a convenience method for
+    ///     AddRune.
     /// </summary>
-    Cell [,]? Contents { get; set; }
+    /// <param name="c">Character to add.</param>
+    void AddRune (char c);
+
+    /// <summary>Adds the string to the display at the current cursor position.</summary>
+    /// <param name="str">String to add.</param>
+    void AddStr (string str);
+
+    /// <summary>Clears the contents of the buffer.</summary>
+    void ClearContents ();
 
     /// <summary>
     ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject
@@ -22,39 +50,56 @@ public interface IOutputBuffer
     public Region? Clip { get; set; }
 
     /// <summary>
-    ///     The <see cref="Attribute"/> that will be used for the next AddRune or AddStr call.
+    ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
+    ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
     /// </summary>
-    Attribute CurrentAttribute { get; set; }
-
-    /// <summary>The number of rows visible in the terminal.</summary>
-    int Rows { get; set; }
+    public int Col { get; }
 
     /// <summary>The number of columns visible in the terminal.</summary>
     int Cols { get; set; }
 
     /// <summary>
-    ///     Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
-    ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
+    ///     The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
     /// </summary>
-    public int Row { get; }
+    Cell [,]? Contents { get; set; }
 
     /// <summary>
-    ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
-    ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
+    ///     The <see cref="Attribute"/> that will be used for the next AddRune or AddStr call.
     /// </summary>
-    public int Col { get; }
+    Attribute CurrentAttribute { get; set; }
 
     /// <summary>
-    ///     The first cell index on left of screen - basically always 0.
-    ///     Changing this may have unexpected consequences.
+    ///     Fills the given <paramref name="rect"/> with the given
+    ///     symbol using the currently selected attribute.
     /// </summary>
-    int Left { get; set; }
+    /// <param name="rect"></param>
+    /// <param name="rune"></param>
+    void FillRect (Rectangle rect, Rune rune);
 
     /// <summary>
-    ///     The first cell index on top of screen - basically always 0.
+    ///     Fills the given <paramref name="rect"/> with the given
+    ///     symbol using the currently selected attribute.
+    /// </summary>
+    /// <param name="rect"></param>
+    /// <param name="rune"></param>
+    void FillRect (Rectangle rect, char rune);
+
+    /// <summary>
+    ///     Tests whether the specified coordinate is valid for drawing the specified Rune.
+    /// </summary>
+    /// <param name="rune">Used to determine if one or two columns are required.</param>
+    /// <param name="col">The column.</param>
+    /// <param name="row">The row.</param>
+    /// <returns>
+    ///     True if the coordinate is valid for the Rune; false otherwise.
+    /// </returns>
+    bool IsValidLocation (Rune rune, int col, int row);
+
+    /// <summary>
+    ///     The first cell index on left of screen - basically always 0.
     ///     Changing this may have unexpected consequences.
     /// </summary>
-    int Top { get; set; }
+    int Left { get; set; }
 
     /// <summary>
     ///     Updates the column and row to the specified location in the buffer.
@@ -63,34 +108,14 @@ public interface IOutputBuffer
     /// <param name="row">The row to move to.</param>
     void Move (int col, int row);
 
-    /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
-    /// <param name="rune">Rune to add.</param>
-    void AddRune (Rune rune);
-
     /// <summary>
-    ///     Adds the specified character to the display at the current cursor position. This is a convenience method for
-    ///     AddRune.
+    ///     Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
+    ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
     /// </summary>
-    /// <param name="c">Character to add.</param>
-    void AddRune (char c);
-
-    /// <summary>Adds the string to the display at the current cursor position.</summary>
-    /// <param name="str">String to add.</param>
-    void AddStr (string str);
-
-    /// <summary>Clears the contents of the buffer.</summary>
-    void ClearContents ();
+    public int Row { get; }
 
-    /// <summary>
-    ///     Tests whether the specified coordinate is valid for drawing the specified Rune.
-    /// </summary>
-    /// <param name="rune">Used to determine if one or two columns are required.</param>
-    /// <param name="col">The column.</param>
-    /// <param name="row">The row.</param>
-    /// <returns>
-    ///     True if the coordinate is valid for the Rune; false otherwise.
-    /// </returns>
-    bool IsValidLocation (Rune rune, int col, int row);
+    /// <summary>The number of rows visible in the terminal.</summary>
+    int Rows { get; set; }
 
     /// <summary>
     ///     Changes the size of the buffer to the given size
@@ -100,18 +125,8 @@ public interface IOutputBuffer
     void SetSize (int cols, int rows);
 
     /// <summary>
-    ///     Fills the given <paramref name="rect"/> with the given
-    ///     symbol using the currently selected attribute.
-    /// </summary>
-    /// <param name="rect"></param>
-    /// <param name="rune"></param>
-    void FillRect (Rectangle rect, Rune rune);
-
-    /// <summary>
-    ///     Fills the given <paramref name="rect"/> with the given
-    ///     symbol using the currently selected attribute.
+    ///     The first cell index on top of screen - basically always 0.
+    ///     Changing this may have unexpected consequences.
     /// </summary>
-    /// <param name="rect"></param>
-    /// <param name="rune"></param>
-    void FillRect (Rectangle rect, char rune);
+    int Top { get; set; }
 }

+ 1 - 1
Terminal.Gui/Drivers/IConsoleSizeMonitor.cs → Terminal.Gui/Drivers/ISizeMonitor.cs

@@ -6,7 +6,7 @@ namespace Terminal.Gui.Drivers;
 ///     Interface for classes responsible for reporting the current
 ///     size of the terminal window.
 /// </summary>
-public interface IConsoleSizeMonitor
+public interface ISizeMonitor
 {
     /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
     event EventHandler<SizeChangedEventArgs>? SizeChanged;

+ 15 - 0
Terminal.Gui/Drivers/ITestableInput.cs

@@ -0,0 +1,15 @@
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+///     Marker interface for IInput implementations that support test input injection.
+/// </summary>
+/// <typeparam name="TInputRecord">The input record type</typeparam>
+public interface ITestableInput<TInputRecord> : IInput<TInputRecord>
+    where TInputRecord : struct
+{
+    /// <summary>
+    ///     Adds an input record that will be returned by Peek/Read for testing.
+    /// </summary>
+    void AddInput (TInputRecord input);
+}
+

+ 89 - 0
Terminal.Gui/Drivers/InputImpl.cs

@@ -0,0 +1,89 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+///     Base class for reading console input in perpetual loop.
+///     The <see cref="Peek"/> and <see cref="Read"/> methods are executed
+///     on the input thread created by <see cref="MainLoopCoordinator{TInputRecord}.StartInputTaskAsync"/>.
+/// </summary>
+/// <typeparam name="TInputRecord"></typeparam>
+public abstract class InputImpl<TInputRecord> : IInput<TInputRecord>
+{
+    private ConcurrentQueue<TInputRecord>? _inputQueue;
+
+    /// <summary>
+    ///     Determines how to get the current system type, adjust
+    ///     in unit tests to simulate specific timings.
+    /// </summary>
+    public Func<DateTime> Now { get; set; } = () => DateTime.Now;
+
+    /// <inheritdoc />
+    public CancellationTokenSource? ExternalCancellationTokenSource { get; set; }
+
+    /// <inheritdoc/>
+    public void Initialize (ConcurrentQueue<TInputRecord> inputQueue) { _inputQueue = inputQueue; }
+
+    /// <inheritdoc/>
+    public void Run (CancellationToken runCancellationToken)
+    {
+        // Create a linked token source if we have an external one
+        CancellationTokenSource? linkedCts = null;
+        CancellationToken effectiveToken = runCancellationToken;
+
+        if (ExternalCancellationTokenSource != null)
+        {
+            linkedCts = CancellationTokenSource.CreateLinkedTokenSource (runCancellationToken, ExternalCancellationTokenSource.Token);
+            effectiveToken = linkedCts.Token;
+        }
+
+        try
+        {
+            if (_inputQueue == null)
+            {
+                throw new ("Cannot run input before Initialization");
+            }
+
+            do
+            {
+                DateTime dt = Now ();
+
+                while (Peek ())
+                {
+                    foreach (TInputRecord r in Read ())
+                    {
+                        _inputQueue.Enqueue (r);
+                    }
+                }
+
+                effectiveToken.ThrowIfCancellationRequested ();
+            }
+            while (!effectiveToken.IsCancellationRequested);
+        }
+        catch (OperationCanceledException)
+        { }
+        finally
+        {
+            Logging.Trace($"Stopping input processing");
+            linkedCts?.Dispose ();
+        }
+    }
+
+    /// <summary>
+    ///     When implemented in a derived class, returns true if there is data available
+    ///     to read from console.
+    /// </summary>
+    /// <returns></returns>
+    public abstract bool Peek ();
+
+    /// <summary>
+    ///     Returns the available data without blocking, called when <see cref="Peek"/>
+    ///     returns <see langword="true"/>.
+    /// </summary>
+    /// <returns></returns>
+    public abstract IEnumerable<TInputRecord> Read ();
+
+    /// <inheritdoc/>
+    public virtual void Dispose () { }
+}

+ 117 - 79
Terminal.Gui/Drivers/InputProcessor.cs → Terminal.Gui/Drivers/InputProcessorImpl.cs

@@ -5,30 +5,67 @@ using Microsoft.Extensions.Logging;
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-///     Processes the queued input buffer contents - which must be of Type <typeparamref name="T"/>.
+///     Processes the queued input queue contents - which must be of Type <typeparamref name="TInputRecord"/>.
 ///     Is responsible for <see cref="ProcessQueue"/> and translating into common Terminal.Gui
-///     events and data models.
+///     events and data models. Runs on the main loop thread.
 /// </summary>
-public abstract class InputProcessor<T> : IInputProcessor
+public abstract class InputProcessorImpl<TInputRecord> : IInputProcessor, IDisposable where TInputRecord : struct
 {
+    /// <summary>
+    ///     Constructs base instance including wiring all relevant
+    ///     parser events and setting <see cref="InputQueue"/> to
+    ///     the provided thread safe input collection.
+    /// </summary>
+    /// <param name="inputBuffer">The collection that will be populated with new input (see <see cref="IInput{T}"/>)</param>
+    /// <param name="keyConverter">
+    ///     Key converter for translating driver specific
+    ///     <typeparamref name="TInputRecord"/> class into Terminal.Gui <see cref="Key"/>.
+    /// </param>
+    protected InputProcessorImpl (ConcurrentQueue<TInputRecord> inputBuffer, IKeyConverter<TInputRecord> keyConverter)
+    {
+        InputQueue = inputBuffer;
+        Parser.HandleMouse = true;
+        Parser.Mouse += (s, e) => RaiseMouseEvent (e);
+
+        Parser.HandleKeyboard = true;
+
+        Parser.Keyboard += (s, k) =>
+                           {
+                               RaiseKeyDownEvent (k);
+                               RaiseKeyUpEvent (k);
+                           };
+
+        // TODO: For now handle all other escape codes with ignore
+        Parser.UnexpectedResponseHandler = str =>
+                                           {
+                                               var cur = new string (str.Select (k => k.Item1).ToArray ());
+                                               Logging.Logger.LogInformation ($"{nameof (InputProcessorImpl<TInputRecord>)} ignored unrecognized response '{cur}'");
+                                               AnsiSequenceSwallowed?.Invoke (this, cur);
+
+                                               return true;
+                                           };
+        KeyConverter = keyConverter;
+    }
+
     /// <summary>
     ///     How long after Esc has been pressed before we give up on getting an Ansi escape sequence
     /// </summary>
     private readonly TimeSpan _escTimeout = TimeSpan.FromMilliseconds (50);
 
-    internal AnsiResponseParser<T> Parser { get; } = new ();
+    internal AnsiResponseParser<TInputRecord> Parser { get; } = new ();
 
     /// <summary>
-    ///     Class responsible for translating the driver specific native input class <typeparamref name="T"/> e.g.
+    ///     Class responsible for translating the driver specific native input class <typeparamref name="TInputRecord"/> e.g.
     ///     <see cref="ConsoleKeyInfo"/> into the Terminal.Gui <see cref="Key"/> class (used for all
     ///     internal library representations of Keys).
     /// </summary>
-    public IKeyConverter<T> KeyConverter { get; }
+    public IKeyConverter<TInputRecord> KeyConverter { get; }
 
     /// <summary>
-    ///     Input buffer which will be drained from by this class.
+    ///     The input queue which is filled by <see cref="IInput{TInputRecord}"/> implementations running on the input thread.
+    ///     Implementations of this class should dequeue from this queue in <see cref="ProcessQueue"/> on the main loop thread.
     /// </summary>
-    public ConcurrentQueue<T> InputBuffer { get; }
+    public ConcurrentQueue<TInputRecord> InputQueue { get; }
 
     /// <inheritdoc />
     public string? DriverName { get; init; }
@@ -38,110 +75,95 @@ public abstract class InputProcessor<T> : IInputProcessor
 
     private readonly MouseInterpreter _mouseInterpreter = new ();
 
-    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
+    /// <inheritdoc />
     public event EventHandler<Key>? KeyDown;
 
-    /// <summary>Event fired when a terminal sequence read from input is not recognized and therefore ignored.</summary>
+    /// <inheritdoc />
     public event EventHandler<string>? AnsiSequenceSwallowed;
 
-    /// <summary>
-    ///     Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
-    ///     <see cref="OnKeyUp"/>.
-    /// </summary>
-    /// <param name="a"></param>
-    public void OnKeyDown (Key a)
+    /// <inheritdoc />
+    public void RaiseKeyDownEvent (Key a)
     {
-        Logging.Trace ($"{nameof (InputProcessor<T>)} raised {a}");
         KeyDown?.Invoke (this, a);
     }
 
-    /// <summary>Event fired when a key is released.</summary>
-    /// <remarks>
-    ///     Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is
-    ///     complete.
-    /// </remarks>
+    /// <inheritdoc />
     public event EventHandler<Key>? KeyUp;
 
-    /// <summary>Called when a key is released. Fires the <see cref="KeyUp"/> event.</summary>
-    /// <remarks>
-    ///     Drivers that do not support key release events will call this method after <see cref="OnKeyDown"/> processing
-    ///     is complete.
-    /// </remarks>
-    /// <param name="a"></param>
-    public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
+    /// <inheritdoc />
+    public void RaiseKeyUpEvent (Key a) { KeyUp?.Invoke (this, a); }
 
-    /// <summary>Event fired when a mouse event occurs.</summary>
-    public event EventHandler<MouseEventArgs>? MouseEvent;
+    /// <summary>
+    /// 
+    /// </summary>
+    public IInput<TInputRecord>? InputImpl { get; set; }  // Set by MainLoopCoordinator
 
-    /// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
-    /// <param name="a"></param>
-    public void OnMouseEvent (MouseEventArgs a)
+    /// <inheritdoc />
+    public void EnqueueKeyDownEvent (Key key)
     {
-        // Ensure ScreenPosition is set
-        a.ScreenPosition = a.Position;
+        // Convert Key → TInputRecord
+        TInputRecord inputRecord = KeyConverter.ToKeyInfo (key);
 
-        foreach (MouseEventArgs e in _mouseInterpreter.Process (a))
+        // If input supports testing, use InputImplPeek/Read pipeline
+        // which runs on the input thread.
+        if (InputImpl is ITestableInput<TInputRecord> testableInput)
         {
-           // Logging.Trace ($"Mouse Interpreter raising {e.Flags}");
-
-            // Pass on
-            MouseEvent?.Invoke (this, e);
+            testableInput.AddInput (inputRecord);
         }
     }
 
-    /// <summary>
-    ///     Constructs base instance including wiring all relevant
-    ///     parser events and setting <see cref="InputBuffer"/> to
-    ///     the provided thread safe input collection.
-    /// </summary>
-    /// <param name="inputBuffer">The collection that will be populated with new input (see <see cref="IConsoleInput{T}"/>)</param>
-    /// <param name="keyConverter">
-    ///     Key converter for translating driver specific
-    ///     <typeparamref name="T"/> class into Terminal.Gui <see cref="Key"/>.
-    /// </param>
-    protected InputProcessor (ConcurrentQueue<T> inputBuffer, IKeyConverter<T> keyConverter)
+    /// <inheritdoc />
+    public void EnqueueKeyUpEvent (Key key)
     {
-        InputBuffer = inputBuffer;
-        Parser.HandleMouse = true;
-        Parser.Mouse += (s, e) => OnMouseEvent (e);
+        // TODO: Determine if we can still support this on Windows
+        throw new NotImplementedException ();
+    }
 
-        Parser.HandleKeyboard = true;
+    /// <inheritdoc />
+    public event EventHandler<MouseEventArgs>? MouseEvent;
 
-        Parser.Keyboard += (s, k) =>
-                           {
-                               OnKeyDown (k);
-                               OnKeyUp (k);
-                           };
+    /// <inheritdoc />
+    public virtual void EnqueueMouseEvent (MouseEventArgs mouseEvent)
+    {
+        // Base implementation: For drivers where TInputRecord cannot represent mouse events
+        // (e.g., ConsoleKeyInfo), derived classes should override this method.
+        // See WindowsInputProcessor for an example implementation that converts MouseEventArgs
+        // to InputRecord and enqueues it.
+        Logging.Logger.LogWarning (
+            $"{DriverName ?? "Unknown"} driver's InputProcessor does not support EnqueueMouseEvent. " +
+            "Override this method to enable mouse event enqueueing for testing.");
+    }
 
-        // TODO: For now handle all other escape codes with ignore
-        Parser.UnexpectedResponseHandler = str =>
-                                           {
-                                               var cur = new string (str.Select (k => k.Item1).ToArray ());
-                                               Logging.Logger.LogInformation ($"{nameof (InputProcessor<T>)} ignored unrecognized response '{cur}'");
-                                               AnsiSequenceSwallowed?.Invoke (this, cur);
+    /// <inheritdoc />
+    public void RaiseMouseEvent (MouseEventArgs a)
+    {
+        // Ensure ScreenPosition is set
+        a.ScreenPosition = a.Position;
 
-                                               return true;
-                                           };
-        KeyConverter = keyConverter;
+        foreach (MouseEventArgs e in _mouseInterpreter.Process (a))
+        {
+            // Logging.Trace ($"Mouse Interpreter raising {e.Flags}");
+
+            // Pass on
+            MouseEvent?.Invoke (this, e);
+        }
     }
 
-    /// <summary>
-    ///     Drains the <see cref="InputBuffer"/> buffer, processing all available keystrokes
-    /// </summary>
+    /// <inheritdoc />
     public void ProcessQueue ()
     {
-        while (InputBuffer.TryDequeue (out T? input))
+        while (InputQueue.TryDequeue (out TInputRecord input))
         {
             Process (input);
         }
 
-        foreach (T input in ReleaseParserHeldKeysIfStale ())
+        foreach (TInputRecord input in ReleaseParserHeldKeysIfStale ())
         {
             ProcessAfterParsing (input);
         }
     }
 
-    private IEnumerable<T> ReleaseParserHeldKeysIfStale ()
+    private IEnumerable<TInputRecord> ReleaseParserHeldKeysIfStale ()
     {
         if (Parser.State is AnsiResponseParserState.ExpectingEscapeSequence or AnsiResponseParserState.InResponse
             && DateTime.Now - Parser.StateChangedAt > _escTimeout)
@@ -154,17 +176,27 @@ public abstract class InputProcessor<T> : IInputProcessor
 
     /// <summary>
     ///     Process the provided single input element <paramref name="input"/>. This method
-    ///     is called sequentially for each value read from <see cref="InputBuffer"/>.
+    ///     is called sequentially for each value read from <see cref="InputQueue"/>.
     /// </summary>
     /// <param name="input"></param>
-    protected abstract void Process (T input);
+    protected abstract void Process (TInputRecord input);
 
     /// <summary>
     ///     Process the provided single input element - short-circuiting the <see cref="Parser"/>
     ///     stage of the processing.
     /// </summary>
     /// <param name="input"></param>
-    protected abstract void ProcessAfterParsing (T input);
+    protected virtual void ProcessAfterParsing (TInputRecord input)
+    {
+        var key = KeyConverter.ToKey (input);
+
+        // If the key is not valid, we don't want to raise any events.
+        if (IsValidInput (key, out key))
+        {
+            RaiseKeyDownEvent (key);
+            RaiseKeyUpEvent (key);
+        }
+    }
 
     private char _highSurrogate = '\0';
 
@@ -221,4 +253,10 @@ public abstract class InputProcessor<T> : IInputProcessor
 
         return true;
     }
+
+    /// <inheritdoc/>
+    public CancellationTokenSource? ExternalCancellationTokenSource { get; set; }
+
+    /// <inheritdoc />
+    public void Dispose () { ExternalCancellationTokenSource?.Dispose (); }
 }

+ 1 - 1
Terminal.Gui/Drivers/KeyCode.cs

@@ -2,7 +2,7 @@
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-///     The <see cref="KeyCode"/> enumeration encodes key information from <see cref="IConsoleDriver"/>s and provides a
+///     The <see cref="KeyCode"/> enumeration encodes key information from <see cref="IDriver"/>s and provides a
 ///     consistent way for application code to specify keys and receive key events.
 ///     <para>
 ///         The <see cref="Key"/> class provides a higher-level abstraction, with helper methods and properties for

+ 2 - 7
Terminal.Gui/Drivers/OutputBase.cs

@@ -2,7 +2,7 @@
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
-///     Abstract base class to assist with implementing <see cref="IConsoleOutput"/>.
+///     Abstract base class to assist with implementing <see cref="IOutput"/>.
 /// </summary>
 public abstract class OutputBase
 {
@@ -18,14 +18,9 @@ public abstract class OutputBase
     /// <param name="visibility"></param>
     public abstract void SetCursorVisibility (CursorVisibility visibility);
 
-    /// <inheritdoc cref="IConsoleOutput.Write(IOutputBuffer)"/>
+    /// <inheritdoc cref="IOutput.Write(IOutputBuffer)"/>
     public virtual void Write (IOutputBuffer buffer)
     {
-        if (ConsoleDriver.RunningUnitTests)
-        {
-            return;
-        }
-
         var top = 0;
         var left = 0;
         int rows = buffer.Rows;

+ 1 - 1
Terminal.Gui/Drivers/OutputBuffer.cs → Terminal.Gui/Drivers/OutputBufferImpl.cs

@@ -8,7 +8,7 @@ namespace Terminal.Gui.Drivers;
 ///     draw operations before being flushed to the console as part of the main loop.
 ///     operation
 /// </summary>
-public class OutputBuffer : IOutputBuffer
+public class OutputBufferImpl : IOutputBuffer
 {
     /// <summary>
     ///     The contents of the application output. The driver outputs this buffer to the terminal when

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است