terminal/samples/ReadConsoleInputStream/ReadConsoleInputStream.cs
Dimitri Papadopoulos Orfanos 71c75561e5
Fix typos found by codespell (#12475)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request

Fix typos found by codespell. Some of it in documentation and user-visible text, mostly in code comments. While I understand you might not be interested in fixing code comments, one of the reasons being extra noise in git history, kindly note that most spell checking tools do not discriminate between documentation and code comments. So it's easier to fix everything for long maintenance.

<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> 
## References

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [ ] Closes #xxx
* [X] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed
* [X] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: [#501](https://github.com/MicrosoftDocs/terminal/pull/501)
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

I have checked and re-checked all changes.
2022-02-17 17:58:31 +00:00

199 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 minuscule 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.");
}
}
}