using Mindmagma.Curses; namespace SCI.CursesWrapper; public class InputHandler { /// /// Handler called by the input handler routine when a key is pressed /// /// Sender object of event callback /// Event arguments with details like the pressed key code public delegate void KeypressEventHandler (object sender, NCursesKeyPressEventArgs e); /// /// Event handlers for NCurses key presses /// public event KeypressEventHandler? OnKeyPress; /// /// Event Handlers called before the regular OnKeyPress handlers are called /// public event KeypressEventHandler? OnKeyPressPrivileged; /// /// Gets or sets the window this InputHandler is listening to /// private Window? _activeWindow; public Window? ActiveWindow { get { return _activeWindow; } set { _activeWindow = value; } } /// /// Root screen to fall back to when no window is active /// private nint rootScreen; /// /// Task running the endless input handler routine /// private Task? listeningTask; /// /// Cancellation Token Source to stop the listening task /// private CancellationTokenSource? listeningRoutineCts; /// /// Class constructor /// Parent screen this InputHandler is attached to /// public InputHandler (nint screen) { rootScreen = screen; } /// /// Start listening to key presses and call registered callbacks /// public void StartListening () { if (IsListening ()) return; listeningRoutineCts = new CancellationTokenSource (); listeningTask = Task.Run (_ListeningRoutine); while (listeningTask.Status == TaskStatus.WaitingToRun) Thread.Sleep (50); } /// /// Stop listening to key presses /// public void StopListening () { if (! IsListening ()) return; if (listeningRoutineCts is null) return; if (listeningTask is null) return; listeningRoutineCts.Cancel (); listeningTask.Wait (); listeningTask = null; if (ActiveWindow is not null) { NCurses.Keypad (ActiveWindow.WindowId, false); } } /// /// Returns the current listening status of the input event handler /// /// True if the event handler routine is listening to key presses public bool IsListening () { return listeningTask is not null && ( listeningTask.Status == TaskStatus.Running || listeningTask.Status == TaskStatus.WaitingForActivation ); } /// /// Prepares the window for proper key press reading /// private void WindowSetup () { if (ActiveWindow is not null) { NCurses.Keypad (ActiveWindow.WindowId, true); NCurses.NoDelay (ActiveWindow.WindowId, false); } else { NCurses.Keypad (rootScreen, true); NCurses.NoDelay (rootScreen, false); } } /// /// Undoes any changes the setup routine configured for the active window /// private void WindowTeardown () { if (ActiveWindow is not null) { NCurses.Keypad (ActiveWindow.WindowId, false); NCurses.NoDelay (ActiveWindow.WindowId, true); } else { NCurses.Keypad (rootScreen, false); NCurses.NoDelay (rootScreen, true); } } /// /// Actual keypress handler being called as Task by StartListening () /// private void _ListeningRoutine () { if (listeningRoutineCts is null) return; WindowSetup (); int keyCode = -1; while (!listeningRoutineCts.Token.IsCancellationRequested) { if (OnKeyPress is null) { Console.Title = "No handlers"; } else { Console.Title = $"Got {OnKeyPress.GetInvocationList ().Length} handlers"; } if (ActiveWindow is not null) { keyCode = NCurses.WindowGetChar (ActiveWindow.WindowId); } else { keyCode = NCurses.GetChar (); } // Do nothing until the key code is larger than -1 if (keyCode < 0) continue; var eventArgs = new NCursesKeyPressEventArgs (keyCode, ActiveWindow); // Handle any registered privileged key handlers if (OnKeyPressPrivileged is not null) { foreach (KeypressEventHandler h in OnKeyPressPrivileged.GetInvocationList ()) { h (this, eventArgs); if (eventArgs.CancelNextEvent == true) { break; } } } // Handle any regular registered key handlers if (OnKeyPress is not null) { foreach (KeypressEventHandler h in OnKeyPress.GetInvocationList ()) { h (this, eventArgs); if (eventArgs.CancelNextEvent == true) continue ; } } } listeningRoutineCts.Dispose (); listeningRoutineCts = null; } } public class NCursesKeyPressEventArgs : EventArgs { private int _keyCode; public int KeyCode { get { return _keyCode; }} private Window? _sourceWindow; public Window? SourceWindow { get { return _sourceWindow; }} public bool CancelNextEvent { get; set; } = false; public NCursesKeyPressEventArgs (int keyCode, Window? sourceWindow) { _keyCode = keyCode; _sourceWindow = sourceWindow; } }