mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-11 13:56:33 -06:00
As VS 2022 doesn't seem to store files with UTF-8 BOM as often anymore, we've been getting more and more pull requests which seemingly randomly change files. This cleans the situation up by removing the BOM from all files that have one. Additionally, `Host.Tests.Feature.rc` was converted from UTF-16 to UTF-8.
198 lines
7.9 KiB
C#
198 lines
7.9 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
|
|
using Vanara.PInvoke;
|
|
|
|
namespace Samples.Terminal
|
|
{
|
|
/// <summary>
|
|
/// Provides a Stream-oriented view over the console's input buffer key events
|
|
/// while also collecting out of band events like buffer resize, menu etc in
|
|
/// a caller-provided BlockingCollection.
|
|
/// </summary>
|
|
/// <remarks>The buffer contains unicode chars, not 8 bit CP encoded chars as we rely on ReadConsoleInputW.</remarks>
|
|
public sealed class ReadConsoleInputStream : Stream
|
|
{
|
|
private const int BufferSize = 256;
|
|
private const int BytesPerWChar = 2;
|
|
private readonly BlockingCollection<Kernel32.INPUT_RECORD> _nonKeyEvents;
|
|
private IntPtr _handle;
|
|
|
|
/// <summary>
|
|
/// Creates an instance of ReadConsoleInputStream over the standard handle for StdIn.
|
|
/// </summary>
|
|
/// <param name="nonKeyEvents">A BlockingCollection provider/consumer collection for collecting non key events.</param>
|
|
public ReadConsoleInputStream(BlockingCollection<Kernel32.INPUT_RECORD> nonKeyEvents) :
|
|
this(Kernel32.GetStdHandle(Kernel32.StdHandleType.STD_INPUT_HANDLE), nonKeyEvents)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an instance of ReadConsoleInputStream over a caller-provided standard handle for stdin.
|
|
/// </summary>
|
|
/// <param name="handle">A HFILE handle representing StdIn</param>
|
|
/// <param name="nonKeyEvents">A BlockingCollection provider/consumer collection for collecting non key events.</param>
|
|
internal ReadConsoleInputStream(HFILE handle,
|
|
BlockingCollection<Kernel32.INPUT_RECORD> nonKeyEvents)
|
|
{
|
|
Debug.Assert(!handle.IsInvalid, "handle.IsInvalid == false");
|
|
|
|
_handle = handle.DangerousGetHandle();
|
|
_nonKeyEvents = nonKeyEvents;
|
|
}
|
|
|
|
public override bool CanRead { get; } = true;
|
|
|
|
public override bool CanWrite => false;
|
|
|
|
public override bool CanSeek => false;
|
|
|
|
public override long Length => throw new NotSupportedException("Seek not supported.");
|
|
|
|
public override long Position
|
|
{
|
|
get => throw new NotSupportedException("Seek not supported.");
|
|
set => throw new NotSupportedException("Seek not supported.");
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
_handle = IntPtr.Zero;
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
ValidateRead(buffer, offset, count);
|
|
|
|
Debug.Assert(offset >= 0, "offset >= 0");
|
|
Debug.Assert(count >= 0, "count >= 0");
|
|
Debug.Assert(buffer != null, "bytes != null");
|
|
|
|
// Don't corrupt memory when multiple threads are erroneously writing
|
|
// to this stream simultaneously.
|
|
if (buffer.Length - offset < count)
|
|
throw new IndexOutOfRangeException("IndexOutOfRange_IORaceCondition");
|
|
|
|
int bytesRead;
|
|
int ret;
|
|
|
|
if (buffer.Length == 0)
|
|
{
|
|
bytesRead = 0;
|
|
ret = Win32Error.ERROR_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
var charsRead = 0;
|
|
bytesRead = 0;
|
|
|
|
var records = new Kernel32.INPUT_RECORD[BufferSize];
|
|
|
|
// begin input loop
|
|
do
|
|
{
|
|
var readSuccess = Kernel32.ReadConsoleInput(_handle, records, BufferSize, out var recordsRead);
|
|
Debug.WriteLine("Read {0} input record(s)", recordsRead);
|
|
|
|
// some of the arithmetic here is deliberately more explicit than it needs to be
|
|
// in order to show how 16-bit unicode WCHARs are packed into the buffer. The console
|
|
// subsystem is one of the last bastions of UCS-2, so until UTF-16 is fully adopted
|
|
// the two-byte character assumptions below will hold.
|
|
if (readSuccess && recordsRead > 0)
|
|
{
|
|
for (var index = 0; index < recordsRead; index++)
|
|
{
|
|
var record = records[index];
|
|
|
|
if (record.EventType == Kernel32.EVENT_TYPE.KEY_EVENT)
|
|
{
|
|
// skip key up events - if not, every key will be duped in the stream
|
|
if (!record.Event.KeyEvent.bKeyDown) continue;
|
|
|
|
// pack ucs-2/utf-16le/unicode chars into position in our byte[] buffer.
|
|
var glyph = (ushort) record.Event.KeyEvent.uChar;
|
|
|
|
var lsb = (byte) (glyph & 0xFFu);
|
|
var msb = (byte) ((glyph >> 8) & 0xFFu);
|
|
|
|
// ensure we accommodate key repeat counts
|
|
for (var n = 0; n < record.Event.KeyEvent.wRepeatCount; n++)
|
|
{
|
|
buffer[offset + charsRead * BytesPerWChar] = lsb;
|
|
buffer[offset + charsRead * BytesPerWChar + 1] = msb;
|
|
|
|
charsRead++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ignore focus events; not doing so makes debugging absolutely hilarious
|
|
// when breakpoints repeatedly cause focus events to occur as your view toggles
|
|
// between IDE and console.
|
|
if (record.EventType != Kernel32.EVENT_TYPE.FOCUS_EVENT)
|
|
{
|
|
// I assume success adding records - this is not so critical
|
|
// if it is critical to you, loop on this with a miniscule delay
|
|
_nonKeyEvents.TryAdd(record);
|
|
}
|
|
}
|
|
}
|
|
bytesRead = charsRead * BytesPerWChar;
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(bytesRead == 0, "bytesRead == 0");
|
|
}
|
|
|
|
} while (bytesRead == 0);
|
|
|
|
Debug.WriteLine("Read {0} character(s)", charsRead);
|
|
|
|
ret = Win32Error.ERROR_SUCCESS;
|
|
}
|
|
|
|
var errCode = ret;
|
|
if (Win32Error.ERROR_SUCCESS != errCode)
|
|
throw NativeMethods.GetExceptionForWin32Error(errCode);
|
|
|
|
return bytesRead;
|
|
}
|
|
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
throw new NotImplementedException("Write operations not implemented.");
|
|
}
|
|
|
|
public override void Flush()
|
|
{
|
|
throw new NotSupportedException("Flush/Write not supported.");
|
|
}
|
|
|
|
public override void SetLength(long value)
|
|
{
|
|
throw new NotSupportedException("Seek not supported.");
|
|
}
|
|
|
|
public override long Seek(long offset, SeekOrigin origin)
|
|
{
|
|
throw new NotSupportedException("Seek not supported.");
|
|
}
|
|
|
|
private void ValidateRead(byte[] buffer, int offset, int count)
|
|
{
|
|
if (buffer == null)
|
|
throw new ArgumentNullException(nameof(buffer));
|
|
if (offset < 0 || count < 0)
|
|
throw new ArgumentOutOfRangeException(offset < 0 ? nameof(offset) : nameof(count),
|
|
"offset or count cannot be negative numbers.");
|
|
if (buffer.Length - offset < count)
|
|
throw new ArgumentException("invalid offset length.");
|
|
|
|
if (!CanRead) throw new NotSupportedException("Get read not supported.");
|
|
}
|
|
}
|
|
} |