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;
}
}