| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- using System.Runtime.InteropServices;
- using Microsoft.Extensions.Logging;
- namespace Terminal.Gui.Drivers;
- internal class UnixInput : ConsoleInput<char>, IUnixInput
- {
- private const int STDIN_FILENO = 0;
- [StructLayout (LayoutKind.Sequential)]
- private struct Termios
- {
- public uint c_iflag;
- public uint c_oflag;
- public uint c_cflag;
- public uint c_lflag;
- [MarshalAs (UnmanagedType.ByValArray, SizeConst = 32)]
- public byte [] c_cc;
- public uint c_ispeed;
- public uint c_ospeed;
- }
- [DllImport ("libc", SetLastError = true)]
- private static extern int tcgetattr (int fd, out Termios termios);
- [DllImport ("libc", SetLastError = true)]
- private static extern int tcsetattr (int fd, int optional_actions, ref Termios termios);
- // try cfmakeraw (glibc and macOS usually export it)
- [DllImport ("libc", EntryPoint = "cfmakeraw", SetLastError = false)]
- private static extern void cfmakeraw_ref (ref Termios termios);
- [DllImport ("libc", SetLastError = true)]
- private static extern nint strerror (int err);
- private const int TCSANOW = 0;
- private const ulong BRKINT = 0x00000002;
- private const ulong ICRNL = 0x00000100;
- private const ulong INPCK = 0x00000010;
- private const ulong ISTRIP = 0x00000020;
- private const ulong IXON = 0x00000400;
- private const ulong OPOST = 0x00000001;
- private const ulong ECHO = 0x00000008;
- private const ulong ICANON = 0x00000100;
- private const ulong IEXTEN = 0x00008000;
- private const ulong ISIG = 0x00000001;
- private const ulong CS8 = 0x00000030;
- private Termios _original;
- [StructLayout (LayoutKind.Sequential)]
- private struct Pollfd
- {
- public int fd;
- public short events;
- public readonly short revents; // readonly signals "don't touch this in managed code"
- }
- /// <summary>Condition on which to wake up from file descriptor activity. These match the Linux/BSD poll definitions.</summary>
- [Flags]
- private enum Condition : short
- {
- /// <summary>There is data to read</summary>
- PollIn = 1,
- /// <summary>There is urgent data to read</summary>
- PollPri = 2,
- /// <summary>Writing to the specified descriptor will not block</summary>
- PollOut = 4,
- /// <summary>Error condition on output</summary>
- PollErr = 8,
- /// <summary>Hang-up on output</summary>
- PollHup = 16,
- /// <summary>File descriptor is not open.</summary>
- PollNval = 32
- }
- [DllImport ("libc", SetLastError = true)]
- private static extern int poll ([In][Out] Pollfd [] ufds, uint nfds, int timeout);
- [DllImport ("libc", SetLastError = true)]
- private static extern int read (int fd, byte [] buf, int count);
- // File descriptor for stdout
- private const int STDOUT_FILENO = 1;
- [DllImport ("libc", SetLastError = true)]
- private static extern int write (int fd, byte [] buf, int count);
- [DllImport ("libc", SetLastError = true)]
- private static extern int tcflush (int fd, int queueSelector);
- private const int TCIFLUSH = 0; // flush data received but not read
- private Pollfd [] _pollMap;
- public UnixInput ()
- {
- Logging.Logger.LogInformation ($"Creating {nameof (UnixInput)}");
- if (ConsoleDriver.RunningUnitTests)
- {
- return;
- }
- _pollMap = new Pollfd [1];
- _pollMap [0].fd = STDIN_FILENO; // stdin
- _pollMap [0].events = (short)Condition.PollIn;
- EnableRawModeAndTreatControlCAsInput ();
- //Enable alternative screen buffer.
- WriteRaw (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
- //Set cursor key to application.
- WriteRaw (EscSeqUtils.CSI_HideCursor);
- WriteRaw (EscSeqUtils.CSI_EnableMouseEvents);
- }
- private void EnableRawModeAndTreatControlCAsInput ()
- {
- if (tcgetattr (STDIN_FILENO, out _original) != 0)
- {
- var e = Marshal.GetLastWin32Error ();
- throw new InvalidOperationException ($"tcgetattr failed errno={e} ({StrError (e)})");
- }
- var raw = _original;
- // Prefer cfmakeraw if available
- try
- {
- cfmakeraw_ref (ref raw);
- }
- catch (EntryPointNotFoundException)
- {
- // fallback: roughly cfmakeraw equivalent
- raw.c_iflag &= ~((uint)BRKINT | (uint)ICRNL | (uint)INPCK | (uint)ISTRIP | (uint)IXON);
- raw.c_oflag &= ~(uint)OPOST;
- raw.c_cflag |= (uint)CS8;
- raw.c_lflag &= ~((uint)ECHO | (uint)ICANON | (uint)IEXTEN | (uint)ISIG);
- }
- if (tcsetattr (STDIN_FILENO, TCSANOW, ref raw) != 0)
- {
- var e = Marshal.GetLastWin32Error ();
- throw new InvalidOperationException ($"tcsetattr failed errno={e} ({StrError (e)})");
- }
- }
- private string StrError (int err)
- {
- var p = strerror (err);
- return p == nint.Zero ? $"errno={err}" : Marshal.PtrToStringAnsi (p) ?? $"errno={err}";
- }
- /// <inheritdoc />
- protected override bool Peek ()
- {
- try
- {
- if (ConsoleDriver.RunningUnitTests)
- {
- return false;
- }
- int n = poll (_pollMap!, (uint)_pollMap!.Length, 0);
- if (n != 0)
- {
- return true;
- }
- return false;
- }
- catch (Exception ex)
- {
- // Optionally log the exception
- Logging.Logger.LogError ($"Error in Peek: {ex.Message}");
- return false;
- }
- }
- private void WriteRaw (string text)
- {
- if (!ConsoleDriver.RunningUnitTests)
- {
- byte [] utf8 = Encoding.UTF8.GetBytes (text);
- // Write to stdout (fd 1)
- write (STDOUT_FILENO, utf8, utf8.Length);
- }
- }
- /// <inheritdoc/>
- protected override IEnumerable<char> Read ()
- {
- while (poll (_pollMap!, (uint)_pollMap!.Length, 0) != 0)
- {
- // Check if stdin has data
- if ((_pollMap [0].revents & (int)Condition.PollIn) != 0)
- {
- var buf = new byte [256];
- int bytesRead = read (0, buf, buf.Length); // Read from stdin
- string input = Encoding.UTF8.GetString (buf, 0, bytesRead);
- foreach (char ch in input)
- {
- yield return ch;
- }
- }
- }
- }
- private void FlushConsoleInput ()
- {
- if (!ConsoleDriver.RunningUnitTests)
- {
- var fds = new Pollfd [1];
- fds [0].fd = STDIN_FILENO;
- fds [0].events = (short)Condition.PollIn;
- var buf = new byte [256];
- while (poll (fds, 1, 0) > 0)
- {
- read (STDIN_FILENO, buf, buf.Length);
- }
- }
- }
- /// <inheritdoc />
- public override void Dispose ()
- {
- base.Dispose ();
- if (!ConsoleDriver.RunningUnitTests)
- {
- // Disable mouse events first
- WriteRaw (EscSeqUtils.CSI_DisableMouseEvents);
- // Drain any pending input already queued by the terminal
- FlushConsoleInput ();
- // Flush kernel input buffer
- tcflush (STDIN_FILENO, TCIFLUSH);
- //Disable alternative screen buffer.
- WriteRaw (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
- //Set cursor key to cursor.
- WriteRaw (EscSeqUtils.CSI_ShowCursor);
- // Restore terminal to original state
- tcsetattr (STDIN_FILENO, TCSANOW, ref _original);
- }
- }
- }
|