Browse Source

Merge branch 'v2_develop' of tig:gui-cs/Terminal.Gui into v2_develop

Tig 1 month ago
parent
commit
d72cfcd317
100 changed files with 1960 additions and 3629 deletions
  1. 48 0
      .cursorrules
  2. 293 0
      .github/copilot-instructions.md
  3. 0 2
      .github/workflows/integration-tests.yml
  4. 2 2
      .github/workflows/unit-tests.yml
  5. 47 0
      AGENTS.md
  6. 4 4
      Directory.Packages.props
  7. 20 1
      Examples/Example/Example.cs
  8. 22 68
      Examples/UICatalog/Properties/launchSettings.json
  9. 1 1
      Examples/UICatalog/Scenarios/CombiningMarks.cs
  10. 1 1
      Examples/UICatalog/Scenarios/Images.cs
  11. 2 2
      Examples/UICatalog/Scenarios/Mazing.cs
  12. 8 72
      Examples/UICatalog/Scenarios/Notepad.cs
  13. 19 15
      Examples/UICatalog/Scenarios/Scrolling.cs
  14. 2 0
      Examples/UICatalog/Scenarios/Shortcuts.cs
  15. 1 1
      Examples/UICatalog/Scenarios/Themes.cs
  16. 0 227
      Examples/UICatalog/Scenarios/TileViewNesting.cs
  17. 38 13
      Examples/UICatalog/UICatalog.cs
  18. 1 1
      Terminal.Gui/App/Application.Driver.cs
  19. 47 40
      Terminal.Gui/App/Application.Initialization.cs
  20. 23 265
      Terminal.Gui/App/Application.Keyboard.cs
  21. 17 46
      Terminal.Gui/App/Application.Navigation.cs
  22. 6 27
      Terminal.Gui/App/Application.Run.cs
  23. 2 2
      Terminal.Gui/App/Application.Screen.cs
  24. 3 8
      Terminal.Gui/App/Application.cs
  25. 276 190
      Terminal.Gui/App/ApplicationImpl.cs
  26. 1 1
      Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs
  27. 32 9
      Terminal.Gui/App/IApplication.cs
  28. 113 0
      Terminal.Gui/App/Keyboard/IKeyboard.cs
  29. 381 0
      Terminal.Gui/App/Keyboard/Keyboard.cs
  30. 20 5
      Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs
  31. 13 4
      Terminal.Gui/App/MainLoop/IApplicationMainLoop.cs
  32. 48 0
      Terminal.Gui/App/MainLoop/IMainLoopCoordinator.cs
  33. 24 0
      Terminal.Gui/App/MainLoop/IMainLoopDriver.cs
  34. 13 26
      Terminal.Gui/App/MainLoop/LegacyMainLoopDriver.cs
  35. 17 13
      Terminal.Gui/App/MainLoop/MainLoopCoordinator.cs
  36. 0 0
      Terminal.Gui/App/MainLoop/MainLoopSyncContext.cs
  37. 4 3
      Terminal.Gui/App/NotInitializedException.cs
  38. 25 5
      Terminal.Gui/App/Timeout/TimedEvents.cs
  39. 1 1
      Terminal.Gui/App/Toplevel/IToplevelTransitionManager.cs
  40. 2 1
      Terminal.Gui/App/Toplevel/ToplevelTransitionManager.cs
  41. 3 0
      Terminal.Gui/Configuration/ConfigProperty.cs
  42. 30 20
      Terminal.Gui/Configuration/ConfigurationManager.cs
  43. 25 15
      Terminal.Gui/Configuration/SchemeManager.cs
  44. 22 26
      Terminal.Gui/Configuration/Scope.cs
  45. 8 9
      Terminal.Gui/Configuration/ScopeJsonConverter.cs
  46. 38 26
      Terminal.Gui/Configuration/ThemeManager.cs
  47. 9 31
      Terminal.Gui/Drawing/Attribute.cs
  48. 1 1
      Terminal.Gui/Drawing/Color/Color.cs
  49. 0 0
      Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequence.cs
  50. 0 0
      Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequenceRequest.cs
  51. 0 0
      Terminal.Gui/Drivers/AnsiHandling/AnsiMouseParser.cs
  52. 0 0
      Terminal.Gui/Drivers/AnsiHandling/AnsiRequestScheduler.cs
  53. 0 0
      Terminal.Gui/Drivers/AnsiHandling/AnsiResponseExpectation.cs
  54. 0 0
      Terminal.Gui/Drivers/AnsiHandling/AnsiResponseParser.cs
  55. 0 0
      Terminal.Gui/Drivers/AnsiHandling/AnsiResponseParserState.cs
  56. 0 0
      Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqReqStatus.cs
  57. 0 0
      Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqRequests.cs
  58. 2 2
      Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqUtils.cs
  59. 0 0
      Terminal.Gui/Drivers/AnsiHandling/GenericHeld.cs
  60. 0 0
      Terminal.Gui/Drivers/AnsiHandling/IAnsiResponseParser.cs
  61. 0 0
      Terminal.Gui/Drivers/AnsiHandling/IHeld.cs
  62. 0 0
      Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParser.cs
  63. 0 0
      Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParserPattern.cs
  64. 0 0
      Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiCursorPattern.cs
  65. 0 0
      Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiKeyPattern.cs
  66. 0 0
      Terminal.Gui/Drivers/AnsiHandling/Keyboard/EscAsAltPattern.cs
  67. 0 0
      Terminal.Gui/Drivers/AnsiHandling/Keyboard/Ss3Pattern.cs
  68. 0 0
      Terminal.Gui/Drivers/AnsiHandling/ReasonCannotSend.cs
  69. 0 0
      Terminal.Gui/Drivers/AnsiHandling/StringHeld.cs
  70. 0 0
      Terminal.Gui/Drivers/ComponentFactory.cs
  71. 8 25
      Terminal.Gui/Drivers/ConsoleDriver.cs
  72. 5 4
      Terminal.Gui/Drivers/ConsoleDriverFacade.cs
  73. 0 0
      Terminal.Gui/Drivers/ConsoleInput.cs
  74. 0 1040
      Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs
  75. 0 5
      Terminal.Gui/Drivers/CursesDriver/README.md
  76. 0 256
      Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs
  77. 0 95
      Terminal.Gui/Drivers/CursesDriver/UnmanagedLibrary.cs
  78. 0 746
      Terminal.Gui/Drivers/CursesDriver/binding.cs
  79. 0 177
      Terminal.Gui/Drivers/CursesDriver/constants.cs
  80. 0 86
      Terminal.Gui/Drivers/CursesDriver/handles.cs
  81. 0 0
      Terminal.Gui/Drivers/DotNetDriver/INetInput.cs
  82. 1 1
      Terminal.Gui/Drivers/DotNetDriver/NetComponentFactory.cs
  83. 0 0
      Terminal.Gui/Drivers/DotNetDriver/NetInput.cs
  84. 1 1
      Terminal.Gui/Drivers/DotNetDriver/NetInputProcessor.cs
  85. 0 0
      Terminal.Gui/Drivers/DotNetDriver/NetKeyConverter.cs
  86. 0 0
      Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs
  87. 0 0
      Terminal.Gui/Drivers/DotNetDriver/NetWinVTConsole.cs
  88. 49 0
      Terminal.Gui/Drivers/FakeDriver/FakeComponentFactory.cs
  89. 42 0
      Terminal.Gui/Drivers/FakeDriver/FakeConsoleInput.cs
  90. 88 0
      Terminal.Gui/Drivers/FakeDriver/FakeConsoleOutput.cs
  91. 6 4
      Terminal.Gui/Drivers/FakeDriver/FakeDriver.cs
  92. 41 0
      Terminal.Gui/Drivers/FakeDriver/FakeWindowSizeMonitor.cs
  93. 1 0
      Terminal.Gui/Drivers/IComponentFactory.cs
  94. 3 3
      Terminal.Gui/Drivers/IConsoleDriver.cs
  95. 0 0
      Terminal.Gui/Drivers/IConsoleDriverFacade.cs
  96. 0 0
      Terminal.Gui/Drivers/IConsoleInput.cs
  97. 0 0
      Terminal.Gui/Drivers/IConsoleOutput.cs
  98. 0 0
      Terminal.Gui/Drivers/IInputProcessor.cs
  99. 0 0
      Terminal.Gui/Drivers/IKeyConverter.cs
  100. 0 0
      Terminal.Gui/Drivers/IOutputBuffer.cs

+ 48 - 0
.cursorrules

@@ -0,0 +1,48 @@
+# Terminal.Gui - Cursor AI Rules
+
+This project uses comprehensive AI agent instructions. See:
+- `.github/copilot-instructions.md` - Complete onboarding guide (primary reference)
+- `AGENTS.md` - General AI agent guidelines
+
+## Quick Reference
+
+### Project Type
+- .NET 8.0 cross-platform console UI toolkit
+- 496 source files in core library
+- GitFlow branching (v2_develop = default)
+
+### Essential Commands
+
+```bash
+# Always run from repo root in this order:
+dotnet restore                                              # First! (~15-20s)
+dotnet build --configuration Debug --no-restore            # ~50s, expect ~326 warnings
+dotnet test Tests/UnitTestsParallelizable --no-build       # Preferred test suite
+dotnet run --project Examples/UICatalog/UICatalog.csproj   # Demo app
+```
+
+### Code Style (Enforced)
+- **DO**: Use explicit types (avoid `var`), target-typed `new()`, 4-space indent
+- **DO**: Format only files you modify (ReSharper/Rider `Ctrl-E-C` or `Ctrl-K-D`)
+- **DO**: Follow `.editorconfig` and `Terminal.sln.DotSettings`
+- **DON'T**: Add new linters, modify unrelated code, decrease test coverage
+
+### Testing Rules
+- Add new tests to `Tests/UnitTestsParallelizable/` (preferred)
+- Avoid `Application.Init` and static dependencies in tests
+- Don't use `[AutoInitShutdown]` attribute (legacy)
+- Maintain 70%+ code coverage on new code
+
+### API Documentation (Required)
+- All public APIs need XML docs (`<summary>`, `<remarks>`, `<example>`)
+- Use `<see cref=""/>` for cross-references
+- Complex topics → `docfx/docs/*.md`
+
+### Common Issues
+- ~326 build warnings are normal (nullable refs, etc.)
+- Tests can take 5-10 minutes
+- Run `dotnet restore` before any build
+- Read `.github/copilot-instructions.md` for full troubleshooting
+
+---
+**See `.github/copilot-instructions.md` for complete instructions**

+ 293 - 0
.github/copilot-instructions.md

@@ -0,0 +1,293 @@
+# Terminal.Gui - Copilot Coding Agent Instructions
+
+This file provides onboarding instructions for GitHub Copilot and other AI coding agents working with Terminal.Gui.
+
+## 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.
+
+**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)
+- **Branching**: GitFlow model (v2_develop is default/active development)
+
+## Building and Testing
+
+### Required Tools
+- **.NET SDK**: 8.0.0 (see `global.json`)
+- **Runtime**: .NET 8.x (latest GA)
+- **Optional**: ReSharper/Rider for code formatting
+
+### Build Commands (In Order)
+
+**ALWAYS run these commands from the repository root:**
+
+1. **Restore packages** (required first, ~15-20 seconds):
+   ```bash
+   dotnet restore
+   ```
+
+2. **Build solution** (Debug, ~50 seconds):
+   ```bash
+   dotnet build --configuration Debug --no-restore
+   ```
+   - Expect ~326 warnings (nullable reference warnings, unused variables, etc.) - these are normal
+   - 0 errors expected
+
+3. **Build Release** (for packaging):
+   ```bash
+   dotnet build --configuration Release --no-restore
+   ```
+
+### Test Commands
+
+**Two test projects exist:**
+
+1. **Non-parallel tests** (depend on static state, ~10 min timeout):
+   ```bash
+   dotnet test Tests/UnitTests --no-build --verbosity normal
+   ```
+   - Uses `Application.Init` and static state
+   - Cannot run in parallel
+   - Includes `--blame` flags for crash diagnostics
+
+2. **Parallel tests** (can run concurrently, ~10 min timeout):
+   ```bash
+   dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal
+   ```
+   - No dependencies on static state
+   - Preferred for new tests
+
+3. **Integration tests**:
+   ```bash
+   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
+```
+
+### Running Examples
+
+**UICatalog** (comprehensive demo app):
+```bash
+dotnet run --project Examples/UICatalog/UICatalog.csproj
+```
+
+## Repository Structure
+
+### Root Directory Files
+- `Terminal.sln` - Main solution file
+- `Terminal.sln.DotSettings` - ReSharper code style settings
+- `.editorconfig` - Code formatting rules (111KB, extensive)
+- `global.json` - .NET SDK version pinning
+- `Directory.Build.props` - Common MSBuild properties
+- `Directory.Packages.props` - Central package version management
+- `GitVersion.yml` - Version numbering configuration
+- `AGENTS.md` - General AI agent instructions (also useful reference)
+- `CONTRIBUTING.md` - Contribution guidelines
+- `README.md` - Project documentation
+
+### Main Directories
+
+**`/Terminal.Gui/`** - Core library (496 C# files):
+- `App/` - Application lifecycle (`Application.cs` static class, `RunState`, `MainLoop`)
+- `Configuration/` - `ConfigurationManager` for settings
+- `Drivers/` - Console driver implementations (`IConsoleDriver`, `NetDriver`, `UnixDriver`, `WindowsDriver`)
+- `Drawing/` - Rendering system (attributes, colors, glyphs)
+- `Input/` - Keyboard and mouse input handling
+- `ViewBase/` - Core `View` class hierarchy and layout
+- `Views/` - Specific View subclasses (Window, Dialog, Button, ListView, etc.)
+- `Text/` - Text manipulation and formatting
+
+**`/Tests/`**:
+- `UnitTests/` - Non-parallel tests (use `Application.Init`, static state)
+- `UnitTestsParallelizable/` - Parallel tests (no static dependencies)
+- `IntegrationTests/` - Integration tests
+- `StressTests/` - Long-running stress tests (scheduled daily)
+- `coverlet.runsettings` - Code coverage configuration
+
+**`/Examples/`**:
+- `UICatalog/` - Comprehensive demo app for manual testing
+- `Example/` - Basic example
+- `NativeAot/`, `SelfContained/` - Deployment examples
+- `ReactiveExample/`, `CommunityToolkitExample/` - Integration examples
+
+**`/docfx/`** - Documentation source:
+- `docs/` - Conceptual documentation (deep dives)
+- `api/` - Generated API docs (gitignored)
+- `docfx.json` - DocFX configuration
+
+**`/Scripts/`** - PowerShell build utilities (requires PowerShell 7.4+)
+
+**`/.github/workflows/`** - CI/CD pipelines:
+- `unit-tests.yml` - Main test workflow (Ubuntu, Windows, macOS)
+- `build-release.yml` - Release build verification
+- `integration-tests.yml` - Integration test workflow
+- `publish.yml` - NuGet package publishing
+- `api-docs.yml` - Documentation building and deployment
+- `codeql-analysis.yml` - Security scanning
+
+## Code Style and Quality
+
+### Formatting
+- **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**
+
+### Code Style Tenets
+1. **Six-Year-Old Reading Level** - Readability over terseness
+2. **Consistency, Consistency, Consistency** - Follow existing patterns ruthlessly
+3. **Don't be Weird** - Follow Microsoft/.NET conventions
+4. **Set and Forget** - Rely on automated tooling
+5. **Documentation is the Spec** - API docs are source of truth
+
+### Coding Conventions
+- Use explicit types (avoid `var` except for basic types like `int`, `string`)
+- Use target-typed `new()`
+- Follow `.editorconfig` settings (e.g., braces on new lines, spaces after keywords)
+- 4-space indentation
+- See `CONTRIBUTING.md` for full guidelines
+
+## Testing Requirements
+
+### Code Coverage
+- **Never decrease code coverage** - PRs must maintain or increase coverage
+- Target: 70%+ coverage for new code
+- CI monitors coverage on each PR
+
+### Test Patterns
+- **Parallelizable tests preferred** - Add new tests to `UnitTestsParallelizable` when possible
+- **Avoid static dependencies** - Don't use `Application.Init`, `ConfigurationManager` in tests
+- **Don't use `[AutoInitShutdown]`** - Legacy pattern, being phased out
+- **Make tests granular** - Each test should cover smallest area possible
+- Follow existing test patterns in respective test projects
+
+### Test Configuration
+- `xunit.runner.json` - xUnit configuration
+- `coverlet.runsettings` - Coverage settings (OpenCover format)
+
+## API Documentation Requirements
+
+**All public APIs MUST have XML documentation:**
+- Clear, concise `<summary>` tags
+- Use `<see cref=""/>` for cross-references
+- Add `<remarks>` for context
+- Include `<example>` for non-obvious usage
+- Complex topics → `docfx/docs/*.md` files
+- Proper English and grammar
+
+## Common Build Issues
+
+### Issue: Build Warnings
+- **Expected**: ~326 warnings (nullable refs, unused vars, xUnit suggestions)
+- **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
+  dotnet restore ./Examples/NativeAot/NativeAot.csproj -f
+  dotnet restore ./Examples/SelfContained/SelfContained.csproj -f
+  ```
+
+## CI/CD Validation
+
+The following checks run on PRs:
+
+1. **Unit Tests** (`unit-tests.yml`):
+   - Runs on Ubuntu, Windows, macOS
+   - Both parallel and non-parallel test suites
+   - Code coverage collection
+   - 10-minute timeout per job
+
+2. **Build Release** (`build-release.yml`):
+   - Verifies Release configuration builds
+   - Tests NativeAot and SelfContained builds
+   - Packs NuGet package
+
+3. **Integration Tests** (`integration-tests.yml`):
+   - Cross-platform integration testing
+   - 10-minute timeout
+
+4. **CodeQL Analysis** (`codeql-analysis.yml`):
+   - Security vulnerability scanning
+
+To replicate CI locally:
+```bash
+# Full CI sequence:
+dotnet restore
+dotnet build --configuration Debug --no-restore
+dotnet test Tests/UnitTests --no-build --verbosity normal
+dotnet test Tests/UnitTestsParallelizable --no-build --verbosity normal
+dotnet build --configuration Release --no-restore
+```
+
+## Branching and PRs
+
+### GitFlow Model
+- `v2_develop` - Default branch, active development
+- `v2_release` - Stable releases, matches NuGet
+- `v1_develop`, `v1_release` - Legacy v1 (maintenance only)
+
+### PR Requirements
+- **Title**: "Fixes #issue. Terse description"
+- **Description**: Include "- Fixes #issue" for each issue
+- **Tests**: Add tests for new functionality
+- **Coverage**: Maintain or increase code coverage
+- **Scenarios**: Update UICatalog scenarios when adding features
+
+## Key Architecture Concepts
+
+### View System
+- `View` base class in `/Terminal.Gui/ViewBase/`
+- Two layout modes: Absolute and Computed
+- Event-driven architecture
+- Adornments: Border, Margin, Padding
+
+### Console Drivers
+- `IConsoleDriver` interface
+- Platform-specific: `WindowsDriver`, `UnixDriver`, `NetDriver`
+- `FakeDriver` for testing
+
+### Application Lifecycle
+- `Application` static class manages lifecycle
+- `MainLoop` handles event processing
+- `RunState` tracks application state
+
+## What NOT to Do
+
+- ❌ Don't add new linters/formatters (use existing)
+- ❌ Don't modify unrelated code
+- ❌ Don't remove/edit unrelated tests
+- ❌ Don't break existing functionality
+- ❌ Don't add tests to `UnitTests` if they can be parallelizable
+- ❌ Don't use `Application.Init` in new tests
+- ❌ Don't decrease code coverage
+- ❌ Don't add `var` everywhere (use explicit types)
+
+## Additional Resources
+
+- **Full Documentation**: https://gui-cs.github.io/Terminal.Gui
+- **API Reference**: https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.App.html
+- **Deep Dives**: `/docfx/docs/` directory
+- **AGENTS.md**: Additional AI agent instructions
+- **CONTRIBUTING.md**: Detailed contribution guidelines
+
+---
+
+**Trust these instructions.** Only search for additional information if instructions are incomplete or incorrect.

+ 0 - 2
.github/workflows/integration-tests.yml

@@ -47,8 +47,6 @@ jobs:
       run: |
       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
        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
      
      
-       # mv -v Tests/IntegrationTests/TestResults/*/*.* TestResults/IntegrationTests/
-
     - name: Upload Test Logs
     - name: Upload Test Logs
       if: always()
       if: always()
       uses: actions/upload-artifact@v4
       uses: actions/upload-artifact@v4

+ 2 - 2
.github/workflows/unit-tests.yml

@@ -50,7 +50,7 @@ jobs:
 
 
     - name: Run UnitTests
     - name: Run UnitTests
       run: |
       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=true
+       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/
        # mv -v Tests/UnitTests/TestResults/*/*.* TestResults/UnitTests/
 
 
@@ -102,7 +102,7 @@ jobs:
 
 
     - name: Run UnitTestsParallelizable
     - name: Run UnitTestsParallelizable
       run: |
       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=true
+       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/
        # mv -v Tests/UnitTestsParallelizable/TestResults/*/*.* TestResults/UnitTestsParallelizable/
 
 

+ 47 - 0
AGENTS.md

@@ -130,3 +130,50 @@ dotnet test
 1. Maintain existing code structure and organization unless explicitly told
 1. Maintain existing code structure and organization unless explicitly told
 2. View sub-classes must not use private APIs
 2. View sub-classes must not use private APIs
 3. Suggest changes to the `./docfx/docs/` folder when appropriate
 3. Suggest changes to the `./docfx/docs/` folder when appropriate
+
+## Working with Pull Request Branches
+
+When creating PRs, include instructions at the end of each PR description for how to pull the branch down locally. Use the following template, adapted for the typical remote setup where `origin` points to the user's fork and `upstream` points to `gui-cs/Terminal.Gui`:
+
+```markdown
+## How to Pull This PR Branch Locally
+
+If you want to test or modify this PR locally, use one of these approaches based on your remote setup:
+
+### Method 1: Fetch from upstream (if branch exists there)
+```bash
+# Fetch the branch from upstream
+git fetch upstream <branch-name>
+
+# Switch to the branch
+git checkout <branch-name>
+
+# Make your changes, then commit them
+git add .
+git commit -m "Your commit message"
+
+# Push to your fork (origin)
+git push origin <branch-name>
+```
+
+### Method 2: Fetch by PR number
+```bash
+# Fetch the PR branch from upstream by PR number
+git fetch upstream pull/<PR_NUMBER>/head:<branch-name>
+
+# Switch to the branch
+git checkout <branch-name>
+
+# Make your changes, then commit them
+git add .
+git commit -m "Your commit message"
+
+# Push to your fork (origin)
+git push origin <branch-name>
+```
+
+The PR will automatically update when you push to the branch in your fork.
+```
+
+**Note:** Adjust the remote names if your setup differs (e.g., if `origin` points to `gui-cs/Terminal.Gui` and you have a separate remote for your fork).
+

+ 4 - 4
Directory.Packages.props

@@ -11,14 +11,14 @@
     <PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0" />
     <PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0" />
     <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[8,9)" />
     <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[8,9)" />
     <PackageVersion Include="ColorHelper" Version="[1.8.1,2)" />
     <PackageVersion Include="ColorHelper" Version="[1.8.1,2)" />
-    <PackageVersion Include="JetBrains.Annotations" Version="[2024.3.0,)" />
+    <PackageVersion Include="JetBrains.Annotations" Version="[2025.2.2,)" />
     <PackageVersion Include="Microsoft.CodeAnalysis" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
     <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
-    <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[9.0.2,10)" />
+    <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[9.0.0,10)" />
     <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" />
     <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" />
-    <PackageVersion Include="System.IO.Abstractions" Version="[22.0.11,23)" />
-    <PackageVersion Include="Wcwidth" Version="[2,3)" />
+    <PackageVersion Include="System.IO.Abstractions" Version="[22.0.16,23)" />
+    <PackageVersion Include="Wcwidth" Version="[3.0.0,)" />
     <PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21.2,2)" />
     <PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21.2,2)" />
     <PackageVersion Include="Serilog" Version="4.2.0" />
     <PackageVersion Include="Serilog" Version="4.2.0" />
     <PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />
     <PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />

+ 20 - 1
Examples/Example/Example.cs

@@ -11,9 +11,11 @@ using Terminal.Gui.Views;
 using Attribute = Terminal.Gui.Drawing.Attribute;
 using Attribute = Terminal.Gui.Drawing.Attribute;
 
 
 // Override the default configuration for the application to use the Light theme
 // Override the default configuration for the application to use the Light theme
-ConfigurationManager.RuntimeConfig = """{ "Theme": "Light" }""";
+//ConfigurationManager.RuntimeConfig = """{ "Theme": "Light" }""";
 ConfigurationManager.Enable(ConfigLocations.All);
 ConfigurationManager.Enable(ConfigLocations.All);
 
 
+
+
 Application.Run<ExampleWindow> ().Dispose ();
 Application.Run<ExampleWindow> ().Dispose ();
 
 
 // Before the application exits, reset Terminal.Gui for clean shutdown
 // Before the application exits, reset Terminal.Gui for clean shutdown
@@ -89,5 +91,22 @@ public class ExampleWindow : Window
 
 
         // Add the views to the Window
         // Add the views to the Window
         Add (usernameLabel, userNameText, passwordLabel, passwordText, btnLogin);
         Add (usernameLabel, userNameText, passwordLabel, passwordText, btnLogin);
+
+        ListView lv = new ListView ()
+        {
+            Y = Pos.AnchorEnd(),
+            Height= Dim.Auto(),
+            Width = Dim.Auto()
+        };
+        lv.SetSource (["One", "Two", "Three", "Four"]);
+        Add (lv);
+    }
+
+    public override void EndInit ()
+    {
+        base.EndInit ();
+        // Set the theme to "Anders" if it exists, otherwise use "Default"
+        ThemeManager.Theme = ThemeManager.GetThemeNames ().FirstOrDefault (x => x == "Anders") ?? "Default";
     }
     }
 }
 }
+ 

+ 22 - 68
Examples/UICatalog/Properties/launchSettings.json

@@ -4,25 +4,13 @@
       "commandName": "Project",
       "commandName": "Project",
       "commandLineArgs": "--debug-log-level Debug"
       "commandLineArgs": "--debug-log-level Debug"
     },
     },
-    "UICatalog --driver NetDriver": {
+    "UICatalog --driver windows": {
       "commandName": "Project",
       "commandName": "Project",
-      "commandLineArgs": "--driver NetDriver"
+      "commandLineArgs": "--driver windows -dl Trace"
     },
     },
-    "UICatalog --driver WindowsDriver": {
+    "UICatalog --driver dotnet": {
       "commandName": "Project",
       "commandName": "Project",
-      "commandLineArgs": "--driver WindowsDriver"
-    },
-    "UICatalog --driver v2": {
-      "commandName": "Project",
-      "commandLineArgs": "--driver v2 -dl Trace"
-    },
-    "UICatalog --driver v2win": {
-      "commandName": "Project",
-      "commandLineArgs": "--driver v2win -dl Trace"
-    },
-    "UICatalog --driver v2net": {
-      "commandName": "Project",
-      "commandLineArgs": "--driver v2net -dl Trace"
+      "commandLineArgs": "--driver dotnet -dl Trace"
     },
     },
     "WSL: UICatalog": {
     "WSL: UICatalog": {
       "commandName": "Executable",
       "commandName": "Executable",
@@ -30,28 +18,16 @@
       "commandLineArgs": "dotnet UICatalog.dll",
       "commandLineArgs": "dotnet UICatalog.dll",
       "distributionName": ""
       "distributionName": ""
     },
     },
-    "WSL: UICatalog --driver NetDriver": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver NetDriver",
-      "distributionName": ""
-    },
-    "WSL: UICatalog --driver v2": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2",
-      "distributionName": ""
-    },
-    "WSL: UICatalog --driver v2unix": {
+    "WSL: UICatalog --driver dotnet": {
       "commandName": "Executable",
       "commandName": "Executable",
       "executablePath": "wsl",
       "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2unix",
+      "commandLineArgs": "dotnet UICatalog.dll --driver dotnet",
       "distributionName": ""
       "distributionName": ""
     },
     },
-    "WSL: UICatalog --driver v2net": {
+    "WSL: UICatalog --driver unix": {
       "commandName": "Executable",
       "commandName": "Executable",
       "executablePath": "wsl",
       "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2net",
+      "commandLineArgs": "dotnet UICatalog.dll --driver unix",
       "distributionName": ""
       "distributionName": ""
     },
     },
     "WSL-Gnome: UICatalog": {
     "WSL-Gnome: UICatalog": {
@@ -60,45 +36,29 @@
       "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll; exec bash\"'",
       "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll; exec bash\"'",
       "distributionName": ""
       "distributionName": ""
     },
     },
-    "WSL-Gnome: UICatalog --driver NetDriver": {
+    "WSL-Gnome: UICatalog --driver dotnet": {
       "commandName": "Executable",
       "commandName": "Executable",
       "executablePath": "wsl",
       "executablePath": "wsl",
-      "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver NetDriver; exec bash\"'",
+      "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver dotnet; exec bash\"'",
       "distributionName": ""
       "distributionName": ""
     },
     },
-    "WSL-Gnome: UICatalog --driver v2": {
+    "WSL-Gnome: UICatalog --driver unix": {
       "commandName": "Executable",
       "commandName": "Executable",
       "executablePath": "wsl",
       "executablePath": "wsl",
-      "commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2; exec bash\"'",
-      "distributionName": ""
-    },
-    "WSL-Gnome: UICatalog --driver v2unix": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2unix; exec bash\"'",
-      "distributionName": ""
-    },
-    "WSL-Gnome: UICatalog --driver v2net": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2net; exec bash\"'",
+      "commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver unix; exec bash\"'",
       "distributionName": ""
       "distributionName": ""
     },
     },
     "Benchmark All": {
     "Benchmark All": {
       "commandName": "Project",
       "commandName": "Project",
       "commandLineArgs": "--benchmark"
       "commandLineArgs": "--benchmark"
     },
     },
-    "Benchmark All --driver NetDriver": {
-      "commandName": "Project",
-      "commandLineArgs": "--driver NetDriver --benchmark"
-    },
-    "Benchmark All --driver v2win": {
+    "Benchmark All --driver dotnet": {
       "commandName": "Project",
       "commandName": "Project",
-      "commandLineArgs": "--driver v2win --benchmark"
+      "commandLineArgs": "--driver dotnet --benchmark"
     },
     },
-    "Benchmark All --driver v2net": {
+    "Benchmark All --driver windows": {
       "commandName": "Project",
       "commandName": "Project",
-      "commandLineArgs": "--driver v2net --benchmark"
+      "commandLineArgs": "--driver windows --benchmark"
     },
     },
     "WSL: Benchmark All": {
     "WSL: Benchmark All": {
       "commandName": "Executable",
       "commandName": "Executable",
@@ -106,22 +66,16 @@
       "commandLineArgs": "dotnet UICatalog.dll --benchmark",
       "commandLineArgs": "dotnet UICatalog.dll --benchmark",
       "distributionName": ""
       "distributionName": ""
     },
     },
-    "WSL: Benchmark All --driver v2": {
-      "commandName": "Executable",
-      "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2 --benchmark",
-      "distributionName": ""
-    },
-    "WSL: Benchmark All --driver v2unix": {
+    "WSL: Benchmark All --driver unix": {
       "commandName": "Executable",
       "commandName": "Executable",
       "executablePath": "wsl",
       "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2unix --benchmark",
+      "commandLineArgs": "dotnet UICatalog.dll --driver unix --benchmark",
       "distributionName": ""
       "distributionName": ""
     },
     },
-    "WSL: Benchmark All --driver v2net": {
+    "WSL: Benchmark All --driver dotnet": {
       "commandName": "Executable",
       "commandName": "Executable",
       "executablePath": "wsl",
       "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll --driver v2net --benchmark",
+      "commandLineArgs": "dotnet UICatalog.dll --driver dotnet --benchmark",
       "distributionName": ""
       "distributionName": ""
     },
     },
     "Docker": {
     "Docker": {
@@ -135,9 +89,9 @@
       "commandName": "Project",
       "commandName": "Project",
       "commandLineArgs": "--disable-cm\r\n"
       "commandLineArgs": "--disable-cm\r\n"
     },
     },
-    "UICatalog --disable-cm --driver v2win": {
+    "UICatalog --disable-cm --driver windows": {
       "commandName": "Project",
       "commandName": "Project",
-      "commandLineArgs": "--disable-cm --driver v2win"
+      "commandLineArgs": "--disable-cm --driver windows"
     },
     },
     "Themes": {
     "Themes": {
       "commandName": "Project",
       "commandName": "Project",

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

@@ -13,7 +13,7 @@ public class CombiningMarks : Scenario
         top.DrawComplete += (s, e) =>
         top.DrawComplete += (s, e) =>
         {
         {
             // Forces reset _lineColsOffset because we're dealing with direct draw
             // Forces reset _lineColsOffset because we're dealing with direct draw
-            Application.ClearScreenNextIteration = true;
+            Application.Top!.SetNeedsDraw ();
 
 
             var i = -1;
             var i = -1;
             top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
             top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");

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

@@ -532,7 +532,7 @@ public class Images : Scenario
             // Application.Driver?.Move (_screenLocationForSixel.X, _screenLocationForSixel.Y);
             // Application.Driver?.Move (_screenLocationForSixel.X, _screenLocationForSixel.Y);
             // Application.Driver?.AddStr (_encodedSixelData);
             // Application.Driver?.AddStr (_encodedSixelData);
 
 
-            // Works in NetDriver but results in screen flicker when moving mouse but vanish instantly
+            // Works in DotNetDriver but results in screen flicker when moving mouse but vanish instantly
             // Console.SetCursorPosition (_screenLocationForSixel.X, _screenLocationForSixel.Y);
             // Console.SetCursorPosition (_screenLocationForSixel.X, _screenLocationForSixel.Y);
             // Console.Write (_encodedSixelData);
             // Console.Write (_encodedSixelData);
         }
         }

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

@@ -5,7 +5,7 @@ namespace UICatalog.Scenarios;
 
 
 [ScenarioMetadata ("A Mazing", "Illustrates how to make a basic maze game.")]
 [ScenarioMetadata ("A Mazing", "Illustrates how to make a basic maze game.")]
 [ScenarioCategory ("Drawing")]
 [ScenarioCategory ("Drawing")]
-[ScenarioCategory ("Mouse and KeyBoard")]
+[ScenarioCategory ("Mouse and Keyboard")]
 [ScenarioCategory ("Games")]
 [ScenarioCategory ("Games")]
 public class Mazing : Scenario
 public class Mazing : Scenario
 {
 {
@@ -33,7 +33,7 @@ public class Mazing : Scenario
         _top.KeyBindings.Add (Key.CursorDown, Command.Down);
         _top.KeyBindings.Add (Key.CursorDown, Command.Down);
 
 
         // Changing the key-bindings of a View is not allowed, however,
         // Changing the key-bindings of a View is not allowed, however,
-        // by default, Toplevel does't bind any of our movement keys, so
+        // by default, Toplevel doesn't bind any of our movement keys, so
         // we can take advantage of the CommandNotBound event to handle them
         // we can take advantage of the CommandNotBound event to handle them
         // 
         // 
         // An alternative implementation would be to create a TopLevel subclass that
         // An alternative implementation would be to create a TopLevel subclass that

+ 8 - 72
Examples/UICatalog/Scenarios/Notepad.cs

@@ -59,12 +59,12 @@ public class Notepad : Scenario
         _tabView.Style.ShowBorder = true;
         _tabView.Style.ShowBorder = true;
         _tabView.ApplyStyleChanges ();
         _tabView.ApplyStyleChanges ();
 
 
-        // Start with only a single view but support splitting to show side by side
-        var split = new TileView (1) { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) };
-        split.Tiles.ElementAt (0).ContentView.Add (_tabView);
-        split.LineStyle = LineStyle.None;
+        _tabView.X = 0;
+        _tabView.Y = 1;
+        _tabView.Width = Dim.Fill ();
+        _tabView.Height = Dim.Fill (1);
 
 
-        top.Add (split);
+        top.Add (_tabView);
         LenShortcut = new (Key.Empty, "Len: ", null);
         LenShortcut = new (Key.Empty, "Len: ", null);
 
 
         var statusBar = new StatusBar (new [] {
         var statusBar = new StatusBar (new [] {
@@ -199,38 +199,10 @@ public class Notepad : Scenario
         tab.View.Dispose ();
         tab.View.Dispose ();
         _focusedTabView = tv;
         _focusedTabView = tv;
 
 
+        // If last tab is closed, open a new one
         if (tv.Tabs.Count == 0)
         if (tv.Tabs.Count == 0)
         {
         {
-            var split = (TileView)tv.SuperView.SuperView;
-
-            // if it is the last TabView on screen don't drop it or we will
-            // be unable to open new docs!
-            if (split.IsRootTileView () && split.Tiles.Count == 1)
-            {
-                return;
-            }
-
-            int tileIndex = split.IndexOf (tv);
-            split.RemoveTile (tileIndex);
-
-            if (split.Tiles.Count == 0)
-            {
-                TileView parent = split.GetParentTileView ();
-
-                if (parent == null)
-                {
-                    return;
-                }
-
-                int idx = parent.IndexOf (split);
-
-                if (idx == -1)
-                {
-                    return;
-                }
-
-                parent.RemoveTile (idx);
-            }
+            New ();
         }
         }
     }
     }
 
 
@@ -286,37 +258,6 @@ public class Notepad : Scenario
 
 
     private void Quit () { Application.RequestStop (); }
     private void Quit () { Application.RequestStop (); }
 
 
-    private void Split (int offset, Orientation orientation, TabView sender, OpenedFile tab)
-    {
-        var split = (TileView)sender.SuperView.SuperView;
-        int tileIndex = split.IndexOf (sender);
-
-        if (tileIndex == -1)
-        {
-            return;
-        }
-
-        if (orientation != split.Orientation)
-        {
-            split.TrySplitTile (tileIndex, 1, out split);
-            split.Orientation = orientation;
-            tileIndex = 0;
-        }
-
-        Tile newTile = split.InsertTile (tileIndex + offset);
-        TabView newTabView = CreateNewTabView ();
-        tab.CloneTo (newTabView);
-        newTile.ContentView.Add (newTabView);
-
-        newTabView.FocusDeepest (NavigationDirection.Forward, null);
-        newTabView.AdvanceFocus (NavigationDirection.Forward, null);
-    }
-
-    private void SplitDown (TabView sender, OpenedFile tab) { Split (1, Orientation.Horizontal, sender, tab); }
-    private void SplitLeft (TabView sender, OpenedFile tab) { Split (0, Orientation.Vertical, sender, tab); }
-    private void SplitRight (TabView sender, OpenedFile tab) { Split (1, Orientation.Vertical, sender, tab); }
-    private void SplitUp (TabView sender, OpenedFile tab) { Split (0, Orientation.Horizontal, sender, tab); }
-
     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}";
@@ -346,12 +287,7 @@ public class Notepad : Scenario
             items =
             items =
             [
             [
                 new MenuItemv2 ("Save", "", () => Save (_focusedTabView, e.Tab)),
                 new MenuItemv2 ("Save", "", () => Save (_focusedTabView, e.Tab)),
-                new MenuItemv2 ("Close", "", () => Close (tv, e.Tab)),
-                new Line (),
-                new MenuItemv2 ("Split Up", "", () => SplitUp (tv, t)),
-                new MenuItemv2 ("Split Down", "", () => SplitDown (tv, t)),
-                new MenuItemv2 ("Split Right", "", () => SplitRight (tv, t)),
-                new MenuItemv2 ("Split Left", "", () => SplitLeft (tv, t))
+                new MenuItemv2 ("Close", "", () => Close (tv, e.Tab))
             ];
             ];
 
 
             PopoverMenu? contextMenu = new (items);
             PopoverMenu? contextMenu = new (items);

+ 19 - 15
Examples/UICatalog/Scenarios/Scrolling.cs

@@ -1,4 +1,8 @@
-namespace UICatalog.Scenarios;
+#nullable enable
+
+using System.Diagnostics;
+
+namespace UICatalog.Scenarios;
 
 
 [ScenarioMetadata ("Scrolling", "Content scrolling, IScrollBars, etc...")]
 [ScenarioMetadata ("Scrolling", "Content scrolling, IScrollBars, etc...")]
 [ScenarioCategory ("Controls")]
 [ScenarioCategory ("Controls")]
@@ -6,6 +10,8 @@
 [ScenarioCategory ("Tests")]
 [ScenarioCategory ("Tests")]
 public class Scrolling : Scenario
 public class Scrolling : Scenario
 {
 {
+    private object? _progressTimer = null;
+
     public override void Main ()
     public override void Main ()
     {
     {
         Application.Init ();
         Application.Init ();
@@ -38,10 +44,6 @@ public class Scrolling : Scenario
 
 
         app.Add (demoView);
         app.Add (demoView);
 
 
-        //// NOTE: This call to EnableScrollBar is technically not needed because the reference
-        //// NOTE: to demoView.HorizontalScrollBar below will cause it to be lazy created.
-        //// NOTE: The call included in this sample to for illustration purposes.
-        //demoView.EnableScrollBar (Orientation.Horizontal);
         var hCheckBox = new CheckBox
         var hCheckBox = new CheckBox
         {
         {
             X = Pos.X (demoView),
             X = Pos.X (demoView),
@@ -52,10 +54,6 @@ public class Scrolling : Scenario
         app.Add (hCheckBox);
         app.Add (hCheckBox);
         hCheckBox.CheckedStateChanged += (sender, args) => { demoView.HorizontalScrollBar.Visible = args.Value == CheckState.Checked; };
         hCheckBox.CheckedStateChanged += (sender, args) => { demoView.HorizontalScrollBar.Visible = args.Value == CheckState.Checked; };
 
 
-        //// NOTE: This call to EnableScrollBar is technically not needed because the reference
-        //// NOTE: to demoView.HorizontalScrollBar below will cause it to be lazy created.
-        //// NOTE: The call included in this sample to for illustration purposes.
-        //demoView.EnableScrollBar (Orientation.Vertical);
         var vCheckBox = new CheckBox
         var vCheckBox = new CheckBox
         {
         {
             X = Pos.Right (hCheckBox) + 3,
             X = Pos.Right (hCheckBox) + 3,
@@ -96,8 +94,6 @@ public class Scrolling : Scenario
 
 
         app.Add (progress);
         app.Add (progress);
 
 
-        var pulsing = true;
-
         app.Initialized += AppOnInitialized;
         app.Initialized += AppOnInitialized;
         app.Unloaded += AppUnloaded;
         app.Unloaded += AppUnloaded;
 
 
@@ -108,17 +104,25 @@ public class Scrolling : Scenario
 
 
         return;
         return;
 
 
-        void AppOnInitialized (object sender, EventArgs e)
+        void AppOnInitialized (object? sender, EventArgs e)
         {
         {
             bool TimerFn ()
             bool TimerFn ()
             {
             {
                 progress.Pulse ();
                 progress.Pulse ();
 
 
-                return pulsing;
+                return _progressTimer is { };
             }
             }
 
 
-            Application.AddTimeout (TimeSpan.FromMilliseconds (200), TimerFn);
+            _progressTimer = Application.AddTimeout (TimeSpan.FromMilliseconds (200), TimerFn);
+        }
+
+        void AppUnloaded (object? sender, EventArgs args)
+        {
+            if (_progressTimer is { })
+            {
+                Application.RemoveTimeout (_progressTimer);
+                _progressTimer = null;
+            }
         }
         }
-        void AppUnloaded (object sender, EventArgs args) { pulsing = false; }
     }
     }
 }
 }

+ 2 - 0
Examples/UICatalog/Scenarios/Shortcuts.cs

@@ -12,6 +12,7 @@ public class Shortcuts : Scenario
     public override void Main ()
     public override void Main ()
     {
     {
         Application.Init ();
         Application.Init ();
+        var quitKey = Application.QuitKey;
         Window app = new ();
         Window app = new ();
 
 
         app.Loaded += App_Loaded;
         app.Loaded += App_Loaded;
@@ -19,6 +20,7 @@ public class Shortcuts : Scenario
         Application.Run (app);
         Application.Run (app);
         app.Dispose ();
         app.Dispose ();
         Application.Shutdown ();
         Application.Shutdown ();
+        Application.QuitKey = quitKey;
     }
     }
 
 
     // Setting everything up in Loaded handler because we change the
     // Setting everything up in Loaded handler because we change the

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

@@ -172,7 +172,7 @@ public sealed class Themes : Scenario
                                                     else
                                                     else
                                                     {
                                                     {
                                                         appWindow.Remove (allViewsView);
                                                         appWindow.Remove (allViewsView);
-                                                        allViewsView.Dispose ();
+                                                        allViewsView!.Dispose ();
                                                         allViewsView = null;
                                                         allViewsView = null;
 
 
                                                         appWindow.Add (viewFrame);
                                                         appWindow.Add (viewFrame);

+ 0 - 227
Examples/UICatalog/Scenarios/TileViewNesting.cs

@@ -1,227 +0,0 @@
-using System.Linq;
-
-namespace UICatalog.Scenarios;
-
-[ScenarioMetadata ("Tile View Nesting", "Demonstrates recursive nesting of TileViews")]
-[ScenarioCategory ("Controls")]
-public class TileViewNesting : Scenario
-{
-    private CheckBox _cbBorder;
-    private CheckBox _cbHorizontal;
-    private CheckBox _cbTitles;
-    private CheckBox _cbUseLabels;
-    private TextField _textField;
-    private int _viewsCreated;
-    private int _viewsToCreate;
-    private View _workArea;
-
-    /// <summary>Setup the scenario.</summary>
-    public override void Main ()
-    {
-        Application.Init ();
-        // Scenario Windows.
-        var win = new Window
-        {
-            Title = GetName (),
-            Y = 1
-        };
-
-        var lblViews = new Label { Text = "Number Of Views:" };
-        _textField = new() { X = Pos.Right (lblViews), Width = 10, Text = "2" };
-
-        _textField.TextChanged += (s, e) => SetupTileView ();
-
-        _cbHorizontal = new() { X = Pos.Right (_textField) + 1, Text = "Horizontal" };
-        _cbHorizontal.CheckedStateChanged += (s, e) => SetupTileView ();
-
-        _cbBorder = new() { X = Pos.Right (_cbHorizontal) + 1, Text = "Border" };
-        _cbBorder.CheckedStateChanged += (s, e) => SetupTileView ();
-
-        _cbTitles = new() { X = Pos.Right (_cbBorder) + 1, Text = "Titles" };
-        _cbTitles.CheckedStateChanged += (s, e) => SetupTileView ();
-
-        _cbUseLabels = new() { X = Pos.Right (_cbTitles) + 1, Text = "Use Labels" };
-        _cbUseLabels.CheckedStateChanged += (s, e) => SetupTileView ();
-
-        _workArea = new() { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill () };
-
-        var menu = new MenuBar
-        {
-            Menus =
-            [
-                new ("_File", new MenuItem [] { new ("_Quit", "", () => Quit ()) })
-            ]
-        };
-
-        win.Add (lblViews);
-        win.Add (_textField);
-        win.Add (_cbHorizontal);
-        win.Add (_cbBorder);
-        win.Add (_cbTitles);
-        win.Add (_cbUseLabels);
-        win.Add (_workArea);
-
-        SetupTileView ();
-
-        var top = new Toplevel ();
-        top.Add (menu);
-        top.Add (win);
-
-        Application.Run (top);
-        top.Dispose ();
-        Application.Shutdown ();
-    }
-
-    private void AddMoreViews (TileView to)
-    {
-        if (_viewsCreated == _viewsToCreate)
-        {
-            return;
-        }
-
-        if (!(to.Tiles.ElementAt (0).ContentView is TileView))
-        {
-            Split (to, true);
-        }
-
-        if (!(to.Tiles.ElementAt (1).ContentView is TileView))
-        {
-            Split (to, false);
-        }
-
-        if (to.Tiles.ElementAt (0).ContentView is TileView && to.Tiles.ElementAt (1).ContentView is TileView)
-        {
-            AddMoreViews ((TileView)to.Tiles.ElementAt (0).ContentView);
-            AddMoreViews ((TileView)to.Tiles.ElementAt (1).ContentView);
-        }
-    }
-
-    private View CreateContentControl (int number) { return _cbUseLabels.CheckedState == CheckState.Checked ? CreateLabelView (number) : CreateTextView (number); }
-
-    private View CreateLabelView (int number)
-    {
-        return new Label
-        {
-            Width = Dim.Fill (),
-            Height = 1,
-
-            Text = number.ToString ().Repeat (1000),
-            CanFocus = true
-        };
-    }
-
-    private View CreateTextView (int number)
-    {
-        return new TextView
-        {
-            Width = Dim.Fill (), Height = Dim.Fill (), Text = number.ToString ().Repeat (1000), AllowsTab = false
-
-            //WordWrap = true,  // TODO: This is very slow (like 10s to render with 45 views)
-        };
-    }
-
-    private TileView CreateTileView (int titleNumber, Orientation orientation)
-    {
-        var toReturn = new TileView
-        {
-            Width = Dim.Fill (),
-            Height = Dim.Fill (),
-
-            // flip the orientation
-            Orientation = orientation
-        };
-
-        toReturn.Tiles.ElementAt (0).Title = _cbTitles.CheckedState == CheckState.Checked ? $"View {titleNumber}" : string.Empty;
-        toReturn.Tiles.ElementAt (1).Title = _cbTitles.CheckedState == CheckState.Checked ? $"View {titleNumber + 1}" : string.Empty;
-
-        return toReturn;
-    }
-
-    private int GetNumberOfViews ()
-    {
-        if (int.TryParse (_textField.Text, out int views) && views >= 0)
-        {
-            return views;
-        }
-
-        return 0;
-    }
-
-    private void Quit () { Application.RequestStop (); }
-
-    private void SetupTileView ()
-    {
-        int numberOfViews = GetNumberOfViews ();
-
-        CheckState titles = _cbTitles.CheckedState;
-        CheckState border = _cbBorder.CheckedState;
-        CheckState startHorizontal = _cbHorizontal.CheckedState;
-
-        foreach (View sub in _workArea.SubViews)
-        {
-            sub.Dispose ();
-        }
-
-        _workArea.RemoveAll ();
-
-        if (numberOfViews <= 0)
-        {
-            return;
-        }
-
-        TileView root = CreateTileView (1, startHorizontal == CheckState.Checked ? Orientation.Horizontal : Orientation.Vertical);
-
-        root.Tiles.ElementAt (0).ContentView.Add (CreateContentControl (1));
-        root.Tiles.ElementAt (0).Title = _cbTitles.CheckedState == CheckState.Checked ? "View 1" : string.Empty;
-        root.Tiles.ElementAt (1).ContentView.Add (CreateContentControl (2));
-        root.Tiles.ElementAt (1).Title = _cbTitles.CheckedState == CheckState.Checked ? "View 2" : string.Empty;
-
-        root.LineStyle = border  == CheckState.Checked? LineStyle.Rounded : LineStyle.None;
-
-        _workArea.Add (root);
-
-        if (numberOfViews == 1)
-        {
-            root.Tiles.ElementAt (1).ContentView.Visible = false;
-        }
-
-        if (numberOfViews > 2)
-        {
-            _viewsCreated = 2;
-            _viewsToCreate = numberOfViews;
-            AddMoreViews (root);
-        }
-    }
-
-    private void Split (TileView to, bool left)
-    {
-        if (_viewsCreated == _viewsToCreate)
-        {
-            return;
-        }
-
-        TileView newView;
-
-        if (left)
-        {
-            to.TrySplitTile (0, 2, out newView);
-        }
-        else
-        {
-            to.TrySplitTile (1, 2, out newView);
-        }
-
-        _viewsCreated++;
-
-        // During splitting the old Title will have been migrated to View1 so we only need
-        // to set the Title on View2 (the one that gets our new TextView)
-        newView.Tiles.ElementAt (1).Title = _cbTitles.CheckedState == CheckState.Checked ? $"View {_viewsCreated}" : string.Empty;
-
-        // Flip orientation
-        newView.Orientation = to.Orientation == Orientation.Vertical
-                                  ? Orientation.Horizontal
-                                  : Orientation.Vertical;
-
-        newView.Tiles.ElementAt (1).ContentView.Add (CreateContentControl (_viewsCreated));
-    }
-}

+ 38 - 13
Examples/UICatalog/UICatalog.cs

@@ -18,6 +18,7 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Globalization;
 using System.Reflection;
 using System.Reflection;
+using System.Reflection.Metadata;
 using System.Text;
 using System.Text;
 using System.Text.Json;
 using System.Text.Json;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
@@ -76,12 +77,25 @@ public class UICatalog
         // Process command line args
         // Process command line args
 
 
         // If no driver is provided, the default driver is used.
         // If no driver is provided, the default driver is used.
-        Option<string> driverOption = new Option<string> ("--driver", "The IConsoleDriver to use.").FromAmong (
-             Application.GetDriverTypes ().Item2.ToArray ()!
-            );
+        // Get allowed driver names
+        string? [] allowedDrivers = Application.GetDriverTypes ().Item2.ToArray ();
+
+        Option<string> driverOption = new Option<string> ("--driver", "The IConsoleDriver to use.")
+            .FromAmong (allowedDrivers!);
+        driverOption.SetDefaultValue (string.Empty);
         driverOption.AddAlias ("-d");
         driverOption.AddAlias ("-d");
         driverOption.AddAlias ("--d");
         driverOption.AddAlias ("--d");
 
 
+        // Add validator separately (not chained)
+        driverOption.AddValidator (result =>
+        {
+            var value = result.GetValueOrDefault<string> ();
+            if (result.Tokens.Count > 0 && !allowedDrivers.Contains (value))
+            {
+                result.ErrorMessage = $"Invalid driver name '{value}'. Allowed values: {string.Join (", ", allowedDrivers)}";
+            }
+        });
+
         // Configuration Management
         // Configuration Management
         Option<bool> disableConfigManagement = new (
         Option<bool> disableConfigManagement = new (
                                                     "--disable-cm",
                                                     "--disable-cm",
@@ -163,6 +177,17 @@ public class UICatalog
             return 0;
             return 0;
         }
         }
 
 
+        var parseResult = parser.Parse (args);
+
+        if (parseResult.Errors.Count > 0)
+        {
+            foreach (var error in parseResult.Errors)
+            {
+                Console.Error.WriteLine (error.Message);
+            }
+            return 1; // Non-zero exit code for error
+        }
+
         Scenario.BenchmarkTimeout = Options.BenchmarkTimeout;
         Scenario.BenchmarkTimeout = Options.BenchmarkTimeout;
 
 
         Logging.Logger = CreateLogger ();
         Logging.Logger = CreateLogger ();
@@ -175,16 +200,16 @@ public class UICatalog
     public static LogEventLevel LogLevelToLogEventLevel (LogLevel logLevel)
     public static LogEventLevel LogLevelToLogEventLevel (LogLevel logLevel)
     {
     {
         return logLevel switch
         return logLevel switch
-               {
-                   LogLevel.Trace => LogEventLevel.Verbose,
-                   LogLevel.Debug => LogEventLevel.Debug,
-                   LogLevel.Information => LogEventLevel.Information,
-                   LogLevel.Warning => LogEventLevel.Warning,
-                   LogLevel.Error => LogEventLevel.Error,
-                   LogLevel.Critical => LogEventLevel.Fatal,
-                   LogLevel.None => LogEventLevel.Fatal, // Default to Fatal if None is specified
-                   _ => LogEventLevel.Fatal // Default to Information for any unspecified LogLevel
-               };
+        {
+            LogLevel.Trace => LogEventLevel.Verbose,
+            LogLevel.Debug => LogEventLevel.Debug,
+            LogLevel.Information => LogEventLevel.Information,
+            LogLevel.Warning => LogEventLevel.Warning,
+            LogLevel.Error => LogEventLevel.Error,
+            LogLevel.Critical => LogEventLevel.Fatal,
+            LogLevel.None => LogEventLevel.Fatal, // Default to Fatal if None is specified
+            _ => LogEventLevel.Fatal // Default to Information for any unspecified LogLevel
+        };
     }
     }
 
 
     private static ILogger CreateLogger ()
     private static ILogger CreateLogger ()

+ 1 - 1
Terminal.Gui/App/Application.Driver.cs

@@ -20,7 +20,7 @@ public static partial class Application // Driver abstractions
 
 
     // BUGBUG: ForceDriver should be nullable.
     // BUGBUG: ForceDriver should be nullable.
     /// <summary>
     /// <summary>
-    ///     Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If not
+    ///     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.
     ///     specified, the driver is selected based on the platform.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>

+ 47 - 40
Terminal.Gui/App/Application.Initialization.cs

@@ -32,7 +32,7 @@ public static partial class Application // Initialization (Init/Shutdown)
     ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
     ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
     /// </param>
     /// </param>
     /// <param name="driverName">
     /// <param name="driverName">
-    ///     The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the
+    ///     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="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
     ///     specified the default driver for the platform will be used.
     ///     specified the default driver for the platform will be used.
     /// </param>
     /// </param>
@@ -40,7 +40,27 @@ public static partial class Application // Initialization (Init/Shutdown)
     [RequiresDynamicCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     public static void Init (IConsoleDriver? driver = null, string? driverName = null)
     public static void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
     {
-        ApplicationImpl.Instance.Init (driver, driverName);
+        // Check if this is a request for a legacy driver (like FakeDriver)
+        // that isn't supported by the modern application architecture
+        if (driver is null)
+        {
+            var driverNameToCheck = string.IsNullOrWhiteSpace (driverName) ? ForceDriver : driverName;
+            if (!string.IsNullOrEmpty (driverNameToCheck))
+            {
+                (List<Type?> drivers, List<string?> driverTypeNames) = GetDriverTypes ();
+                Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (driverNameToCheck, StringComparison.InvariantCultureIgnoreCase));
+                
+                // If it's a legacy IConsoleDriver (not a Facade), use InternalInit which supports legacy drivers
+                if (driverType is { } && !typeof (IConsoleDriverFacade).IsAssignableFrom (driverType))
+                {
+                    InternalInit (driver, driverName);
+                    return;
+                }
+            }
+        }
+        
+        // Otherwise delegate to the ApplicationImpl instance (which uses the modern architecture)
+        ApplicationImpl.Instance.Init (driver, driverName ?? ForceDriver);
     }
     }
 
 
     internal static int MainThreadId { get; set; } = -1;
     internal static int MainThreadId { get; set; } = -1;
@@ -90,44 +110,31 @@ public static partial class Application // Initialization (Init/Shutdown)
             ForceDriver = driverName;
             ForceDriver = driverName;
         }
         }
 
 
+        // Check if we need to use a legacy driver (like FakeDriver)
+        // or go through the modern application architecture
         if (Driver is null)
         if (Driver is null)
         {
         {
-            PlatformID p = Environment.OSVersion.Platform;
-
-            if (string.IsNullOrEmpty (ForceDriver))
+            //// Try to find a legacy IConsoleDriver type that matches the driver name
+            //bool useLegacyDriver = false;
+            //if (!string.IsNullOrEmpty (ForceDriver))
+            //{
+            //    (List<Type?> drivers, List<string?> driverTypeNames) = GetDriverTypes ();
+            //    Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase));
+                
+            //    if (driverType is { } && !typeof (IConsoleDriverFacade).IsAssignableFrom (driverType))
+            //    {
+            //        // This is a legacy driver (not a ConsoleDriverFacade)
+            //        Driver = (IConsoleDriver)Activator.CreateInstance (driverType)!;
+            //        useLegacyDriver = true;
+            //    }
+            //}
+            
+            //// Use the modern application architecture
+            //if (!useLegacyDriver)
             {
             {
-                if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
-                {
-                    Driver = new WindowsDriver ();
-                }
-                else
-                {
-                    Driver = new CursesDriver ();
-                }
-            }
-            else
-            {
-                (List<Type?> drivers, List<string?> driverTypeNames) = GetDriverTypes ();
-                Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase));
-
-                if (driverType is { })
-                {
-                    Driver = (IConsoleDriver)Activator.CreateInstance (driverType)!;
-                }
-                else if (ForceDriver?.StartsWith ("v2") ?? false)
-                {
-                    ApplicationImpl.ChangeInstance (new ApplicationV2 ());
-                    ApplicationImpl.Instance.Init (driver, ForceDriver);
-                    Debug.Assert (Driver is { });
-
-                    return;
-                }
-                else
-                {
-                    throw new ArgumentException (
-                                                 $"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t!.Name))}"
-                                                );
-                }
+                ApplicationImpl.Instance.Init (driver, driverName);
+                Debug.Assert (Driver is { });
+                return;
             }
             }
         }
         }
 
 
@@ -137,8 +144,6 @@ public static partial class Application // Initialization (Init/Shutdown)
         Debug.Assert (Popover is null);
         Debug.Assert (Popover is null);
         Popover = new ();
         Popover = new ();
 
 
-        AddKeyBindings ();
-
         try
         try
         {
         {
             MainLoop = Driver!.Init ();
             MainLoop = Driver!.Init ();
@@ -217,9 +222,11 @@ public static partial class Application // Initialization (Init/Shutdown)
         List<string?> driverTypeNames = driverTypes
         List<string?> driverTypeNames = driverTypes
                                         .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
                                         .Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
                                         .Select (d => d!.Name)
                                         .Select (d => d!.Name)
-                                        .Union (["v2", "v2win", "v2net", "v2unix"])
+                                        .Union (["dotnet", "windows", "unix", "fake"])
                                         .ToList ()!;
                                         .ToList ()!;
 
 
+
+
         return (driverTypes, driverTypeNames);
         return (driverTypes, driverTypeNames);
     }
     }
 
 

+ 23 - 265
Terminal.Gui/App/Application.Keyboard.cs

@@ -4,6 +4,16 @@ namespace Terminal.Gui.App;
 
 
 public static partial class Application // Keyboard handling
 public static partial class Application // Keyboard handling
 {
 {
+    /// <summary>
+    /// Static reference to the current <see cref="IApplication"/> <see cref="IKeyboard"/>.
+    /// </summary>
+    public static IKeyboard Keyboard
+    {
+        get => ApplicationImpl.Instance.Keyboard;
+        set => ApplicationImpl.Instance.Keyboard = value ??
+                                                           throw new ArgumentNullException(nameof(value));
+    }
+
     /// <summary>
     /// <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="IConsoleDriver"/>). Raises the cancelable
     ///     <see cref="KeyDown"/> event, then calls <see cref="View.NewKeyDownEvent"/> on all top level views, and finally
     ///     <see cref="KeyDown"/> event, then calls <see cref="View.NewKeyDownEvent"/> on all top level views, and finally
@@ -12,63 +22,7 @@ public static partial class Application // Keyboard handling
     /// <remarks>Can be used to simulate key press events.</remarks>
     /// <remarks>Can be used to simulate key press events.</remarks>
     /// <param name="key"></param>
     /// <param name="key"></param>
     /// <returns><see langword="true"/> if the key was handled.</returns>
     /// <returns><see langword="true"/> if the key was handled.</returns>
-    public static bool RaiseKeyDownEvent (Key key)
-    {
-        Logging.Debug ($"{key}");
-
-        // TODO: Add a way to ignore certain keys, esp for debugging.
-        //#if DEBUG
-        //        if (key == Key.Empty.WithAlt || key == Key.Empty.WithCtrl)
-        //        {
-        //            Logging.Debug ($"Ignoring {key}");
-        //            return false;
-        //        }
-        //#endif
-
-        // TODO: This should match standard event patterns
-        KeyDown?.Invoke (null, key);
-
-        if (key.Handled)
-        {
-            return true;
-        }
-
-        if (Popover?.DispatchKeyDown (key) is true)
-        {
-            return true;
-        }
-
-        if (Top is null)
-        {
-            foreach (Toplevel topLevel in TopLevels.ToList ())
-            {
-                if (topLevel.NewKeyDownEvent (key))
-                {
-                    return true;
-                }
-
-                if (topLevel.Modal)
-                {
-                    break;
-                }
-            }
-        }
-        else
-        {
-            if (Top.NewKeyDownEvent (key))
-            {
-                return true;
-            }
-        }
-
-        bool? commandHandled = InvokeCommandsBoundToKey (key);
-        if(commandHandled is true)
-        {
-            return true;
-        }
-
-        return false;
-    }
+    public static bool RaiseKeyDownEvent (Key key) => Keyboard.RaiseKeyDownEvent (key);
 
 
     /// <summary>
     /// <summary>
     ///     Invokes any commands bound at the Application-level to <paramref name="key"/>.
     ///     Invokes any commands bound at the Application-level to <paramref name="key"/>.
@@ -79,38 +33,7 @@ public static partial class Application // Keyboard handling
     ///     <see langword="false"/> if the command was invoked and was not handled (or cancelled); 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.
     ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
     /// </returns>
     /// </returns>
-    public static bool? InvokeCommandsBoundToKey (Key key)
-    {
-        bool? handled = null;
-        // Invoke any Application-scoped KeyBindings.
-        // The first view that handles the key will stop the loop.
-        // foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
-        if (KeyBindings.TryGet (key, out KeyBinding binding))
-        {
-            if (binding.Target is { })
-            {
-                if (!binding.Target.Enabled)
-                {
-                    return null;
-                }
-
-                handled = binding.Target?.InvokeCommands (binding.Commands, binding);
-            }
-            else
-            {
-                bool? toReturn = null;
-
-                foreach (Command command in binding.Commands)
-                {
-                    toReturn = InvokeCommand (command, key, binding);
-                }
-
-                handled = toReturn ?? true;
-            }
-        }
-
-        return handled;
-    }
+    public static bool? InvokeCommandsBoundToKey (Key key) => Keyboard.InvokeCommandsBoundToKey (key);
 
 
     /// <summary>
     /// <summary>
     ///     Invokes an Application-bound command.
     ///     Invokes an Application-bound command.
@@ -124,24 +47,7 @@ public static partial class Application // Keyboard handling
     ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
     ///     <see langword="true"/> if the command was invoked the command was handled (or cancelled); input processing should stop.
     /// </returns>
     /// </returns>
     /// <exception cref="NotSupportedException"></exception>
     /// <exception cref="NotSupportedException"></exception>
-    public static bool? InvokeCommand (Command command, Key key, KeyBinding binding)
-    {
-        if (!_commandImplementations!.ContainsKey (command))
-        {
-            throw new NotSupportedException (
-                                             @$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by Application."
-                                            );
-        }
-
-        if (_commandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
-        {
-            CommandContext<KeyBinding> context = new (command, null, binding); // Create the context here
-
-            return implementation (context);
-        }
-
-        return null;
-    }
+    public static bool? InvokeCommand (Command command, Key key, KeyBinding binding) => Keyboard.InvokeCommand (command, key, binding);
 
 
     /// <summary>
     /// <summary>
     ///     Raised when the user presses a key.
     ///     Raised when the user presses a key.
@@ -151,11 +57,15 @@ public static partial class Application // Keyboard handling
     ///     </para>
     ///     </para>
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
-    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Unix) do not support firing the
     ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
     ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
     ///     <para>Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.</para>
     ///     <para>Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.</para>
     /// </remarks>
     /// </remarks>
-    public static event EventHandler<Key>? KeyDown;
+    public static event EventHandler<Key>? KeyDown
+    {
+        add => Keyboard.KeyDown += value;
+        remove => Keyboard.KeyDown -= value;
+    }
 
 
     /// <summary>
     /// <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="IConsoleDriver"/>). Raises the cancelable
@@ -166,168 +76,16 @@ public static partial class Application // Keyboard handling
     /// <remarks>Can be used to simulate key release events.</remarks>
     /// <remarks>Can be used to simulate key release events.</remarks>
     /// <param name="key"></param>
     /// <param name="key"></param>
     /// <returns><see langword="true"/> if the key was handled.</returns>
     /// <returns><see langword="true"/> if the key was handled.</returns>
-    public static bool RaiseKeyUpEvent (Key key)
-    {
-        if (!Initialized)
-        {
-            return true;
-        }
-
-        KeyUp?.Invoke (null, key);
-
-        if (key.Handled)
-        {
-            return true;
-        }
-
-
-        // TODO: Add Popover support
-
-        foreach (Toplevel topLevel in TopLevels.ToList ())
-        {
-            if (topLevel.NewKeyUpEvent (key))
-            {
-                return true;
-            }
-
-            if (topLevel.Modal)
-            {
-                break;
-            }
-        }
-
-        return false;
-    }
-
-    #region Application-scoped KeyBindings
-
-    static Application ()
-    {
-        AddKeyBindings ();
-    }
+    public static bool RaiseKeyUpEvent (Key key) => Keyboard.RaiseKeyUpEvent (key);
 
 
     /// <summary>Gets the Application-scoped key bindings.</summary>
     /// <summary>Gets the Application-scoped key bindings.</summary>
-    public static KeyBindings KeyBindings { get; internal set; } = new (null);
+    public static KeyBindings KeyBindings => Keyboard.KeyBindings;
 
 
     internal static void AddKeyBindings ()
     internal static void AddKeyBindings ()
     {
     {
-        _commandImplementations.Clear ();
-
-        // Things Application knows how to do
-        AddCommand (
-                    Command.Quit,
-                    static () =>
-                    {
-                        RequestStop ();
-
-                        return true;
-                    }
-                   );
-        AddCommand (
-                    Command.Suspend,
-                    static () =>
-                    {
-                        Driver?.Suspend ();
-
-                        return true;
-                    }
-                   );
-        AddCommand (
-                    Command.NextTabStop,
-                    static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));
-
-        AddCommand (
-                    Command.PreviousTabStop,
-                    static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop));
-
-        AddCommand (
-                    Command.NextTabGroup,
-                    static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup));
-
-        AddCommand (
-                    Command.PreviousTabGroup,
-                    static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup));
-
-        AddCommand (
-                    Command.Refresh,
-                    static () =>
-                    {
-                        LayoutAndDraw (true);
-
-                        return true;
-                    }
-                   );
-
-        AddCommand (
-                    Command.Arrange,
-                    static () =>
-                    {
-                        View? viewToArrange = Navigation?.GetFocused ();
-
-                        // Go up the superview hierarchy and find the first that is not ViewArrangement.Fixed
-                        while (viewToArrange is { SuperView: { }, Arrangement: ViewArrangement.Fixed })
-                        {
-                            viewToArrange = viewToArrange.SuperView;
-                        }
-
-                        if (viewToArrange is { })
-                        {
-                            return viewToArrange.Border?.EnterArrangeMode (ViewArrangement.Fixed);
-                        }
-
-                        return false;
-                    });
-
-        //SetKeysToHardCodedDefaults ();
-
-        // Need to clear after setting the above to ensure actually clear
-        // because set_QuitKey etc.. may call Add
-        KeyBindings.Clear ();
-
-        KeyBindings.Add (QuitKey, Command.Quit);
-        KeyBindings.Add (NextTabKey, Command.NextTabStop);
-        KeyBindings.Add (PrevTabKey, Command.PreviousTabStop);
-        KeyBindings.Add (NextTabGroupKey, Command.NextTabGroup);
-        KeyBindings.Add (PrevTabGroupKey, Command.PreviousTabGroup);
-        KeyBindings.Add (ArrangeKey, Command.Arrange);
-
-        KeyBindings.Add (Key.CursorRight, Command.NextTabStop);
-        KeyBindings.Add (Key.CursorDown, Command.NextTabStop);
-        KeyBindings.Add (Key.CursorLeft, Command.PreviousTabStop);
-        KeyBindings.Add (Key.CursorUp, Command.PreviousTabStop);
-
-        // TODO: Refresh Key should be configurable
-        KeyBindings.Add (Key.F5, Command.Refresh);
-
-        // TODO: Suspend Key should be configurable
-        if (Environment.OSVersion.Platform == PlatformID.Unix)
+        if (Keyboard is Keyboard keyboard)
         {
         {
-            KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
+            keyboard.AddKeyBindings ();
         }
         }
     }
     }
-
-    #endregion Application-scoped KeyBindings
-
-    /// <summary>
-    ///     <para>
-    ///         Sets the function that will be invoked for a <see cref="Command"/>.
-    ///     </para>
-    ///     <para>
-    ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
-    ///         replace the old one.
-    ///     </para>
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         This version of AddCommand is for commands that do not require a <see cref="ICommandContext"/>.
-    ///     </para>
-    /// </remarks>
-    /// <param name="command">The command.</param>
-    /// <param name="f">The function.</param>
-    private static void AddCommand (Command command, Func<bool?> f) { _commandImplementations! [command] = ctx => f (); }
-
-    /// <summary>
-    ///     Commands for Application.
-    /// </summary>
-    private static readonly Dictionary<Command, View.CommandImplementation> _commandImplementations = new ();
 }
 }

+ 17 - 46
Terminal.Gui/App/Application.Navigation.cs

@@ -9,42 +9,22 @@ public static partial class Application // Navigation stuff
     /// </summary>
     /// </summary>
     public static ApplicationNavigation? Navigation { get; internal set; }
     public static ApplicationNavigation? Navigation { get; internal set; }
 
 
-    private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrides
-    private static Key _nextTabKey = Key.Tab; // Resources/config.json overrides
-    private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrides
-    private static Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrides
-
     /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
     /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key NextTabGroupKey
     public static Key NextTabGroupKey
     {
     {
-        get => _nextTabGroupKey;
-        set
-        {
-            //if (_nextTabGroupKey != value)
-            {
-                KeyBindings.Replace (_nextTabGroupKey, value);
-                _nextTabGroupKey = value;
-            }
-        }
+        get => Keyboard.NextTabGroupKey;
+        set => Keyboard.NextTabGroupKey = value;
     }
     }
 
 
-    /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
+    /// <summary>Alternative key to navigate forwards through views. Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key NextTabKey
     public static Key NextTabKey
     {
     {
-        get => _nextTabKey;
-        set
-        {
-            //if (_nextTabKey != value)
-            {
-                KeyBindings.Replace (_nextTabKey, value);
-                _nextTabKey = value;
-            }
-        }
+        get => Keyboard.NextTabKey;
+        set => Keyboard.NextTabKey = value;
     }
     }
 
 
-
     /// <summary>
     /// <summary>
     ///     Raised when the user releases a key.
     ///     Raised when the user releases a key.
     ///     <para>
     ///     <para>
@@ -53,38 +33,29 @@ public static partial class Application // Navigation stuff
     ///     </para>
     ///     </para>
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
-    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Unix) do not support firing the
     ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
     ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
     ///     <para>Fired after <see cref="KeyDown"/>.</para>
     ///     <para>Fired after <see cref="KeyDown"/>.</para>
     /// </remarks>
     /// </remarks>
-    public static event EventHandler<Key>? KeyUp;
+    public static event EventHandler<Key>? KeyUp
+    {
+        add => Keyboard.KeyUp += value;
+        remove => Keyboard.KeyUp -= value;
+    }
+
     /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
     /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key PrevTabGroupKey
     public static Key PrevTabGroupKey
     {
     {
-        get => _prevTabGroupKey;
-        set
-        {
-            //if (_prevTabGroupKey != value)
-            {
-                KeyBindings.Replace (_prevTabGroupKey, value);
-                _prevTabGroupKey = value;
-            }
-        }
+        get => Keyboard.PrevTabGroupKey;
+        set => Keyboard.PrevTabGroupKey = value;
     }
     }
 
 
-    /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
+    /// <summary>Alternative key to navigate backwards through views. Shift+Tab is the primary key.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key PrevTabKey
     public static Key PrevTabKey
     {
     {
-        get => _prevTabKey;
-        set
-        {
-            //if (_prevTabKey != value)
-            {
-                KeyBindings.Replace (_prevTabKey, value);
-                _prevTabKey = value;
-            }
-        }
+        get => Keyboard.PrevTabKey;
+        set => Keyboard.PrevTabKey = value;
     }
     }
 }
 }

+ 6 - 27
Terminal.Gui/App/Application.Run.cs

@@ -6,38 +6,20 @@ namespace Terminal.Gui.App;
 
 
 public static partial class Application // Run (Begin, Run, End, Stop)
 public static partial class Application // Run (Begin, Run, End, Stop)
 {
 {
-    private static Key _quitKey = Key.Esc; // Resources/config.json overrides
-
     /// <summary>Gets or sets the key to quit the application.</summary>
     /// <summary>Gets or sets the key to quit the application.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key QuitKey
     public static Key QuitKey
     {
     {
-        get => _quitKey;
-        set
-        {
-            //if (_quitKey != value)
-            {
-                KeyBindings.Replace (_quitKey, value);
-                _quitKey = value;
-            }
-        }
+        get => Keyboard.QuitKey;
+        set => Keyboard.QuitKey = value;
     }
     }
 
 
-    private static Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
-
     /// <summary>Gets or sets the key to activate arranging views using the keyboard.</summary>
     /// <summary>Gets or sets the key to activate arranging views using the keyboard.</summary>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
     public static Key ArrangeKey
     public static Key ArrangeKey
     {
     {
-        get => _arrangeKey;
-        set
-        {
-            //if (_arrangeKey != value)
-            {
-                KeyBindings.Replace (_arrangeKey, value);
-                _arrangeKey = value;
-            }
-        }
+        get => Keyboard.ArrangeKey;
+        set => Keyboard.ArrangeKey = value;
     }
     }
 
 
     // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`.
     // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`.
@@ -333,8 +315,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     /// <param name="errorHandler"></param>
     /// <param name="errorHandler"></param>
     /// <param name="driver">
     /// <param name="driver">
     ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
     ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
-    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
-    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
+    ///     be used. Must be <see langword="null"/> if <see cref="Init"/> has already been called.
     /// </param>
     /// </param>
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresUnreferencedCode ("AOT")]
@@ -425,9 +406,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     ///     If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and
     ///     If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and
     ///     should only be overriden for testing.
     ///     should only be overriden for testing.
     /// </param>
     /// </param>
-    public static void LayoutAndDraw (bool forceDraw = false) { ApplicationImpl.Instance.LayoutAndDraw (forceDraw); }
-
-    internal static void LayoutAndDrawImpl (bool forceDraw = false)
+    public static void LayoutAndDraw (bool forceDraw = false)
     {
     {
         List<View> tops = [.. TopLevels];
         List<View> tops = [.. TopLevels];
 
 

+ 2 - 2
Terminal.Gui/App/Application.Screen.cs

@@ -82,8 +82,8 @@ public static partial class Application // Screen related stuff
     ///     Gets or sets whether the screen will be cleared, and all Views redrawn, during the next Application iteration.
     ///     Gets or sets whether the screen will be cleared, and all Views redrawn, during the next Application iteration.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
-    ///     This is typicall set to true when a View's <see cref="View.Frame"/> changes and that view has no
+    ///     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.
     ///     SuperView (e.g. when <see cref="Application.Top"/> is moved or resized.
     /// </remarks>
     /// </remarks>
-    public static bool ClearScreenNextIteration { get; set; }
+    internal static bool ClearScreenNextIteration { get; set; }
 }
 }

+ 3 - 8
Terminal.Gui/App/Application.cs

@@ -179,8 +179,6 @@ public static partial class Application
     // starts running and after Shutdown returns.
     // starts running and after Shutdown returns.
     internal static void ResetState (bool ignoreDisposed = false)
     internal static void ResetState (bool ignoreDisposed = false)
     {
     {
-        Navigation = new ();
-
         // Shutdown is the bookend for Init. As such it needs to clean up all resources
         // 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.
         // Init created. Apps that do any threading will need to code defensively for this.
         // e.g. see Issue #537
         // e.g. see Issue #537
@@ -245,6 +243,7 @@ public static partial class Application
         NotifyNewRunState = null;
         NotifyNewRunState = null;
         NotifyStopRunState = null;
         NotifyStopRunState = null;
         MouseGrabHandler = new MouseGrabHandler ();
         MouseGrabHandler = new MouseGrabHandler ();
+        // Keyboard will be lazy-initialized in ApplicationImpl on next access
         Initialized = false;
         Initialized = false;
 
 
         // Mouse
         // Mouse
@@ -254,16 +253,12 @@ public static partial class Application
         CachedViewsUnderMouse.Clear ();
         CachedViewsUnderMouse.Clear ();
         MouseEvent = null;
         MouseEvent = null;
 
 
-        // Keyboard
-        KeyDown = null;
-        KeyUp = null;
+        // Keyboard events and bindings are now managed by the Keyboard instance
+
         SizeChanging = null;
         SizeChanging = null;
 
 
         Navigation = null;
         Navigation = null;
 
 
-        KeyBindings.Clear ();
-        AddKeyBindings ();
-
         // Reset synchronization context to allow the user to run async/await,
         // Reset synchronization context to allow the user to run async/await,
         // as the main loop has been ended, the synchronization context from
         // as the main loop has been ended, the synchronization context from
         // gui.cs does no longer process any callbacks. See #1084 for more details:
         // gui.cs does no longer process any callbacks. See #1084 for more details:

+ 276 - 190
Terminal.Gui/App/ApplicationImpl.cs

@@ -1,14 +1,23 @@
-#nullable enable
+#nullable enable
+using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Logging;
+using Terminal.Gui.Drivers;
 
 
 namespace Terminal.Gui.App;
 namespace Terminal.Gui.App;
 
 
 /// <summary>
 /// <summary>
-/// Original Terminal.Gui implementation of core <see cref="Application"/> methods.
+/// Implementation of core <see cref="Application"/> methods using the modern
+/// main loop architecture with component factories for different platforms.
 /// </summary>
 /// </summary>
 public class ApplicationImpl : IApplication
 public class ApplicationImpl : IApplication
 {
 {
+    private readonly IComponentFactory? _componentFactory;
+    private IMainLoopCoordinator? _coordinator;
+    private string? _driverName;
+    private readonly ITimedEvents _timedEvents = new TimedEvents ();
+
     // Private static readonly Lazy instance of Application
     // Private static readonly Lazy instance of Application
     private static Lazy<IApplication> _lazyInstance = new (() => new ApplicationImpl ());
     private static Lazy<IApplication> _lazyInstance = new (() => new ApplicationImpl ());
 
 
@@ -18,15 +27,87 @@ public class ApplicationImpl : IApplication
     /// </summary>
     /// </summary>
     public static IApplication Instance => _lazyInstance.Value;
     public static IApplication Instance => _lazyInstance.Value;
 
 
-
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public virtual ITimedEvents? TimedEvents => Application.MainLoop?.TimedEvents;
+    public ITimedEvents? TimedEvents => _timedEvents;
+
+    internal IMainLoopCoordinator? Coordinator => _coordinator;
 
 
     /// <summary>
     /// <summary>
     /// Handles which <see cref="View"/> (if any) has captured the mouse
     /// Handles which <see cref="View"/> (if any) has captured the mouse
     /// </summary>
     /// </summary>
     public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler ();
     public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler ();
 
 
+    private IKeyboard? _keyboard;
+
+    /// <summary>
+    /// Handles keyboard input and key bindings at the Application level
+    /// </summary>
+    public IKeyboard Keyboard
+    {
+        get
+        {
+            if (_keyboard is null)
+            {
+                _keyboard = new Keyboard { Application = this };
+            }
+            return _keyboard;
+        }
+        set => _keyboard = value ?? throw new ArgumentNullException (nameof (value));
+    }
+
+    /// <inheritdoc/>
+    public IConsoleDriver? Driver
+    {
+        get => Application.Driver;
+        set => Application.Driver = value;
+    }
+
+    /// <inheritdoc/>
+    public bool Initialized
+    {
+        get => Application.Initialized;
+        set => Application.Initialized = value;
+    }
+
+    /// <inheritdoc/>
+    public ApplicationPopover? Popover
+    {
+        get => Application.Popover;
+        set => Application.Popover = value;
+    }
+
+    /// <inheritdoc/>
+    public ApplicationNavigation? Navigation
+    {
+        get => Application.Navigation;
+        set => Application.Navigation = value;
+    }
+
+    /// <inheritdoc/>
+    public Toplevel? Top
+    {
+        get => Application.Top;
+        set => Application.Top = value;
+    }
+
+    /// <inheritdoc/>
+    public ConcurrentStack<Toplevel> TopLevels => Application.TopLevels;
+
+    /// <inheritdoc/>
+    public void RequestStop () => Application.RequestStop ();
+
+    /// <summary>
+    /// Creates a new instance of the Application backend.
+    /// </summary>
+    public ApplicationImpl ()
+    {
+    }
+
+    internal ApplicationImpl (IComponentFactory componentFactory)
+    {
+        _componentFactory = componentFactory;
+    }
+
     /// <summary>
     /// <summary>
     /// Change the singleton implementation, should not be called except before application
     /// Change the singleton implementation, should not be called except before application
     /// startup. This method lets you provide alternative implementations of core static gateway
     /// startup. This method lets you provide alternative implementations of core static gateway
@@ -41,25 +122,155 @@ public class ApplicationImpl : IApplication
     /// <inheritdoc/>
     /// <inheritdoc/>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public virtual void Init (IConsoleDriver? driver = null, string? driverName = null)
+    public void Init (IConsoleDriver? driver = null, string? driverName = null)
+    {
+        if (Application.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 = Application.ForceDriver;
+        }
+
+        Debug.Assert(Application.Navigation is null);
+        Application.Navigation = new ();
+
+        Debug.Assert (Application.Popover is null);
+        Application.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 Keyboard { 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);
+
+        Application.Initialized = true;
+
+        Application.OnInitializedChanged (this, new (true));
+        Application.SubscribeDriverEvents ();
+
+        SynchronizationContext.SetSynchronizationContext (new ());
+        Application.MainThreadId = Thread.CurrentThread.ManagedThreadId;
+    }
+
+    private void CreateDriver (string? driverName)
+    {
+        // When running unit tests, always use FakeDriver unless explicitly specified
+        if (ConsoleDriver.RunningUnitTests && 
+            string.IsNullOrEmpty (driverName) && 
+            _componentFactory is null)
+        {
+            Logging.Logger.LogDebug ("Unit test safeguard: forcing FakeDriver (RunningUnitTests=true, driverName=null, componentFactory=null)");
+            _coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
+            _coordinator.StartAsync ().Wait ();
+
+            if (Application.Driver == null)
+            {
+                throw new ("Application.Driver was null even after booting MainLoopCoordinator");
+            }
+
+            return;
+        }
+
+        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 ());
+        }
+        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 (Application.Driver == null)
+        {
+            throw new ("Application.Driver was null even after booting MainLoopCoordinator");
+        }
+    }
+
+    private IMainLoopCoordinator CreateSubcomponents<T> (Func<IComponentFactory<T>> fallbackFactory)
     {
     {
-        Application.InternalInit (driver, string.IsNullOrWhiteSpace (driverName) ? Application.ForceDriver : driverName);
+        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>
     /// <summary>
     ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
     ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
     ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
     ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
     /// </summary>
     /// </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>
     /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
@@ -69,168 +280,71 @@ public class ApplicationImpl : IApplication
     ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
     ///     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})"/>.
     ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
     /// </summary>
     /// </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="errorHandler"></param>
     /// <param name="driver">
     /// <param name="driver">
     ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
     ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
-    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
-    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
+    ///     be used. Must be <see langword="null"/> if <see cref="Init"/> has already been called.
     /// </param>
     /// </param>
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public virtual T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
+    public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
         where T : Toplevel, new()
         where T : Toplevel, new()
     {
     {
         if (!Application.Initialized)
         if (!Application.Initialized)
         {
         {
-            // Init() has NOT been called.
-            Application.InternalInit (driver, Application.ForceDriver, true);
-        }
-
-        if (Instance is ApplicationV2)
-        {
-            return Instance.Run<T> (errorHandler, driver);
+            // Init() has NOT been called. Auto-initialize as per interface contract.
+            Init (driver, null);
         }
         }
 
 
-        var top = new T ();
-
+        T top = new ();
         Run (top, errorHandler);
         Run (top, errorHandler);
-
         return top;
         return top;
     }
     }
 
 
     /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
     /// <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="Application.Begin(Toplevel)"/>, followed by <see cref="Application.RunLoop(RunState)"/>, and then calling
-    ///         <see cref="Application.End(RunState)"/>.
-    ///     </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.
-    ///     </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="Application.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="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 virtual void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
+    /// <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);
         ArgumentNullException.ThrowIfNull (view);
 
 
-        if (Application.Initialized)
+        if (!Application.Initialized)
         {
         {
-            if (Application.Driver is null)
-            {
-                // Disposing before throwing
-                view.Dispose ();
-
-                // This code path should be impossible because Init(null, null) will select the platform default driver
-                throw new InvalidOperationException (
-                                                     "Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called."
-                                                    );
-            }
+            throw new NotInitializedException (nameof (Run));
         }
         }
-        else
+
+        if (Application.Driver == null)
         {
         {
-            // Init() has NOT been called.
-            throw new InvalidOperationException (
-                                                 "Init() has not been called. Only Run() or Run<T>() can be used without calling Init()."
-                                                );
+            throw new  InvalidOperationException ("Driver was inexplicably null when trying to Run view");
         }
         }
 
 
-        var resume = true;
+        Application.Top = view;
 
 
-        while (resume)
+        RunState rs = Application.Begin (view);
+
+        Application.Top.Running = true;
+
+        while (Application.TopLevels.TryPeek (out Toplevel? found) && found == view && view.Running)
         {
         {
-#if !DEBUG
-            try
+            if (_coordinator is null)
             {
             {
-#endif
-                resume = false;
-                RunState runState = Application.Begin (view);
-
-                // If EndAfterFirstIteration is true then the user must dispose of the runToken
-                // by using NotifyStopRunState event.
-                Application.RunLoop (runState);
-
-                if (runState.Toplevel is null)
-                {
-#if DEBUG_IDISPOSABLE
-                if (View.EnableDebugIDisposableAsserts)
-                {
-                    Debug.Assert (Application.TopLevels.Count == 0);
-                }
-#endif
-                    runState.Dispose ();
-
-                    return;
-                }
-
-                if (!Application.EndAfterFirstIteration)
-                {
-                    Application.End (runState);
-                }
-#if !DEBUG
+                throw new ($"{nameof (IMainLoopCoordinator)} inexplicably became null during Run");
             }
             }
-            catch (Exception error)
-            {
-                Logging.Warning ($"Release Build Exception: {error}");
-                if (errorHandler is null)
-                {
-                    throw;
-                }
 
 
-                resume = errorHandler (error);
-            }
-#endif
+            _coordinator.RunIteration ();
         }
         }
+
+        Logging.Information ($"Run - Calling End");
+        Application.End (rs);
     }
     }
 
 
     /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
     /// <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 virtual void Shutdown ()
+    public void Shutdown ()
     {
     {
-        // TODO: Throw an exception if Init hasn't been called.
-
+        _coordinator?.Stop ();
+        
         bool wasInitialized = Application.Initialized;
         bool wasInitialized = Application.Initialized;
         Application.ResetState ();
         Application.ResetState ();
         ConfigurationManager.PrintJsonErrors ();
         ConfigurationManager.PrintJsonErrors ();
@@ -238,24 +352,27 @@ public class ApplicationImpl : IApplication
         if (wasInitialized)
         if (wasInitialized)
         {
         {
             bool init = Application.Initialized;
             bool init = Application.Initialized;
-
             Application.OnInitializedChanged (this, new (in init));
             Application.OnInitializedChanged (this, new (in init));
         }
         }
 
 
+        Application.Driver = null;
+        _keyboard = null;
         _lazyInstance = new (() => new ApplicationImpl ());
         _lazyInstance = new (() => new ApplicationImpl ());
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
-    public virtual void RequestStop (Toplevel? top)
+    public void RequestStop (Toplevel? top)
     {
     {
+        Logging.Logger.LogInformation ($"RequestStop '{(top is {} ? top : "null")}'");
+
         top ??= Application.Top;
         top ??= Application.Top;
 
 
-        if (!top!.Running)
+        if (top == null)
         {
         {
             return;
             return;
         }
         }
 
 
-        var ev = new ToplevelClosingEventArgs (top);
+        ToplevelClosingEventArgs ev = new (top);
         top.OnClosing (ev);
         top.OnClosing (ev);
 
 
         if (ev.Cancel)
         if (ev.Cancel)
@@ -264,71 +381,40 @@ public class ApplicationImpl : IApplication
         }
         }
 
 
         top.Running = false;
         top.Running = false;
-        Application.OnNotifyStopRunState (top);
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
-    public virtual void Invoke (Action action)
+    public void Invoke (Action action)
     {
     {
-
         // If we are already on the main UI thread
         // If we are already on the main UI thread
         if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId)
         if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId)
         {
         {
             action ();
             action ();
-            WakeupMainLoop ();
-
-            return;
-        }
-
-        if (Application.MainLoop == null)
-        {
-            Logging.Warning ("Ignored Invoke because MainLoop is not initialized yet");
             return;
             return;
         }
         }
 
 
-
-        Application.AddTimeout (TimeSpan.Zero,
-                           () =>
-                           {
-                               action ();
-
-                               return false;
-                           }
-                          );
-
-        WakeupMainLoop ();
-
-        void WakeupMainLoop ()
-        {
-            // Ensure the action is executed in the main loop
-            // Wakeup mainloop if it's waiting for events
-            Application.MainLoop?.Wakeup ();
-        }
+        _timedEvents.Add (TimeSpan.Zero,
+                              () =>
+                              {
+                                  action ();
+                                  return false;
+                              }
+                             );
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
-    public bool IsLegacy { get; protected set; } = true;
+    public bool IsLegacy => false;
 
 
     /// <inheritdoc />
     /// <inheritdoc />
-    public virtual object AddTimeout (TimeSpan time, Func<bool> callback)
-    {
-        if (Application.MainLoop is null)
-        {
-            throw new NotInitializedException ("Cannot add timeout before main loop is initialized", null);
-        }
-
-        return Application.MainLoop.TimedEvents.Add (time, callback);
-    }
+    public object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.Add (time, callback); }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
-    public virtual bool RemoveTimeout (object token)
-    {
-        return Application.MainLoop?.TimedEvents.Remove (token) ?? false;
-    }
+    public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
-    public virtual void LayoutAndDraw (bool forceDraw)
+    public void LayoutAndDraw (bool forceDraw)
     {
     {
-        Application.LayoutAndDrawImpl (forceDraw);
+        Application.Top?.SetNeedsDraw();
+        Application.Top?.SetNeedsLayout ();
     }
     }
 }
 }

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

@@ -5,7 +5,7 @@ namespace Terminal.Gui.App;
 
 
 /// <summary>
 /// <summary>
 ///     Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
 ///     Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
-///     CursesDriver, 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 IConsoleDriver.cs.
 /// </summary>
 /// </summary>
 internal static class ClipboardProcessRunner
 internal static class ClipboardProcessRunner
 {
 {

+ 32 - 9
Terminal.Gui/App/IApplication.cs

@@ -20,6 +20,36 @@ public interface IApplication
     /// </summary>
     /// </summary>
     IMouseGrabHandler MouseGrabHandler { get; set; }
     IMouseGrabHandler MouseGrabHandler { get; set; }
 
 
+    /// <summary>
+    /// Handles keyboard input and key bindings at the Application level.
+    /// </summary>
+    IKeyboard Keyboard { get; set; }
+
+    /// <summary>Gets or sets the console driver being used.</summary>
+    IConsoleDriver? Driver { get; set; }
+
+    /// <summary>Gets or sets whether the application has been initialized.</summary>
+    bool Initialized { get; set; }
+
+    /// <summary>Gets or sets the popover manager.</summary>
+    ApplicationPopover? Popover { get; set; }
+
+    /// <summary>Gets or sets the navigation manager.</summary>
+    ApplicationNavigation? Navigation { get; set; }
+
+    /// <summary>Gets the currently active Toplevel.</summary>
+    Toplevel? Top { get; set; }
+
+    /// <summary>Gets the stack of all Toplevels.</summary>
+    System.Collections.Concurrent.ConcurrentStack<Toplevel> TopLevels { get; }
+
+    /// <summary>Requests that the application stop running.</summary>
+    void RequestStop ();
+
+    /// <summary>Forces all views to be laid out and drawn.</summary>
+    /// <param name="clearScreen">If true, clears the screen before drawing.</param>
+    void LayoutAndDraw (bool clearScreen = false);
+
     /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
     /// <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>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
     /// <para>
     /// <para>
@@ -44,7 +74,7 @@ public interface IApplication
     ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
     ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
     /// </param>
     /// </param>
     /// <param name="driverName">
     /// <param name="driverName">
-    ///     The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the
+    ///     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
     ///     <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.
     ///     specified the default driver for the platform will be used.
     /// </param>
     /// </param>
@@ -89,7 +119,7 @@ public interface IApplication
     /// <param name="errorHandler"></param>
     /// <param name="errorHandler"></param>
     /// <param name="driver">
     /// <param name="driver">
     ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
     ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
-    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
+    ///     be used. Must be
     ///     <see langword="null"/> if <see cref="Init"/> has already been called.
     ///     <see langword="null"/> if <see cref="Init"/> has already been called.
     /// </param>
     /// </param>
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
@@ -187,11 +217,4 @@ public interface IApplication
     /// <see langword="false"/>
     /// <see langword="false"/>
     /// if the timeout is not found.</returns>
     /// if the timeout is not found.</returns>
     bool RemoveTimeout (object token);
     bool RemoveTimeout (object token);
-
-    /// <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="forceDraw">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>
-    void LayoutAndDraw (bool forceDraw);
 }
 }

+ 113 - 0
Terminal.Gui/App/Keyboard/IKeyboard.cs

@@ -0,0 +1,113 @@
+#nullable enable
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     Defines a contract for managing keyboard input and key bindings at the Application level.
+///     <para>
+///         This interface decouples keyboard handling state from the static <see cref="Application"/> class,
+///         enabling parallelizable unit tests and better testability.
+///     </para>
+/// </summary>
+public interface IKeyboard
+{
+    /// <summary>
+    /// Sets the application instance that this keyboard handler is associated with.
+    /// This provides access to application state without coupling to static Application class.
+    /// </summary>
+    IApplication? Application { get; set; }
+
+    /// <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>
+    bool RaiseKeyDownEvent (Key key);
+
+    /// <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>
+    bool RaiseKeyUpEvent (Key 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>
+    bool? InvokeCommandsBoundToKey (Key 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>
+    bool? InvokeCommand (Command command, Key key, KeyBinding binding);
+
+    /// <summary>
+    ///     Raised when the user presses a key.
+    ///     <para>
+    ///         Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
+    ///         additional processing.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Unix) do not support firing the
+    ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+    ///     <para>Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.</para>
+    /// </remarks>
+    event EventHandler<Key>? KeyDown;
+
+    /// <summary>
+    ///     Raised when the user releases a key.
+    ///     <para>
+    ///         Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
+    ///         additional processing.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Unix) do not support firing the
+    ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+    ///     <para>Fired after <see cref="KeyDown"/>.</para>
+    /// </remarks>
+    event EventHandler<Key>? KeyUp;
+
+    /// <summary>Gets the Application-scoped key bindings.</summary>
+    KeyBindings KeyBindings { get; }
+
+    /// <summary>Gets or sets the key to quit the application.</summary>
+    Key QuitKey { get; set; }
+
+    /// <summary>Gets or sets the key to activate arranging views using the keyboard.</summary>
+    Key ArrangeKey { get; set; }
+
+    /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
+    Key NextTabGroupKey { get; set; }
+
+    /// <summary>Alternative key to navigate forwards through views. Tab is the primary key.</summary>
+    Key NextTabKey { get; set; }
+
+    /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
+    Key PrevTabGroupKey { get; set; }
+
+    /// <summary>Alternative key to navigate backwards through views. Shift+Tab is the primary key.</summary>
+    Key PrevTabKey { get; set; }
+}

+ 381 - 0
Terminal.Gui/App/Keyboard/Keyboard.cs

@@ -0,0 +1,381 @@
+#nullable enable
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     INTERNAL: Implements <see cref="IKeyboard"/> to manage keyboard input and key bindings at the Application level.
+///     <para>
+///         This implementation decouples keyboard handling state from the static <see cref="Application"/> class,
+///         enabling parallelizable unit tests and better testability.
+///     </para>
+///     <para>
+///         See <see cref="IKeyboard"/> for usage details.
+///     </para>
+/// </summary>
+internal class Keyboard : IKeyboard
+{
+    private Key _quitKey = Key.Esc; // Resources/config.json overrides
+    private Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
+    private Key _nextTabGroupKey = Key.F6; // Resources/config.json overrides
+    private Key _nextTabKey = Key.Tab; // Resources/config.json overrides
+    private Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrides
+    private Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrides
+
+    /// <summary>
+    ///     Commands for Application.
+    /// </summary>
+    private readonly Dictionary<Command, View.CommandImplementation> _commandImplementations = new ();
+
+    /// <inheritdoc/>
+    public IApplication? Application { get; set; }
+
+    /// <inheritdoc/>
+    public KeyBindings KeyBindings { get; internal set; } = new (null);
+
+    /// <inheritdoc/>
+    public Key QuitKey
+    {
+        get => _quitKey;
+        set
+        {
+            KeyBindings.Replace (_quitKey, value);
+            _quitKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public Key ArrangeKey
+    {
+        get => _arrangeKey;
+        set
+        {
+            KeyBindings.Replace (_arrangeKey, value);
+            _arrangeKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public Key NextTabGroupKey
+    {
+        get => _nextTabGroupKey;
+        set
+        {
+            KeyBindings.Replace (_nextTabGroupKey, value);
+            _nextTabGroupKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public Key NextTabKey
+    {
+        get => _nextTabKey;
+        set
+        {
+            KeyBindings.Replace (_nextTabKey, value);
+            _nextTabKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public Key PrevTabGroupKey
+    {
+        get => _prevTabGroupKey;
+        set
+        {
+            KeyBindings.Replace (_prevTabGroupKey, value);
+            _prevTabGroupKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public Key PrevTabKey
+    {
+        get => _prevTabKey;
+        set
+        {
+            KeyBindings.Replace (_prevTabKey, value);
+            _prevTabKey = value;
+        }
+    }
+
+    /// <inheritdoc/>
+    public event EventHandler<Key>? KeyDown;
+
+    /// <inheritdoc/>
+    public event EventHandler<Key>? KeyUp;
+
+    /// <summary>
+    ///     Initializes keyboard bindings.
+    /// </summary>
+    public Keyboard ()
+    {
+        AddKeyBindings ();
+    }
+
+    /// <inheritdoc/>
+    public bool RaiseKeyDownEvent (Key key)
+    {
+        Logging.Debug ($"{key}");
+
+        // TODO: Add a way to ignore certain keys, esp for debugging.
+        //#if DEBUG
+        //        if (key == Key.Empty.WithAlt || key == Key.Empty.WithCtrl)
+        //        {
+        //            Logging.Debug ($"Ignoring {key}");
+        //            return false;
+        //        }
+        //#endif
+
+        // TODO: This should match standard event patterns
+        KeyDown?.Invoke (null, key);
+
+        if (key.Handled)
+        {
+            return true;
+        }
+
+        if (Application?.Popover?.DispatchKeyDown (key) is true)
+        {
+            return true;
+        }
+
+        if (Application?.Top is null)
+        {
+            if (Application?.TopLevels is { })
+            {
+                foreach (Toplevel topLevel in Application.TopLevels.ToList ())
+                {
+                    if (topLevel.NewKeyDownEvent (key))
+                    {
+                        return true;
+                    }
+
+                    if (topLevel.Modal)
+                    {
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            if (Application.Top.NewKeyDownEvent (key))
+            {
+                return true;
+            }
+        }
+
+        bool? commandHandled = InvokeCommandsBoundToKey (key);
+        if(commandHandled is true)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <inheritdoc/>
+    public bool RaiseKeyUpEvent (Key key)
+    {
+        if (Application?.Initialized != true)
+        {
+            return true;
+        }
+
+        KeyUp?.Invoke (null, key);
+
+        if (key.Handled)
+        {
+            return true;
+        }
+
+
+        // TODO: Add Popover support
+
+        if (Application?.TopLevels is { })
+        {
+            foreach (Toplevel topLevel in Application.TopLevels.ToList ())
+            {
+                if (topLevel.NewKeyUpEvent (key))
+                {
+                    return true;
+                }
+
+                if (topLevel.Modal)
+                {
+                    break;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /// <inheritdoc/>
+    public bool? InvokeCommandsBoundToKey (Key key)
+    {
+        bool? handled = null;
+        // Invoke any Application-scoped KeyBindings.
+        // The first view that handles the key will stop the loop.
+        // foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
+        if (KeyBindings.TryGet (key, out KeyBinding binding))
+        {
+            if (binding.Target is { })
+            {
+                if (!binding.Target.Enabled)
+                {
+                    return null;
+                }
+
+                handled = binding.Target?.InvokeCommands (binding.Commands, binding);
+            }
+            else
+            {
+                bool? toReturn = null;
+
+                foreach (Command command in binding.Commands)
+                {
+                    toReturn = InvokeCommand (command, key, binding);
+                }
+
+                handled = toReturn ?? true;
+            }
+        }
+
+        return handled;
+    }
+
+    /// <inheritdoc/>
+    public bool? InvokeCommand (Command command, Key key, KeyBinding binding)
+    {
+        if (!_commandImplementations.ContainsKey (command))
+        {
+            throw new NotSupportedException (
+                                             @$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by Application."
+                                            );
+        }
+
+        if (_commandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
+        {
+            CommandContext<KeyBinding> context = new (command, null, binding); // Create the context here
+
+            return implementation (context);
+        }
+
+        return null;
+    }
+
+    /// <summary>
+    ///     <para>
+    ///         Sets the function that will be invoked for a <see cref="Command"/>.
+    ///     </para>
+    ///     <para>
+    ///         If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
+    ///         replace the old one.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This version of AddCommand is for commands that do not require a <see cref="ICommandContext"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="command">The command.</param>
+    /// <param name="f">The function.</param>
+    private void AddCommand (Command command, Func<bool?> f) { _commandImplementations [command] = ctx => f (); }
+
+    internal void AddKeyBindings ()
+    {
+        _commandImplementations.Clear ();
+
+        // Things Application knows how to do
+        AddCommand (
+                    Command.Quit,
+                    () =>
+                    {
+                        Application?.RequestStop ();
+
+                        return true;
+                    }
+                   );
+        AddCommand (
+                    Command.Suspend,
+                    () =>
+                    {
+                        Application?.Driver?.Suspend ();
+
+                        return true;
+                    }
+                   );
+        AddCommand (
+                    Command.NextTabStop,
+                    () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));
+
+        AddCommand (
+                    Command.PreviousTabStop,
+                    () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop));
+
+        AddCommand (
+                    Command.NextTabGroup,
+                    () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup));
+
+        AddCommand (
+                    Command.PreviousTabGroup,
+                    () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup));
+
+        AddCommand (
+                    Command.Refresh,
+                    () =>
+                    {
+                        Application?.LayoutAndDraw (true);
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Arrange,
+                    () =>
+                    {
+                        View? viewToArrange = Application?.Navigation?.GetFocused ();
+
+                        // Go up the superview hierarchy and find the first that is not ViewArrangement.Fixed
+                        while (viewToArrange is { SuperView: { }, Arrangement: ViewArrangement.Fixed })
+                        {
+                            viewToArrange = viewToArrange.SuperView;
+                        }
+
+                        if (viewToArrange is { })
+                        {
+                            return viewToArrange.Border?.EnterArrangeMode (ViewArrangement.Fixed);
+                        }
+
+                        return false;
+                    });
+
+        //SetKeysToHardCodedDefaults ();
+
+        // Need to clear after setting the above to ensure actually clear
+        // because set_QuitKey etc.. may call Add
+        KeyBindings.Clear ();
+
+        KeyBindings.Add (QuitKey, Command.Quit);
+        KeyBindings.Add (NextTabKey, Command.NextTabStop);
+        KeyBindings.Add (PrevTabKey, Command.PreviousTabStop);
+        KeyBindings.Add (NextTabGroupKey, Command.NextTabGroup);
+        KeyBindings.Add (PrevTabGroupKey, Command.PreviousTabGroup);
+        KeyBindings.Add (ArrangeKey, Command.Arrange);
+
+        KeyBindings.Add (Key.CursorRight, Command.NextTabStop);
+        KeyBindings.Add (Key.CursorDown, Command.NextTabStop);
+        KeyBindings.Add (Key.CursorLeft, Command.PreviousTabStop);
+        KeyBindings.Add (Key.CursorUp, Command.PreviousTabStop);
+
+        // TODO: Refresh Key should be configurable
+        KeyBindings.Add (Key.F5, Command.Refresh);
+
+        // TODO: Suspend Key should be configurable
+        if (Environment.OSVersion.Platform == PlatformID.Unix)
+        {
+            KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
+        }
+    }
+}

+ 20 - 5
Terminal.Gui/Drivers/V2/MainLoop.cs → Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs

@@ -1,11 +1,26 @@
 #nullable enable
 #nullable enable
+using Terminal.Gui.Drivers;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Diagnostics;
 
 
-namespace Terminal.Gui.Drivers;
-
-/// <inheritdoc/>
-public class MainLoop<T> : IMainLoop<T>
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     The main application loop that runs Terminal.Gui's UI rendering and event processing.
+/// </summary>
+/// <remarks>
+///     This class coordinates the Terminal.Gui application lifecycle by:
+///     <list type="bullet">
+///         <item>Processing buffered input events and translating them to UI events</item>
+///         <item>Executing user timeout callbacks at scheduled intervals</item>
+///         <item>Detecting which views need redrawing or layout updates</item>
+///         <item>Rendering UI changes to the console output buffer</item>
+///         <item>Managing cursor position and visibility</item>
+///         <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>
 {
 {
     private ITimedEvents? _timedEvents;
     private ITimedEvents? _timedEvents;
     private ConcurrentQueue<T>? _inputBuffer;
     private ConcurrentQueue<T>? _inputBuffer;
@@ -143,7 +158,7 @@ public class MainLoop<T> : IMainLoop<T>
             {
             {
                 Logging.Redraws.Add (1);
                 Logging.Redraws.Add (1);
 
 
-                Application.LayoutAndDrawImpl (true);
+                Application.LayoutAndDraw (true);
 
 
                 Out.Write (OutputBuffer);
                 Out.Write (OutputBuffer);
 
 

+ 13 - 4
Terminal.Gui/Drivers/V2/IMainLoop.cs → Terminal.Gui/App/MainLoop/IApplicationMainLoop.cs

@@ -1,13 +1,22 @@
 #nullable enable
 #nullable enable
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 
 
-namespace Terminal.Gui.Drivers;
+namespace Terminal.Gui.App;
 
 
 /// <summary>
 /// <summary>
-///     Interface for main loop that runs the core Terminal.Gui UI loop.
+///     Interface for the main application loop that runs the core Terminal.Gui UI rendering and event processing.
 /// </summary>
 /// </summary>
-/// <typeparam name="T">Type of raw input events processed by the loop e.g. <see cref="ConsoleKeyInfo"/></typeparam>
-public interface IMainLoop<T> : IDisposable
+/// <remarks>
+///     This interface defines the contract for the main loop that coordinates:
+///     <list type="bullet">
+///         <item>Processing input events from the console</item>
+///         <item>Running user timeout callbacks</item>
+///         <item>Detecting UI changes that need redrawing</item>
+///         <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
 {
 {
     /// <summary>
     /// <summary>
     ///     Gets the class responsible for servicing user timeouts
     ///     Gets the class responsible for servicing user timeouts

+ 48 - 0
Terminal.Gui/App/MainLoop/IMainLoopCoordinator.cs

@@ -0,0 +1,48 @@
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     Interface for the main loop coordinator that manages UI loop initialization and threading.
+/// </summary>
+/// <remarks>
+///     The coordinator is responsible for:
+///     <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>
+public interface IMainLoopCoordinator
+{
+    /// <summary>
+    ///     Initializes all required subcomponents and starts the input thread.
+    /// </summary>
+    /// <remarks>
+    ///     This method:
+    ///     <list type="number">
+    ///         <item>Starts the input thread that reads console input asynchronously</item>
+    ///         <item>Initializes the main UI loop on the calling thread</item>
+    ///         <item>Waits for both to be ready before returning</item>
+    ///     </list>
+    /// </remarks>
+    /// <returns>A task that completes when initialization is done</returns>
+    public Task StartAsync ();
+
+    /// <summary>
+    ///     Stops the input thread and performs cleanup.
+    /// </summary>
+    /// <remarks>
+    ///     This method blocks until the input thread has exited.
+    ///     It must be called only from the main UI thread.
+    /// </remarks>
+    public void Stop ();
+
+    /// <summary>
+    ///     Executes a single iteration of the main UI loop.
+    /// </summary>
+    /// <remarks>
+    ///     Each iteration processes input, runs timeouts, checks for UI changes,
+    ///     and renders any necessary updates.
+    /// </remarks>
+    void RunIteration ();
+}

+ 24 - 0
Terminal.Gui/App/MainLoop/IMainLoopDriver.cs

@@ -0,0 +1,24 @@
+#nullable enable
+namespace Terminal.Gui.App;
+
+/// <summary>Interface to create a platform specific <see cref="MainLoop"/> driver.</summary>
+internal interface IMainLoopDriver
+{
+    /// <summary>Must report whether there are any events pending, or even block waiting for events.</summary>
+    /// <returns><see langword="true"/>, if there were pending events, <see langword="false"/> otherwise.</returns>
+    bool EventsPending ();
+
+    /// <summary>The iteration function.</summary>
+    void Iteration ();
+
+    /// <summary>Initializes the <see cref="MainLoop"/>, gets the calling main loop for the initialization.</summary>
+    /// <remarks>Call <see cref="TearDown"/> to release resources.</remarks>
+    /// <param name="mainLoop">Main loop.</param>
+    void Setup (MainLoop mainLoop);
+
+    /// <summary>Tears down the <see cref="MainLoop"/> driver. Releases resources created in <see cref="Setup"/>.</summary>
+    void TearDown ();
+
+    /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input, must be thread safe.</summary>
+    void Wakeup ();
+}

+ 13 - 26
Terminal.Gui/App/MainLoop.cs → Terminal.Gui/App/MainLoop/LegacyMainLoopDriver.cs

@@ -1,6 +1,6 @@
 #nullable enable
 #nullable enable
 //
 //
-// MainLoop.cs: IMainLoopDriver and MainLoop for Terminal.Gui
+// LegacyMainLoopDriver.cs: IMainLoopDriver and MainLoop for legacy v1 driver based applications
 //
 //
 // Authors:
 // Authors:
 //   Miguel de Icaza ([email protected])
 //   Miguel de Icaza ([email protected])
@@ -10,33 +10,20 @@ using System.Collections.ObjectModel;
 
 
 namespace Terminal.Gui.App;
 namespace Terminal.Gui.App;
 
 
-/// <summary>Interface to create a platform specific <see cref="MainLoop"/> driver.</summary>
-internal interface IMainLoopDriver
-{
-    /// <summary>Must report whether there are any events pending, or even block waiting for events.</summary>
-    /// <returns><see langword="true"/>, if there were pending events, <see langword="false"/> otherwise.</returns>
-    bool EventsPending ();
-
-    /// <summary>The iteration function.</summary>
-    void Iteration ();
-
-    /// <summary>Initializes the <see cref="MainLoop"/>, gets the calling main loop for the initialization.</summary>
-    /// <remarks>Call <see cref="TearDown"/> to release resources.</remarks>
-    /// <param name="mainLoop">Main loop.</param>
-    void Setup (MainLoop mainLoop);
-
-    /// <summary>Tears down the <see cref="MainLoop"/> driver. Releases resources created in <see cref="Setup"/>.</summary>
-    void TearDown ();
-
-    /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input, must be thread safe.</summary>
-    void Wakeup ();
-}
-
-/// <summary>The main event loop of v1 driver based applications.</summary>
+/// <summary>
+///     The main event loop of legacy v1 driver based applications.
+/// </summary>
 /// <remarks>
 /// <remarks>
-///     Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this
-///     on Windows.
+///     <para>
+///         This class is provided for backward compatibility with the legacy FakeDriver implementation.
+///         New code should use the modern <see cref="ApplicationMainLoop{T}"/> architecture instead.
+///     </para>
+///     <para>
+///         Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this
+///         on Windows.
+///     </para>
 /// </remarks>
 /// </remarks>
+[Obsolete ("This class is for legacy FakeDriver compatibility only. Use ApplicationMainLoop<T> for new code.")]
 public class MainLoop : IDisposable
 public class MainLoop : IDisposable
 {
 {
     /// <summary>
     /// <summary>

+ 17 - 13
Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs → Terminal.Gui/App/MainLoop/MainLoopCoordinator.cs

@@ -1,21 +1,26 @@
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
+using Terminal.Gui.Drivers;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
-namespace Terminal.Gui.Drivers;
+namespace Terminal.Gui.App;
 
 
 /// <summary>
 /// <summary>
 ///     <para>
 ///     <para>
-///         Handles creating the input loop thread and bootstrapping the
-///         <see cref="MainLoop{T}"/> that handles layout/drawing/events etc.
+///         Coordinates the creation and startup of the main UI loop and input thread.
 ///     </para>
 ///     </para>
-///     <para>This class is designed to be managed by <see cref="ApplicationV2"/></para>
+///     <para>
+///         This class bootstraps the <see cref="ApplicationMainLoop{T}"/> that handles
+///         UI layout, drawing, and event processing while also managing a separate thread
+///         for reading console input asynchronously.
+///     </para>
+///     <para>This class is designed to be managed by <see cref="ApplicationImpl"/></para>
 /// </summary>
 /// </summary>
-/// <typeparam name="T"></typeparam>
+/// <typeparam name="T">Type of raw input events, e.g. <see cref="ConsoleKeyInfo"/> for .NET driver</typeparam>
 internal class MainLoopCoordinator<T> : IMainLoopCoordinator
 internal class MainLoopCoordinator<T> : IMainLoopCoordinator
 {
 {
     private readonly ConcurrentQueue<T> _inputBuffer;
     private readonly ConcurrentQueue<T> _inputBuffer;
     private readonly IInputProcessor _inputProcessor;
     private readonly IInputProcessor _inputProcessor;
-    private readonly IMainLoop<T> _loop;
+    private readonly IApplicationMainLoop<T> _loop;
     private readonly IComponentFactory<T> _componentFactory;
     private readonly IComponentFactory<T> _componentFactory;
     private readonly CancellationTokenSource _tokenSource = new ();
     private readonly CancellationTokenSource _tokenSource = new ();
     private IConsoleInput<T> _input;
     private IConsoleInput<T> _input;
@@ -28,17 +33,16 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
     private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
     private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
 
 
     /// <summary>
     /// <summary>
-    ///     Creates a new coordinator
+    ///     Creates a new coordinator that will manage the main UI loop and input thread.
     /// </summary>
     /// </summary>
-    /// <param name="timedEvents"></param>
-    /// <param name="inputBuffer"></param>
-    /// <param name="loop"></param>
-    /// <param name="componentFactory">Factory for creating driver components
-    /// (<see cref="IConsoleOutput"/>, <see cref="IConsoleInput{T}"/> etc)</param>
+    /// <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="loop">The main application loop instance</param>
+    /// <param name="componentFactory">Factory for creating driver-specific components (input, output, etc.)</param>
     public MainLoopCoordinator (
     public MainLoopCoordinator (
         ITimedEvents timedEvents,
         ITimedEvents timedEvents,
         ConcurrentQueue<T> inputBuffer,
         ConcurrentQueue<T> inputBuffer,
-        IMainLoop<T> loop,
+        IApplicationMainLoop<T> loop,
         IComponentFactory<T> componentFactory
         IComponentFactory<T> componentFactory
     )
     )
     {
     {

+ 0 - 0
Terminal.Gui/App/MainLoopSyncContext.cs → Terminal.Gui/App/MainLoop/MainLoopSyncContext.cs


+ 4 - 3
Terminal.Gui/Drivers/V2/NotInitializedException.cs → Terminal.Gui/App/NotInitializedException.cs

@@ -1,10 +1,11 @@
-namespace Terminal.Gui.Drivers;
+namespace Terminal.Gui.App;
 
 
 /// <summary>
 /// <summary>
 ///     Thrown when user code attempts to access a property or perform a method
 ///     Thrown when user code attempts to access a property or perform a method
-///     that is only supported after Initialization e.g. of an <see cref="IMainLoop{T}"/>
+///     Exception type thrown when trying to use a property or method
+///     that is only supported after initialization, e.g. of an <see cref="IApplicationMainLoop{T}"/>
 /// </summary>
 /// </summary>
-public class NotInitializedException : Exception
+public class NotInitializedException : InvalidOperationException
 {
 {
     /// <summary>
     /// <summary>
     ///     Creates a new instance of the exception indicating that the class
     ///     Creates a new instance of the exception indicating that the class

+ 25 - 5
Terminal.Gui/App/Timeout/TimedEvents.cs

@@ -1,11 +1,13 @@
 #nullable enable
 #nullable enable
+using System.Diagnostics;
+
 namespace Terminal.Gui.App;
 namespace Terminal.Gui.App;
 
 
 /// <summary>
 /// <summary>
 ///     Manages scheduled timeouts (timed callbacks) for the application.
 ///     Manages scheduled timeouts (timed callbacks) for the application.
 ///     <para>
 ///     <para>
 ///         Allows scheduling of callbacks to be invoked after a specified delay, with optional repetition.
 ///         Allows scheduling of callbacks to be invoked after a specified delay, with optional repetition.
-///         Timeouts are stored in a sorted list by their scheduled execution time (UTC ticks).
+///         Timeouts are stored in a sorted list by their scheduled execution time (high-resolution ticks).
 ///         Thread-safe for concurrent access.
 ///         Thread-safe for concurrent access.
 ///     </para>
 ///     </para>
 ///     <para>
 ///     <para>
@@ -26,6 +28,10 @@ namespace Terminal.Gui.App;
 ///         </list>
 ///         </list>
 ///     </para>
 ///     </para>
 /// </summary>
 /// </summary>
+/// <remarks>
+///     Uses <see cref="Stopwatch.GetTimestamp"/> for high-resolution timing instead of <see cref="DateTime.UtcNow"/>
+///     to provide microsecond-level precision and eliminate race conditions from timer resolution issues.
+/// </remarks>
 public class TimedEvents : ITimedEvents
 public class TimedEvents : ITimedEvents
 {
 {
     internal SortedList<long, Timeout> _timeouts = new ();
     internal SortedList<long, Timeout> _timeouts = new ();
@@ -40,6 +46,18 @@ public class TimedEvents : ITimedEvents
     /// <inheritdoc/>
     /// <inheritdoc/>
     public event EventHandler<TimeoutEventArgs>? Added;
     public event EventHandler<TimeoutEventArgs>? Added;
 
 
+    /// <summary>
+    ///     Gets the current high-resolution timestamp in TimeSpan ticks.
+    ///     Uses <see cref="Stopwatch.GetTimestamp"/> for microsecond-level precision.
+    /// </summary>
+    /// <returns>Current timestamp in TimeSpan ticks (100-nanosecond units).</returns>
+    private static long GetTimestampTicks ()
+    {
+        // Convert Stopwatch ticks to TimeSpan ticks (100-nanosecond units)
+        // Stopwatch.Frequency gives ticks per second, so we need to scale appropriately
+        return Stopwatch.GetTimestamp () * TimeSpan.TicksPerSecond / Stopwatch.Frequency;
+    }
+
     /// <inheritdoc/>
     /// <inheritdoc/>
     public void RunTimers ()
     public void RunTimers ()
     {
     {
@@ -92,7 +110,7 @@ public class TimedEvents : ITimedEvents
     /// <inheritdoc/>
     /// <inheritdoc/>
     public bool CheckTimers (out int waitTimeout)
     public bool CheckTimers (out int waitTimeout)
     {
     {
-        long now = DateTime.UtcNow.Ticks;
+        long now = GetTimestampTicks ();
 
 
         waitTimeout = 0;
         waitTimeout = 0;
 
 
@@ -125,12 +143,14 @@ public class TimedEvents : ITimedEvents
     {
     {
         lock (_timeoutsLockToken)
         lock (_timeoutsLockToken)
         {
         {
-            long k = (DateTime.UtcNow + time).Ticks;
+            long k = GetTimestampTicks () + time.Ticks;
 
 
             // if user wants to run as soon as possible set timer such that it expires right away (no race conditions)
             // if user wants to run as soon as possible set timer such that it expires right away (no race conditions)
             if (time == TimeSpan.Zero)
             if (time == TimeSpan.Zero)
             {
             {
-                k -= 100;
+                // Use a more substantial buffer (1ms) to ensure it's truly in the past
+                // even under debugger overhead and extreme timing variations
+                k -= TimeSpan.TicksPerMillisecond;
             }
             }
 
 
             _timeouts.Add (NudgeToUniqueKey (k), timeout);
             _timeouts.Add (NudgeToUniqueKey (k), timeout);
@@ -159,7 +179,7 @@ public class TimedEvents : ITimedEvents
 
 
     private void RunTimersImpl ()
     private void RunTimersImpl ()
     {
     {
-        long now = DateTime.UtcNow.Ticks;
+        long now = GetTimestampTicks ();
         SortedList<long, Timeout> copy;
         SortedList<long, Timeout> copy;
 
 
         // lock prevents new timeouts being added
         // lock prevents new timeouts being added

+ 1 - 1
Terminal.Gui/Drivers/V2/IToplevelTransitionManager.cs → Terminal.Gui/App/Toplevel/IToplevelTransitionManager.cs

@@ -1,4 +1,4 @@
-namespace Terminal.Gui.Drivers;
+namespace Terminal.Gui.App;
 
 
 /// <summary>
 /// <summary>
 ///     Interface for class that handles bespoke behaviours that occur when application
 ///     Interface for class that handles bespoke behaviours that occur when application

+ 2 - 1
Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs → Terminal.Gui/App/Toplevel/ToplevelTransitionManager.cs

@@ -1,6 +1,7 @@
 #nullable enable
 #nullable enable
+using Terminal.Gui.Drivers;
 
 
-namespace Terminal.Gui.Drivers;
+namespace Terminal.Gui.App;
 
 
 /// <summary>
 /// <summary>
 ///     Handles bespoke behaviours that occur when application top level changes.
 ///     Handles bespoke behaviours that occur when application top level changes.

+ 3 - 0
Terminal.Gui/Configuration/ConfigProperty.cs

@@ -1,6 +1,7 @@
 #nullable enable
 #nullable enable
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Collections.Immutable;
 using System.Collections.Immutable;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.Json;
@@ -74,6 +75,8 @@ public class ConfigProperty
             {
             {
                 // Use DeepCloner to create a deep copy of PropertyValue
                 // Use DeepCloner to create a deep copy of PropertyValue
                 object? val = DeepCloner.DeepClone (PropertyValue);
                 object? val = DeepCloner.DeepClone (PropertyValue);
+
+                Debug.Assert (!Immutable);
                 PropertyInfo.SetValue (null, val);
                 PropertyInfo.SetValue (null, val);
 
 
             }
             }

+ 30 - 20
Terminal.Gui/Configuration/ConfigurationManager.cs

@@ -51,7 +51,7 @@ public static class ConfigurationManager
 {
 {
     /// <summary>The backing property for <see cref="Settings"/> (config settings of <see cref="SettingsScope"/>).</summary>
     /// <summary>The backing property for <see cref="Settings"/> (config settings of <see cref="SettingsScope"/>).</summary>
     /// <remarks>
     /// <remarks>
-    ///     Is <see langword="null"/> until <see cref="ResetToCurrentValues"/> is called. Gets set to a new instance by
+    ///     Is <see langword="null"/> until <see cref="UpdateToCurrentValues"/> is called. Gets set to a new instance by
     ///     deserialization
     ///     deserialization
     ///     (see <see cref="Load"/>).
     ///     (see <see cref="Load"/>).
     /// </remarks>
     /// </remarks>
@@ -117,15 +117,19 @@ public static class ConfigurationManager
         }
         }
     }
     }
 
 
+    // TODO: Find a way to make this cache truly read-only at the leaf node level. 
+    // TODO: Right now, the dictionary is frozen, but the ConfigProperty instances can still be modified   
+    // TODO: if the PropertyValue is a reference type.
+    // TODO: See https://github.com/gui-cs/Terminal.Gui/issues/4288
     /// <summary>
     /// <summary>
     ///     A cache of all<see cref="ConfigurationPropertyAttribute"/> properties and their hard coded values.
     ///     A cache of all<see cref="ConfigurationPropertyAttribute"/> properties and their hard coded values.
     /// </summary>
     /// </summary>
     /// <remarks>Is <see langword="null"/> until <see cref="Initialize"/> is called.</remarks>
     /// <remarks>Is <see langword="null"/> until <see cref="Initialize"/> is called.</remarks>
 #pragma warning disable IDE1006 // Naming Styles
 #pragma warning disable IDE1006 // Naming Styles
     internal static FrozenDictionary<string, ConfigProperty>? _hardCodedConfigPropertyCache;
     internal static FrozenDictionary<string, ConfigProperty>? _hardCodedConfigPropertyCache;
+
     private static readonly object _hardCodedConfigPropertyCacheLock = new ();
     private static readonly object _hardCodedConfigPropertyCacheLock = new ();
 #pragma warning restore IDE1006 // Naming Styles
 #pragma warning restore IDE1006 // Naming Styles
-
     internal static FrozenDictionary<string, ConfigProperty>? GetHardCodedConfigPropertyCache ()
     internal static FrozenDictionary<string, ConfigProperty>? GetHardCodedConfigPropertyCache ()
     {
     {
         lock (_hardCodedConfigPropertyCacheLock)
         lock (_hardCodedConfigPropertyCacheLock)
@@ -183,7 +187,7 @@ public static class ConfigurationManager
         lock (_uninitializedConfigPropertiesCacheCacheLock)
         lock (_uninitializedConfigPropertiesCacheCacheLock)
         {
         {
             // _allConfigProperties: for ordered, iterable access (LINQ-friendly)
             // _allConfigProperties: for ordered, iterable access (LINQ-friendly)
-            // _frozenConfigPropertyCache: for high-speed key lookup (frozen)
+            // _hardCodedConfigPropertyCache: for high-speed key lookup (frozen)
 
 
             // Note GetAllConfigProperties returns a new instance and all the properties !HasValue and Immutable.
             // Note GetAllConfigProperties returns a new instance and all the properties !HasValue and Immutable.
             _uninitializedConfigPropertiesCache = ConfigProperty.GetAllConfigProperties ();
             _uninitializedConfigPropertiesCache = ConfigProperty.GetAllConfigProperties ();
@@ -209,6 +213,11 @@ public static class ConfigurationManager
         }
         }
 
 
         LoadHardCodedDefaults ();
         LoadHardCodedDefaults ();
+
+        // BUGBUG: ThemeScope is broken and needs to be fixed to not have the hard coded schemes get overwritten.
+        // BUGBUG: This a partial workaround.
+        // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/4288
+        ThemeManager.Themes? [ThemeManager.Theme]?.Apply ();
     }
     }
 
 
     #endregion Initialization
     #endregion Initialization
@@ -291,6 +300,7 @@ public static class ConfigurationManager
 
 
         if (resetToHardCodedDefaults)
         if (resetToHardCodedDefaults)
         {
         {
+            // Calls Apply
             ResetToHardCodedDefaults ();
             ResetToHardCodedDefaults ();
         }
         }
     }
     }
@@ -299,16 +309,17 @@ public static class ConfigurationManager
 
 
     #region Reset
     #region Reset
 
 
-    // `Reset` - Reset the configuration to either the current values or the hard-coded defaults.
-    // Resetting does not load the configuration; it only resets the configuration to the default values.
+    // `Update` - Updates the configuration from either the current values or the hard-coded defaults.
+    // Updating does not load the configuration; it only updates the configuration to the values currently
+    // in the static ConfigProperties.
 
 
     /// <summary>
     /// <summary>
-    ///     INTERNAL: Resets <see cref="ConfigurationManager"/>. Loads settings from the current
+    ///     INTERNAL: Updates <see cref="ConfigurationManager"/> to the settings from the current
     ///     values of the static <see cref="ConfigurationPropertyAttribute"/> properties.
     ///     values of the static <see cref="ConfigurationPropertyAttribute"/> properties.
     /// </summary>
     /// </summary>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    internal static void ResetToCurrentValues ()
+    internal static void UpdateToCurrentValues ()
     {
     {
         if (!IsInitialized ())
         if (!IsInitialized ())
         {
         {
@@ -327,13 +338,13 @@ public static class ConfigurationManager
             _settingsLockSlim.ExitWriteLock ();
             _settingsLockSlim.ExitWriteLock ();
         }
         }
 
 
-        Settings!.LoadCurrentValues ();
+        Settings!.UpdateToCurrentValues ();
         ThemeManager.UpdateToCurrentValues ();
         ThemeManager.UpdateToCurrentValues ();
-        AppSettings!.LoadCurrentValues ();
+        AppSettings!.UpdateToCurrentValues ();
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     INTERNAL: Resets <see cref="ConfigurationManager"/>. Loads the hard-coded values of the
+    ///     INTERNAL: Loads the hard-coded values of the
     ///     <see cref="ConfigurationPropertyAttribute"/> properties and applies them.
     ///     <see cref="ConfigurationPropertyAttribute"/> properties and applies them.
     /// </summary>
     /// </summary>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresUnreferencedCode ("AOT")]
@@ -374,7 +385,7 @@ public static class ConfigurationManager
 
 
         Settings = new ();
         Settings = new ();
         Settings!.LoadHardCodedDefaults ();
         Settings!.LoadHardCodedDefaults ();
-        ThemeManager.ResetToHardCodedDefaults ();
+        ThemeManager.LoadHardCodedDefaults ();
         AppSettings!.LoadHardCodedDefaults ();
         AppSettings!.LoadHardCodedDefaults ();
     }
     }
 
 
@@ -447,10 +458,6 @@ public static class ConfigurationManager
         {
         {
             SourcesManager?.Load (Settings, $"~/.tui/{AppName}.{_configFilename}", ConfigLocations.AppHome);
             SourcesManager?.Load (Settings, $"~/.tui/{AppName}.{_configFilename}", ConfigLocations.AppHome);
         }
         }
-
-        Settings!.Validate ();
-        ThemeManager.Validate ();
-        AppSettings!.Validate ();
     }
     }
 
 
     // TODO: Rename to Loaded?
     // TODO: Rename to Loaded?
@@ -566,7 +573,7 @@ public static class ConfigurationManager
 
 
     [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
     [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
     internal static readonly SourceGenerationContext SerializerContext = new (
     internal static readonly SourceGenerationContext SerializerContext = new (
-                                                                              new JsonSerializerOptions
+                                                                              new()
                                                                               {
                                                                               {
                                                                                   // Be relaxed
                                                                                   // Be relaxed
                                                                                   ReadCommentHandling = JsonCommentHandling.Skip,
                                                                                   ReadCommentHandling = JsonCommentHandling.Skip,
@@ -638,7 +645,7 @@ public static class ConfigurationManager
                 if (!appSettingsConfigProperty.HasValue)
                 if (!appSettingsConfigProperty.HasValue)
                 {
                 {
                     var appSettings = new AppSettingsScope ();
                     var appSettings = new AppSettingsScope ();
-                    appSettings.LoadCurrentValues ();
+                    appSettings.UpdateToCurrentValues ();
 
 
                     return appSettings;
                     return appSettings;
                 }
                 }
@@ -710,8 +717,9 @@ public static class ConfigurationManager
         {
         {
             if (_jsonErrors.Length > 0)
             if (_jsonErrors.Length > 0)
             {
             {
-                Console.WriteLine (@"Terminal.Gui ConfigurationManager encountered these errors while reading configuration files" +
-                                   @"(set ThrowOnJsonErrors to have these caught during execution):");
+                Console.WriteLine (
+                                   @"Terminal.Gui ConfigurationManager encountered these errors while reading configuration files"
+                                   + @"(set ThrowOnJsonErrors to have these caught during execution):");
                 Console.WriteLine (_jsonErrors.ToString ());
                 Console.WriteLine (_jsonErrors.ToString ());
             }
             }
         }
         }
@@ -783,8 +791,10 @@ public static class ConfigurationManager
 
 
             Debug.Assert (filtered is { });
             Debug.Assert (filtered is { });
 
 
-            IEnumerable<KeyValuePair<string, ConfigProperty>> configPropertiesByScope = filtered as KeyValuePair<string, ConfigProperty> [] ?? filtered.ToArray ();
+            IEnumerable<KeyValuePair<string, ConfigProperty>> configPropertiesByScope =
+                filtered as KeyValuePair<string, ConfigProperty> [] ?? filtered.ToArray ();
             Debug.Assert (configPropertiesByScope.All (v => !v.Value.HasValue));
             Debug.Assert (configPropertiesByScope.All (v => !v.Value.HasValue));
+
             return configPropertiesByScope;
             return configPropertiesByScope;
         }
         }
     }
     }

+ 25 - 15
Terminal.Gui/Configuration/SchemeManager.cs

@@ -7,25 +7,29 @@ using System.Text.Json.Serialization;
 namespace Terminal.Gui.Configuration;
 namespace Terminal.Gui.Configuration;
 
 
 /// <summary>
 /// <summary>
-///     Holds the <see cref="Drawing.Scheme"/>s that define the <see cref="System.Attribute"/>s that are used by views to render
-///     themselves. A Scheme is a mapping from <see cref="Drawing.VisualRole"/>s (such as <see cref="Drawing.VisualRole.Focus"/>) to <see cref="System.Attribute"/>s.
+///     Holds the <see cref="Drawing.Scheme"/>s that define the <see cref="System.Attribute"/>s that are used by views to
+///     render
+///     themselves. A Scheme is a mapping from <see cref="Drawing.VisualRole"/>s (such as
+///     <see cref="Drawing.VisualRole.Focus"/>) to <see cref="System.Attribute"/>s.
 ///     A Scheme defines how a `View` should look based on its purpose (e.g. Menu or Dialog).
 ///     A Scheme defines how a `View` should look based on its purpose (e.g. Menu or Dialog).
 /// </summary>
 /// </summary>
-public sealed class SchemeManager// : INotifyCollectionChanged, IDictionary<string, Scheme?>
+public sealed class SchemeManager // : INotifyCollectionChanged, IDictionary<string, Scheme?>
 {
 {
 #pragma warning disable IDE1006 // Naming Styles
 #pragma warning disable IDE1006 // Naming Styles
     private static readonly object _schemesLock = new ();
     private static readonly object _schemesLock = new ();
 #pragma warning restore IDE1006 // Naming Styles
 #pragma warning restore IDE1006 // Naming Styles
 
 
     /// <summary>
     /// <summary>
-    ///     INTERNAL: Gets the hard-coded schemes defined by <see cref="View"/>. These are not loaded from the configuration files,
+    ///     INTERNAL: Gets the hard-coded schemes defined by <see cref="View"/>. These are not loaded from the configuration
+    ///     files,
     ///     but are hard-coded in the source code. Used for unit testing when ConfigurationManager is not initialized.
     ///     but are hard-coded in the source code. Used for unit testing when ConfigurationManager is not initialized.
     /// </summary>
     /// </summary>
     /// <returns></returns>
     /// <returns></returns>
     internal static ImmutableSortedDictionary<string, Scheme?>? GetHardCodedSchemes () { return Scheme.GetHardCodedSchemes ()!; }
     internal static ImmutableSortedDictionary<string, Scheme?>? GetHardCodedSchemes () { return Scheme.GetHardCodedSchemes ()!; }
 
 
     /// <summary>
     /// <summary>
-    ///     Use <see cref="AddScheme"/>, <see cref="GetScheme(Drawing.Schemes)"/>, <see cref="GetSchemeNames"/>, <see cref="GetSchemesForCurrentTheme"/>, etc... instead.
+    ///     Use <see cref="AddScheme"/>, <see cref="GetScheme(Drawing.Schemes)"/>, <see cref="GetSchemeNames"/>,
+    ///     <see cref="GetSchemesForCurrentTheme"/>, etc... instead.
     /// </summary>
     /// </summary>
     [ConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
     [ConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
     [JsonConverter (typeof (DictionaryJsonConverter<Scheme?>))]
     [JsonConverter (typeof (DictionaryJsonConverter<Scheme?>))]
@@ -54,7 +58,7 @@ public sealed class SchemeManager// : INotifyCollectionChanged, IDictionary<stri
     /// <summary>INTERNAL: The set method for <see cref="Schemes"/>.</summary>
     /// <summary>INTERNAL: The set method for <see cref="Schemes"/>.</summary>
     [RequiresUnreferencedCode ("Calls Terminal.Gui.ConfigProperty.UpdateFrom(Object)")]
     [RequiresUnreferencedCode ("Calls Terminal.Gui.ConfigProperty.UpdateFrom(Object)")]
     [RequiresDynamicCode ("Calls Terminal.Gui.ConfigProperty.UpdateFrom(Object)")]
     [RequiresDynamicCode ("Calls Terminal.Gui.ConfigProperty.UpdateFrom(Object)")]
-    private static void SetSchemes (Dictionary<string, Scheme?>? value)
+    internal static void SetSchemes (Dictionary<string, Scheme?>? value)
     {
     {
         lock (_schemesLock)
         lock (_schemesLock)
         {
         {
@@ -117,6 +121,7 @@ public sealed class SchemeManager// : INotifyCollectionChanged, IDictionary<stri
     {
     {
         // Convert schemeName to string via Enum api
         // Convert schemeName to string via Enum api
         string? schemeNameString = SchemesToSchemeName (schemeName);
         string? schemeNameString = SchemesToSchemeName (schemeName);
+
         if (schemeNameString is null)
         if (schemeNameString is null)
         {
         {
             throw new ArgumentException ($"Invalid scheme name: {schemeName}");
             throw new ArgumentException ($"Invalid scheme name: {schemeName}");
@@ -131,10 +136,7 @@ public sealed class SchemeManager// : INotifyCollectionChanged, IDictionary<stri
     /// <param name="schemeName"></param>
     /// <param name="schemeName"></param>
     /// <returns></returns>
     /// <returns></returns>
     /// <exception cref="ArgumentException"></exception>
     /// <exception cref="ArgumentException"></exception>
-    public static Scheme GetScheme (string schemeName)
-    {
-        return GetSchemesForCurrentTheme ()! [schemeName]!;
-    }
+    public static Scheme GetScheme (string schemeName) { return GetSchemesForCurrentTheme ()! [schemeName]!; }
 
 
     /// <summary>
     /// <summary>
     ///     Gets the name of the specified <see cref="Schemes"/>. Will throw an exception if <paramref name="schemeName"/>
     ///     Gets the name of the specified <see cref="Schemes"/>. Will throw an exception if <paramref name="schemeName"/>
@@ -142,10 +144,7 @@ public sealed class SchemeManager// : INotifyCollectionChanged, IDictionary<stri
     /// </summary>
     /// </summary>
     /// <param name="schemeName"></param>
     /// <param name="schemeName"></param>
     /// <returns>The name of scheme.</returns>
     /// <returns>The name of scheme.</returns>
-    public static string? SchemesToSchemeName (Schemes schemeName)
-    {
-        return Enum.GetName (typeof (Schemes), schemeName);
-    }
+    public static string? SchemesToSchemeName (Schemes schemeName) { return Enum.GetName (typeof (Schemes), schemeName); }
 
 
     /// <summary>
     /// <summary>
     ///     Converts a string to a <see cref="Schemes"/> enum value.
     ///     Converts a string to a <see cref="Schemes"/> enum value.
@@ -158,11 +157,12 @@ public sealed class SchemeManager// : INotifyCollectionChanged, IDictionary<stri
         {
         {
             return value?.ToString ();
             return value?.ToString ();
         }
         }
+
         return null;
         return null;
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Get the dictionary schemes from the selected theme loaded from configuration.
+    ///     Get the dictionary of schemes from the current theme. Current means active.
     /// </summary>
     /// </summary>
     /// <returns></returns>
     /// <returns></returns>
     public static Dictionary<string, Scheme?> GetSchemesForCurrentTheme ()
     public static Dictionary<string, Scheme?> GetSchemesForCurrentTheme ()
@@ -195,4 +195,14 @@ public sealed class SchemeManager// : INotifyCollectionChanged, IDictionary<stri
             return GetSchemes ()!.Keys.ToImmutableList ();
             return GetSchemes ()!.Keys.ToImmutableList ();
         }
         }
     }
     }
+
+    [RequiresUnreferencedCode ("Calls SetSchemes")]
+    [RequiresDynamicCode ("Calls SetSchemes")]
+    internal static void LoadToHardCodedDefaults ()
+    {
+        // BUGBUG: SchemeManager is broken and needs to be fixed to not have the hard coded schemes get overwritten.
+        // BUGBUG: This is a partial workaround
+        // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/4288
+        SetSchemes (GetHardCodedSchemes ()!.ToDictionary ());
+    }
 }
 }

+ 22 - 26
Terminal.Gui/Configuration/Scope.cs

@@ -1,7 +1,7 @@
 #nullable enable
 #nullable enable
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
-using System.Text.Json;
 
 
 namespace Terminal.Gui.Configuration;
 namespace Terminal.Gui.Configuration;
 
 
@@ -20,13 +20,12 @@ public class Scope<T> : ConcurrentDictionary<string, ConfigProperty>
     ///     will be <see langword="false"/>).
     ///     will be <see langword="false"/>).
     /// </summary>
     /// </summary>
     [RequiresUnreferencedCode (
     [RequiresUnreferencedCode (
-        "Uses cached configuration properties filtered by type T. This is AOT-safe as long as T is one of the known scope types (SettingsScope, ThemeScope, AppSettingsScope).")]
-    public Scope () : base (StringComparer.InvariantCultureIgnoreCase)
-    {
-    }
+                                  "Uses cached configuration properties filtered by type T. This is AOT-safe as long as T is one of the known scope types (SettingsScope, ThemeScope, AppSettingsScope).")]
+    public Scope () : base (StringComparer.InvariantCultureIgnoreCase) { }
 
 
     /// <summary>
     /// <summary>
-    ///     INTERNAL: Adds a new ConfigProperty given a <paramref name="value"/>. Determines the correct PropertyInfo etc... by retrieving the
+    ///     INTERNAL: Adds a new ConfigProperty given a <paramref name="value"/>. Determines the correct PropertyInfo etc... by
+    ///     retrieving the
     ///     hard coded value for <paramref name="name"/>.
     ///     hard coded value for <paramref name="name"/>.
     /// </summary>
     /// </summary>
     /// <param name="name"></param>
     /// <param name="name"></param>
@@ -42,20 +41,20 @@ public class Scope<T> : ConcurrentDictionary<string, ConfigProperty>
 
 
         TryAdd (name, ConfigProperty.CreateCopy (configProperty));
         TryAdd (name, ConfigProperty.CreateCopy (configProperty));
         this [name].PropertyValue = configProperty.PropertyValue;
         this [name].PropertyValue = configProperty.PropertyValue;
-
     }
     }
 
 
     internal ConfigProperty? GetHardCodedProperty (string name)
     internal ConfigProperty? GetHardCodedProperty (string name)
     {
     {
         ConfigProperty? configProperty = ConfigurationManager.GetHardCodedConfigPropertiesByScope (typeof (T).Name)!
         ConfigProperty? configProperty = ConfigurationManager.GetHardCodedConfigPropertiesByScope (typeof (T).Name)!
-                                                             .FirstOrDefault (hardCodedKeyValuePair => hardCodedKeyValuePair.Key == name).Value;
+                                                             .FirstOrDefault (hardCodedKeyValuePair => hardCodedKeyValuePair.Key == name)
+                                                             .Value;
 
 
         if (configProperty is null)
         if (configProperty is null)
         {
         {
             return null;
             return null;
         }
         }
 
 
-        ConfigProperty copy = ConfigProperty.CreateCopy (configProperty);
+        var copy = ConfigProperty.CreateCopy (configProperty);
         copy.PropertyValue = configProperty.PropertyValue;
         copy.PropertyValue = configProperty.PropertyValue;
 
 
         return copy;
         return copy;
@@ -64,17 +63,18 @@ public class Scope<T> : ConcurrentDictionary<string, ConfigProperty>
     internal ConfigProperty GetUninitializedProperty (string name)
     internal ConfigProperty GetUninitializedProperty (string name)
     {
     {
         ConfigProperty? configProperty = ConfigurationManager.GetUninitializedConfigPropertiesByScope (typeof (T).Name)!
         ConfigProperty? configProperty = ConfigurationManager.GetUninitializedConfigPropertiesByScope (typeof (T).Name)!
-                                                             .FirstOrDefault (hardCodedKeyValuePair => hardCodedKeyValuePair.Key == name).Value;
+                                                             .FirstOrDefault (hardCodedKeyValuePair => hardCodedKeyValuePair.Key == name)
+                                                             .Value;
 
 
         if (configProperty is null)
         if (configProperty is null)
         {
         {
             throw new InvalidOperationException ($@"{name} is not a ConfigProperty.");
             throw new InvalidOperationException ($@"{name} is not a ConfigProperty.");
         }
         }
-        ConfigProperty  copy = ConfigProperty.CreateCopy (configProperty);
+
+        var copy = ConfigProperty.CreateCopy (configProperty);
         copy.PropertyValue = configProperty.PropertyValue;
         copy.PropertyValue = configProperty.PropertyValue;
 
 
         return copy;
         return copy;
-
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -82,7 +82,7 @@ public class Scope<T> : ConcurrentDictionary<string, ConfigProperty>
     ///     <see cref="ConfigurationPropertyAttribute"/> properties.
     ///     <see cref="ConfigurationPropertyAttribute"/> properties.
     /// </summary>
     /// </summary>
     [RequiresDynamicCode ("Uses reflection to retrieve property values")]
     [RequiresDynamicCode ("Uses reflection to retrieve property values")]
-    internal void LoadCurrentValues ()
+    internal void UpdateToCurrentValues ()
     {
     {
         foreach (KeyValuePair<string, ConfigProperty> validProperties in this.Where (cp => cp.Value.PropertyInfo is { }))
         foreach (KeyValuePair<string, ConfigProperty> validProperties in this.Where (cp => cp.Value.PropertyInfo is { }))
         {
         {
@@ -97,7 +97,7 @@ public class Scope<T> : ConcurrentDictionary<string, ConfigProperty>
     {
     {
         foreach (KeyValuePair<string, ConfigProperty> hardCodedKeyValuePair in ConfigurationManager.GetHardCodedConfigPropertiesByScope (typeof (T).Name)!)
         foreach (KeyValuePair<string, ConfigProperty> hardCodedKeyValuePair in ConfigurationManager.GetHardCodedConfigPropertiesByScope (typeof (T).Name)!)
         {
         {
-            ConfigProperty copy = ConfigProperty.CreateCopy (hardCodedKeyValuePair.Value);
+            var copy = ConfigProperty.CreateCopy (hardCodedKeyValuePair.Value);
             TryAdd (hardCodedKeyValuePair.Key, copy);
             TryAdd (hardCodedKeyValuePair.Key, copy);
             this [hardCodedKeyValuePair.Key].PropertyValue = hardCodedKeyValuePair.Value.PropertyValue;
             this [hardCodedKeyValuePair.Key].PropertyValue = hardCodedKeyValuePair.Value.PropertyValue;
         }
         }
@@ -127,7 +127,7 @@ public class Scope<T> : ConcurrentDictionary<string, ConfigProperty>
                 }
                 }
 
 
                 // Add an empty (HasValue = false) property to this scope
                 // Add an empty (HasValue = false) property to this scope
-                ConfigProperty copy = ConfigProperty.CreateCopy (prop.Value);
+                var copy = ConfigProperty.CreateCopy (prop.Value);
                 copy.PropertyValue = prop.Value.PropertyValue;
                 copy.PropertyValue = prop.Value.PropertyValue;
                 TryAdd (prop.Key, copy);
                 TryAdd (prop.Key, copy);
             }
             }
@@ -160,32 +160,28 @@ public class Scope<T> : ConcurrentDictionary<string, ConfigProperty>
             if (propWithValue.Value.PropertyInfo != null)
             if (propWithValue.Value.PropertyInfo != null)
             {
             {
                 object? currentValue = propWithValue.Value.PropertyInfo.GetValue (null);
                 object? currentValue = propWithValue.Value.PropertyInfo.GetValue (null);
+                object? newValue = null;
 
 
                 // QUESTION: Should we avoid setting if currentValue == newValue?
                 // QUESTION: Should we avoid setting if currentValue == newValue?
 
 
                 if (propWithValue.Value.PropertyValue is Scope<T> scopeSource && currentValue is Scope<T> scopeDest)
                 if (propWithValue.Value.PropertyValue is Scope<T> scopeSource && currentValue is Scope<T> scopeDest)
                 {
                 {
-                    propWithValue.Value.PropertyInfo.SetValue (null, scopeDest.UpdateFrom (scopeSource));
+                    newValue = scopeDest.UpdateFrom (scopeSource);
                 }
                 }
                 else
                 else
                 {
                 {
                     // Use DeepCloner to create a deep copy of the property value
                     // Use DeepCloner to create a deep copy of the property value
-                    object? val = DeepCloner.DeepClone (propWithValue.Value.PropertyValue);
-                    propWithValue.Value.PropertyInfo.SetValue (null, val);
+                    newValue = DeepCloner.DeepClone (propWithValue.Value.PropertyValue);
                 }
                 }
 
 
+                // Logging.Debug($"{propWithValue.Key}: {currentValue} -> {newValue}");
+                Debug.Assert (!propWithValue.Value.Immutable);
+                propWithValue.Value.PropertyInfo.SetValue (null, newValue);
+
                 set = true;
                 set = true;
             }
             }
         }
         }
 
 
         return set;
         return set;
     }
     }
-
-    internal virtual void Validate ()
-    {
-        if (IsEmpty)
-        {
-            //throw new JsonException ($@"Empty!");
-        }
-    }
 }
 }

+ 8 - 9
Terminal.Gui/Configuration/ScopeJsonConverter.cs

@@ -107,13 +107,12 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
             {
             {
                 // It is not a config property. Maybe it's just a property on the Scope with [JsonInclude]
                 // It is not a config property. Maybe it's just a property on the Scope with [JsonInclude]
                 // like ScopeSettings.$schema.
                 // like ScopeSettings.$schema.
-                // If so, don't add it to the dictionary but apply it to the underlying property on 
-                // the scopeT. 
-                // BUGBUG: This is a really bad design. The only time it's used is for $schema though.
+                // If so, don't add it to the dictionary but apply it to the underlying property on
+                // the scopeT.
+                // BUGBUG: This is terrible design. The only time it's used is for $schema though.
                 PropertyInfo? property = scope!.GetType ()
                 PropertyInfo? property = scope!.GetType ()
                                                .GetProperties ()
                                                .GetProperties ()
-                                               .Where (
-                                                       p =>
+                                               .Where (p =>
                                                        {
                                                        {
                                                            if (p.GetCustomAttribute (typeof (JsonIncludeAttribute)) is JsonIncludeAttribute { } jia)
                                                            if (p.GetCustomAttribute (typeof (JsonIncludeAttribute)) is JsonIncludeAttribute { } jia)
                                                            {
                                                            {
@@ -143,6 +142,7 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
                 {
                 {
                     // Set the value of propertyName on the scopeT.
                     // Set the value of propertyName on the scopeT.
                     PropertyInfo prop = scope.GetType ().GetProperty (propertyName!)!;
                     PropertyInfo prop = scope.GetType ().GetProperty (propertyName!)!;
+
                     prop.SetValue (scope, JsonSerializer.Deserialize (ref reader, prop.PropertyType, ConfigurationManager.SerializerContext));
                     prop.SetValue (scope, JsonSerializer.Deserialize (ref reader, prop.PropertyType, ConfigurationManager.SerializerContext));
                 }
                 }
                 else
                 else
@@ -168,8 +168,7 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
 
 
         IEnumerable<PropertyInfo> properties = scope!.GetType ()
         IEnumerable<PropertyInfo> properties = scope!.GetType ()
                                                      .GetProperties ()
                                                      .GetProperties ()
-                                                     .Where (
-                                                             p => p.GetCustomAttribute (typeof (JsonIncludeAttribute))
+                                                     .Where (p => p.GetCustomAttribute (typeof (JsonIncludeAttribute))
                                                                   != null
                                                                   != null
                                                             );
                                                             );
 
 
@@ -181,8 +180,7 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
         }
         }
 
 
         foreach (KeyValuePair<string, ConfigProperty> p in from p in scope
         foreach (KeyValuePair<string, ConfigProperty> p in from p in scope
-                                                               .Where (
-                                                                       cp =>
+                                                               .Where (cp =>
                                                                            cp.Value.PropertyInfo?.GetCustomAttribute (
                                                                            cp.Value.PropertyInfo?.GetCustomAttribute (
                                                                                     typeof (
                                                                                     typeof (
                                                                                         ConfigurationPropertyAttribute)
                                                                                         ConfigurationPropertyAttribute)
@@ -223,6 +221,7 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
             else
             else
             {
             {
                 object? prop = p.Value.PropertyValue;
                 object? prop = p.Value.PropertyValue;
+
                 if (prop == null)
                 if (prop == null)
                 {
                 {
                     writer.WriteNullValue ();
                     writer.WriteNullValue ();

+ 38 - 26
Terminal.Gui/Configuration/ThemeManager.cs

@@ -22,18 +22,19 @@ public static class ThemeManager
     public static ThemeScope GetCurrentTheme () { return Themes! [Theme]; }
     public static ThemeScope GetCurrentTheme () { return Themes! [Theme]; }
 
 
     /// <summary>
     /// <summary>
+    ///     INTERNAL: Getter for <see cref="Themes"/>.
     ///     Convenience method to get the themes dictionary. The themes dictionary is a dictionary of <see cref="ThemeScope"/>
     ///     Convenience method to get the themes dictionary. The themes dictionary is a dictionary of <see cref="ThemeScope"/>
     ///     objects, with the key being the name of the theme.
     ///     objects, with the key being the name of the theme.
     /// </summary>
     /// </summary>
     /// <returns></returns>
     /// <returns></returns>
     /// <exception cref="InvalidOperationException"></exception>
     /// <exception cref="InvalidOperationException"></exception>
-    public static ConcurrentDictionary<string, ThemeScope> GetThemes ()
+    private static ConcurrentDictionary<string, ThemeScope> GetThemes ()
     {
     {
         if (!ConfigurationManager.IsInitialized ())
         if (!ConfigurationManager.IsInitialized ())
         {
         {
             // We're being called from the module initializer.
             // We're being called from the module initializer.
             // We need to provide a dictionary of themes containing the hard-coded theme.
             // We need to provide a dictionary of themes containing the hard-coded theme.
-            return HardCodedThemes ()!;
+            return GetHardCodedThemes ()!;
         }
         }
 
 
         if (ConfigurationManager.Settings is null)
         if (ConfigurationManager.Settings is null)
@@ -48,14 +49,14 @@ public static class ThemeManager
                 return (themes.PropertyValue as ConcurrentDictionary<string, ThemeScope>)!;
                 return (themes.PropertyValue as ConcurrentDictionary<string, ThemeScope>)!;
             }
             }
 
 
-            return HardCodedThemes ()!;
+            return GetHardCodedThemes ()!;
         }
         }
 
 
         throw new InvalidOperationException ("Settings has no Themes property.");
         throw new InvalidOperationException ("Settings has no Themes property.");
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Convenience method to get a list of theme names.
+    ///    INTERNAL: Convenience method to get a list of theme names.
     /// </summary>
     /// </summary>
     /// <returns></returns>
     /// <returns></returns>
     /// <exception cref="InvalidOperationException"></exception>
     /// <exception cref="InvalidOperationException"></exception>
@@ -65,7 +66,7 @@ public static class ThemeManager
         {
         {
             // We're being called from the module initializer.
             // We're being called from the module initializer.
             // We need to provide a dictionary of themes containing the hard-coded theme.
             // We need to provide a dictionary of themes containing the hard-coded theme.
-            return HardCodedThemes ()!.Keys.ToImmutableList ();
+            return GetHardCodedThemes ()!.Keys.ToImmutableList ();
         }
         }
 
 
         if (ConfigurationManager.Settings is null)
         if (ConfigurationManager.Settings is null)
@@ -86,7 +87,7 @@ public static class ThemeManager
         }
         }
         else
         else
         {
         {
-            returnConcurrentDictionary = HardCodedThemes ();
+            returnConcurrentDictionary = GetHardCodedThemes ();
         }
         }
 
 
         return returnConcurrentDictionary!.Keys
         return returnConcurrentDictionary!.Keys
@@ -121,6 +122,11 @@ public static class ThemeManager
         internal set => SetThemes (value);
         internal set => SetThemes (value);
     }
     }
 
 
+    /// <summary>
+    ///     INTERNAL: Setter for <see cref="Themes"/>.
+    /// </summary>
+    /// <param name="dictionary"></param>
+    /// <exception cref="InvalidOperationException"></exception>
     private static void SetThemes (ConcurrentDictionary<string, ThemeScope>? dictionary)
     private static void SetThemes (ConcurrentDictionary<string, ThemeScope>? dictionary)
     {
     {
         if (dictionary is { } && !dictionary.ContainsKey (DEFAULT_THEME_NAME))
         if (dictionary is { } && !dictionary.ContainsKey (DEFAULT_THEME_NAME))
@@ -138,7 +144,12 @@ public static class ThemeManager
         throw new InvalidOperationException ("Settings is null.");
         throw new InvalidOperationException ("Settings is null.");
     }
     }
 
 
-    private static ConcurrentDictionary<string, ThemeScope>? HardCodedThemes ()
+    /// <summary>
+    ///     INTERNAL: Returns the hard-coded Themes dictionary.
+    /// </summary>
+    /// <returns></returns>
+    /// <exception cref="InvalidOperationException"></exception>
+    private static ConcurrentDictionary<string, ThemeScope>? GetHardCodedThemes ()
     {
     {
         ThemeScope? hardCodedThemeScope = GetHardCodedThemeScope ();
         ThemeScope? hardCodedThemeScope = GetHardCodedThemeScope ();
 
 
@@ -151,10 +162,10 @@ public static class ThemeManager
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Returns a dictionary of hard-coded ThemeScope properties.
+    ///     INTERNAL: Returns the ThemeScope containing the hard-coded Themes.
     /// </summary>
     /// </summary>
     /// <returns></returns>
     /// <returns></returns>
-    private static ThemeScope? GetHardCodedThemeScope ()
+    private static ThemeScope GetHardCodedThemeScope ()
     {
     {
         IEnumerable<KeyValuePair<string, ConfigProperty>>? hardCodedThemeProperties = ConfigurationManager.GetHardCodedConfigPropertiesByScope ("ThemeScope");
         IEnumerable<KeyValuePair<string, ConfigProperty>>? hardCodedThemeProperties = ConfigurationManager.GetHardCodedConfigPropertiesByScope ("ThemeScope");
 
 
@@ -173,9 +184,9 @@ public static class ThemeManager
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Since Theme is a dynamic property, we need to cache the value of the selected theme for when CM is not enabled.
+    ///     The name of the default theme ("Default").
     /// </summary>
     /// </summary>
-    internal const string DEFAULT_THEME_NAME = "Default";
+    public const string DEFAULT_THEME_NAME = "Default";
 
 
     /// <summary>
     /// <summary>
     ///     The currently selected theme. The backing store is <c><see cref="ConfigurationManager.Settings"/> ["Theme"]</c>.
     ///     The currently selected theme. The backing store is <c><see cref="ConfigurationManager.Settings"/> ["Theme"]</c>.
@@ -256,13 +267,19 @@ public static class ThemeManager
     /// </summary>
     /// </summary>
     [RequiresUnreferencedCode ("Calls Terminal.Gui.ThemeManager.Themes")]
     [RequiresUnreferencedCode ("Calls Terminal.Gui.ThemeManager.Themes")]
     [RequiresDynamicCode ("Calls Terminal.Gui.ThemeManager.Themes")]
     [RequiresDynamicCode ("Calls Terminal.Gui.ThemeManager.Themes")]
-    internal static void UpdateToCurrentValues () { Themes! [Theme].LoadCurrentValues (); }
+    internal static void UpdateToCurrentValues ()
+    {
+        // BUGBUG: This corrupts _hardCodedDefaults. See #4288
+        Themes! [Theme].UpdateToCurrentValues ();
+    }
 
 
     /// <summary>
     /// <summary>
-    ///     INTERNAL: Resets all themes to the values the <see cref="ConfigurationPropertyAttribute"/> properties contained
-    ///     when the module was initialized.
+    ///     INTERNAL: Loads all Themes to their hard-coded default values.
     /// </summary>
     /// </summary>
-    internal static void ResetToHardCodedDefaults ()
+    [RequiresUnreferencedCode ("Calls SchemeManager.LoadToHardCodedDefaults")]
+    [RequiresDynamicCode ("Calls SchemeManager.LoadToHardCodedDefaults")]
+
+    internal static void LoadHardCodedDefaults ()
     {
     {
         if (!ConfigurationManager.IsInitialized ())
         if (!ConfigurationManager.IsInitialized ())
         {
         {
@@ -288,8 +305,14 @@ public static class ThemeManager
                                                                         },
                                                                         },
                                                                         StringComparer.InvariantCultureIgnoreCase);
                                                                         StringComparer.InvariantCultureIgnoreCase);
 
 
+        // BUGBUG: SchemeManager is broken and needs to be fixed to not have the hard coded schemes get overwritten.
+        // BUGBUG: This is a partial workaround
+        // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/4288
+        SchemeManager.LoadToHardCodedDefaults ();
+
         ConfigurationManager.Settings ["Themes"].PropertyValue = hardCodedThemes;
         ConfigurationManager.Settings ["Themes"].PropertyValue = hardCodedThemes;
         ConfigurationManager.Settings ["Theme"].PropertyValue = DEFAULT_THEME_NAME;
         ConfigurationManager.Settings ["Theme"].PropertyValue = DEFAULT_THEME_NAME;
+
     }
     }
 
 
     /// <summary>Called when the selected theme has changed. Fires the <see cref="ThemeChanged"/> event.</summary>
     /// <summary>Called when the selected theme has changed. Fires the <see cref="ThemeChanged"/> event.</summary>
@@ -302,15 +325,4 @@ public static class ThemeManager
 
 
     /// <summary>Raised when the selected theme has changed.</summary>
     /// <summary>Raised when the selected theme has changed.</summary>
     public static event EventHandler<EventArgs<string>>? ThemeChanged;
     public static event EventHandler<EventArgs<string>>? ThemeChanged;
-
-    /// <summary>
-    ///     Validates all themes in the <see cref="Themes"/> dictionary.
-    /// </summary>
-    public static void Validate ()
-    {
-        foreach (ThemeScope theme in Themes!.Values)
-        {
-            theme.Validate ();
-        }
-    }
 }
 }

+ 9 - 31
Terminal.Gui/Drawing/Attribute.cs

@@ -25,11 +25,6 @@ public readonly record struct Attribute : IEqualityOperators<Attribute, Attribut
     [JsonIgnore]
     [JsonIgnore]
     public static Attribute Default => new (Color.White, Color.Black);
     public static Attribute Default => new (Color.White, Color.Black);
 
 
-    // TODO: Once CursesDriver is dead, remove this property
-    /// <summary>INTERNAL: The <see cref="IConsoleDriver"/>-specific color value.</summary>
-    [JsonIgnore (Condition = JsonIgnoreCondition.Always)]
-    internal int PlatformColor { get; init; }
-
     /// <summary>
     /// <summary>
     ///     Gets the foreground <see cref="Color"/> used to render text.
     ///     Gets the foreground <see cref="Color"/> used to render text.
     /// </summary>
     /// </summary>
@@ -51,24 +46,12 @@ public readonly record struct Attribute : IEqualityOperators<Attribute, Attribut
     /// <summary>
     /// <summary>
     ///     Initializes a new instance of the <see cref="Attribute"/> struct with default values.
     ///     Initializes a new instance of the <see cref="Attribute"/> struct with default values.
     /// </summary>
     /// </summary>
-    public Attribute () { this = Default with { PlatformColor = -1 }; }
+    public Attribute () { this = Default; }
 
 
     /// <summary>
     /// <summary>
     ///     Initializes a new <see cref="Attribute"/> from an existing instance, preserving explicit state.
     ///     Initializes a new <see cref="Attribute"/> from an existing instance, preserving explicit state.
     /// </summary>
     /// </summary>
-    public Attribute (in Attribute attr) { this = attr with { PlatformColor = -1 }; }
-
-    /// <summary>INTERNAL: Initializes a new instance of the <see cref="Attribute"/> struct.</summary>
-    /// <param name="platformColor">platform-dependent color value.</param>
-    /// <param name="foreground">Foreground</param>
-    /// <param name="background">Background</param>
-    internal Attribute (in int platformColor, in Color foreground, in Color background)
-    {
-        Foreground = foreground;
-        Background = background;
-        PlatformColor = platformColor;
-        Style = TextStyle.None;
-    }
+    public Attribute (in Attribute attr) { this = attr; }
 
 
     /// <summary>
     /// <summary>
     ///     Initializes an instance using two named colors.
     ///     Initializes an instance using two named colors.
@@ -78,8 +61,6 @@ public readonly record struct Attribute : IEqualityOperators<Attribute, Attribut
         Foreground = foreground;
         Foreground = foreground;
         Background = background;
         Background = background;
 
 
-        // TODO: Once CursesDriver supports true color all the PlatformColor stuff goes away
-        PlatformColor = Application.Driver?.MakeColor (in foreground, in background).PlatformColor ?? -1;
         Style = TextStyle.None;
         Style = TextStyle.None;
     }
     }
 
 
@@ -91,9 +72,6 @@ public readonly record struct Attribute : IEqualityOperators<Attribute, Attribut
         Foreground = foreground;
         Foreground = foreground;
         Background = background;
         Background = background;
         Style = style;
         Style = style;
-
-        // TODO: Once CursesDriver supports true color all the PlatformColor stuff goes away
-        PlatformColor = Application.Driver?.MakeColor (in foreground, in background).PlatformColor ?? -1;
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -111,7 +89,6 @@ public readonly record struct Attribute : IEqualityOperators<Attribute, Attribut
         Style = style is { } && Enum.TryParse (style, true, out TextStyle parsedStyle)
         Style = style is { } && Enum.TryParse (style, true, out TextStyle parsedStyle)
                     ? parsedStyle
                     ? parsedStyle
                     : TextStyle.None;
                     : TextStyle.None;
-        PlatformColor = Application.Driver?.MakeColor (Foreground, Background).PlatformColor ?? -1;
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -127,7 +104,6 @@ public readonly record struct Attribute : IEqualityOperators<Attribute, Attribut
         Background = Color.Parse (background);
         Background = Color.Parse (background);
 
 
         Style = style;
         Style = style;
-        PlatformColor = Application.Driver?.MakeColor (Foreground, Background).PlatformColor ?? -1;
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -186,20 +162,22 @@ public readonly record struct Attribute : IEqualityOperators<Attribute, Attribut
     /// <summary>
     /// <summary>
     ///     Initializes a new instance with foreground and background colors and a <see cref="TextStyle"/>.
     ///     Initializes a new instance with foreground and background colors and a <see cref="TextStyle"/>.
     /// </summary>
     /// </summary>
-    public Attribute (in StandardColor foreground, in StandardColor background, in TextStyle style) : this (new (in foreground), new Color (in background), style) { }
-
+    public Attribute (in StandardColor foreground, in StandardColor background, in TextStyle style) : this (
+                                                                                                            new (in foreground),
+                                                                                                            new Color (in background),
+                                                                                                            style)
+    { }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
     public bool Equals (Attribute other)
     public bool Equals (Attribute other)
     {
     {
-        return PlatformColor == other.PlatformColor
-               && Foreground.Equals (other.Foreground)
+        return Foreground.Equals (other.Foreground)
                && Background.Equals (other.Background)
                && Background.Equals (other.Background)
                && Style == other.Style;
                && Style == other.Style;
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public override int GetHashCode () { return HashCode.Combine (PlatformColor, Foreground, Background, Style); }
+    public override int GetHashCode () { return HashCode.Combine (Foreground, Background, Style); }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
     public override string ToString ()
     public override string ToString ()

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

@@ -113,7 +113,7 @@ public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanPar
 
 
     /// <summary>Initializes a new instance of the <see cref="Color"/> color from a value in the <see cref="StandardColor"/> enum.</summary>
     /// <summary>Initializes a new instance of the <see cref="Color"/> color from a value in the <see cref="StandardColor"/> enum.</summary>
     /// <param name="colorName">The 16-color value.</param>
     /// <param name="colorName">The 16-color value.</param>
-    public Color (in StandardColor colorName) : this ((int)colorName) { }
+    public Color (in StandardColor colorName) : this (StandardColors.GetArgb (colorName)) { }
 
 
     /// <summary>
     /// <summary>
     ///     Initializes a new instance of the <see cref="Color"/> color from string. See
     ///     Initializes a new instance of the <see cref="Color"/> color from string. See

+ 0 - 0
Terminal.Gui/Drivers/AnsiEscapeSequence.cs → Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequence.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiEscapeSequenceRequest.cs → Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequenceRequest.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/AnsiMouseParser.cs → Terminal.Gui/Drivers/AnsiHandling/AnsiMouseParser.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/AnsiRequestScheduler.cs → Terminal.Gui/Drivers/AnsiHandling/AnsiRequestScheduler.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/AnsiResponseExpectation.cs → Terminal.Gui/Drivers/AnsiHandling/AnsiResponseExpectation.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/AnsiResponseParser.cs → Terminal.Gui/Drivers/AnsiHandling/AnsiResponseParser.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/AnsiResponseParserState.cs → Terminal.Gui/Drivers/AnsiHandling/AnsiResponseParserState.cs


+ 0 - 0
Terminal.Gui/Drivers/EscSeqUtils/EscSeqReqStatus.cs → Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqReqStatus.cs


+ 0 - 0
Terminal.Gui/Drivers/EscSeqUtils/EscSeqRequests.cs → Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqRequests.cs


+ 2 - 2
Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs → Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqUtils.cs

@@ -384,7 +384,7 @@ public static class EscSeqUtils
                 else
                 else
                 {
                 {
                     // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/2803
                     // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/2803
-                    // This is caused by NetDriver depending on Console.KeyAvailable?
+                    // This is caused by DotNetDriver depending on Console.KeyAvailable?
                     //throw new InvalidOperationException ("CSI response, but there's no terminator");
                     //throw new InvalidOperationException ("CSI response, but there's no terminator");
 
 
                     IncompleteCkInfos = cki;
                     IncompleteCkInfos = cki;
@@ -1503,7 +1503,7 @@ public static class EscSeqUtils
             if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
             if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
                 || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control))
                 || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control))
             {
             {
-                // NetDriver doesn't support Shift-Ctrl/Shift-Alt combos
+                // DotNetDriver doesn't support Shift-Ctrl/Shift-Alt combos
                 return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key);
                 return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key);
             }
             }
 
 

+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/GenericHeld.cs → Terminal.Gui/Drivers/AnsiHandling/GenericHeld.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/IAnsiResponseParser.cs → Terminal.Gui/Drivers/AnsiHandling/IAnsiResponseParser.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/IHeld.cs → Terminal.Gui/Drivers/AnsiHandling/IHeld.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs → Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParser.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs → Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParserPattern.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/CsiCursorPattern.cs → Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiCursorPattern.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs → Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiKeyPattern.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/EscAsAltPattern.cs → Terminal.Gui/Drivers/AnsiHandling/Keyboard/EscAsAltPattern.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs → Terminal.Gui/Drivers/AnsiHandling/Keyboard/Ss3Pattern.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/ReasonCannotSend.cs → Terminal.Gui/Drivers/AnsiHandling/ReasonCannotSend.cs


+ 0 - 0
Terminal.Gui/Drivers/AnsiResponseParser/StringHeld.cs → Terminal.Gui/Drivers/AnsiHandling/StringHeld.cs


+ 0 - 0
Terminal.Gui/Drivers/V2/ComponentFactory.cs → Terminal.Gui/Drivers/ComponentFactory.cs


+ 8 - 25
Terminal.Gui/Drivers/ConsoleDriver.cs

@@ -6,9 +6,11 @@ namespace Terminal.Gui.Drivers;
 
 
 /// <summary>Base class for Terminal.Gui IConsoleDriver implementations.</summary>
 /// <summary>Base class for Terminal.Gui IConsoleDriver implementations.</summary>
 /// <remarks>
 /// <remarks>
-///     There are currently four implementations: - <see cref="CursesDriver"/> (for Unix and Mac) -
-///     <see cref="WindowsDriver"/> - <see cref="NetDriver"/> that uses the .NET Console API - <see cref="FakeConsole"/>
-///     for unit testing.
+///     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>
 /// </remarks>
 public abstract class ConsoleDriver : IConsoleDriver
 public abstract class ConsoleDriver : IConsoleDriver
 {
 {
@@ -32,7 +34,7 @@ public abstract class ConsoleDriver : IConsoleDriver
 
 
     #region ANSI Esc Sequence Handling
     #region ANSI Esc Sequence Handling
 
 
-    // QUESTION: This appears to be an API to help in debugging. It's only implemented in CursesDriver and WindowsDriver.
+    // 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?
     // QUESTION: Can it be factored such that it does not contaminate the ConsoleDriver API?
     /// <summary>
     /// <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="ConsoleDriver"/>.
@@ -535,7 +537,7 @@ public abstract class ConsoleDriver : IConsoleDriver
     #endregion Cursor Handling
     #endregion Cursor Handling
 
 
     /// <summary>Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.</summary>
     /// <summary>Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.</summary>
-    /// <remarks>This is only implemented in <see cref="CursesDriver"/>.</remarks>
+    /// <remarks>This is only implemented in <see cref="UnixDriver"/>.</remarks>
     public abstract void Suspend ();
     public abstract void Suspend ();
 
 
     /// <summary>Sets the position of the terminal cursor to <see cref="Col"/> and <see cref="Row"/>.</summary>
     /// <summary>Sets the position of the terminal cursor to <see cref="Col"/> and <see cref="Row"/>.</summary>
@@ -579,7 +581,6 @@ public abstract class ConsoleDriver : IConsoleDriver
         set => Application.Force16Colors = value || !SupportsTrueColor;
         set => Application.Force16Colors = value || !SupportsTrueColor;
     }
     }
 
 
-    private Attribute _currentAttribute;
     private int _cols;
     private int _cols;
     private int _rows;
     private int _rows;
 
 
@@ -587,22 +588,7 @@ public abstract class ConsoleDriver : IConsoleDriver
     ///     The <see cref="Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/>
     ///     The <see cref="Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/>
     ///     call.
     ///     call.
     /// </summary>
     /// </summary>
-    public Attribute CurrentAttribute
-    {
-        get => _currentAttribute;
-        set
-        {
-            // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. Once Attribute.PlatformColor is removed, this can be fixed.
-            if (Application.Driver is { })
-            {
-                _currentAttribute = new (value.Foreground, value.Background, value.Style);
-
-                return;
-            }
-
-            _currentAttribute = value;
-        }
-    }
+    public Attribute CurrentAttribute { get; set; }
 
 
     /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
     /// <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>
     /// <remarks>Implementations should call <c>base.SetAttribute(c)</c>.</remarks>
@@ -619,8 +605,6 @@ public abstract class ConsoleDriver : IConsoleDriver
     /// <returns>The current attribute.</returns>
     /// <returns>The current attribute.</returns>
     public Attribute GetAttribute () { return CurrentAttribute; }
     public Attribute GetAttribute () { return CurrentAttribute; }
 
 
-    // TODO: This is only overridden by CursesDriver. Once CursesDriver supports 24-bit color, this virtual method can be
-    // removed (and Attribute can lose the platformColor property).
     /// <summary>Makes an <see cref="Attribute"/>.</summary>
     /// <summary>Makes an <see cref="Attribute"/>.</summary>
     /// <param name="foreground">The foreground color.</param>
     /// <param name="foreground">The foreground color.</param>
     /// <param name="background">The background color.</param>
     /// <param name="background">The background color.</param>
@@ -629,7 +613,6 @@ public abstract class ConsoleDriver : IConsoleDriver
     {
     {
         // Encode the colors into the int value.
         // Encode the colors into the int value.
         return new (
         return new (
-                    0xFF, // only used by cursesdriver!
                     foreground,
                     foreground,
                     background
                     background
                    );
                    );

+ 5 - 4
Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs → Terminal.Gui/Drivers/ConsoleDriverFacade.cs

@@ -50,7 +50,9 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     {
     {
         if (FakeDriver.FakeBehaviors.UseFakeClipboard)
         if (FakeDriver.FakeBehaviors.UseFakeClipboard)
         {
         {
-            Clipboard = new FakeDriver.FakeClipboard ();
+            Clipboard = new FakeDriver.FakeClipboard (
+                FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException,
+                FakeDriver.FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse);
 
 
             return;
             return;
         }
         }
@@ -65,7 +67,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
         {
         {
             Clipboard = new MacOSXClipboard ();
             Clipboard = new MacOSXClipboard ();
         }
         }
-        else if (CursesDriver.Is_WSL_Platform ())
+        else if (PlatformDetection.IsWSLPlatform ())
         {
         {
             Clipboard = new WSLClipboard ();
             Clipboard = new WSLClipboard ();
         }
         }
@@ -262,7 +264,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     {
     {
         string type = InputProcessor.DriverName ?? throw new ArgumentNullException (nameof (InputProcessor.DriverName));
         string type = InputProcessor.DriverName ?? throw new ArgumentNullException (nameof (InputProcessor.DriverName));
 
 
-        return "v2" + type;
+        return type;
     }
     }
 
 
     /// <summary>Tests if the specified rune is supported by the driver.</summary>
     /// <summary>Tests if the specified rune is supported by the driver.</summary>
@@ -388,7 +390,6 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     {
     {
         // TODO: what even is this? why Attribute constructor wants to call Driver method which must return an instance of Attribute? ?!?!?!
         // TODO: what even is this? why Attribute constructor wants to call Driver method which must return an instance of Attribute? ?!?!?!
         return new (
         return new (
-                    0xFF, // only used by cursesdriver!
                     foreground,
                     foreground,
                     background
                     background
                    );
                    );

+ 0 - 0
Terminal.Gui/Drivers/V2/ConsoleInput.cs → Terminal.Gui/Drivers/ConsoleInput.cs


+ 0 - 1040
Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs

@@ -1,1040 +0,0 @@
-#nullable enable
-//
-// Driver.cs: Curses-based Driver
-//
-
-using System.Runtime.InteropServices;
-using Unix.Terminal;
-
-namespace Terminal.Gui.Drivers;
-
-/// <summary>A Linux/Mac driver based on the Curses library.</summary>
-internal class CursesDriver : ConsoleDriver
-{
-    public override string GetVersionInfo () { return $"{Curses.curses_version ()}"; }
-
-    public override int Cols
-    {
-        get => Curses.Cols;
-        set
-        {
-            Curses.Cols = value;
-            ClearContents ();
-        }
-    }
-
-    public override int Rows
-    {
-        get => Curses.Lines;
-        set
-        {
-            Curses.Lines = value;
-            ClearContents ();
-        }
-    }
-
-    public override bool IsRuneSupported (Rune rune)
-    {
-        // See Issue #2615 - CursesDriver is broken with non-BMP characters
-        return base.IsRuneSupported (rune) && rune.IsBmp;
-    }
-
-    public override void Move (int col, int row)
-    {
-        base.Move (col, row);
-
-        if (RunningUnitTests)
-        {
-            return;
-        }
-
-        if (IsValidLocation (default, col, row))
-        {
-            Curses.move (row, col);
-        }
-        else
-        {
-            // Not a valid location (outside screen or clip region)
-            // Move within the clip region, then AddRune will actually move to Col, Row
-            Rectangle clipRect = Clip!.GetBounds ();
-            Curses.move (clipRect.Y, clipRect.X);
-        }
-    }
-
-    public void StartReportingMouseMoves ()
-    {
-        if (!RunningUnitTests)
-        {
-            Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
-        }
-    }
-
-    public void StopReportingMouseMoves ()
-    {
-        if (!RunningUnitTests)
-        {
-            Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
-        }
-    }
-
-
-    public override void Suspend ()
-    {
-        StopReportingMouseMoves ();
-
-        if (!RunningUnitTests)
-        {
-            Platform.Suspend ();
-        }
-
-        StartReportingMouseMoves ();
-    }
-
-    public override void UpdateCursor ()
-    {
-        EnsureCursorVisibility ();
-
-        if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows)
-        {
-            _mainLoopDriver?.WriteRaw (EscSeqUtils.CSI_SetCursorPosition (Row + 1, Col + 1));
-        }
-    }
-
-    public override bool UpdateScreen ()
-    {
-        bool updated = false;
-        if (RunningUnitTests
-            || Console.WindowHeight < 1
-            || Contents?.Length != Rows * Cols
-            || Rows != Console.WindowHeight)
-        {
-            return updated;
-        }
-
-        var top = 0;
-        var left = 0;
-        int rows = Rows;
-        int cols = Cols;
-        var output = new StringBuilder ();
-        Attribute? redrawAttr = null;
-        int lastCol = -1;
-
-        CursorVisibility? savedVisibility = _currentCursorVisibility;
-        SetCursorVisibility (CursorVisibility.Invisible);
-
-        for (int row = top; row < rows; row++)
-        {
-            if (Console.WindowHeight < 1)
-            {
-                return updated;
-            }
-
-            if (!_dirtyLines! [row])
-            {
-                continue;
-            }
-
-            if (!SetCursorPosition (0, row))
-            {
-                return updated;
-            }
-
-            updated = true;
-            _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;
-
-                        if (Force16Colors)
-                        {
-                            output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
-                            output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
-                        }
-                        else
-                        {
-                            output.Append (
-                                           EscSeqUtils.CSI_SetForegroundColorRGB (
-                                                                                  attr.Foreground.R,
-                                                                                  attr.Foreground.G,
-                                                                                  attr.Foreground.B
-                                                                                 )
-                                          );
-
-                            output.Append (
-                                           EscSeqUtils.CSI_SetBackgroundColorRGB (
-                                                                                  attr.Background.R,
-                                                                                  attr.Background.G,
-                                                                                  attr.Background.B
-                                                                                 )
-                                          );
-                        }
-                    }
-
-                    outputWidth++;
-                    Rune rune = Contents [row, col].Rune;
-                    output.Append (rune);
-
-                    if (Contents [row, col].CombiningMarks.Count > 0)
-                    {
-                        // 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 `[é  ]`.
-                        // 
-                        // For now, we just ignore the list of CMs.
-                        //foreach (var combMark in Contents [row, col].CombiningMarks) {
-                        //	output.Append (combMark);
-                        //}
-                        // WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                    }
-                    else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
-                    {
-                        WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                        SetCursorPosition (col - 1, row);
-                    }
-
-                    Contents [row, col].IsDirty = false;
-                }
-            }
-
-            if (output.Length > 0)
-            {
-                SetCursorPosition (lastCol, row);
-                Console.Write (output);
-            }
-
-            foreach (var s in Application.Sixel)
-            {
-                if (!string.IsNullOrWhiteSpace (s.SixelData))
-                {
-                    SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y);
-                    Console.Write (s.SixelData);
-                }
-            }
-        }
-
-        SetCursorPosition (0, 0);
-
-        _currentCursorVisibility = savedVisibility;
-
-        void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
-        {
-            SetCursorPosition (lastCol, row);
-            Console.Write (output);
-            output.Clear ();
-            lastCol += outputWidth;
-            outputWidth = 0;
-        }
-
-        return updated;
-    }
-
-    #region Color Handling
-
-    public override bool SupportsTrueColor => true;
-
-    /// <summary>Creates an Attribute from the provided curses-based foreground and background color numbers</summary>
-    /// <param name="foreground">Contains the curses color number for the foreground (color, plus any attributes)</param>
-    /// <param name="background">Contains the curses color number for the background (color, plus any attributes)</param>
-    /// <returns></returns>
-    private static Attribute MakeColor (short foreground, short background)
-    {
-        //var v = (short)((ushort)foreground | (background << 4));
-        var v = (short)(((ushort)(foreground & 0xffff) << 16) | (background & 0xffff));
-
-        // TODO: for TrueColor - Use InitExtendedPair
-        Curses.InitColorPair (v, foreground, background);
-
-        return new (
-                    Curses.ColorPair (v),
-                    CursesColorNumberToColorName16 (foreground),
-                    CursesColorNumberToColorName16 (background)
-                   );
-    }
-
-    private static short ColorNameToCursesColorNumber (ColorName16 color)
-    {
-        switch (color)
-        {
-            case ColorName16.Black:
-                return Curses.COLOR_BLACK;
-            case ColorName16.Blue:
-                return Curses.COLOR_BLUE;
-            case ColorName16.Green:
-                return Curses.COLOR_GREEN;
-            case ColorName16.Cyan:
-                return Curses.COLOR_CYAN;
-            case ColorName16.Red:
-                return Curses.COLOR_RED;
-            case ColorName16.Magenta:
-                return Curses.COLOR_MAGENTA;
-            case ColorName16.Yellow:
-                return Curses.COLOR_YELLOW;
-            case ColorName16.Gray:
-                return Curses.COLOR_WHITE;
-            case ColorName16.DarkGray:
-                return Curses.COLOR_GRAY;
-            case ColorName16.BrightBlue:
-                return Curses.COLOR_BLUE | Curses.COLOR_GRAY;
-            case ColorName16.BrightGreen:
-                return Curses.COLOR_GREEN | Curses.COLOR_GRAY;
-            case ColorName16.BrightCyan:
-                return Curses.COLOR_CYAN | Curses.COLOR_GRAY;
-            case ColorName16.BrightRed:
-                return Curses.COLOR_RED | Curses.COLOR_GRAY;
-            case ColorName16.BrightMagenta:
-                return Curses.COLOR_MAGENTA | Curses.COLOR_GRAY;
-            case ColorName16.BrightYellow:
-                return Curses.COLOR_YELLOW | Curses.COLOR_GRAY;
-            case ColorName16.White:
-                return Curses.COLOR_WHITE | Curses.COLOR_GRAY;
-        }
-
-        throw new ArgumentException ("Invalid color code");
-    }
-
-    private static ColorName16 CursesColorNumberToColorName16 (short color)
-    {
-        switch (color)
-        {
-            case Curses.COLOR_BLACK:
-                return ColorName16.Black;
-            case Curses.COLOR_BLUE:
-                return ColorName16.Blue;
-            case Curses.COLOR_GREEN:
-                return ColorName16.Green;
-            case Curses.COLOR_CYAN:
-                return ColorName16.Cyan;
-            case Curses.COLOR_RED:
-                return ColorName16.Red;
-            case Curses.COLOR_MAGENTA:
-                return ColorName16.Magenta;
-            case Curses.COLOR_YELLOW:
-                return ColorName16.Yellow;
-            case Curses.COLOR_WHITE:
-                return ColorName16.Gray;
-            case Curses.COLOR_GRAY:
-                return ColorName16.DarkGray;
-            case Curses.COLOR_BLUE | Curses.COLOR_GRAY:
-                return ColorName16.BrightBlue;
-            case Curses.COLOR_GREEN | Curses.COLOR_GRAY:
-                return ColorName16.BrightGreen;
-            case Curses.COLOR_CYAN | Curses.COLOR_GRAY:
-                return ColorName16.BrightCyan;
-            case Curses.COLOR_RED | Curses.COLOR_GRAY:
-                return ColorName16.BrightRed;
-            case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY:
-                return ColorName16.BrightMagenta;
-            case Curses.COLOR_YELLOW | Curses.COLOR_GRAY:
-                return ColorName16.BrightYellow;
-            case Curses.COLOR_WHITE | Curses.COLOR_GRAY:
-                return ColorName16.White;
-        }
-
-        throw new ArgumentException ("Invalid curses color code");
-    }
-
-    #endregion
-
-    private CursorVisibility? _currentCursorVisibility;
-    private CursorVisibility? _initialCursorVisibility;
-
-
-    private void EnsureCursorVisibility ()
-    {
-        if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
-        {
-            GetCursorVisibility (out CursorVisibility cursorVisibility);
-            _currentCursorVisibility = cursorVisibility;
-            SetCursorVisibility (CursorVisibility.Invisible);
-
-            return;
-        }
-
-        SetCursorVisibility (_currentCursorVisibility ?? CursorVisibility.Default);
-    }
-
-    /// <inheritdoc/>
-    public override bool GetCursorVisibility (out CursorVisibility visibility)
-    {
-        visibility = CursorVisibility.Invisible;
-
-        if (!_currentCursorVisibility.HasValue)
-        {
-            return false;
-        }
-
-        visibility = _currentCursorVisibility.Value;
-
-        return true;
-    }
-
-    private EscSeqUtils.DECSCUSR_Style? _currentDecscusrStyle;
-
-    /// <inheritdoc/>
-    public override bool SetCursorVisibility (CursorVisibility visibility)
-    {
-        if (_initialCursorVisibility.HasValue == false)
-        {
-            return false;
-        }
-
-        if (!RunningUnitTests)
-        {
-            Curses.curs_set (((int)visibility >> 16) & 0x000000FF);
-            Curses.leaveok (_window!.Handle, !Force16Colors);
-        }
-
-        if (visibility != CursorVisibility.Invisible)
-        {
-            if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))
-            {
-                _currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF);
-
-                _mainLoopDriver?.WriteRaw (
-                                           EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle)
-                                          );
-            }
-        }
-
-        _currentCursorVisibility = visibility;
-
-        return true;
-    }
-
-    private bool SetCursorPosition (int col, int row)
-    {
-        // + 1 is needed because non-Windows is based on 1 instead of 0 and
-        // Console.CursorTop/CursorLeft isn't reliable.
-        Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
-
-        return true;
-    }
-
-    #region Init/End/MainLoop
-
-    private Curses.Window? _window;
-    private UnixMainLoop? _mainLoopDriver;
-    private object? _processInputToken;
-
-    public override MainLoop Init ()
-    {
-        _mainLoopDriver = new (this);
-
-        if (!RunningUnitTests)
-        {
-            _window = Curses.initscr ();
-            Curses.set_escdelay (10);
-
-            // Ensures that all procedures are performed at some previous closing.
-            Curses.doupdate ();
-
-            // 
-            // We are setting Invisible as default, so we could ignore XTerm DECSUSR setting
-            //
-            switch (Curses.curs_set (0))
-            {
-                case 0:
-                    _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Invisible;
-
-                    break;
-
-                case 1:
-                    _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Underline;
-                    Curses.curs_set (1);
-
-                    break;
-
-                case 2:
-                    _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Box;
-                    Curses.curs_set (2);
-
-                    break;
-
-                default:
-                    _currentCursorVisibility = _initialCursorVisibility = null;
-
-                    break;
-            }
-
-            if (!Curses.HasColors)
-            {
-                throw new InvalidOperationException ("V2 - This should never happen. File an Issue if it does.");
-            }
-
-            Curses.raw ();
-            Curses.noecho ();
-
-            Curses.Window.Standard.keypad (true);
-
-            Curses.StartColor ();
-            Curses.UseDefaultColors ();
-
-            if (!RunningUnitTests)
-            {
-                Curses.timeout (0);
-            }
-
-            _processInputToken = _mainLoopDriver.AddWatch (
-                                                           0,
-                                                           UnixMainLoop.Condition.PollIn,
-                                                           x =>
-                                                           {
-                                                               ProcessInput ();
-
-                                                               return true;
-                                                           }
-                                                          );
-        }
-
-        CurrentAttribute = new (ColorName16.White, ColorName16.Black);
-
-        if (Environment.OSVersion.Platform == PlatformID.Win32NT)
-        {
-            Clipboard = new FakeDriver.FakeClipboard ();
-        }
-        else
-        {
-            if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
-            {
-                Clipboard = new MacOSXClipboard ();
-            }
-            else
-            {
-                if (Is_WSL_Platform ())
-                {
-                    Clipboard = new WSLClipboard ();
-                }
-                else
-                {
-                    Clipboard = new CursesClipboard ();
-                }
-            }
-        }
-
-        ClearContents ();
-        StartReportingMouseMoves ();
-
-        if (!RunningUnitTests)
-        {
-            Curses.CheckWinChange ();
-
-            // On Init this call is needed no mater Force16Colors or not
-            Curses.refresh ();
-
-            EscSeqUtils.ContinuousButtonPressed += EscSeqUtils_ContinuousButtonPressed;
-        }
-
-        return new (_mainLoopDriver);
-    }
-
-    private readonly AnsiResponseParser _parser = new ();
-    /// <inheritdoc />
-    internal override IAnsiResponseParser GetParser () => _parser;
-
-    internal void ProcessInput ()
-    {
-        int wch;
-        int code = Curses.get_wch (out wch);
-
-        //System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}");
-        if (code == Curses.ERR)
-        {
-            return;
-        }
-
-        var k = KeyCode.Null;
-
-        if (code == Curses.KEY_CODE_YES)
-        {
-            while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize)
-            {
-                ProcessWinChange ();
-                code = Curses.get_wch (out wch);
-            }
-
-            if (wch == 0)
-            {
-                return;
-            }
-
-            if (wch == Curses.KeyMouse)
-            {
-                int wch2 = wch;
-
-                while (wch2 == Curses.KeyMouse)
-                {
-                    Key? kea = null;
-
-                    ConsoleKeyInfo [] cki =
-                    {
-                        new ((char)KeyCode.Esc, 0, false, false, false),
-                        new ('[', 0, false, false, false),
-                        new ('<', 0, false, false, false)
-                    };
-                    code = 0;
-                    HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea!, ref cki!);
-                }
-
-                return;
-            }
-
-            k = MapCursesKey (wch);
-
-            if (wch >= 277 && wch <= 288)
-            {
-                // Shift+(F1 - F12)
-                wch -= 12;
-                k = KeyCode.ShiftMask | MapCursesKey (wch);
-            }
-            else if (wch >= 289 && wch <= 300)
-            {
-                // Ctrl+(F1 - F12)
-                wch -= 24;
-                k = KeyCode.CtrlMask | MapCursesKey (wch);
-            }
-            else if (wch >= 301 && wch <= 312)
-            {
-                // Ctrl+Shift+(F1 - F12)
-                wch -= 36;
-                k = KeyCode.CtrlMask | KeyCode.ShiftMask | MapCursesKey (wch);
-            }
-            else if (wch >= 313 && wch <= 324)
-            {
-                // Alt+(F1 - F12)
-                wch -= 48;
-                k = KeyCode.AltMask | MapCursesKey (wch);
-            }
-            else if (wch >= 325 && wch <= 327)
-            {
-                // Shift+Alt+(F1 - F3)
-                wch -= 60;
-                k = KeyCode.ShiftMask | KeyCode.AltMask | MapCursesKey (wch);
-            }
-            else if (wch == 520) // Ctrl+Delete
-            {
-                k = KeyCode.CtrlMask | KeyCode.Delete;
-            }
-
-            OnKeyDown (new Key (k));
-            OnKeyUp (new Key (k));
-
-            return;
-        }
-
-        // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey
-        if (wch == 27)
-        {
-            Curses.timeout (10);
-
-            code = Curses.get_wch (out int wch2);
-
-            if (code == Curses.KEY_CODE_YES)
-            {
-                k = KeyCode.AltMask | MapCursesKey (wch);
-            }
-
-            Key? key = null;
-
-            if (code == 0)
-            {
-                // The ESC-number handling, debatable.
-                // Simulates the AltMask itself by pressing Alt + Space.
-                // Needed for macOS
-                if (wch2 == (int)KeyCode.Space)
-                {
-                    k = KeyCode.AltMask | KeyCode.Space;
-                }
-                else if (wch2 - (int)KeyCode.Space >= (uint)KeyCode.A
-                         && wch2 - (int)KeyCode.Space <= (uint)KeyCode.Z)
-                {
-                    k = (KeyCode)((uint)KeyCode.AltMask + (wch2 - (int)KeyCode.Space));
-                }
-                else if (wch2 >= (uint)KeyCode.A - 64 && wch2 <= (uint)KeyCode.Z - 64)
-                {
-                    k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + (wch2 + 64));
-                }
-                else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9)
-                {
-                    k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0));
-                }
-                else
-                {
-                    ConsoleKeyInfo [] cki =
-                    [
-                        new ((char)KeyCode.Esc, 0, false, false, false), new ((char)wch2, 0, false, false, false)
-                    ];
-                    HandleEscSeqResponse (ref code, ref k, ref wch2, ref key!, ref cki!);
-
-                    return;
-                }
-                //else if (wch2 == Curses.KeyCSI)
-                //{
-                //    ConsoleKeyInfo [] cki =
-                //    {
-                //        new ((char)KeyCode.Esc, 0, false, false, false), new ('[', 0, false, false, false)
-                //    };
-                //    HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
-
-                //    return;
-                //}
-                //else
-                //{
-                //    // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
-                //    if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0)
-                //    {
-                //        k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask));
-                //    }
-
-                //    if (wch2 == 0)
-                //    {
-                //        k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space;
-                //    }
-                //    //else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z)
-                //    //{
-                //    //    k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space;
-                //    //}
-                //    else if (wch2 < 256)
-                //    {
-                //        k = (KeyCode)wch2; // | KeyCode.AltMask;
-                //    }
-                //    else
-                //    {
-                //        k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2);
-                //    }
-                //}
-
-                key = new Key (k);
-            }
-            else
-            {
-                key = Key.Esc;
-            }
-
-            OnKeyDown (key);
-            OnKeyUp (key);
-        }
-        else if (wch == 8) // Ctrl+Backspace
-        {
-            k = KeyCode.Backspace | KeyCode.CtrlMask;
-            OnKeyDown (new Key (k));
-            OnKeyUp (new Key (k));
-        }
-        else if (wch == Curses.KeyTab)
-        {
-            k = MapCursesKey (wch);
-            OnKeyDown (new Key (k));
-            OnKeyUp (new Key (k));
-        }
-        else if (wch == 127)
-        {
-            // Backspace needed for macOS
-            k = KeyCode.Backspace;
-            OnKeyDown (new Key (k));
-            OnKeyUp (new Key (k));
-        }
-        else
-        {
-            // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
-            k = (KeyCode)wch;
-
-            if (wch == 0)
-            {
-                k = KeyCode.CtrlMask | KeyCode.Space;
-            }
-            else if (wch >= (uint)KeyCode.A - 64 && wch <= (uint)KeyCode.Z - 64)
-            {
-                if ((KeyCode)(wch + 64) != KeyCode.J)
-                {
-                    k = KeyCode.CtrlMask | (KeyCode)(wch + 64);
-                }
-            }
-            else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z)
-            {
-                k = (KeyCode)wch | KeyCode.ShiftMask;
-            }
-
-            if (wch == '\n' || wch == '\r')
-            {
-                k = KeyCode.Enter;
-            }
-
-            // Strip the KeyCode.Space flag off if it's set
-            //if (k != KeyCode.Space && k.HasFlag (KeyCode.Space))
-            if (Key.GetIsKeyCodeAtoZ (k) && (k & KeyCode.Space) != 0)
-            {
-                k &= ~KeyCode.Space;
-            }
-
-            if (IsValidInput (k, out k))
-            {
-                OnKeyDown (new (k));
-                OnKeyUp (new (k));
-            }
-        }
-    }
-
-    internal void ProcessWinChange ()
-    {
-        if (!RunningUnitTests && Curses.CheckWinChange ())
-        {
-            ClearContents ();
-            OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows)));
-        }
-    }
-    static string ConvertToString (ConsoleKeyInfo [] keyInfos)
-    {
-        char [] chars = new char [keyInfos.Length];
-        for (int i = 0; i < keyInfos.Length; i++)
-        {
-            chars [i] = keyInfos [i].KeyChar;
-        }
-        return new string (chars);
-    }
-
-    private void HandleEscSeqResponse (
-        ref int code,
-        ref KeyCode k,
-        ref int wch2,
-        ref Key keyEventArgs,
-        ref ConsoleKeyInfo []? cki
-    )
-    {
-        ConsoleKey ck = 0;
-        ConsoleModifiers mod = 0;
-
-        while (code == 0)
-        {
-            code = Curses.get_wch (out wch2);
-            var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false);
-
-            if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse)
-            {
-                // Give ansi parser a chance to deal with the escape sequence
-                if (cki != null && string.IsNullOrEmpty(_parser.ProcessInput (ConvertToString(cki))))
-                {
-                    // Parser fully consumed all keys meaning keys are processed - job done
-                    return;
-                }
-
-                // Ansi parser could not deal with it either because it is not expecting
-                // the given terminator (e.g. mouse) or did not understand format somehow.
-                // Carry on with the older code for processing curses escape codes
-
-                EscSeqUtils.DecodeEscSeq (
-                                          ref consoleKeyInfo,
-                                          ref ck,
-                                          cki!,
-                                          ref mod,
-                                          out _,
-                                          out _,
-                                          out _,
-                                          out _,
-                                          out bool isKeyMouse,
-                                          out List<MouseFlags> mouseFlags,
-                                          out Point pos,
-                                          out _,
-                                          EscSeqUtils.ProcessMouseEvent
-                                         );
-
-                if (isKeyMouse)
-                {
-                    foreach (MouseFlags mf in mouseFlags)
-                    {
-                        OnMouseEvent (new () { Flags = mf, Position = pos });
-                    }
-
-                    cki = null;
-
-                    if (wch2 == 27)
-                    {
-                        cki = EscSeqUtils.ResizeArray (
-                                                       new ConsoleKeyInfo (
-                                                                           (char)KeyCode.Esc,
-                                                                           0,
-                                                                           false,
-                                                                           false,
-                                                                           false
-                                                                          ),
-                                                       cki
-                                                      );
-                    }
-                }
-                else
-                {
-                    k = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
-                    keyEventArgs = new Key (k);
-                    OnKeyDown (keyEventArgs);
-                }
-            }
-            else
-            {
-                cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
-            }
-        }
-    }
-
-    private void EscSeqUtils_ContinuousButtonPressed (object? sender, MouseEventArgs e)
-    {
-        OnMouseEvent (e);
-    }
-
-    private static KeyCode MapCursesKey (int cursesKey)
-    {
-        switch (cursesKey)
-        {
-            case Curses.KeyF1: return KeyCode.F1;
-            case Curses.KeyF2: return KeyCode.F2;
-            case Curses.KeyF3: return KeyCode.F3;
-            case Curses.KeyF4: return KeyCode.F4;
-            case Curses.KeyF5: return KeyCode.F5;
-            case Curses.KeyF6: return KeyCode.F6;
-            case Curses.KeyF7: return KeyCode.F7;
-            case Curses.KeyF8: return KeyCode.F8;
-            case Curses.KeyF9: return KeyCode.F9;
-            case Curses.KeyF10: return KeyCode.F10;
-            case Curses.KeyF11: return KeyCode.F11;
-            case Curses.KeyF12: return KeyCode.F12;
-            case Curses.KeyUp: return KeyCode.CursorUp;
-            case Curses.KeyDown: return KeyCode.CursorDown;
-            case Curses.KeyLeft: return KeyCode.CursorLeft;
-            case Curses.KeyRight: return KeyCode.CursorRight;
-            case Curses.KeyHome: return KeyCode.Home;
-            case Curses.KeyEnd: return KeyCode.End;
-            case Curses.KeyNPage: return KeyCode.PageDown;
-            case Curses.KeyPPage: return KeyCode.PageUp;
-            case Curses.KeyDeleteChar: return KeyCode.Delete;
-            case Curses.KeyInsertChar: return KeyCode.Insert;
-            case Curses.KeyTab: return KeyCode.Tab;
-            case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask;
-            case Curses.KeyBackspace: return KeyCode.Backspace;
-            case Curses.ShiftKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask;
-            case Curses.ShiftKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask;
-            case Curses.ShiftKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask;
-            case Curses.ShiftKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask;
-            case Curses.ShiftKeyHome: return KeyCode.Home | KeyCode.ShiftMask;
-            case Curses.ShiftKeyEnd: return KeyCode.End | KeyCode.ShiftMask;
-            case Curses.ShiftKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask;
-            case Curses.ShiftKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask;
-            case Curses.AltKeyUp: return KeyCode.CursorUp | KeyCode.AltMask;
-            case Curses.AltKeyDown: return KeyCode.CursorDown | KeyCode.AltMask;
-            case Curses.AltKeyLeft: return KeyCode.CursorLeft | KeyCode.AltMask;
-            case Curses.AltKeyRight: return KeyCode.CursorRight | KeyCode.AltMask;
-            case Curses.AltKeyHome: return KeyCode.Home | KeyCode.AltMask;
-            case Curses.AltKeyEnd: return KeyCode.End | KeyCode.AltMask;
-            case Curses.AltKeyNPage: return KeyCode.PageDown | KeyCode.AltMask;
-            case Curses.AltKeyPPage: return KeyCode.PageUp | KeyCode.AltMask;
-            case Curses.CtrlKeyUp: return KeyCode.CursorUp | KeyCode.CtrlMask;
-            case Curses.CtrlKeyDown: return KeyCode.CursorDown | KeyCode.CtrlMask;
-            case Curses.CtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.CtrlMask;
-            case Curses.CtrlKeyRight: return KeyCode.CursorRight | KeyCode.CtrlMask;
-            case Curses.CtrlKeyHome: return KeyCode.Home | KeyCode.CtrlMask;
-            case Curses.CtrlKeyEnd: return KeyCode.End | KeyCode.CtrlMask;
-            case Curses.CtrlKeyNPage: return KeyCode.PageDown | KeyCode.CtrlMask;
-            case Curses.CtrlKeyPPage: return KeyCode.PageUp | KeyCode.CtrlMask;
-            case Curses.ShiftCtrlKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
-            case Curses.ShiftCtrlKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
-            case Curses.ShiftCtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask;
-            case Curses.ShiftCtrlKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask;
-            case Curses.ShiftCtrlKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask;
-            case Curses.ShiftCtrlKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask;
-            case Curses.ShiftCtrlKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
-            case Curses.ShiftCtrlKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
-            case Curses.ShiftAltKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.AltMask;
-            case Curses.ShiftAltKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.AltMask;
-            case Curses.ShiftAltKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.AltMask;
-            case Curses.ShiftAltKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.AltMask;
-            case Curses.ShiftAltKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.AltMask;
-            case Curses.ShiftAltKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask;
-            case Curses.ShiftAltKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.AltMask;
-            case Curses.ShiftAltKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.AltMask;
-            case Curses.AltCtrlKeyNPage: return KeyCode.PageDown | KeyCode.AltMask | KeyCode.CtrlMask;
-            case Curses.AltCtrlKeyPPage: return KeyCode.PageUp | KeyCode.AltMask | KeyCode.CtrlMask;
-            case Curses.AltCtrlKeyHome: return KeyCode.Home | KeyCode.AltMask | KeyCode.CtrlMask;
-            case Curses.AltCtrlKeyEnd: return KeyCode.End | KeyCode.AltMask | KeyCode.CtrlMask;
-            default: return KeyCode.Null;
-        }
-    }
-
-    public override void End ()
-    {
-        EscSeqUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed;
-
-        StopReportingMouseMoves ();
-        SetCursorVisibility (CursorVisibility.Default);
-
-        if (_mainLoopDriver is { } && _processInputToken != null)
-        {
-            _mainLoopDriver.RemoveWatch (_processInputToken);
-        }
-
-        if (RunningUnitTests)
-        {
-            return;
-        }
-
-        // throws away any typeahead that has been typed by
-        // the user and has not yet been read by the program.
-        Curses.flushinp ();
-
-        Curses.endwin ();
-    }
-
-    #endregion Init/End/MainLoop
-
-    public static bool Is_WSL_Platform ()
-    {
-        // xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell
-        //if (new CursesClipboard ().IsSupported) {
-        //	// If xclip is installed on Linux under WSL, this will return true.
-        //	return false;
-        //}
-        (int exitCode, string result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
-
-        if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL"))
-        {
-            return true;
-        }
-
-        return false;
-    }
-
-    /// <inheritdoc/>
-    public override void WriteRaw (string ansi) { _mainLoopDriver?.WriteRaw (ansi); }
-}

+ 0 - 5
Terminal.Gui/Drivers/CursesDriver/README.md

@@ -1,5 +0,0 @@
-This directory contains a copy of the MonoCurses binding from:
-
-http://github.com/mono/mono-curses
-
-The code has diverged from `mono-curses` a it's been leveraged for `Terminal.Gui`'s Curses driver.

+ 0 - 256
Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs

@@ -1,256 +0,0 @@
-#nullable enable
-//
-// mainloop.cs: Linux/Curses MainLoop implementation.
-//
-
-using System.Runtime.InteropServices;
-using IMainLoopDriver = Terminal.Gui.App.IMainLoopDriver;
-using MainLoop = Terminal.Gui.App.MainLoop;
-
-namespace Terminal.Gui.Drivers;
-
-/// <summary>Unix main loop, suitable for using on Posix systems</summary>
-/// <remarks>
-///     In addition to the general functions of the MainLoop, the Unix version can watch file descriptors using the
-///     AddWatch methods.
-/// </remarks>
-internal class UnixMainLoop : IMainLoopDriver
-{
-    /// <summary>Condition on which to wake up from file descriptor activity.  These match the Linux/BSD poll definitions.</summary>
-    [Flags]
-    internal enum Condition : short
-    {
-        /// <summary>There is data to read</summary>
-        PollIn = 1,
-
-        /// <summary>Writing to the specified descriptor will not block</summary>
-        PollOut = 4,
-
-        /// <summary>There is urgent data to read</summary>
-        PollPri = 2,
-
-        /// <summary>Error condition on output</summary>
-        PollErr = 8,
-
-        /// <summary>Hang-up on output</summary>
-        PollHup = 16,
-
-        /// <summary>File descriptor is not open.</summary>
-        PollNval = 32
-    }
-
-    public const int KEY_RESIZE = unchecked((int)0xffffffffffffffff);
-    private static readonly nint _ignore = Marshal.AllocHGlobal (1);
-
-    private readonly CursesDriver _cursesDriver;
-    private readonly Dictionary<int, Watch> _descriptorWatchers = new ();
-    private readonly int [] _wakeUpPipes = new int [2];
-    private MainLoop? _mainLoop;
-    private bool _pollDirty = true;
-    private Pollfd []? _pollMap;
-    private bool _winChanged;
-
-    public UnixMainLoop (IConsoleDriver IConsoleDriver)
-    {
-        ArgumentNullException.ThrowIfNull (IConsoleDriver);
-
-        _cursesDriver = (CursesDriver)IConsoleDriver;
-    }
-
-    void IMainLoopDriver.Wakeup ()
-    {
-        if (!ConsoleDriver.RunningUnitTests)
-        {
-            write (_wakeUpPipes [1], _ignore, 1);
-        }
-    }
-
-    void IMainLoopDriver.Setup (MainLoop mainLoop)
-    {
-        _mainLoop = mainLoop;
-
-        if (ConsoleDriver.RunningUnitTests)
-        {
-            return;
-        }
-
-        try
-        {
-            pipe (_wakeUpPipes);
-
-            AddWatch (
-                      _wakeUpPipes [0],
-                      Condition.PollIn,
-                      _ =>
-                      {
-                          read (_wakeUpPipes [0], _ignore, 1);
-
-                          return true;
-                      }
-                     );
-        }
-        catch (DllNotFoundException e)
-        {
-            throw new NotSupportedException ("libc not found", e);
-        }
-    }
-
-    bool IMainLoopDriver.EventsPending ()
-    {
-        if (ConsoleDriver.RunningUnitTests)
-        {
-            return true;
-        }
-
-        UpdatePollMap ();
-
-        bool checkTimersResult = _mainLoop!.TimedEvents.CheckTimers (out int pollTimeout);
-
-        int n = poll (_pollMap!, (uint)_pollMap!.Length, pollTimeout);
-
-        if (n == KEY_RESIZE)
-        {
-            _winChanged = true;
-        }
-
-        return checkTimersResult || n >= KEY_RESIZE;
-    }
-
-    void IMainLoopDriver.Iteration ()
-    {
-        if (ConsoleDriver.RunningUnitTests)
-        {
-            return;
-        }
-
-        if (_winChanged)
-        {
-            _winChanged = false;
-            _cursesDriver.ProcessInput ();
-
-            // This is needed on the mac. See https://github.com/gui-cs/Terminal.Gui/pull/2922#discussion_r1365992426
-            _cursesDriver.ProcessWinChange ();
-        }
-
-        if (_pollMap is null)
-        {
-            return;
-        }
-
-        foreach (Pollfd p in _pollMap)
-        {
-            if (p.revents == 0)
-            {
-                continue;
-            }
-
-            if (!_descriptorWatchers.TryGetValue (p.fd, out Watch? watch))
-            {
-                continue;
-            }
-
-            if (!watch.Callback (_mainLoop!))
-            {
-                _descriptorWatchers.Remove (p.fd);
-            }
-        }
-    }
-
-    void IMainLoopDriver.TearDown ()
-    {
-        _descriptorWatchers.Clear ();
-
-        _mainLoop = null;
-    }
-
-    /// <summary>Watches a file descriptor for activity.</summary>
-    /// <remarks>
-    ///     When the condition is met, the provided callback is invoked.  If the callback returns false, the watch is
-    ///     automatically removed. The return value is a token that represents this watch, you can use this token to remove the
-    ///     watch by calling RemoveWatch.
-    /// </remarks>
-    internal object AddWatch (int fileDescriptor, Condition condition, Func<MainLoop, bool> callback)
-    {
-        ArgumentNullException.ThrowIfNull (callback);
-
-        var watch = new Watch { Condition = condition, Callback = callback, File = fileDescriptor };
-        _descriptorWatchers [fileDescriptor] = watch;
-        _pollDirty = true;
-
-        return watch;
-    }
-
-    /// <summary>Removes an active watch from the mainloop.</summary>
-    /// <remarks>The token parameter is the value returned from AddWatch</remarks>
-    internal void RemoveWatch (object token)
-    {
-        if (!ConsoleDriver.RunningUnitTests)
-        {
-            if (token is not Watch watch)
-            {
-                return;
-            }
-
-            _descriptorWatchers.Remove (watch.File);
-        }
-    }
-
-    private void UpdatePollMap ()
-    {
-        if (!_pollDirty)
-        {
-            return;
-        }
-
-        _pollDirty = false;
-
-        _pollMap = new Pollfd [_descriptorWatchers.Count];
-        var i = 0;
-
-        foreach (int fd in _descriptorWatchers.Keys)
-        {
-            _pollMap [i].fd = fd;
-            _pollMap [i].events = (short)_descriptorWatchers [fd].Condition;
-            i++;
-        }
-    }
-
-    internal void WriteRaw (string ansiRequest)
-    {
-        // Write to stdout (fd 1)
-        write (STDOUT_FILENO, ansiRequest, ansiRequest.Length);
-    }
-
-    [DllImport ("libc")]
-    private static extern int pipe ([In][Out] int [] pipes);
-
-    [DllImport ("libc")]
-    private static extern int poll ([In] [Out] Pollfd [] ufds, uint nfds, int timeout);
-
-    [DllImport ("libc")]
-    private static extern int read (int fd, nint buf, nint n);
-
-    [DllImport ("libc")]
-    private static extern int write (int fd, nint buf, nint n);
-
-    // File descriptor for stdout
-    private const int STDOUT_FILENO = 1;
-
-    [DllImport ("libc")]
-    private static extern int write (int fd, string buf, int n);
-
-    [StructLayout (LayoutKind.Sequential)]
-    private struct Pollfd
-    {
-        public int fd;
-        public short events;
-        public readonly short revents;
-    }
-
-    private class Watch
-    {
-        public Func<MainLoop, bool>? Callback;
-        public Condition Condition;
-        public int File;
-    }
-}

+ 0 - 95
Terminal.Gui/Drivers/CursesDriver/UnmanagedLibrary.cs

@@ -1,95 +0,0 @@
-// Copyright 2015 gRPC authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//	 http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.InteropServices;
-
-namespace Unix.Terminal;
-
-/// <summary>
-///     Represents a dynamically loaded unmanaged library in a (partially) platform independent manner. First, the
-///     native library is loaded using dlopen (on Unix systems) or using LoadLibrary (on Windows). dlsym or GetProcAddress
-///     are then used to obtain symbol addresses. <c>Marshal.GetDelegateForFunctionPointer</c> transforms the addresses
-///     into delegates to native methods. See
-///     http://stackoverflow.com/questions/13461989/p-invoke-to-dynamically-loaded-library-on-mono.
-/// </summary>
-internal class UnmanagedLibrary
-{
-    public readonly string LibraryPath;
-    public nint NativeLibraryHandle { get; }
-
-    //
-    // if isFullPath is set to true, the provided array of libraries are full paths
-    // and are tested for the file existing, otherwise the file is merely the name
-    // of the shared library that we pass to dlopen
-    //
-    public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
-    {
-        if (isFullPath)
-        {
-            foreach (string path in libraryPathAlternatives)
-            {
-                if (File.Exists (path))
-                {
-                    LibraryPath = path;
-                    break;
-                }
-            }
-
-            if (LibraryPath is null)
-                throw new FileNotFoundException ($"Error loading native library. Not found in any of the possible locations: {string.Join (",", libraryPathAlternatives)}");
-
-            NativeLibraryHandle = NativeLibrary.Load (LibraryPath);
-        }
-        else
-        {
-            foreach (string lib in libraryPathAlternatives)
-            {
-                NativeLibraryHandle = NativeLibrary.Load (lib);
-                if (NativeLibraryHandle != nint.Zero)
-                {
-                    LibraryPath = lib;
-
-                    break;
-                }
-            }
-        }
-
-        if (NativeLibraryHandle == nint.Zero)
-        {
-            throw new IOException ($"Error loading native library \"{string.Join (", ", libraryPathAlternatives)}\"");
-        }
-    }
-
-    /// <summary>Loads symbol in a platform specific way.</summary>
-    /// <param name="symbolName"></param>
-    /// <returns></returns>
-    public nint LoadSymbol (string symbolName)
-    {
-        return NativeLibrary.GetExport(NativeLibraryHandle, symbolName);
-    }
-
-    public T GetNativeMethodDelegate<T> (string methodName)
-        where T : class
-    {
-        nint ptr = LoadSymbol (methodName);
-
-        if (ptr == nint.Zero)
-        {
-            throw new MissingMethodException (string.Format ("The native method \"{0}\" does not exist", methodName));
-        }
-
-        return Marshal.GetDelegateForFunctionPointer<T> (ptr); // non-generic version is obsolete
-    }
-}

+ 0 - 746
Terminal.Gui/Drivers/CursesDriver/binding.cs

@@ -1,746 +0,0 @@
-//
-// TODO:
-// * FindNCurses needs to remove the old probing code
-// * Removal of that proxy code
-// * Need to implement reading pointers with the new API
-// * Can remove the manual Dlopen features
-// * initscr() diagnostics based on DLL can be fixed
-//
-// binding.cs.in: Core binding for curses.
-//
-// This file attempts to call into ncurses without relying on Mono's
-// dllmap, so it will work with .NET Core.  This means that it needs
-// two sets of bindings, one for "ncurses" which works on OSX, and one
-// that works against "libncursesw.so.5" which is what you find on
-// assorted Linux systems.
-//
-// Additionally, I do not want to rely on an external native library
-// which is why all this pain to bind two separate ncurses is here.
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-// Copyright (C) 2007 Novell (http://www.novell.com)
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-// 
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-// 
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System.Runtime.InteropServices;
-
-namespace Unix.Terminal;
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-
-internal partial class Curses
-{
-    // We encode ESC + char (what Alt-char generates) as 0x2000 + char
-    public const int KeyAlt = 0x2000;
-    private static nint curses_handle, curscr_ptr, lines_ptr, cols_ptr;
-
-    // If true, uses the DllImport into "ncurses", otherwise "libncursesw.so.5"
-    //static bool use_naked_driver;
-    private static UnmanagedLibrary curses_library;
-    private static int lines, cols;
-    private static Window main_window;
-    private static NativeMethods methods;
-    private static char [] r = new char [1];
-    private static nint stdscr;
-    public static int ColorPairs => methods.COLOR_PAIRS ();
-
-    public static int Cols
-    {
-        get => cols;
-        internal set =>
-
-            // For unit tests
-            cols = value;
-    }
-
-    public static bool HasColors => methods.has_colors ();
-
-    public static int Lines
-    {
-        get => lines;
-        internal set =>
-
-            // For unit tests
-            lines = value;
-    }
-
-    //
-    // Have to wrap the native addch, as it can not
-    // display unicode characters, we have to use addstr
-    // for that.   but we need addch to render special ACS
-    // characters
-    //
-    public static int addch (int ch)
-    {
-        if (ch < 127 || ch > 0xffff)
-        {
-            return methods.addch (ch);
-        }
-
-        var c = (char)ch;
-
-        return addwstr (new string (c, 1));
-    }
-
-    public static int addstr (string format, params object [] args)
-    {
-        string s = string.Format (format, args);
-
-        return addwstr (s);
-    }
-
-    public static int addwstr (string s) { return methods.addwstr (s); }
-    public static int attroff (int attrs) { return methods.attroff (attrs); }
-
-    //static public int wechochar (IntPtr win, int ch) => methods.wechochar (win, ch);
-    public static int attron (int attrs) { return methods.attron (attrs); }
-    public static int attrset (int attrs) { return methods.attrset (attrs); }
-    public static int cbreak () { return methods.cbreak (); }
-
-    //
-    // Returns true if the window changed since the last invocation, as a
-    // side effect, the Lines and Cols properties are updated
-    //
-    public static bool CheckWinChange ()
-    {
-        int l, c;
-
-        console_sharp_get_dims (out l, out c);
-
-        if (l < 1)
-        {
-            l = 1;
-        }
-
-        if (l != lines || c != cols)
-        {
-            lines = l;
-            cols = c;
-
-            return true;
-        }
-
-        return false;
-    }
-
-    public static int clearok (nint win, bool bf) { return methods.clearok (win, bf); }
-    public static int COLOR_PAIRS () { return methods.COLOR_PAIRS (); }
-    public static int curs_set (int visibility) { return methods.curs_set (visibility); }
-
-    public static string curses_version ()
-    {
-        nint v = methods.curses_version ();
-
-        return $"{Marshal.PtrToStringAnsi (v)}, {curses_library.LibraryPath}";
-    }
-
-    public static int def_prog_mode () { return methods.def_prog_mode (); }
-    public static int def_shell_mode () { return methods.def_shell_mode (); }
-    public static int doupdate () { return methods.doupdate (); }
-    public static int echo () { return methods.echo (); }
-
-    //static public int addch (int ch) => methods.addch (ch);
-    public static int echochar (int ch) { return methods.echochar (ch); }
-
-    //
-    // The proxy methods to call into each version
-    //
-    public static int endwin () { return methods.endwin (); }
-    public static int flushinp () { return methods.flushinp (); }
-    public static int get_wch (out int sequence) { return methods.get_wch (out sequence); }
-    public static int getch () { return methods.getch (); }
-    public static uint getmouse (out MouseEvent ev) { return methods.getmouse (out ev); }
-    public static int halfdelay (int t) { return methods.halfdelay (t); }
-    public static bool has_colors () { return methods.has_colors (); }
-    public static void idcok (nint win, bool bf) { methods.idcok (win, bf); }
-    public static int idlok (nint win, bool bf) { return methods.idlok (win, bf); }
-    public static void immedok (nint win, bool bf) { methods.immedok (win, bf); }
-    public static int init_pair (short pair, short f, short b) { return methods.init_pair (pair, f, b); }
-
-    /// <summary>
-    ///     The init_pair routine changes the definition of a color-pair.It takes three arguments: the number of the
-    ///     color-pair to be changed, the  fore- ground color number, and the background color number.For portable ap-
-    ///     plications: o The first argument must be a legal color pair  value.If  default colors are used (see
-    ///     use_default_colors(3x)) the upper limit is ad- justed to allow for extra pairs which use a default color in  fore-
-    ///     ground and/or background. o The second and third arguments must be legal color values. If the  color-pair was
-    ///     previously initialized, the screen is refreshed and all occurrences of that color-pair are changed to the new
-    ///     defini- tion. As an  extension,  ncurses allows you to set color pair 0 via the as- sume_default_colors (3x)
-    ///     routine, or to specify the use of default  col- ors (color number  -1) if you first invoke the use_default_colors
-    ///     (3x) routine.
-    /// </summary>
-    /// <param name="pair"></param>
-    /// <param name="foreground"></param>
-    /// <param name="background"></param>
-    /// <returns></returns>
-    public static int InitColorPair (short pair, short foreground, short background) { return methods.init_pair (pair, foreground, background); }
-
-    public static Window initscr ()
-    {
-        setlocale (LC_ALL, "");
-        FindNCurses ();
-
-        // Prevents the terminal from being locked after exiting.
-        reset_shell_mode ();
-
-        main_window = new Window (methods.initscr ());
-
-        try
-        {
-            console_sharp_get_dims (out lines, out cols);
-        }
-        catch (DllNotFoundException)
-        {
-            endwin ();
-
-            Console.Error.WriteLine (
-                                     "Unable to find the @MONO_CURSES@ native library\n"
-                                     + "this is different than the managed mono-curses.dll\n\n"
-                                     + "Typically you need to install to a LD_LIBRARY_PATH directory\n"
-                                     + "or DYLD_LIBRARY_PATH directory or run /sbin/ldconfig"
-                                    );
-            Environment.Exit (1);
-        }
-
-        //Console.Error.WriteLine ($"using curses {Curses.curses_version ()}");
-
-        return main_window;
-    }
-
-    public static int intrflush (nint win, bool bf) { return methods.intrflush (win, bf); }
-    public static bool is_term_resized (int lines, int columns) { return methods.is_term_resized (lines, columns); }
-
-    public static int IsAlt (int key)
-    {
-        if ((key & KeyAlt) != 0)
-        {
-            return key & ~KeyAlt;
-        }
-
-        return 0;
-    }
-
-    public static bool isendwin () { return methods.isendwin (); }
-    public static int keypad (nint win, bool bf) { return methods.keypad (win, bf); }
-    public static int leaveok (nint win, bool bf) { return methods.leaveok (win, bf); }
-    public static int meta (nint win, bool bf) { return methods.meta (win, bf); }
-    public static int mouseinterval (int interval) { return methods.mouseinterval (interval); }
-
-    public static Event mousemask (Event newmask, out Event oldmask)
-    {
-        nint e;
-        var ret = (Event)methods.mousemask ((nint)newmask, out e);
-        oldmask = (Event)e;
-
-        return ret;
-    }
-
-    public static int move (int line, int col) { return methods.move (line, col); }
-
-    public static int mvaddch (int y, int x, int ch)
-    {
-        if (ch < 127 || ch > 0xffff)
-        {
-            return methods.mvaddch (y, x, ch);
-        }
-
-        var c = (char)ch;
-
-        return mvaddwstr (y, x, new string (c, 1));
-    }
-
-    public static int mvaddwstr (int y, int x, string s) { return methods.mvaddwstr (y, x, s); }
-    public static int mvgetch (int y, int x) { return methods.mvgetch (y, x); }
-    public static int nl () { return methods.nl (); }
-    public static int nocbreak () { return methods.nocbreak (); }
-    public static int noecho () { return methods.noecho (); }
-    public static int nonl () { return methods.nonl (); }
-    public static void noqiflush () { methods.noqiflush (); }
-    public static int noraw () { return methods.noraw (); }
-    public static int notimeout (nint win, bool bf) { return methods.notimeout (win, bf); }
-    public static void qiflush () { methods.qiflush (); }
-    public static int raw () { return methods.raw (); }
-    public static int redrawwin (nint win) { return methods.redrawwin (win); }
-    public static int refresh () { return methods.refresh (); }
-    public static int reset_prog_mode () { return methods.reset_prog_mode (); }
-    public static int reset_shell_mode () { return methods.reset_shell_mode (); }
-    public static int resetty () { return methods.resetty (); }
-    public static int resize_term (int lines, int columns) { return methods.resize_term (lines, columns); }
-    public static int resizeterm (int lines, int columns) { return methods.resizeterm (lines, columns); }
-    public static int savetty () { return methods.savetty (); }
-    public static int scrollok (nint win, bool bf) { return methods.scrollok (win, bf); }
-    public static int set_escdelay (int size) { return methods.set_escdelay (size); }
-
-    [DllImport ("libc")]
-    public static extern int setlocale (int cate, [MarshalAs (UnmanagedType.LPStr)] string locale);
-
-    public static int setscrreg (int top, int bot) { return methods.setscrreg (top, bot); }
-    public static int start_color () { return methods.start_color (); }
-    public static int StartColor () { return methods.start_color (); }
-    public static int timeout (int delay) { return methods.timeout (delay); }
-    public static int typeahead (nint fd) { return methods.typeahead (fd); }
-    public static int ungetch (int ch) { return methods.ungetch (ch); }
-    public static uint ungetmouse (ref MouseEvent ev) { return methods.ungetmouse (ref ev); }
-    public static int use_default_colors () { return methods.use_default_colors (); }
-    public static void use_env (bool f) { methods.use_env (f); }
-
-    // TODO: Upgrade to ncurses 6.1 and use the extended version
-    //public static int InitExtendedPair (int pair, int foreground, int background) => methods.init_extended_pair (pair, foreground, background);
-    public static int UseDefaultColors () { return methods.use_default_colors (); }
-    public static int waddch (nint win, int ch) { return methods.waddch (win, ch); }
-    public static int wmove (nint win, int line, int col) { return methods.wmove (win, line, col); }
-
-    //static public int wredrawwin (IntPtr win, int beg_line, int num_lines) => methods.wredrawwin (win, beg_line, num_lines);
-    public static int wnoutrefresh (nint win) { return methods.wnoutrefresh (win); }
-    public static int wrefresh (nint win) { return methods.wrefresh (win); }
-    public static int wsetscrreg (nint win, int top, int bot) { return methods.wsetscrreg (win, top, bot); }
-    public static int wtimeout (nint win, int delay) { return methods.wtimeout (win, delay); }
-    internal static nint console_sharp_get_curscr () { return Marshal.ReadIntPtr (curscr_ptr); }
-
-    internal static void console_sharp_get_dims (out int lines, out int cols)
-    {
-        lines = Marshal.ReadInt32 (lines_ptr);
-        cols = Marshal.ReadInt32 (cols_ptr);
-
-        //int cmd;
-        //if (UnmanagedLibrary.IsMacOSPlatform) {
-        //	cmd = TIOCGWINSZ_MAC;
-        //} else {
-        //	cmd = TIOCGWINSZ;
-        //}
-
-        //if (ioctl (1, cmd, out winsize ws) == 0) {
-        //	lines = ws.ws_row;
-        //	cols = ws.ws_col;
-
-        //	if (lines == Lines && cols == Cols) {
-        //		return;
-        //	}
-
-        //	resizeterm (lines, cols);
-        //} else {
-        //	lines = Lines;
-        //	cols = Cols;
-        //}
-    }
-
-    internal static nint console_sharp_get_stdscr () { return stdscr; }
-
-    internal static nint read_static_ptr (string key)
-    {
-        nint ptr = get_ptr (key);
-
-        return Marshal.ReadIntPtr (ptr);
-    }
-
-    private static void FindNCurses ()
-    {
-        LoadMethods ();
-        curses_handle = methods.UnmanagedLibrary.NativeLibraryHandle;
-
-        stdscr = read_static_ptr ("stdscr");
-        curscr_ptr = get_ptr ("curscr");
-        lines_ptr = get_ptr ("LINES");
-        cols_ptr = get_ptr ("COLS");
-    }
-
-    private static nint get_ptr (string key)
-    {
-        nint ptr = curses_library.LoadSymbol (key);
-
-        if (ptr == nint.Zero)
-        {
-            throw new Exception ("Could not load the key " + key);
-        }
-
-        return ptr;
-    }
-
-    //[DllImport ("libc")]
-    //public extern static int ioctl (int fd, int cmd, out winsize argp);
-
-    private static void LoadMethods ()
-    {
-        string [] libs = OperatingSystem.IsMacOS()
-                             ? ["libncurses.dylib"]
-                             : ["libncursesw.so.6", "libncursesw.so.5"];
-        var attempts = 1;
-
-        while (true)
-        {
-            try
-            {
-                curses_library = new UnmanagedLibrary (libs, false);
-                methods = new NativeMethods (curses_library);
-
-                break;
-            }
-            catch (Exception ex)
-            {
-                if (attempts == 1)
-                {
-                    attempts++;
-
-                    (int exitCode, string result) =
-                        ClipboardProcessRunner.Bash ("cat /etc/os-release", waitForOutput: true);
-
-                    if (exitCode == 0 && result.Contains ("opensuse"))
-                    {
-                        libs [0] = "libncursesw.so.5";
-                    }
-                }
-                else
-                {
-                    throw ex.GetBaseException ();
-                }
-            }
-        }
-    }
-
-    //[StructLayout (LayoutKind.Sequential)]
-    //public struct winsize {
-    //	public ushort ws_row;
-    //	public ushort ws_col;
-    //	public ushort ws_xpixel;   /* unused */
-    //	public ushort ws_ypixel;   /* unused */
-    //};
-
-    [StructLayout (LayoutKind.Sequential)]
-    internal struct MouseEvent
-    {
-        public short ID;
-        public int X, Y, Z;
-        public Event ButtonState;
-    }
-}
-
-#pragma warning disable RCS1102 // Make class static.'
-internal class Delegates
-{
-#pragma warning restore RCS1102 // Make class static.
-#pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
-    public delegate nint initscr ();
-
-    public delegate int endwin ();
-
-    public delegate bool isendwin ();
-
-    public delegate int cbreak ();
-
-    public delegate int nocbreak ();
-
-    public delegate int echo ();
-
-    public delegate int noecho ();
-
-    public delegate int halfdelay (int t);
-
-    public delegate int raw ();
-
-    public delegate int noraw ();
-
-    public delegate void noqiflush ();
-
-    public delegate void qiflush ();
-
-    public delegate int typeahead (nint fd);
-
-    public delegate int timeout (int delay);
-
-    public delegate int wtimeout (nint win, int delay);
-
-    public delegate int notimeout (nint win, bool bf);
-
-    public delegate int keypad (nint win, bool bf);
-
-    public delegate int meta (nint win, bool bf);
-
-    public delegate int intrflush (nint win, bool bf);
-
-    public delegate int clearok (nint win, bool bf);
-
-    public delegate int idlok (nint win, bool bf);
-
-    public delegate void idcok (nint win, bool bf);
-
-    public delegate void immedok (nint win, bool bf);
-
-    public delegate int leaveok (nint win, bool bf);
-
-    public delegate int wsetscrreg (nint win, int top, int bot);
-
-    public delegate int scrollok (nint win, bool bf);
-
-    public delegate int nl ();
-
-    public delegate int nonl ();
-
-    public delegate int setscrreg (int top, int bot);
-
-    public delegate int refresh ();
-
-    public delegate int doupdate ();
-
-    public delegate int wrefresh (nint win);
-
-    public delegate int redrawwin (nint win);
-
-    //public delegate int wredrawwin (IntPtr win, int beg_line, int num_lines);
-    public delegate int wnoutrefresh (nint win);
-
-    public delegate int move (int line, int col);
-
-    public delegate int curs_set (int visibility);
-
-    public delegate int addch (int ch);
-
-    public delegate int echochar (int ch);
-
-    public delegate int mvaddch (int y, int x, int ch);
-
-    public delegate int addwstr ([MarshalAs (UnmanagedType.LPWStr)] string s);
-
-    public delegate int mvaddwstr (int y, int x, [MarshalAs (UnmanagedType.LPWStr)] string s);
-
-    public delegate int wmove (nint win, int line, int col);
-
-    public delegate int waddch (nint win, int ch);
-
-    public delegate int attron (int attrs);
-
-    public delegate int attroff (int attrs);
-
-    public delegate int attrset (int attrs);
-
-    public delegate int getch ();
-
-    public delegate int get_wch (out int sequence);
-
-    public delegate int ungetch (int ch);
-
-    public delegate int mvgetch (int y, int x);
-
-    public delegate bool has_colors ();
-
-    public delegate int start_color ();
-
-    public delegate int init_pair (short pair, short f, short b);
-
-    public delegate int use_default_colors ();
-
-    public delegate int COLOR_PAIRS ();
-
-    public delegate uint getmouse (out Curses.MouseEvent ev);
-
-    public delegate uint ungetmouse (ref Curses.MouseEvent ev);
-
-    public delegate int mouseinterval (int interval);
-
-    public delegate nint mousemask (nint newmask, out nint oldMask);
-
-    public delegate bool is_term_resized (int lines, int columns);
-
-    public delegate int resize_term (int lines, int columns);
-
-    public delegate int resizeterm (int lines, int columns);
-
-    public delegate void use_env (bool f);
-
-    public delegate int flushinp ();
-
-    public delegate int def_prog_mode ();
-
-    public delegate int def_shell_mode ();
-
-    public delegate int reset_prog_mode ();
-
-    public delegate int reset_shell_mode ();
-
-    public delegate int savetty ();
-
-    public delegate int resetty ();
-
-    public delegate int set_escdelay (int size);
-
-    public delegate nint curses_version ();
-}
-
-internal class NativeMethods
-{
-    public readonly Delegates.addch addch;
-    public readonly Delegates.addwstr addwstr;
-    public readonly Delegates.attroff attroff;
-
-    //public readonly Delegates.wechochar wechochar;
-    public readonly Delegates.attron attron;
-    public readonly Delegates.attrset attrset;
-    public readonly Delegates.cbreak cbreak;
-    public readonly Delegates.clearok clearok;
-    public readonly Delegates.COLOR_PAIRS COLOR_PAIRS;
-    public readonly Delegates.curs_set curs_set;
-    public readonly Delegates.curses_version curses_version;
-    public readonly Delegates.def_prog_mode def_prog_mode;
-    public readonly Delegates.def_shell_mode def_shell_mode;
-    public readonly Delegates.doupdate doupdate;
-    public readonly Delegates.echo echo;
-    public readonly Delegates.echochar echochar;
-    public readonly Delegates.endwin endwin;
-    public readonly Delegates.flushinp flushinp;
-    public readonly Delegates.get_wch get_wch;
-    public readonly Delegates.getch getch;
-    public readonly Delegates.getmouse getmouse;
-    public readonly Delegates.halfdelay halfdelay;
-    public readonly Delegates.has_colors has_colors;
-    public readonly Delegates.idcok idcok;
-    public readonly Delegates.idlok idlok;
-    public readonly Delegates.immedok immedok;
-    public readonly Delegates.init_pair init_pair;
-    public readonly Delegates.initscr initscr;
-    public readonly Delegates.intrflush intrflush;
-    public readonly Delegates.is_term_resized is_term_resized;
-    public readonly Delegates.isendwin isendwin;
-    public readonly Delegates.keypad keypad;
-    public readonly Delegates.leaveok leaveok;
-    public readonly Delegates.meta meta;
-    public readonly Delegates.mouseinterval mouseinterval;
-    public readonly Delegates.mousemask mousemask;
-    public readonly Delegates.move move;
-    public readonly Delegates.mvaddch mvaddch;
-    public readonly Delegates.mvaddwstr mvaddwstr;
-    public readonly Delegates.mvgetch mvgetch;
-    public readonly Delegates.nl nl;
-    public readonly Delegates.nocbreak nocbreak;
-    public readonly Delegates.noecho noecho;
-    public readonly Delegates.nonl nonl;
-    public readonly Delegates.noqiflush noqiflush;
-    public readonly Delegates.noraw noraw;
-    public readonly Delegates.notimeout notimeout;
-    public readonly Delegates.qiflush qiflush;
-    public readonly Delegates.raw raw;
-    public readonly Delegates.redrawwin redrawwin;
-    public readonly Delegates.refresh refresh;
-    public readonly Delegates.reset_prog_mode reset_prog_mode;
-    public readonly Delegates.reset_shell_mode reset_shell_mode;
-    public readonly Delegates.resetty resetty;
-    public readonly Delegates.resize_term resize_term;
-    public readonly Delegates.resizeterm resizeterm;
-    public readonly Delegates.savetty savetty;
-    public readonly Delegates.scrollok scrollok;
-    public readonly Delegates.set_escdelay set_escdelay;
-    public readonly Delegates.setscrreg setscrreg;
-    public readonly Delegates.start_color start_color;
-    public readonly Delegates.timeout timeout;
-    public readonly Delegates.typeahead typeahead;
-    public readonly Delegates.ungetch ungetch;
-    public readonly Delegates.ungetmouse ungetmouse;
-    public readonly Delegates.use_default_colors use_default_colors;
-    public readonly Delegates.use_env use_env;
-    public readonly Delegates.waddch waddch;
-    public readonly Delegates.wmove wmove;
-
-    //public readonly Delegates.wredrawwin wredrawwin;
-    public readonly Delegates.wnoutrefresh wnoutrefresh;
-    public readonly Delegates.wrefresh wrefresh;
-    public readonly Delegates.wsetscrreg wsetscrreg;
-    public readonly Delegates.wtimeout wtimeout;
-    public UnmanagedLibrary UnmanagedLibrary;
-
-    public NativeMethods (UnmanagedLibrary lib)
-    {
-        UnmanagedLibrary = lib;
-        initscr = lib.GetNativeMethodDelegate<Delegates.initscr> ("initscr");
-        endwin = lib.GetNativeMethodDelegate<Delegates.endwin> ("endwin");
-        isendwin = lib.GetNativeMethodDelegate<Delegates.isendwin> ("isendwin");
-        cbreak = lib.GetNativeMethodDelegate<Delegates.cbreak> ("cbreak");
-        nocbreak = lib.GetNativeMethodDelegate<Delegates.nocbreak> ("nocbreak");
-        echo = lib.GetNativeMethodDelegate<Delegates.echo> ("echo");
-        noecho = lib.GetNativeMethodDelegate<Delegates.noecho> ("noecho");
-        halfdelay = lib.GetNativeMethodDelegate<Delegates.halfdelay> ("halfdelay");
-        raw = lib.GetNativeMethodDelegate<Delegates.raw> ("raw");
-        noraw = lib.GetNativeMethodDelegate<Delegates.noraw> ("noraw");
-        noqiflush = lib.GetNativeMethodDelegate<Delegates.noqiflush> ("noqiflush");
-        qiflush = lib.GetNativeMethodDelegate<Delegates.qiflush> ("qiflush");
-        typeahead = lib.GetNativeMethodDelegate<Delegates.typeahead> ("typeahead");
-        timeout = lib.GetNativeMethodDelegate<Delegates.timeout> ("timeout");
-        wtimeout = lib.GetNativeMethodDelegate<Delegates.wtimeout> ("wtimeout");
-        notimeout = lib.GetNativeMethodDelegate<Delegates.notimeout> ("notimeout");
-        keypad = lib.GetNativeMethodDelegate<Delegates.keypad> ("keypad");
-        meta = lib.GetNativeMethodDelegate<Delegates.meta> ("meta");
-        intrflush = lib.GetNativeMethodDelegate<Delegates.intrflush> ("intrflush");
-        clearok = lib.GetNativeMethodDelegate<Delegates.clearok> ("clearok");
-        idlok = lib.GetNativeMethodDelegate<Delegates.idlok> ("idlok");
-        idcok = lib.GetNativeMethodDelegate<Delegates.idcok> ("idcok");
-        immedok = lib.GetNativeMethodDelegate<Delegates.immedok> ("immedok");
-        leaveok = lib.GetNativeMethodDelegate<Delegates.leaveok> ("leaveok");
-        wsetscrreg = lib.GetNativeMethodDelegate<Delegates.wsetscrreg> ("wsetscrreg");
-        scrollok = lib.GetNativeMethodDelegate<Delegates.scrollok> ("scrollok");
-        nl = lib.GetNativeMethodDelegate<Delegates.nl> ("nl");
-        nonl = lib.GetNativeMethodDelegate<Delegates.nonl> ("nonl");
-        setscrreg = lib.GetNativeMethodDelegate<Delegates.setscrreg> ("setscrreg");
-        refresh = lib.GetNativeMethodDelegate<Delegates.refresh> ("refresh");
-        doupdate = lib.GetNativeMethodDelegate<Delegates.doupdate> ("doupdate");
-        wrefresh = lib.GetNativeMethodDelegate<Delegates.wrefresh> ("wrefresh");
-        redrawwin = lib.GetNativeMethodDelegate<Delegates.redrawwin> ("redrawwin");
-
-        //wredrawwin = lib.GetNativeMethodDelegate<Delegates.wredrawwin> ("wredrawwin");
-        wnoutrefresh = lib.GetNativeMethodDelegate<Delegates.wnoutrefresh> ("wnoutrefresh");
-        move = lib.GetNativeMethodDelegate<Delegates.move> ("move");
-        curs_set = lib.GetNativeMethodDelegate<Delegates.curs_set> ("curs_set");
-        addch = lib.GetNativeMethodDelegate<Delegates.addch> ("addch");
-        echochar = lib.GetNativeMethodDelegate<Delegates.echochar> ("echochar");
-        mvaddch = lib.GetNativeMethodDelegate<Delegates.mvaddch> ("mvaddch");
-        addwstr = lib.GetNativeMethodDelegate<Delegates.addwstr> ("addwstr");
-        mvaddwstr = lib.GetNativeMethodDelegate<Delegates.mvaddwstr> ("mvaddwstr");
-        wmove = lib.GetNativeMethodDelegate<Delegates.wmove> ("wmove");
-        waddch = lib.GetNativeMethodDelegate<Delegates.waddch> ("waddch");
-        attron = lib.GetNativeMethodDelegate<Delegates.attron> ("attron");
-        attroff = lib.GetNativeMethodDelegate<Delegates.attroff> ("attroff");
-        attrset = lib.GetNativeMethodDelegate<Delegates.attrset> ("attrset");
-        getch = lib.GetNativeMethodDelegate<Delegates.getch> ("getch");
-        get_wch = lib.GetNativeMethodDelegate<Delegates.get_wch> ("get_wch");
-        ungetch = lib.GetNativeMethodDelegate<Delegates.ungetch> ("ungetch");
-        mvgetch = lib.GetNativeMethodDelegate<Delegates.mvgetch> ("mvgetch");
-        has_colors = lib.GetNativeMethodDelegate<Delegates.has_colors> ("has_colors");
-        start_color = lib.GetNativeMethodDelegate<Delegates.start_color> ("start_color");
-        init_pair = lib.GetNativeMethodDelegate<Delegates.init_pair> ("init_pair");
-        use_default_colors = lib.GetNativeMethodDelegate<Delegates.use_default_colors> ("use_default_colors");
-        COLOR_PAIRS = lib.GetNativeMethodDelegate<Delegates.COLOR_PAIRS> ("COLOR_PAIRS");
-        getmouse = lib.GetNativeMethodDelegate<Delegates.getmouse> ("getmouse");
-        ungetmouse = lib.GetNativeMethodDelegate<Delegates.ungetmouse> ("ungetmouse");
-        mouseinterval = lib.GetNativeMethodDelegate<Delegates.mouseinterval> ("mouseinterval");
-        mousemask = lib.GetNativeMethodDelegate<Delegates.mousemask> ("mousemask");
-        is_term_resized = lib.GetNativeMethodDelegate<Delegates.is_term_resized> ("is_term_resized");
-        resize_term = lib.GetNativeMethodDelegate<Delegates.resize_term> ("resize_term");
-        resizeterm = lib.GetNativeMethodDelegate<Delegates.resizeterm> ("resizeterm");
-        use_env = lib.GetNativeMethodDelegate<Delegates.use_env> ("use_env");
-        flushinp = lib.GetNativeMethodDelegate<Delegates.flushinp> ("flushinp");
-        def_prog_mode = lib.GetNativeMethodDelegate<Delegates.def_prog_mode> ("def_prog_mode");
-        def_shell_mode = lib.GetNativeMethodDelegate<Delegates.def_shell_mode> ("def_shell_mode");
-        reset_prog_mode = lib.GetNativeMethodDelegate<Delegates.reset_prog_mode> ("reset_prog_mode");
-        reset_shell_mode = lib.GetNativeMethodDelegate<Delegates.reset_shell_mode> ("reset_shell_mode");
-        savetty = lib.GetNativeMethodDelegate<Delegates.savetty> ("savetty");
-        resetty = lib.GetNativeMethodDelegate<Delegates.resetty> ("resetty");
-        set_escdelay = lib.GetNativeMethodDelegate<Delegates.set_escdelay> ("set_escdelay");
-        curses_version = lib.GetNativeMethodDelegate<Delegates.curses_version> ("curses_version");
-    }
-}
-#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
-#pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.

+ 0 - 177
Terminal.Gui/Drivers/CursesDriver/constants.cs

@@ -1,177 +0,0 @@
-/*
- * This file is autogenerated by the attrib.c program, do not edit
- */
-
-//#define XTERM1006
-
-using System.Runtime.InteropServices;
-
-namespace Unix.Terminal;
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-internal partial class Curses
-{
-    public const int A_NORMAL = 0x0;
-    public const int A_STANDOUT = 0x10000;
-    public const int A_UNDERLINE = 0x20000;
-    public const int A_REVERSE = 0x40000;
-    public const int A_BLINK = 0x80000;
-    public const int A_DIM = 0x100000;
-    public const int A_BOLD = 0x200000;
-    public const int A_PROTECT = 0x1000000;
-    public const int A_INVIS = 0x800000;
-    public const int ACS_LLCORNER = 0x40006d;
-    public const int ACS_LRCORNER = 0x40006a;
-    public const int ACS_HLINE = 0x400071;
-    public const int ACS_ULCORNER = 0x40006c;
-    public const int ACS_URCORNER = 0x40006b;
-    public const int ACS_VLINE = 0x400078;
-    public const int ACS_LTEE = 0x400074;
-    public const int ACS_RTEE = 0x400075;
-    public const int ACS_BTEE = 0x400076;
-    public const int ACS_TTEE = 0x400077;
-    public const int ACS_PLUS = 0x40006e;
-    public const int ACS_S1 = 0x40006f;
-    public const int ACS_S9 = 0x400073;
-    public const int ACS_DIAMOND = 0x400060;
-    public const int ACS_CKBOARD = 0x400061;
-    public const int ACS_DEGREE = 0x400066;
-    public const int ACS_PLMINUS = 0x400067;
-    public const int ACS_BULLET = 0x40007e;
-    public const int ACS_LARROW = 0x40002c;
-    public const int ACS_RARROW = 0x40002b;
-    public const int ACS_DARROW = 0x40002e;
-    public const int ACS_UARROW = 0x40002d;
-    public const int ACS_BOARD = 0x400068;
-    public const int ACS_LANTERN = 0x400069;
-    public const int ACS_BLOCK = 0x400030;
-    public const int COLOR_BLACK = 0x0;
-    public const int COLOR_RED = 0x1;
-    public const int COLOR_GREEN = 0x2;
-    public const int COLOR_YELLOW = 0x3;
-    public const int COLOR_BLUE = 0x4;
-    public const int COLOR_MAGENTA = 0x5;
-    public const int COLOR_CYAN = 0x6;
-    public const int COLOR_WHITE = 0x7;
-    public const int COLOR_GRAY = 0x8;
-    public const int KEY_CODE_YES = 0x100;
-    public const int ERR = unchecked ((int)0xffffffff);
-    public const int TIOCGWINSZ = 0x5413;
-    public const int TIOCGWINSZ_MAC = 0x40087468;
-    [Flags]
-    internal enum Event : long
-    {
-        Button1Pressed = 0x2,
-        Button1Released = 0x1,
-        Button1Clicked = 0x4,
-        Button1DoubleClicked = 0x8,
-        Button1TripleClicked = 0x10,
-        Button2Pressed = 0x40,
-        Button2Released = 0x20,
-        Button2Clicked = 0x80,
-        Button2DoubleClicked = 0x100,
-        Button2TripleClicked = 0x200,
-        Button3Pressed = 0x800,
-        Button3Released = 0x400,
-        Button3Clicked = 0x1000,
-        Button3DoubleClicked = 0x2000,
-        Button3TripleClicked = 0x4000,
-        ButtonWheeledUp = 0x10000,
-        ButtonWheeledDown = 0x200000,
-        Button4Pressed = 0x80000,
-        Button4Released = 0x40000,
-        Button4Clicked = 0x100000,
-        Button4DoubleClicked = 0x20000,
-        Button4TripleClicked = 0x400000,
-        ButtonShift = 0x4000000,
-        ButtonCtrl = 0x2000000,
-        ButtonAlt = 0x8000000,
-        ReportMousePosition = 0x10000000,
-        AllEvents = 0x7ffffff
-    }
-#if XTERM1006
-    public const int LeftRightUpNPagePPage = unchecked ((int)0x8);
-    public const int DownEnd = unchecked ((int)0x6);
-    public const int Home = unchecked ((int)0x7);
-#else
-    public const int LeftRightUpNPagePPage = 0x0;
-    public const int DownEnd = 0x0;
-    public const int Home = 0x0;
-#endif
-    public const int KeyBackspace = 0x107;
-    public const int KeyUp = 0x103;
-    public const int KeyDown = 0x102;
-    public const int KeyLeft = 0x104;
-    public const int KeyRight = 0x105;
-    public const int KeyNPage = 0x152;
-    public const int KeyPPage = 0x153;
-    public const int KeyHome = 0x106;
-    public const int KeyMouse = 0x199;
-    public const int KeyEnd = 0x168;
-    public const int KeyDeleteChar = 0x14a;
-    public const int KeyInsertChar = 0x14b;
-    public const int KeyTab = 0x009;
-    public const int KeyBackTab = 0x161;
-    public const int KeyF1 = 0x109;
-    public const int KeyF2 = 0x10a;
-    public const int KeyF3 = 0x10b;
-    public const int KeyF4 = 0x10c;
-    public const int KeyF5 = 0x10d;
-    public const int KeyF6 = 0x10e;
-    public const int KeyF7 = 0x10f;
-    public const int KeyF8 = 0x110;
-    public const int KeyF9 = 0x111;
-    public const int KeyF10 = 0x112;
-    public const int KeyF11 = 0x113;
-    public const int KeyF12 = 0x114;
-    public const int KeyResize = 0x19a;
-    public const int ShiftKeyUp = 0x151;
-    public const int ShiftKeyDown = 0x150;
-    public const int ShiftKeyLeft = 0x189;
-    public const int ShiftKeyRight = 0x192;
-    public const int ShiftKeyNPage = 0x18c;
-    public const int ShiftKeyPPage = 0x18e;
-    public const int ShiftKeyHome = 0x187;
-    public const int ShiftKeyEnd = 0x182;
-    public const int AltKeyUp = unchecked (0x234 + LeftRightUpNPagePPage);
-    public const int AltKeyDown = unchecked (0x20b + DownEnd);
-    public const int AltKeyLeft = unchecked (0x21f + LeftRightUpNPagePPage);
-    public const int AltKeyRight = unchecked (0x22e + LeftRightUpNPagePPage);
-    public const int AltKeyNPage = unchecked (0x224 + LeftRightUpNPagePPage);
-    public const int AltKeyPPage = unchecked (0x229 + LeftRightUpNPagePPage);
-    public const int AltKeyHome = unchecked (0x215 + Home);
-    public const int AltKeyEnd = unchecked (0x210 + DownEnd);
-    public const int CtrlKeyUp = unchecked (0x236 + LeftRightUpNPagePPage);
-    public const int CtrlKeyDown = unchecked (0x20d + DownEnd);
-    public const int CtrlKeyLeft = unchecked (0x221 + LeftRightUpNPagePPage);
-    public const int CtrlKeyRight = unchecked (0x230 + LeftRightUpNPagePPage);
-    public const int CtrlKeyNPage = unchecked (0x226 + LeftRightUpNPagePPage);
-    public const int CtrlKeyPPage = unchecked (0x22b + LeftRightUpNPagePPage);
-    public const int CtrlKeyHome = unchecked (0x217 + Home);
-    public const int CtrlKeyEnd = unchecked (0x212 + DownEnd);
-    public const int ShiftCtrlKeyUp = unchecked (0x237 + LeftRightUpNPagePPage);
-    public const int ShiftCtrlKeyDown = unchecked (0x20e + DownEnd);
-    public const int ShiftCtrlKeyLeft = unchecked (0x222 + LeftRightUpNPagePPage);
-    public const int ShiftCtrlKeyRight = unchecked (0x231 + LeftRightUpNPagePPage);
-    public const int ShiftCtrlKeyNPage = unchecked (0x227 + LeftRightUpNPagePPage);
-    public const int ShiftCtrlKeyPPage = unchecked (0x22c + LeftRightUpNPagePPage);
-    public const int ShiftCtrlKeyHome = unchecked (0x218 + Home);
-    public const int ShiftCtrlKeyEnd = unchecked (0x213 + DownEnd);
-    public const int ShiftAltKeyUp = unchecked (0x235 + LeftRightUpNPagePPage);
-    public const int ShiftAltKeyDown = unchecked (0x20c + DownEnd);
-    public const int ShiftAltKeyLeft = unchecked (0x220 + LeftRightUpNPagePPage);
-    public const int ShiftAltKeyRight = unchecked (0x22f + LeftRightUpNPagePPage);
-    public const int ShiftAltKeyNPage = unchecked (0x225 + LeftRightUpNPagePPage);
-    public const int ShiftAltKeyPPage = unchecked (0x22a + LeftRightUpNPagePPage);
-    public const int ShiftAltKeyHome = unchecked (0x216 + Home);
-    public const int ShiftAltKeyEnd = unchecked (0x211 + DownEnd);
-    public const int AltCtrlKeyNPage = unchecked (0x228 + LeftRightUpNPagePPage);
-    public const int AltCtrlKeyPPage = unchecked (0x22d + LeftRightUpNPagePPage);
-    public const int AltCtrlKeyHome = unchecked (0x219 + Home);
-    public const int AltCtrlKeyEnd = unchecked (0x214 + DownEnd);
-
-    // see #949
-    public static int LC_ALL { get; }
-    static Curses () { LC_ALL = RuntimeInformation.IsOSPlatform (OSPlatform.OSX) ? 0 : 6; }
-    public static int ColorPair (int n) { return 0 + n * 256; }
-}
-#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member

+ 0 - 86
Terminal.Gui/Drivers/CursesDriver/handles.cs

@@ -1,86 +0,0 @@
-//
-// handles.cs: OO wrappers for some curses objects
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-// Copyright (C) 2007 Novell (http://www.novell.com)
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-// 
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-// 
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-namespace Unix.Terminal;
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-internal partial class Curses
-{
-    internal class Window
-    {
-        public readonly nint Handle;
-
-        static Window ()
-        {
-            initscr ();
-            Standard = new Window (console_sharp_get_stdscr ());
-            Current = new Window (console_sharp_get_curscr ());
-        }
-
-        internal Window (nint handle) { Handle = handle; }
-        public static Window Standard { get; }
-        public static Window Current { get; }
-        public int wtimeout (int delay) { return Curses.wtimeout (Handle, delay); }
-        public int notimeout (bool bf) { return Curses.notimeout (Handle, bf); }
-        public int keypad (bool bf) { return Curses.keypad (Handle, bf); }
-        public int meta (bool bf) { return Curses.meta (Handle, bf); }
-        public int intrflush (bool bf) { return Curses.intrflush (Handle, bf); }
-        public int clearok (bool bf) { return Curses.clearok (Handle, bf); }
-        public int idlok (bool bf) { return Curses.idlok (Handle, bf); }
-        public void idcok (bool bf) { Curses.idcok (Handle, bf); }
-        public void immedok (bool bf) { Curses.immedok (Handle, bf); }
-        public int leaveok (bool bf) { return Curses.leaveok (Handle, bf); }
-        public int setscrreg (int top, int bot) { return wsetscrreg (Handle, top, bot); }
-        public int scrollok (bool bf) { return Curses.scrollok (Handle, bf); }
-        public int wrefresh () { return Curses.wrefresh (Handle); }
-        public int redrawwin () { return Curses.redrawwin (Handle); }
-#if false
-			public int wredrawwin (int beg_line, int num_lines)
-			{
-				return Curses.wredrawwin (Handle, beg_line, num_lines);
-			}
-#endif
-        public int wnoutrefresh () { return Curses.wnoutrefresh (Handle); }
-        public int move (int line, int col) { return wmove (Handle, line, col); }
-        public int addch (char ch) { return waddch (Handle, ch); }
-
-        //public int echochar (char ch)
-        //{
-        //	return Curses.wechochar (Handle, ch);
-        //}
-        public int refresh () { return Curses.wrefresh (Handle); }
-    }
-
-    // Currently unused, to do later
-    internal class Screen
-    {
-        public readonly nint Handle;
-        internal Screen (nint handle) { Handle = handle; }
-    }
-
-#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
-}

+ 0 - 0
Terminal.Gui/Drivers/V2/INetInput.cs → Terminal.Gui/Drivers/DotNetDriver/INetInput.cs


+ 1 - 1
Terminal.Gui/Drivers/V2/NetComponentFactory.cs → Terminal.Gui/Drivers/DotNetDriver/NetComponentFactory.cs

@@ -4,7 +4,7 @@ using System.Collections.Concurrent;
 namespace Terminal.Gui.Drivers;
 namespace Terminal.Gui.Drivers;
 
 
 /// <summary>
 /// <summary>
-/// <see cref="IComponentFactory{T}"/> implementation for native csharp console I/O i.e. v2net.
+/// <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.
 /// This factory creates instances of internal classes <see cref="NetInput"/>, <see cref="NetOutput"/> etc.
 /// </summary>
 /// </summary>
 public class NetComponentFactory : ComponentFactory<ConsoleKeyInfo>
 public class NetComponentFactory : ComponentFactory<ConsoleKeyInfo>

+ 0 - 0
Terminal.Gui/Drivers/V2/NetInput.cs → Terminal.Gui/Drivers/DotNetDriver/NetInput.cs


+ 1 - 1
Terminal.Gui/Drivers/V2/NetInputProcessor.cs → Terminal.Gui/Drivers/DotNetDriver/NetInputProcessor.cs

@@ -22,7 +22,7 @@ public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
     /// <inheritdoc/>
     /// <inheritdoc/>
     public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ())
     public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ())
     {
     {
-        DriverName = "net";
+        DriverName = "dotnet";
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>

+ 0 - 0
Terminal.Gui/Drivers/V2/NetKeyConverter.cs → Terminal.Gui/Drivers/DotNetDriver/NetKeyConverter.cs


+ 0 - 0
Terminal.Gui/Drivers/V2/NetOutput.cs → Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs


+ 0 - 0
Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs → Terminal.Gui/Drivers/DotNetDriver/NetWinVTConsole.cs


+ 49 - 0
Terminal.Gui/Drivers/FakeDriver/FakeComponentFactory.cs

@@ -0,0 +1,49 @@
+#nullable enable
+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.
+/// </summary>
+public class FakeComponentFactory : ComponentFactory<ConsoleKeyInfo>
+{
+    private readonly ConcurrentQueue<ConsoleKeyInfo>? _predefinedInput;
+    private readonly FakeConsoleOutput? _output;
+
+    /// <summary>
+    /// Creates a new FakeComponentFactory with optional predefined input and output capture.
+    /// </summary>
+    /// <param name="predefinedInput">Optional queue of predefined input events to simulate.</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)
+    {
+        _predefinedInput = predefinedInput;
+        _output = output;
+    }
+
+    /// <inheritdoc/>
+    public override IConsoleInput<ConsoleKeyInfo> CreateInput ()
+    {
+        return new FakeConsoleInput (_predefinedInput);
+    }
+
+    /// <inheritdoc />
+    public override IConsoleOutput CreateOutput ()
+    {
+        return _output ?? new FakeConsoleOutput ();
+    }
+
+    /// <inheritdoc />
+    public override IInputProcessor CreateInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer)
+    {
+        return new NetInputProcessor (inputBuffer);
+    }
+
+    /// <inheritdoc />
+    public override IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer)
+    {
+        return new FakeWindowSizeMonitor(consoleOutput, outputBuffer);
+    }
+}

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

@@ -0,0 +1,42 @@
+#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;
+        }
+    }
+}

+ 88 - 0
Terminal.Gui/Drivers/FakeDriver/FakeConsoleOutput.cs

@@ -0,0 +1,88 @@
+#nullable enable
+using System;
+using System.Text;
+
+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
+{
+    private readonly StringBuilder _output = new ();
+    private int _cursorLeft;
+    private int _cursorTop;
+    private Size _windowSize = new (80, 25);
+
+    /// <summary>
+    /// Gets the captured output as a string.
+    /// </summary>
+    public string Output => _output.ToString ();
+
+    /// <summary>
+    /// Clears the captured output.
+    /// </summary>
+    public void ClearOutput () => _output.Clear ();
+
+    /// <inheritdoc/>
+    public void SetCursorPosition (int col, int row)
+    {
+        SetCursorPositionImpl (col, row);
+    }
+
+    /// <inheritdoc/>
+    protected override bool SetCursorPositionImpl (int col, int row)
+    {
+        _cursorLeft = col;
+        _cursorTop = row;
+        return true;
+    }
+
+    /// <summary>
+    /// Sets the fake window size.
+    /// </summary>
+    public void SetWindowSize (int width, int height)
+    {
+        _windowSize = new Size (width, height);
+    }
+
+    /// <summary>
+    /// Gets the current cursor position.
+    /// </summary>
+    public (int left, int top) GetCursorPosition () => (_cursorLeft, _cursorTop);
+
+    /// <inheritdoc/>
+    public Size GetWindowSize () => _windowSize;
+
+    /// <inheritdoc/>
+    public void Write (ReadOnlySpan<char> text)
+    {
+        _output.Append (text);
+    }
+
+    /// <inheritdoc/>
+    public override void SetCursorVisibility (CursorVisibility visibility)
+    {
+        // Capture but don't act on it in fake output
+    }
+
+    /// <inheritdoc/>
+    public void Dispose ()
+    {
+        // Nothing to dispose
+    }
+
+    /// <inheritdoc/>
+    protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
+    {
+        // For testing, we can skip the actual color/style output
+        // or capture it if needed for verification
+    }
+
+    /// <inheritdoc/>
+    protected override void Write (StringBuilder output)
+    {
+        _output.Append (output);
+    }
+
+}

+ 6 - 4
Terminal.Gui/Drivers/FakeDriver/FakeDriver.cs

@@ -48,6 +48,9 @@ public class FakeDriver : ConsoleDriver
 
 
     public FakeDriver ()
     public FakeDriver ()
     {
     {
+        // FakeDriver implies UnitTests
+        RunningUnitTests = true;
+
         base.Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
         base.Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
         base.Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
         base.Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
 
 
@@ -70,13 +73,13 @@ public class FakeDriver : ConsoleDriver
             }
             }
             else
             else
             {
             {
-                if (CursesDriver.Is_WSL_Platform ())
+                if (PlatformDetection.IsWSLPlatform ())
                 {
                 {
                     Clipboard = new WSLClipboard ();
                     Clipboard = new WSLClipboard ();
                 }
                 }
                 else
                 else
                 {
                 {
-                    Clipboard = new CursesClipboard ();
+                    Clipboard = new UnixClipboard ();
                 }
                 }
             }
             }
         }
         }
@@ -235,7 +238,7 @@ public class FakeDriver : ConsoleDriver
     #region Color Handling
     #region Color Handling
 
 
     ///// <remarks>
     ///// <remarks>
-    ///// In the FakeDriver, colors are encoded as an int; same as NetDriver
+    ///// In the FakeDriver, colors are encoded as an int; same as DotNetDriver
     ///// However, the foreground color is stored in the most significant 16 bits, 
     ///// However, the foreground color is stored in the most significant 16 bits, 
     ///// and the background color is stored in the least significant 16 bits.
     ///// and the background color is stored in the least significant 16 bits.
     ///// </remarks>
     ///// </remarks>
@@ -243,7 +246,6 @@ public class FakeDriver : ConsoleDriver
     //{
     //{
     //	// Encode the colors into the int value.
     //	// Encode the colors into the int value.
     //	return new Attribute (
     //	return new Attribute (
-    //		platformColor: 0,//((((int)foreground.ColorName) & 0xffff) << 16) | (((int)background.ColorName) & 0xffff),
     //		foreground: foreground,
     //		foreground: foreground,
     //		background: background
     //		background: background
     //	);
     //	);

+ 41 - 0
Terminal.Gui/Drivers/FakeDriver/FakeWindowSizeMonitor.cs

@@ -0,0 +1,41 @@
+using Microsoft.Extensions.Logging;
+
+namespace Terminal.Gui.Drivers;
+
+internal class FakeWindowSizeMonitor (IConsoleOutput consoleOut, IOutputBuffer outputBuffer) : IWindowSizeMonitor
+{
+    private Size _lastSize = new (0, 0);
+
+    /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
+    public event EventHandler<SizeChangedEventArgs> SizeChanging;
+
+    /// <summary>Raises the <see cref="SizeChanging"/> event with the specified size. Used for testing.</summary>
+    /// <param name="newSize">The new size to report.</param>
+    public void RaiseSizeChanging (Size newSize)
+    {
+        SizeChanging?.Invoke (this, new (newSize));
+    }
+
+    /// <inheritdoc/>
+    public bool Poll ()
+    {
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return false;
+        }
+
+        Size size = consoleOut.GetWindowSize ();
+
+        if (size != _lastSize)
+        {
+            Logging.Logger.LogInformation ($"Console size changes from '{_lastSize}' to {size}");
+            outputBuffer.SetWindowSize (size.Width, size.Height);
+            _lastSize = size;
+            SizeChanging?.Invoke (this, new (size));
+
+            return true;
+        }
+
+        return false;
+    }
+}

+ 1 - 0
Terminal.Gui/Drivers/V2/IComponentFactory.cs → Terminal.Gui/Drivers/IComponentFactory.cs

@@ -1,5 +1,6 @@
 #nullable enable
 #nullable enable
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
+using Terminal.Gui.App;
 
 
 namespace Terminal.Gui.Drivers;
 namespace Terminal.Gui.Drivers;
 
 

+ 3 - 3
Terminal.Gui/Drivers/IConsoleDriver.cs

@@ -4,8 +4,8 @@ namespace Terminal.Gui.Drivers;
 
 
 /// <summary>Base interface for Terminal.Gui ConsoleDriver implementations.</summary>
 /// <summary>Base interface for Terminal.Gui ConsoleDriver implementations.</summary>
 /// <remarks>
 /// <remarks>
-///     There are currently four implementations: - <see cref="CursesDriver"/> (for Unix and Mac) -
-///     <see cref="WindowsDriver"/> - <see cref="NetDriver"/> that uses the .NET Console API - <see cref="FakeConsole"/>
+///     There are currently four implementations: - <see cref="UnixDriver"/> (for Unix and Mac) -
+///     <see cref="WindowsDriver"/> - <see cref="DotNetDriver"/> that uses the .NET Console API - <see cref="FakeConsole"/>
 ///     for unit testing.
 ///     for unit testing.
 /// </remarks>
 /// </remarks>
 public interface IConsoleDriver
 public interface IConsoleDriver
@@ -206,7 +206,7 @@ public interface IConsoleDriver
     event EventHandler<SizeChangedEventArgs>? SizeChanged;
     event EventHandler<SizeChangedEventArgs>? SizeChanged;
 
 
     /// <summary>Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.</summary>
     /// <summary>Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.</summary>
-    /// <remarks>This is only implemented in <see cref="CursesDriver"/>.</remarks>
+    /// <remarks>This is only implemented in <see cref="UnixDriver"/>.</remarks>
     void Suspend ();
     void Suspend ();
 
 
     /// <summary>
     /// <summary>

+ 0 - 0
Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs → Terminal.Gui/Drivers/IConsoleDriverFacade.cs


+ 0 - 0
Terminal.Gui/Drivers/V2/IConsoleInput.cs → Terminal.Gui/Drivers/IConsoleInput.cs


+ 0 - 0
Terminal.Gui/Drivers/V2/IConsoleOutput.cs → Terminal.Gui/Drivers/IConsoleOutput.cs


+ 0 - 0
Terminal.Gui/Drivers/V2/IInputProcessor.cs → Terminal.Gui/Drivers/IInputProcessor.cs


+ 0 - 0
Terminal.Gui/Drivers/V2/IKeyConverter.cs → Terminal.Gui/Drivers/IKeyConverter.cs


+ 0 - 0
Terminal.Gui/Drivers/V2/IOutputBuffer.cs → Terminal.Gui/Drivers/IOutputBuffer.cs


Some files were not shown because too many files changed in this diff