using System.Diagnostics; 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; } } /// /// Task running the endless input handler routine /// private Task? listeningTask; /// /// Cancellation Token Source to stop the listening task /// private CancellationTokenSource? listeningRoutineCts; /// /// 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 ); } /// /// Actual keypress handler being called as Task by StartListening () /// private void _ListeningRoutine () { if (listeningRoutineCts is null) return; while (!listeningRoutineCts.Token.IsCancellationRequested) { int keyCode = -1; int keyCode2 = -1; if (ActiveWindow is not null) { keyCode = NCurses.WindowGetChar (ActiveWindow.WindowId); // If this block looks yanky; it is. // This code checks if ESC was pressed and waits for another // key press for another 10 milliseconds. This is to catch // key combinations with the Alt key, as it is sent as // [Alt] + [] if (keyCode == CursesKey.ESC) { NCurses.WindowTimeOut (ActiveWindow.WindowId, 10); try { keyCode2 = NCurses.WindowGetChar (ActiveWindow.WindowId); } catch (Exception) { keyCode2 = -1; } NCurses.WindowTimeOut (ActiveWindow.WindowId, -1); } } else { keyCode = NCurses.GetChar (); // Janky solution, see above if (keyCode == CursesKey.ESC) { NCurses.TimeOut (10); try { keyCode2 = NCurses.GetChar (); } catch (Exception) { keyCode2 = -1; } NCurses.TimeOut (-1); } } // Do nothing until either key code is larger than -1 if (keyCode < 0 && keyCode2 < 0) continue; // Test if [Alt]+[key] combination was pressed. // This is due to [Alt]+[key] being sent as [ESC], [key] bool isAltCombo = keyCode == CursesKey.ESC && keyCode2 != -1; if (isAltCombo) { keyCode = keyCode2; } var eventArgs = new NCursesKeyPressEventArgs (keyCode, ActiveWindow, isAltCombo); // 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; private bool _isAltCombination = false; public bool IsAltCombination { get { return _isAltCombination; } } public NCursesKeyPressEventArgs (int keyCode, Window? sourceWindow, bool isAltCombination) { _keyCode = keyCode; _sourceWindow = sourceWindow; _isAltCombination = isAltCombination; } }