Files
LazyBearWorks/LazyBear.MCP/TUI/GlobalKeyboardService.cs
Shahovalov MIkhail 9b27cd7dc2 fix: исправить навигацию клавиатуры в TUI через GlobalKeyboardService
- Добавить GlobalKeyboardService — выделенный поток с блокирующим
  Console.ReadKey, единственный источник клавишных событий для TUI
- Убрать FocusManager из App.razor: перехватывал Tab до компонентов
- Удалить @onkeydown с <Select>: RazorConsole не пробрасывает Tab/стрелки
  через этот механизм
- Использовать FocusedValue вместо Value в Select для корректной подсветки
- Обновить CLAUDE.md и AGENTS.md: архитектура TUI, RazorConsole gotchas
- Добавить docs/tui_log.md: разбор проблемы и справочник по RazorConsole

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 23:31:53 +03:00

75 lines
2.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Extensions.Hosting;
namespace LazyBear.MCP.TUI;
/// <summary>
/// Фоновый сервис для глобального чтения клавиш консоли.
/// Единственный источник клавишных событий для всего TUI.
///
/// Использует выделенный поток с блокирующим Console.ReadKey — никакого
/// polling-а, нет обращений к Console.KeyAvailable, которые захватывают
/// консольный mutex и мешают рендерингу RazorConsole.
/// </summary>
public sealed class GlobalKeyboardService : IHostedService, IDisposable
{
public event Action<ConsoleKeyInfo>? OnKeyPressed;
private Thread? _thread;
private volatile bool _stopping;
public Task StartAsync(CancellationToken cancellationToken)
{
if (Console.IsInputRedirected)
{
return Task.CompletedTask;
}
_thread = new Thread(ReadLoop)
{
IsBackground = true,
Name = "KeyboardReader"
};
_thread.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_stopping = true;
// Console.ReadKey не поддерживает CancellationToken — поток
// завершится сам при выходе приложения (IsBackground = true).
return Task.CompletedTask;
}
private void ReadLoop()
{
while (!_stopping)
{
try
{
// Блокирующий вызов: не нагружает CPU и не трогает консольный
// mutex в паузах между нажатиями — рендеринг не страдает.
var key = Console.ReadKey(intercept: true);
if (!_stopping)
{
OnKeyPressed?.Invoke(key);
}
}
catch (InvalidOperationException)
{
// stdin стал недоступен (перенаправление и т.п.)
break;
}
catch
{
Thread.Sleep(100);
}
}
}
public void Dispose()
{
_stopping = true;
}
}