You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
5.0 KiB
182 lines
5.0 KiB
using System.Diagnostics;
|
|
using Mindmagma.Curses;
|
|
|
|
namespace SCI.CursesWrapper;
|
|
|
|
public class InputHandler {
|
|
/// <summary>
|
|
/// Handler called by the input handler routine when a key is pressed
|
|
/// </summary>
|
|
/// <param name="sender">Sender object of event callback</param>
|
|
/// <param name="e">Event arguments with details like the pressed key code</param>
|
|
public delegate void KeypressEventHandler (object sender, NCursesKeyPressEventArgs e);
|
|
|
|
/// <summary>
|
|
/// Event handlers for NCurses key presses
|
|
/// </summary>
|
|
public event KeypressEventHandler? OnKeyPress;
|
|
|
|
/// <summary>
|
|
/// Event Handlers called before the regular OnKeyPress handlers are called
|
|
/// </summary>
|
|
public event KeypressEventHandler? OnKeyPressPrivileged;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the window this InputHandler is listening to
|
|
/// </summary>
|
|
private Window? _activeWindow;
|
|
public Window? ActiveWindow {
|
|
get {
|
|
return _activeWindow;
|
|
}
|
|
set {
|
|
_activeWindow = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Task running the endless input handler routine
|
|
/// </summary>
|
|
private Task? listeningTask;
|
|
|
|
/// <summary>
|
|
/// Cancellation Token Source to stop the listening task
|
|
/// </summary>
|
|
private CancellationTokenSource? listeningRoutineCts;
|
|
|
|
/// <summary>
|
|
/// Start listening to key presses and call registered callbacks
|
|
/// </summary>
|
|
public void StartListening () {
|
|
if (IsListening ()) return;
|
|
|
|
listeningRoutineCts = new CancellationTokenSource ();
|
|
|
|
listeningTask = Task.Run (_ListeningRoutine);
|
|
while (listeningTask.Status == TaskStatus.WaitingToRun) Thread.Sleep (50);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop listening to key presses
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the current listening status of the input event handler
|
|
/// </summary>
|
|
/// <returns>True if the event handler routine is listening to key presses</returns>
|
|
public bool IsListening () {
|
|
return listeningTask is not null && (
|
|
listeningTask.Status == TaskStatus.Running ||
|
|
listeningTask.Status == TaskStatus.WaitingForActivation
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Actual keypress handler being called as Task by StartListening ()
|
|
/// </summary>
|
|
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] + [<key>]
|
|
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;
|
|
}
|
|
} |