PowerShell/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs
PowerShell Team c748652c34 Copy all mapped files from [SD:725290]
commit 8cec8f150da7583b7af5efbe2853efee0179750c
2016-07-28 23:23:03 -07:00

3611 lines
145 KiB
C#

/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
// Implementation notes: In the functions that take ConsoleHandle parameters, we only assert that the handle is valid and not
// closed, as opposed to doing a check and throwing an exception. This is because the win32 APIs that those functions wrap will
// fail on invalid/closed handles, and the check for API failure will throw the exception.
//
// On the use of DangerousGetHandle: If the handle has been invalidated, then the API we pass it to will return an error. These
// handles should not be exposed to recycling attacks (because they are not exposed at all), but if they were, the worse they
// could do is diddle with the console buffer.
#pragma warning disable 1634, 1691
using System;
using System.Text;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.ComponentModel;
using System.Collections.Generic;
using System.Globalization;
using System.Diagnostics;
using Microsoft.Win32.SafeHandles;
using Dbg = System.Management.Automation.Diagnostics;
using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle;
using WORD = System.UInt16;
using ULONG = System.UInt32;
using DWORD = System.UInt32;
using NakedWin32Handle = System.IntPtr;
using HWND = System.IntPtr;
using HDC = System.IntPtr;
namespace Microsoft.PowerShell
{
/// <summary>
///
/// Class ConsoleControl is used to wrap the various win32 console APIs 1:1 (i.e. at a low level, without attempting to be a
/// "true" object-oriented library.
///
/// </summary>
internal static class ConsoleControl
{
#region structs
internal enum InputRecordEventTypes : ushort
{
// from wincon.h. These look like bit flags, but of course they could not really be used that way, since it would
// not make sense to have more than one of the INPUT_RECORD union members "in effect" at any one time.
KEY_EVENT = 0x0001,
MOUSE_EVENT = 0x0002,
WINDOW_BUFFER_SIZE_EVENT = 0x0004,
MENU_EVENT = 0x0008,
FOCUS_EVENT = 0x0010
}
[StructLayout(LayoutKind.Sequential)]
internal struct INPUT_RECORD
{
internal WORD EventType;
internal KEY_EVENT_RECORD KeyEvent;
}
[Flags]
internal enum ControlKeyStates : uint
{
// From wincon.h.
RIGHT_ALT_PRESSED = 0x0001, // the right alt key is pressed.
LEFT_ALT_PRESSED = 0x0002, // the left alt key is pressed.
RIGHT_CTRL_PRESSED = 0x0004, // the right ctrl key is pressed.
LEFT_CTRL_PRESSED = 0x0008, // the left ctrl key is pressed.
SHIFT_PRESSED = 0x0010, // the shift key is pressed.
NUMLOCK_ON = 0x0020, // the numlock light is on.
SCROLLLOCK_ON = 0x0040, // the scrolllock light is on.
CAPSLOCK_ON = 0x0080, // the capslock light is on.
ENHANCED_KEY = 0x0100 // the key is enhanced.
}
// LayoutKind must be Explicit
[StructLayout(LayoutKind.Sequential)]
internal struct KEY_EVENT_RECORD
{
internal bool KeyDown;
internal WORD RepeatCount;
internal WORD VirtualKeyCode;
internal WORD VirtualScanCode;
internal char UnicodeChar;
internal DWORD ControlKeyState;
}
[StructLayout(LayoutKind.Sequential)]
internal struct COORD
{
internal short X;
internal short Y;
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0},{1}", X, Y);
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct CONSOLE_READCONSOLE_CONTROL
{
// from public/internal/windows/inc/winconp.h
internal ULONG nLength;
internal ULONG nInitialChars;
internal ULONG dwCtrlWakeupMask;
internal /* out */ ULONG dwControlKeyState;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct CONSOLE_FONT_INFO_EX
{
internal int cbSize;
internal int nFont;
internal short FontWidth;
internal short FontHeight;
internal int FontFamily;
internal int FontWeight;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
internal string FontFace;
}
[StructLayout(LayoutKind.Sequential)]
internal struct CHAR_INFO
{
internal ushort UnicodeChar;
internal WORD Attributes;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SMALL_RECT
{
internal short Left;
internal short Top;
internal short Right;
internal short Bottom;
public override string ToString()
{
return String.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", Left, Top, Right, Bottom);
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct CONSOLE_SCREEN_BUFFER_INFO
{
internal COORD BufferSize;
internal COORD CursorPosition;
internal WORD Attributes;
internal SMALL_RECT WindowRect;
internal COORD MaxWindowSize;
// NTRAID#Windows Out Of Band Releases-938428-2006/07/17-jwh
// Bring the total size of the struct to 24 bytes.
internal DWORD Padding;
}
[StructLayout(LayoutKind.Sequential)]
internal struct CONSOLE_CURSOR_INFO
{
internal DWORD Size;
internal bool Visible;
public override string ToString()
{
return String.Format(CultureInfo.InvariantCulture, "Size: {0}, Visible: {1}", Size, Visible);
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct FONTSIGNATURE
{
//From public\sdk\inc\wingdi.h
// fsUsb*: A 128-bit Unicode subset bitfield (USB) identifying up to 126 Unicode subranges
internal DWORD fsUsb0;
internal DWORD fsUsb1;
internal DWORD fsUsb2;
internal DWORD fsUsb3;
// fsCsb*: A 64-bit, code-page bitfield (CPB) that identifies a specific character set or code page.
internal DWORD fsCsb0;
internal DWORD fsCsb1;
}
[StructLayout(LayoutKind.Sequential)]
internal struct CHARSETINFO
{
//From public\sdk\inc\wingdi.h
internal uint ciCharset; // Character set value.
internal uint ciACP; // ANSI code-page identifier.
internal FONTSIGNATURE fs;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct TEXTMETRIC
{
//From public\sdk\inc\wingdi.h
public int tmHeight;
public int tmAscent;
public int tmDescent;
public int tmInternalLeading;
public int tmExternalLeading;
public int tmAveCharWidth;
public int tmMaxCharWidth;
public int tmWeight;
public int tmOverhang;
public int tmDigitizedAspectX;
public int tmDigitizedAspectY;
public char tmFirstChar;
public char tmLastChar;
public char tmDefaultChar;
public char tmBreakChar;
public byte tmItalic;
public byte tmUnderlined;
public byte tmStruckOut;
public byte tmPitchAndFamily;
public byte tmCharSet;
}
#region SentInput Data Structures
[StructLayout(LayoutKind.Sequential)]
internal struct INPUT
{
internal DWORD Type;
internal MouseKeyboardHardwareInput Data;
}
[StructLayout(LayoutKind.Explicit)]
internal struct MouseKeyboardHardwareInput
{
[FieldOffset(0)]
internal MouseInput Mouse;
[FieldOffset(0)]
internal KeyboardInput Keyboard;
[FieldOffset(0)]
internal HardwareInput Hardware;
}
[StructLayout(LayoutKind.Sequential)]
internal struct MouseInput
{
/// <summary>
/// The absolute position of the mouse, or the amount of motion since the last mouse event was generated, depending on the value of the dwFlags member.
/// Absolute data is specified as the x coordinate of the mouse; relative data is specified as the number of pixels moved.
/// </summary>
internal Int32 X;
/// <summary>
/// The absolute position of the mouse, or the amount of motion since the last mouse event was generated, depending on the value of the dwFlags member.
/// Absolute data is specified as the y coordinate of the mouse; relative data is specified as the number of pixels moved.
/// </summary>
internal Int32 Y;
/// <summary>
/// If dwFlags contains MOUSEEVENTF_WHEEL, then mouseData specifies the amount of wheel movement. A positive value indicates that the wheel was rotated forward, away from the user;
/// a negative value indicates that the wheel was rotated backward, toward the user. One wheel click is defined as WHEEL_DELTA, which is 120.
/// </summary>
internal DWORD MouseData;
/// <summary>
/// A set of bit flags that specify various aspects of mouse motion and button clicks.
/// See (http://msdn.microsoft.com/en-us/library/ms646273(VS.85).aspx)
/// </summary>
internal DWORD Flags;
/// <summary>
/// The time stamp for the event, in milliseconds. If this parameter is 0, the system will provide its own time stamp.
/// </summary>
internal DWORD Time;
/// <summary>
/// An additional value associated with the mouse event. An application calls GetMessageExtraInfo to obtain this extra information
/// </summary>
internal IntPtr ExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct KeyboardInput
{
/// <summary>
/// A virtual-key code. The code must be a value in the range 1 to 254.
/// If the dwFlags member specifies KEYEVENTF_UNICODE, wVk must be 0.
/// </summary>
internal WORD Vk;
/// <summary>
/// A hardware scan code for the key. If dwFlags specifies KEYEVENTF_UNICODE,
/// wScan specifies a Unicode character which is to be sent to the foreground application.
/// </summary>
internal WORD Scan;
/// <summary>
/// Specifies various aspects of a keystroke.
/// This member can be certain combinations of the following values.
/// </summary>
internal DWORD Flags;
/// <summary>
/// The time stamp for the event, in milliseconds.
/// If this parameter is zero, the system will provide its own time stamp.
/// </summary>
internal DWORD Time;
/// <summary>
/// An additional value associated with the keystroke.
/// Use the GetMessageExtraInfo function to obtain this information.
/// </summary>
internal IntPtr ExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HardwareInput
{
/// <summary>
/// The message generated by the input hardware.
/// </summary>
internal DWORD Msg;
/// <summary>
/// The low-order word of the lParam parameter for uMsg.
/// </summary>
internal WORD ParamL;
/// <summary>
/// The high-order word of the lParam parameter for uMsg.
/// </summary>
internal WORD ParamH;
}
internal enum VirtualKeyCode : ushort
{
/// <summary>
/// LEFT ARROW key
/// </summary>
Left = 0x25,
/// <summary>
/// ENTER key
/// </summary>
Return = 0x0D,
}
/// <summary>
/// Specify the type of the input
/// </summary>
internal enum InputType : uint
{
/// <summary>
/// INPUT_MOUSE = 0x00
/// </summary>
Mouse = 0,
/// <summary>
/// INPUT_KEYBOARD = 0x01
/// </summary>
Keyboard = 1,
/// <summary>
/// INPUT_HARDWARE = 0x02
/// </summary>
Hardware = 2,
}
internal enum KeyboardFlag : uint
{
/// <summary>
/// If specified, the scan code was preceded by a prefix byte that has the value 0xE0 (224).
/// </summary>
ExtendedKey = 0x0001,
/// <summary>
/// If specified, the key is being released. If not specified, the key is being pressed.
/// </summary>
KeyUp = 0x0002,
/// <summary>
/// If specified, wScan identifies the key and wVk is ignored.
/// </summary>
Unicode = 0x0004,
/// <summary>
/// If specified, the system synthesizes a VK_PACKET keystroke. The wVk parameter must be zero.
/// This flag can only be combined with the KEYEVENTF_KEYUP flag.
/// </summary>
ScanCode = 0x0008
}
#endregion SentInput Data Structures
#endregion structs
#region Window Visibility
[DllImport(PinvokeDllNames.GetConsoleWindowDllName)]
internal static extern IntPtr GetConsoleWindow();
internal const int SW_HIDE = 0;
internal const int SW_SHOWNORMAL = 1;
internal const int SW_NORMAL = 1;
internal const int SW_SHOWMINIMIZED = 2;
internal const int SW_SHOWMAXIMIZED = 3;
internal const int SW_MAXIMIZE = 3;
internal const int SW_SHOWNOACTIVATE = 4;
internal const int SW_SHOW = 5;
internal const int SW_MINIMIZE = 6;
internal const int SW_SHOWMINNOACTIVE = 7;
internal const int SW_SHOWNA = 8;
internal const int SW_RESTORE = 9;
internal const int SW_SHOWDEFAULT = 10;
internal const int SW_FORCEMINIMIZE = 11;
internal const int SW_MAX = 11;
#if !CORECLR // ProcessWindowStyle does Not exist on CoreCLR
/// <summary>
/// Code to control the display properties of the a window...
/// </summary>
/// <param name="hWnd">The window to show...</param>
/// <param name="nCmdShow">The command to do</param>
/// <returns>true it it was successful</returns>
[DllImport("user32.dll")]
internal static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
internal static void SetConsoleMode(ProcessWindowStyle style)
{
IntPtr hwnd = GetConsoleWindow();
Dbg.Assert(hwnd != IntPtr.Zero, "Console handle should never be zero");
switch(style)
{
case ProcessWindowStyle.Hidden:
ShowWindow(hwnd, SW_HIDE);
break;
case ProcessWindowStyle.Maximized:
ShowWindow(hwnd, SW_MAXIMIZE);
break;
case ProcessWindowStyle.Minimized:
ShowWindow(hwnd, SW_MINIMIZE);
break;
case ProcessWindowStyle.Normal:
ShowWindow(hwnd, SW_NORMAL);
break;
}
}
#endif
#endregion
#region Input break handler (Ctrl-C, Ctrl-Break)
/// <summary>
///
/// Types of control ConsoleBreakSignals received by break Win32Handler delegates
///
/// </summary>
internal enum ConsoleBreakSignal : uint
{
// These correspond to the CRTL_XXX_EVENT #defines in public/sdk/inc/wincon.h
CtrlC = 0,
CtrlBreak = 1,
Close = 2,
Logoff = 5,
// This only gets received by services
Shutdown = 6,
// None is not really a signal -- it's used to indicate that no signal exists.
None = 0xFF
}
// NOTE: this delegate will be executed in its own thread
internal delegate bool BreakHandler(ConsoleBreakSignal ConsoleBreakSignal);
/// <summary>
/// Set the console's break handler
/// </summary>
/// <param name="handlerDelegate"></param>
/// <exception cref="HostException">
/// If Win32's SetConsoleCtrlHandler fails
/// </exception>
internal static void AddBreakHandler(BreakHandler handlerDelegate)
{
bool result = NativeMethods.SetConsoleCtrlHandler(handlerDelegate, true);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "AddBreakHandler",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.AddBreakHandlerExceptionMessage);
throw e;
}
}
/// <summary>
/// Set the console's break handler to null
/// </summary>
/// <exception cref="HostException">
/// If Win32's SetConsoleCtrlHandler fails
/// </exception>
internal static void RemoveBreakHandler()
{
bool result = NativeMethods.SetConsoleCtrlHandler(null, false);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "RemoveBreakHandler",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.RemoveBreakHandlerExceptionTemplate);
throw e;
}
}
#endregion
#region Win32Handles
static readonly Lazy<ConsoleHandle> _keyboardInputHandle = new Lazy<SafeFileHandle>(() =>
{
var handle = NativeMethods.CreateFile(
"CONIN$",
(UInt32)
(NativeMethods.AccessQualifiers.GenericRead | NativeMethods.AccessQualifiers.GenericWrite),
(UInt32)NativeMethods.ShareModes.ShareRead,
(IntPtr)0,
(UInt32)NativeMethods.CreationDisposition.OpenExisting,
0,
(IntPtr)0);
if (handle == NativeMethods.INVALID_HANDLE_VALUE)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "RetreiveInputConsoleHandle",
ErrorCategory.ResourceUnavailable,
ConsoleControlStrings.GetInputModeExceptionTemplate);
throw e;
}
return new ConsoleHandle(handle, true);
}
);
/// <summary>
/// Returns a ConsoleHandle to the console (keyboard device)
/// </summary>
internal static ConsoleHandle GetConioDeviceHandle()
{
return _keyboardInputHandle.Value;
}
static readonly Lazy<ConsoleHandle> _outputHandle = new Lazy<SafeFileHandle>(() =>
{
// We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
var handle = NativeMethods.CreateFile(
"CONOUT$",
(UInt32)(NativeMethods.AccessQualifiers.GenericRead | NativeMethods.AccessQualifiers.GenericWrite),
(UInt32)NativeMethods.ShareModes.ShareWrite,
(IntPtr)0,
(UInt32)NativeMethods.CreationDisposition.OpenExisting,
0,
(IntPtr)0);
if (handle == NativeMethods.INVALID_HANDLE_VALUE)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "RetreiveActiveScreenBufferConsoleHandle",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.GetActiveScreenBufferHandleExceptionTemplate);
throw e;
}
return new ConsoleHandle(handle, true);
}
);
/// <summary>
/// Returns a ConsoleHandle to the active screen buffer, even if that output has been redirected.
/// </summary>
/// <returns></returns>
/// <exception cref="HostException">
/// If Win32's CreateFile fails
/// </exception>
internal static ConsoleHandle GetActiveScreenBufferHandle()
{
return _outputHandle.Value;
}
#endregion
#region Mode
/// <summary>
///
/// flags used by ConsoleControl.GetMode and ConsoleControl.SetMode
///
/// </summary>
[Flags]
internal enum ConsoleModes : uint
{
// These values from wincon.h
// input modes
ProcessedInput = 0x001,
LineInput = 0x002,
EchoInput = 0x004,
WindowInput = 0x008,
MouseInput = 0x010,
Insert = 0x020,
QuickEdit = 0x040,
Extended = 0x080,
AutoPosition = 0x100,
// output modes
ProcessedOutput = 0x001, // yes, I know they are the same values as some flags defined above.
WrapEndOfLine = 0x002,
VirtualTerminal = 0x004,
}
/// <summary>
///
/// Returns a mask of ConsoleModes flags describing the current modality of the console
///
/// </summary>
/// <exception cref="HostException">
/// If Win32's GetConsoleMode fails
/// </exception>
internal static ConsoleModes GetMode(ConsoleHandle consoleHandle)
{
Dbg.Assert(!consoleHandle.IsInvalid, "consoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
UInt32 m = 0;
bool result = NativeMethods.GetConsoleMode(consoleHandle.DangerousGetHandle(), out m);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "GetConsoleMode",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.GetModeExceptionTemplate);
throw e;
}
return (ConsoleModes)m;
}
/// <summary>
///
/// Sets the current mode of the console device
///
/// </summary>
/// <param name="consoleHandle">
///
/// Handle to the console device returned by GetInputHandle
///
/// </param>
/// <param name="mode">
///
/// Mask of mode flags
///
/// </param>
/// <exception cref="HostException">
///
/// If Win32's SetConsoleMode fails
///
/// </exception>
internal static void SetMode(ConsoleHandle consoleHandle, ConsoleModes mode)
{
Dbg.Assert(!consoleHandle.IsInvalid, "consoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
bool result = NativeMethods.SetConsoleMode(consoleHandle.DangerousGetHandle(), (DWORD)mode);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "SetConsoleMode",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SetModeExceptionTemplate);
throw e;
}
}
#endregion
#region Input
/// <summary>
///
/// Reads input from the console device according to the mode in effect (see GetMode, SetMode)
///
/// </summary>
/// <param name="consoleHandle"></param>
///
/// Handle to the console device returned by GetInputHandle
///
/// <param name="initialContent">
///
/// Initial contents of the edit buffer, if any. charactersToRead should be at least as large as the length of this string.
///
/// </param>
/// <param name="charactersToRead">
///
/// Number of characters to read from the device.
///
/// </param>
/// <param name="endOnTab">
///
/// true to allow the user to terminate input by hitting the tab or shift-tab key, in addition to the enter key
///
/// </param>
/// <param name="keyState">
///
/// bit mask indicating the state of the control/shift keys at the point input was terminated.
///
/// </param>
/// <returns></returns>
/// <exception cref="HostException">
///
/// If Win32's ReadConsole fails
///
/// </exception>
internal static string ReadConsole(ConsoleHandle consoleHandle, string initialContent,
int charactersToRead, bool endOnTab, out uint keyState)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
Dbg.Assert(initialContent != null, "if no initial content is desired, pass String.Empty");
keyState = 0;
CONSOLE_READCONSOLE_CONTROL control = new CONSOLE_READCONSOLE_CONTROL();
control.nLength = (ULONG)Marshal.SizeOf(control);
control.nInitialChars = (ULONG)initialContent.Length;
control.dwControlKeyState = 0;
if (endOnTab)
{
const int TAB = 0x9;
control.dwCtrlWakeupMask = (1 << TAB);
}
DWORD charsReadUnused = 0;
StringBuilder buffer = new StringBuilder(initialContent, charactersToRead);
bool result =
NativeMethods.ReadConsole(
consoleHandle.DangerousGetHandle(),
buffer,
(DWORD)charactersToRead,
out charsReadUnused,
ref control);
keyState = control.dwControlKeyState;
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "ReadConsole",
ErrorCategory.ReadError, ConsoleControlStrings.ReadConsoleExceptionTemplate);
throw e;
}
if (charsReadUnused > (uint) buffer.Length)
charsReadUnused = (uint) buffer.Length;
return buffer.ToString(0, (int) charsReadUnused);
}
/// <summary>
/// Wraps Win32 ReadConsoleInput.
/// Returns the number of records read in buffer.
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where input is read
///
/// </param>
/// <param name="buffer">
///
/// array where data read are stored
///
/// </param>
/// <returns>
///
/// actual number of input records read
///
/// </returns>
/// <exception cref="HostException">
/// If Win32's ReadConsoleInput fails
/// </exception>
internal static int ReadConsoleInput(ConsoleHandle consoleHandle, ref INPUT_RECORD[] buffer)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
DWORD recordsRead = 0;
bool result =
NativeMethods.ReadConsoleInput(
consoleHandle.DangerousGetHandle(),
buffer,
(DWORD)buffer.Length,
out recordsRead);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "ReadConsoleInput",
ErrorCategory.ReadError, ConsoleControlStrings.ReadConsoleInputExceptionTemplate);
throw e;
}
return (int)recordsRead;
}
/// <summary>
/// Wraps Win32 PeekConsoleInput
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where input is peeked
///
/// </param>
/// <param name="buffer">
///
/// array where data read are stored
///
/// </param>
/// <returns>
///
/// acutal number of input records peeked
///
/// </returns>
/// <exception cref="HostException">
/// If Win32's PeekConsoleInput fails
/// </exception>
internal static int PeekConsoleInput
(
ConsoleHandle consoleHandle,
ref INPUT_RECORD[] buffer
)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
DWORD recordsRead;
bool result =
NativeMethods.PeekConsoleInput(
consoleHandle.DangerousGetHandle(),
buffer,
(DWORD)buffer.Length,
out recordsRead);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "PeekConsoleInput",
ErrorCategory.ReadError, ConsoleControlStrings.PeekConsoleInputExceptionTemplate);
throw e;
}
return (int)recordsRead;
}
/// <summary>
/// Wraps Win32 GetNumberOfConsoleInputEvents
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where the number of console input events is obtained
///
/// </param>
/// <returns>
///
/// number of console input events
///
/// </returns>
/// <exception cref="HostException">
/// If Win32's GetNumberOfConsoleInputEvents fails
/// </exception>
internal static int GetNumberOfConsoleInputEvents(ConsoleHandle consoleHandle)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
DWORD numEvents;
bool result = NativeMethods.GetNumberOfConsoleInputEvents(consoleHandle.DangerousGetHandle(), out numEvents);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "GetNumberOfConsoleInputEvents",
ErrorCategory.ReadError, ConsoleControlStrings.GetNumberOfConsoleInputEventsExceptionTemplate);
throw e;
}
return (int)numEvents;
}
/// <summary>
/// Wraps Win32 FlushConsoleInputBuffer
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where the input buffer is flushed
///
/// </param>
/// <exception cref="HostException">
/// If Win32's FlushConsoleInputBuffer fails
/// </exception>
internal static void FlushConsoleInputBuffer(ConsoleHandle consoleHandle)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
bool result = false;
NakedWin32Handle h = consoleHandle.DangerousGetHandle();
result = NativeMethods.FlushConsoleInputBuffer(h);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "FlushConsoleInputBuffer",
ErrorCategory.ReadError, ConsoleControlStrings.FlushConsoleInputBufferExceptionTemplate);
throw e;
}
}
#endregion Input
#region Buffer
/// <summary>
/// Wraps Win32 GetConsoleScreenBufferInfo
/// Returns Console Screen Buffer Info
/// </summary>
/// <param name="consoleHandle">
///
/// Handle for the console where the screen buffer info is obtained
///
/// </param>
/// <returns>
///
/// info about the screen buffer. See the definition of CONSOLE_SCREEN_BUFFER_INFO
///
/// </returns>
/// <exception cref="HostException">
/// If Win32's GetConsoleScreenBufferInfo fails
/// </exception>
internal static CONSOLE_SCREEN_BUFFER_INFO GetConsoleScreenBufferInfo(ConsoleHandle consoleHandle)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
bool result = NativeMethods.GetConsoleScreenBufferInfo(consoleHandle.DangerousGetHandle(), out bufferInfo);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "GetConsoleScreenBufferInfo",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.GetConsoleScreenBufferInfoExceptionTemplate);
throw e;
}
return bufferInfo;
}
/// <summary>
/// set the output buffer's size
/// </summary>
/// <param name="consoleHandle"></param>
/// <param name="newSize"></param>
/// <exception cref="HostException">
/// If Win32's SetConsoleScreenBufferSize fails
/// </exception>
internal static void SetConsoleScreenBufferSize(ConsoleHandle consoleHandle, Size newSize)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
COORD s;
s.X = (short)newSize.Width;
s.Y = (short)newSize.Height;
bool result = NativeMethods.SetConsoleScreenBufferSize(consoleHandle.DangerousGetHandle(), s);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "SetConsoleScreenBufferSize",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SetConsoleScreenBufferSizeExceptionTemplate);
throw e;
}
}
internal static bool IsConsoleColor(ConsoleColor c)
{
switch (c)
{
case ConsoleColor.Black:
case ConsoleColor.Blue:
case ConsoleColor.Cyan:
case ConsoleColor.DarkBlue:
case ConsoleColor.DarkCyan:
case ConsoleColor.DarkGray:
case ConsoleColor.DarkGreen:
case ConsoleColor.DarkMagenta:
case ConsoleColor.DarkRed:
case ConsoleColor.DarkYellow:
case ConsoleColor.Gray:
case ConsoleColor.Green:
case ConsoleColor.Magenta:
case ConsoleColor.Red:
case ConsoleColor.White:
case ConsoleColor.Yellow:
return true;
}
return false;
}
internal static void WORDToColor(WORD attribute, out ConsoleColor foreground, out ConsoleColor background)
{
// foreground color is the low-byte in the word, background color is the hi-byte.
foreground = (ConsoleColor)(attribute & 0x0f);
background = (ConsoleColor)((attribute & 0xf0) >> 4);
Dbg.Assert(IsConsoleColor(foreground), "unknown color");
Dbg.Assert(IsConsoleColor(background), "unknown color");
}
internal static WORD ColorToWORD(ConsoleColor foreground, ConsoleColor background)
{
WORD result = (WORD)(((int)background << 4) | (int)foreground);
return result;
}
/// <summary>
/// Wrap32 WriteConsoleOutput.
/// This wrapper is not limited to 64K or 8K CHAR_INFO to which Win32's WriteConsoleOutput
/// is constrained.
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where output is written
///
/// </param>
/// <param name="origin">
///
/// location on screen buffer where writing starts
///
/// </param>
/// <param name="contents">
///
/// 2D array of cells. Caller needs to ensure that the array is 2D.
///
/// </param>
/// <exception cref="HostException">
/// If Win32's GetConsoleScreenBufferInfo fails
/// If there is not enough memory to complete calls to Win32's WriteConsoleOutput
/// </exception>
/// <exception cref="ArgumentNullException">
/// If <paramref name="contents"/> is null
/// </exception>
/// <exception cref="ArgumentException">
/// If it is illegal to write <paramref name="contents"/> to the output buffer
/// </exception>
internal static void WriteConsoleOutput(ConsoleHandle consoleHandle, Coordinates origin, BufferCell[,] contents)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
if (contents == null)
{
throw PSTraceSource.NewArgumentNullException("contents");
}
uint codePage;
if (IsCJKOutputCodePage(out codePage))
{
// contentsRegion indicates the area in contents (declared below) in which
// the data read from ReadConsoleOutput is stored.
Rectangle contentsRegion = new Rectangle();
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo =
GetConsoleScreenBufferInfo(consoleHandle);
int bufferWidth = bufferInfo.BufferSize.X;
int bufferHeight = bufferInfo.BufferSize.Y;
Rectangle screenRegion = new Rectangle(
origin.X, origin.Y,
Math.Min(origin.X + contents.GetLength(1) - 1, bufferWidth - 1),
Math.Min(origin.Y + contents.GetLength(0) - 1, bufferHeight - 1));
contentsRegion.Left = contents.GetLowerBound(1);
contentsRegion.Top = contents.GetLowerBound(0);
contentsRegion.Right = contentsRegion.Left +
screenRegion.Right - screenRegion.Left;
contentsRegion.Bottom = contentsRegion.Top +
screenRegion.Bottom - screenRegion.Top;
#if DEBUG
//Check contents in contentsRegion
CheckWriteConsoleOutputContents(contents, contentsRegion);
#endif
//Identify edges and areas of identical contiguous edges in contentsRegion
List<BufferCellArrayRowTypeRange> sameEdgeAreas = new List<BufferCellArrayRowTypeRange>();
int firstLeftTrailingRow = -1, firstRightLeadingRow = -1;
BuildEdgeTypeInfo(contentsRegion, contents,
sameEdgeAreas, out firstLeftTrailingRow, out firstRightLeadingRow);
#if DEBUG
CheckWriteEdges(consoleHandle, codePage, origin, contents, contentsRegion,
bufferInfo, firstLeftTrailingRow, firstRightLeadingRow);
#endif
foreach (BufferCellArrayRowTypeRange area in sameEdgeAreas)
{
Coordinates o = new Coordinates(origin.X,
origin.Y + area.Start - contentsRegion.Top);
Rectangle contRegion = new Rectangle(
contentsRegion.Left, area.Start, contentsRegion.Right, area.End);
if ((area.Type & BufferCellArrayRowType.LeftTrailing) != 0)
{
contRegion.Left++;
o.X++;
if (o.X >= bufferWidth || contRegion.Right < contRegion.Left)
{
return;
}
}
WriteConsoleOutputCJK(consoleHandle, o, contRegion, contents, area.Type);
}
}
else
{
WriteConsoleOutputPlain(consoleHandle, origin, contents);
}
}
private static void BuildEdgeTypeInfo(
Rectangle contentsRegion,
BufferCell[,] contents,
List<BufferCellArrayRowTypeRange> sameEdgeAreas,
out int firstLeftTrailingRow,
out int firstRightLeadingRow)
{
firstLeftTrailingRow = -1;
firstRightLeadingRow = -1;
BufferCellArrayRowType edgeType =
GetEdgeType(contents[contentsRegion.Top, contentsRegion.Left],
contents[contentsRegion.Top, contentsRegion.Right]);
for (int r = contentsRegion.Top; r <= contentsRegion.Bottom; )
{
BufferCellArrayRowTypeRange range;
range.Start = r;
range.Type = edgeType;
if (firstLeftTrailingRow == -1 && ((range.Type & BufferCellArrayRowType.LeftTrailing) != 0))
{
firstLeftTrailingRow = r;
}
if (firstRightLeadingRow == -1 && ((range.Type & BufferCellArrayRowType.RightLeading) != 0))
{
firstRightLeadingRow = r;
}
for (; ; )
{
r++;
if (r > contentsRegion.Bottom)
{
range.End = r - 1;
sameEdgeAreas.Add(range);
return;
}
edgeType = GetEdgeType(contents[r, contentsRegion.Left], contents[r, contentsRegion.Right]);
if (edgeType != range.Type)
{
range.End = r - 1;
sameEdgeAreas.Add(range);
break;
}
}
}
}
private static BufferCellArrayRowType GetEdgeType(BufferCell left, BufferCell right)
{
BufferCellArrayRowType edgeType = 0;
if (left.BufferCellType == BufferCellType.Trailing)
{
edgeType |= BufferCellArrayRowType.LeftTrailing;
}
if (right.BufferCellType == BufferCellType.Leading)
{
edgeType |= BufferCellArrayRowType.RightLeading;
}
return edgeType;
}
private struct BufferCellArrayRowTypeRange
{
internal int Start;
internal int End;
internal BufferCellArrayRowType Type;
}
[Flags]
private enum BufferCellArrayRowType : uint
{
LeftTrailing = 0x1,
RightLeading = 0x2
}
/// <summary>
/// Check the existing screen columns left and right of areas to be written
/// </summary>
/// <param name="consoleHandle"></param>
/// <param name="codePage"></param>
/// <param name="origin">must be within the screen buffer</param>
/// <param name="contents"></param>
/// <param name="contentsRegion"></param>
/// <param name="bufferInfo"></param>
/// <param name="firstLeftTrailingRow"></param>
/// <param name="firstRightLeadingRow"></param>
/// <exception cref="ArgumentException">
/// If it is illegal to write <paramref name="contents"/> at <paramref name="origin"/>
/// </exception>
/// <exception cref="HostException">
/// If there is not enough memory to complete calls to Win32's ReadConsoleOutput
/// </exception>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called in CHK builds")]
internal static void CheckWriteEdges(
ConsoleHandle consoleHandle,
uint codePage, Coordinates origin,
BufferCell[,] contents,
Rectangle contentsRegion,
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo,
int firstLeftTrailingRow,
int firstRightLeadingRow)
{
Rectangle existingRegion = new Rectangle(0, 0, 1, contentsRegion.Bottom - contentsRegion.Top);
if (origin.X == 0)
{
if (firstLeftTrailingRow >= 0)
{
throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]",
firstLeftTrailingRow, contentsRegion.Left));
}
}
else
{
// use ReadConsoleOutputCJK because checking the left and right edges of the existing output
// is NOT needed
BufferCell[,] leftExisting = new BufferCell[existingRegion.Bottom + 1, 2];
ReadConsoleOutputCJK(consoleHandle, codePage,
new Coordinates(origin.X - 1, origin.Y), existingRegion, ref leftExisting);
for (int r = contentsRegion.Top, i = 0; r <= contentsRegion.Bottom; r++, i++)
{
if (leftExisting[r, 0].BufferCellType == BufferCellType.Leading ^
contents[r, contentsRegion.Left].BufferCellType == BufferCellType.Trailing)
{
throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]",
r, contentsRegion.Left));
}
}
}
//Check right edge
if (origin.X + (contentsRegion.Right - contentsRegion.Left) + 1 >= bufferInfo.BufferSize.X)
{
if (firstRightLeadingRow >= 0)
{
throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]",
firstRightLeadingRow, contentsRegion.Right));
}
}
else
{
// use ReadConsoleOutputCJK becaue checking the left and right edges of the existing output
// is NOT needed
BufferCell[,] rightExisting = new BufferCell[existingRegion.Bottom + 1, 2];
ReadConsoleOutputCJK(consoleHandle, codePage,
new Coordinates(origin.X + (contentsRegion.Right - contentsRegion.Left), origin.Y), existingRegion, ref rightExisting);
for (int r = contentsRegion.Top, i = 0; r <= contentsRegion.Bottom; r++, i++)
{
if (rightExisting[r, 0].BufferCellType == BufferCellType.Leading ^
contents[r, contentsRegion.Right].BufferCellType == BufferCellType.Leading)
{
throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]",
r, contentsRegion.Right));
}
}
}
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called in CHK builds")]
private static void CheckWriteConsoleOutputContents(BufferCell[,] contents, Rectangle contentsRegion)
{
for (int r = contentsRegion.Top; r <= contentsRegion.Bottom; r++)
{
for (int c = contentsRegion.Left; c <= contentsRegion.Right; c++)
{
// Changes have been made in the following code such that 2 cell characters
// (Chinese, Japanese or Korean) can be in a single BufferCell structure
// which is complete
if (contents[r, c].BufferCellType == BufferCellType.Trailing &&
contents[r, c].Character != 0)
{
// trailing character is not 0
throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]", r, c));
}
if (contents[r, c].BufferCellType == BufferCellType.Leading)
{
c++;
if (c > contentsRegion.Right)
{
break;
}
if (contents[r, c].Character != 0 || contents[r, c].BufferCellType != BufferCellType.Trailing)
{
// for a 2 cell character, either there is no trailing BufferCell or
// the trailing BufferCell's character is not 0
throw PSTraceSource.NewArgumentException(string.Format(CultureInfo.InvariantCulture, "contents[{0}, {1}]", r, c));
}
}
}
}
}
private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinates origin, Rectangle contentsRegion, BufferCell[,] contents, BufferCellArrayRowType rowType)
{
Dbg.Assert(origin.X >= 0 && origin.Y >= 0,
"origin must be within the output buffer");
int rows = contentsRegion.Bottom - contentsRegion.Top + 1;
int cols = contentsRegion.Right - contentsRegion.Left + 1;
CONSOLE_FONT_INFO_EX fontInfo = GetConsoleFontInfo(consoleHandle);
int fontType = fontInfo.FontFamily & NativeMethods.FontTypeMask;
bool trueTypeInUse = (fontType & NativeMethods.TrueTypeFont) == NativeMethods.TrueTypeFont;
int bufferLimit = 2 * 1024; // Limit is 8K bytes as each CHAR_INFO takes 4 bytes
COORD bufferCoord;
bufferCoord.X = 0;
bufferCoord.Y = 0;
// keeps track of which screen area write
SMALL_RECT writeRegion;
writeRegion.Top = (short)origin.Y;
int rowsRemaining = rows;
while (rowsRemaining > 0)
{
// Iteration of columns is nested inside iteration of rows.
// If the size of contents exceeds the buffer limit, writing is
// done in blocks of size equal to the bufferlimit from left to right
// then top to bottom.
// For each iteration of rows,
// - writeRegion.Left and bufferSize.X are reset
// - rowsRemaining, writeRegion.Top, writeRegion.Bottom, and bufferSize.Y
// are updated
// For each iteration of columns,
// - writeRegion.Left, writeRegion.Right and bufferSize.X are updated
writeRegion.Left = (short)origin.X;
COORD bufferSize;
bufferSize.X = (short)Math.Min(cols, bufferLimit);
bufferSize.Y = (short)Math.Min
(
rowsRemaining,
bufferLimit / bufferSize.X
);
writeRegion.Bottom = (short)(writeRegion.Top + bufferSize.Y - 1);
// atRow is at which row of contents a particular iteration is operating
int atRow = rows - rowsRemaining + contentsRegion.Top;
// number of columns yet to be written
int colsRemaining = cols;
while (colsRemaining > 0)
{
writeRegion.Right = (short)(writeRegion.Left + bufferSize.X - 1);
// atCol is at which column of contents a particular iteration is operating
int atCol = cols - colsRemaining + contentsRegion.Left;
// if this is not the last column iteration &&
// the leftmost BufferCell is a leading cell, don't write that cell
if (colsRemaining > bufferSize.X &&
contents[atRow, atCol + bufferSize.X - 1].BufferCellType == BufferCellType.Leading)
{
bufferSize.X--;
writeRegion.Right--;
}
CHAR_INFO[] characterBuffer = new CHAR_INFO[bufferSize.Y * bufferSize.X];
// copy characterBuffer to contents;
int characterBufferIndex = 0;
bool lastCharIsLeading = false;
BufferCell lastLeadingCell = new BufferCell();
for (int r = atRow; r < bufferSize.Y + atRow; r++)
{
for (int c = atCol; c < bufferSize.X + atCol; c++, characterBufferIndex++)
{
if (contents[r, c].BufferCellType == BufferCellType.Complete)
{
characterBuffer[characterBufferIndex].UnicodeChar =
(ushort)contents[r, c].Character;
characterBuffer[characterBufferIndex].Attributes =
(ushort)(ColorToWORD(contents[r, c].ForegroundColor, contents[r, c].BackgroundColor));
lastCharIsLeading = false;
}
else if (contents[r, c].BufferCellType == BufferCellType.Leading)
{
characterBuffer[characterBufferIndex].UnicodeChar =
(ushort)contents[r, c].Character;
characterBuffer[characterBufferIndex].Attributes =
(ushort)(ColorToWORD(contents[r, c].ForegroundColor, contents[r, c].BackgroundColor)
| (ushort)NativeMethods.CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE);
lastCharIsLeading = true;
lastLeadingCell = contents[r, c];
}
else if (contents[r, c].BufferCellType == BufferCellType.Trailing)
{
// The FontFamily is a 8-bit integer. The low-order bit (bit 0) specifies the pitch of the font.
// If it is 1, the font is variable pitch (or proportional). If it is 0, the font is fixed pitch
// (or monospace). Bits 1 and 2 specify the font type. If both bits are 0, the font is a raster font;
// if bit 1 is 1 and bit 2 is 0, the font is a vector font; if bit 1 is 0 and bit 2 is set, or if both
// bits are 1, the font is true type. Bit 3 is 1 if the font is a device font; otherwise, it is 0.
// We only care about the bit 1 and 2, which indicate the font type.
// There are only two font type defined for the Console, at
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\.
// Console\Nls --- national language supports
// Console\RasterFonts --- raster type font
// Console\TrueTypeFont --- true type font
// For CJK characters, if it's TrueType, we need to output the trailing character marked with "Trailing_byte"
// attribute. But if it's RasterFont, we ignore the trailing character, and the "Leading_byte"/"Trailing_byte"
// attributes are not effective at all when reading the character from the console buffer.
if (lastCharIsLeading && trueTypeInUse)
{
// For TrueType Font, we output the trailing byte with "Trailing_byte" attribute
characterBuffer[characterBufferIndex].UnicodeChar = lastLeadingCell.Character;
characterBuffer[characterBufferIndex].Attributes =
(ushort)(ColorToWORD(contents[r, c].ForegroundColor, contents[r, c].BackgroundColor)
| (ushort)NativeMethods.CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE);
}
else
{
// We don't output anything for this cell if Raster font is in use, or if the last cell is not a leading byte
characterBufferIndex--;
}
lastCharIsLeading = false;
}
}
}
// Now writeRegion, bufferSize and characterBuffer are updated.
// Call NativeMethods.WriteConsoleOutput
bool result;
if ((rowType & BufferCellArrayRowType.RightLeading) != 0 &&
colsRemaining == bufferSize.X)
{
COORD bSize = bufferSize;
bSize.X++;
SMALL_RECT wRegion = writeRegion;
wRegion.Right++;
// Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
// get the error code.
#pragma warning disable 56523
result = NativeMethods.WriteConsoleOutput(
consoleHandle.DangerousGetHandle(),
characterBuffer,
bSize,
bufferCoord,
ref wRegion);
}
else
{
// Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
// get the error code.
#pragma warning disable 56523
result = NativeMethods.WriteConsoleOutput(
consoleHandle.DangerousGetHandle(),
characterBuffer,
bufferSize,
bufferCoord,
ref writeRegion);
}
if (result == false)
{
// When WriteConsoleOutput fails, half bufferLimit
if (bufferLimit < 2)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "WriteConsoleOutput",
ErrorCategory.WriteError, ConsoleControlStrings.WriteConsoleOutputExceptionTemplate);
throw e;
}
bufferLimit /= 2;
if (cols == colsRemaining)
{
// if cols == colsRemaining, nothing is guaranteed written in this pass and
// the unwritten area is still rectangular
bufferSize.Y = 0;
break;
}
else
{
// some areas have been written. This could only happen when the number of columns
// to write is larger than bufferLimit. In that case, the algorithm writes one row
// at a time => bufferSize.Y == 1. Then, we can safely leave bufferSize.Y unchanged
// to retry with a smaller bufferSize.X.
Dbg.Assert(bufferSize.Y == 1, string.Format(CultureInfo.InvariantCulture, "bufferSize.Y should be 1, but is {0}", bufferSize.Y));
bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit);
continue;
}
}
colsRemaining -= bufferSize.X;
writeRegion.Left += bufferSize.X;
bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit);
} // column iteration
rowsRemaining -= bufferSize.Y;
writeRegion.Top += bufferSize.Y;
} // row iteration
}
private static void WriteConsoleOutputPlain(ConsoleHandle consoleHandle, Coordinates origin, BufferCell[,] contents)
{
int rows = contents.GetLength(0);
int cols = contents.GetLength(1);
if ((rows <= 0) || cols <= 0)
{
tracer.WriteLine("contents passed in has 0 rows and columns");
return;
}
int bufferLimit = 2 * 1024; // Limit is 8K bytes as each CHAR_INFO takes 4 bytes
COORD bufferCoord;
bufferCoord.X = 0;
bufferCoord.Y = 0;
// keeps track of which screen area write
SMALL_RECT writeRegion;
writeRegion.Top = (short)origin.Y;
int rowsRemaining = rows;
while (rowsRemaining > 0)
{
// Iteration of columns is nested inside iteration of rows.
// If the size of contents exceeds the buffer limit, writing is
// done in blocks of size equal to the bufferlimit from left to right
// then top to bottom.
// For each iteration of rows,
// - writeRegion.Left and bufferSize.X are reset
// - rowsRemaining, writeRegion.Top, writeRegion.Bottom, and bufferSize.Y
// are updated
// For each iteration of columns,
// - writeRegion.Left, writeRegion.Right and bufferSize.X are updated
writeRegion.Left = (short)origin.X;
COORD bufferSize;
bufferSize.X = (short)Math.Min(cols, bufferLimit);
bufferSize.Y = (short)Math.Min
(
rowsRemaining,
bufferLimit / bufferSize.X
);
writeRegion.Bottom = (short)(writeRegion.Top + bufferSize.Y - 1);
// atRow is at which row of contents a particular iteration is operating
int atRow = rows - rowsRemaining + contents.GetLowerBound(0);
// number of columns yet to be written
int colsRemaining = cols;
while (colsRemaining > 0)
{
writeRegion.Right = (short)(writeRegion.Left + bufferSize.X - 1);
// atCol is at which column of contents a particular iteration is operating
int atCol = cols - colsRemaining + contents.GetLowerBound(1);
CHAR_INFO[] characterBuffer = new CHAR_INFO[bufferSize.Y * bufferSize.X];
// copy characterBuffer to contents;
for (int r = atRow, characterBufferIndex = 0;
r < bufferSize.Y + atRow; r++)
{
for (int c = atCol; c < bufferSize.X + atCol; c++, characterBufferIndex++)
{
characterBuffer[characterBufferIndex].UnicodeChar =
(ushort)contents[r, c].Character;
characterBuffer[characterBufferIndex].Attributes =
ColorToWORD(contents[r, c].ForegroundColor, contents[r, c].BackgroundColor);
}
}
// Now writeRegion, bufferSize and characterBuffer are updated.
// Call NativeMethods.WriteConsoleOutput
bool result =
NativeMethods.WriteConsoleOutput(
consoleHandle.DangerousGetHandle(),
characterBuffer,
bufferSize,
bufferCoord,
ref writeRegion);
if (result == false)
{
// When WriteConsoleOutput fails, half bufferLimit
if (bufferLimit < 2)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "WriteConsoleOutput",
ErrorCategory.WriteError, ConsoleControlStrings.WriteConsoleOutputExceptionTemplate);
throw e;
}
bufferLimit /= 2;
if (cols == colsRemaining)
{
// if cols == colsRemaining, nothing is guaranteed written in this pass and
// the unwritten area is still rectangular
bufferSize.Y = 0;
break;
}
else
{
// some areas have been written. This could only happen when the number of columns
// to write is larger than bufferLimit. In that case, the algorithm writes one row
// at a time => bufferSize.Y == 1. Then, we can safely leave bufferSize.Y unchanged
// to retry with a smaller bufferSize.X.
Dbg.Assert(bufferSize.Y == 1, string.Format(CultureInfo.InvariantCulture, "bufferSize.Y should be 1, but is {0}", bufferSize.Y));
bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit);
continue;
}
}
colsRemaining -= bufferSize.X;
writeRegion.Left += bufferSize.X;
bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit);
} // column iteration
rowsRemaining -= bufferSize.Y;
writeRegion.Top += bufferSize.Y;
} // row iteration
}
/// <summary>
/// Wrap32 ReadConsoleOutput
/// This wrapper is not limited to 64K or 8K CHAR_INFO to which Win32's ReadConsoleOutput
/// is constrained.
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where output is read
///
/// </param>
/// <param name="origin">
///
/// location on screen buffer where reading begins
///
/// </param>
/// <param name="contentsRegion">
///
/// indicates the area in <paramref name="contents"/> where the data read
/// is stored.
///
/// </param>
/// <param name="contents">
///
/// this is ref because the bounds and size of the array are needed.
///
/// </param>
/// <exception cref="HostException">
/// If there is not enough memory to complete calls to Win32's ReadConsoleOutput
/// </exception>
internal static void ReadConsoleOutput
(
ConsoleHandle consoleHandle,
Coordinates origin,
Rectangle contentsRegion,
ref BufferCell[,] contents
)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
uint codePage;
if (IsCJKOutputCodePage(out codePage))
{
ReadConsoleOutputCJK(consoleHandle, codePage, origin, contentsRegion, ref contents);
// check left edge
BufferCell[,] cellArray = null;
Coordinates checkOrigin;
Rectangle cellArrayRegion = new Rectangle(0, 0, 1, contentsRegion.Bottom - contentsRegion.Top);
if (origin.X > 0 && ShouldCheck(contentsRegion.Left, contents, contentsRegion))
{
cellArray = new BufferCell[cellArrayRegion.Bottom + 1, 2];
checkOrigin = new Coordinates(origin.X - 1, origin.Y);
ReadConsoleOutputCJK(consoleHandle, codePage, checkOrigin,
cellArrayRegion, ref cellArray);
for (int i = 0; i <= cellArrayRegion.Bottom; i++)
{
if (cellArray[i, 0].BufferCellType == BufferCellType.Leading)
{
contents[contentsRegion.Top + i, 0].Character = (char)0;
contents[contentsRegion.Top + i, 0].BufferCellType = BufferCellType.Trailing;
}
}
}
// check right edge
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo =
GetConsoleScreenBufferInfo(consoleHandle);
if (origin.X + (contentsRegion.Right - contentsRegion.Left) + 1 < bufferInfo.BufferSize.X &&
ShouldCheck(contentsRegion.Right, contents, contentsRegion))
{
if (cellArray == null)
{
cellArray = new BufferCell[cellArrayRegion.Bottom + 1, 2];
}
checkOrigin = new Coordinates(origin.X +
(contentsRegion.Right - contentsRegion.Left), origin.Y);
ReadConsoleOutputCJK(consoleHandle, codePage, checkOrigin,
cellArrayRegion, ref cellArray);
for (int i = 0; i <= cellArrayRegion.Bottom; i++)
{
if (cellArray[i, 0].BufferCellType == BufferCellType.Leading)
{
contents[contentsRegion.Top + i, contentsRegion.Right] = cellArray[i, 0];
}
}
}
}
else
{
ReadConsoleOutputPlain(consoleHandle, origin, contentsRegion, ref contents);
}
}
#region ReadConsoleOutput CJK
/// <summary>
/// If an edge cell read is a blank, it is potentially part of a double width character. Hence,
/// at least one of the left and right edges should be checked
/// </summary>
/// <param name="edge"></param>
/// <param name="contents"></param>
/// <param name="contentsRegion"></param>
/// <returns></returns>
private static bool ShouldCheck(int edge, BufferCell[,] contents, Rectangle contentsRegion)
{
for (int i = contentsRegion.Top; i <= contentsRegion.Bottom; i++)
{
if (contents[i, edge].Character == ' ')
{
return true;
}
}
return false;
}
private static bool ReadConsoleOutputCJKSmall
(
ConsoleHandle consoleHandle,
uint codePage,
Coordinates origin,
Rectangle contentsRegion,
ref BufferCell[,] contents
)
{
COORD bufferSize;
bufferSize.X = (short)(contentsRegion.Right - contentsRegion.Left + 1);
bufferSize.Y = (short)(contentsRegion.Bottom - contentsRegion.Top + 1);
COORD bufferCoord;
bufferCoord.X = 0;
bufferCoord.Y = 0;
CHAR_INFO[] characterBuffer = new CHAR_INFO[bufferSize.X * bufferSize.Y];
SMALL_RECT readRegion;
readRegion.Left = (short)origin.X;
readRegion.Top = (short)origin.Y;
readRegion.Right = (short)(origin.X + bufferSize.X - 1);
readRegion.Bottom = (short)(origin.Y + bufferSize.Y - 1);
// Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
// get the error code.
#pragma warning disable 56523
bool result = NativeMethods.ReadConsoleOutput(
consoleHandle.DangerousGetHandle(),
characterBuffer,
bufferSize,
bufferCoord,
ref readRegion);
if (!result)
{
return false;
}
int characterBufferIndex = 0;
for (int r = contentsRegion.Top; r <= contentsRegion.Bottom; r++)
{
for (int c = contentsRegion.Left; c <= contentsRegion.Right; c++, characterBufferIndex++)
{
ConsoleColor fgColor, bgColor;
contents[r, c].Character = (char)characterBuffer[characterBufferIndex].UnicodeChar;
WORDToColor(characterBuffer[characterBufferIndex].Attributes,
out fgColor,
out bgColor);
contents[r, c].ForegroundColor = fgColor;
contents[r, c].BackgroundColor = bgColor;
// Set the attributes of the buffercells to be the same as that of the
// incoming CHAR_INFO. In case where the CHAR_INFO character is a
// trailing byte set the Character of BufferCell to 0. This is done
// because at a lot of places this check is being done. Having a trailing
// character to be 0 is by design.
if ((characterBuffer[characterBufferIndex].Attributes & (ushort)NativeMethods.CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE)
== (ushort)NativeMethods.CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE)
{
contents[r, c].BufferCellType = BufferCellType.Leading;
}
else if ((characterBuffer[characterBufferIndex].Attributes & (ushort)NativeMethods.CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE)
== (ushort)NativeMethods.CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE)
{
contents[r, c].Character = (char)0;
contents[r, c].BufferCellType = BufferCellType.Trailing;
}
else
{
int charLength = LengthInBufferCells(contents[r, c].Character);
if (charLength == 2)
{
// When it's RasterFont, the "Leading_byte"/"Trailing_byte" are not effective, we
// need to decide the leading byte by checking the char length.
contents[r, c].BufferCellType = BufferCellType.Leading;
c++;
contents[r, c].Character = (char)0;
contents[r, c].ForegroundColor = fgColor;
contents[r, c].BackgroundColor = bgColor;
contents[r, c].BufferCellType = BufferCellType.Trailing;
}
else
{
contents[r, c].BufferCellType = BufferCellType.Complete;
}
}
}
}
return true;
}
/// <summary>
/// Can handle reading CJK characters, but the left and right edges are not checked
/// </summary>
/// <param name="consoleHandle"></param>
/// <param name="codePage"></param>
/// <param name="origin"></param>
/// <param name="contentsRegion"></param>
/// <param name="contents"></param>
/// <exception cref="HostException">
/// If there is not enough memory to complete calls to Win32's ReadConsoleOutput
/// </exception>
internal static void ReadConsoleOutputCJK
(
ConsoleHandle consoleHandle,
uint codePage,
Coordinates origin,
Rectangle contentsRegion,
ref BufferCell[,] contents
)
{
int rows = contentsRegion.Bottom - contentsRegion.Top + 1;
int cols = contentsRegion.Right - contentsRegion.Left + 1;
if ((rows <= 0) || cols <= 0)
{
tracer.WriteLine("invalid contents region");
return;
}
int bufferLimit = 2 * 1024; // Limit is 8K bytes as each CHAR_INFO takes 4 bytes
COORD bufferCoord;
bufferCoord.X = 0;
bufferCoord.Y = 0;
// keeps track of which screen area is read
SMALL_RECT readRegion;
readRegion.Top = (short)origin.Y;
int rowsRemaining = rows;
while (rowsRemaining > 0)
{
// Iteration of columns is nested inside iteration of rows.
// If the size of contents exceeds the buffer limit, reading is
// done in blocks of size equal to the bufferlimit from left to right
// then top to bottom.
// For each iteration of rows,
// - readRegion.Left and bufferSize.X are reset
// - rowsRemaining, readRegion.Top, readRegion.Bottom, and bufferSize.Y
// are updated
// For each iteration of columns,
// - readRegion.Left, readRegion.Right and bufferSize.X are updated
readRegion.Left = (short)origin.X;
COORD bufferSize;
bufferSize.X = (short)Math.Min(cols, bufferLimit);
bufferSize.Y = (short)Math.Min
(
rowsRemaining,
bufferLimit / bufferSize.X
);
readRegion.Bottom = (short)(readRegion.Top + bufferSize.Y - 1);
// atContentsRow is at which row of contents a particular iteration is operating
int atContentsRow = rows - rowsRemaining + contentsRegion.Top;
// number of columns yet to be read
int colsRemaining = cols;
while (colsRemaining > 0)
{
// atContentsCol is at which column of contents a particular iteration is operating
int atContentsCol = cols - colsRemaining + contentsRegion.Left;
readRegion.Right = (short)(readRegion.Left + bufferSize.X - 1);
// Now readRegion and bufferSize are updated.
Rectangle atContents = new Rectangle(atContentsCol, atContentsRow,
atContentsCol + bufferSize.X - 1, atContentsRow + bufferSize.Y - 1);
bool result =
ReadConsoleOutputCJKSmall(consoleHandle, codePage,
new Coordinates(readRegion.Left, readRegion.Top),
atContents,
ref contents);
if (result == false)
{
// When WriteConsoleOutput fails, half bufferLimit
if (bufferLimit < 2)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "ReadConsoleOutput",
ErrorCategory.ReadError, ConsoleControlStrings.ReadConsoleOutputExceptionTemplate);
throw e;
}
else
{
// if cols == colsRemaining, nothing is guaranteed read in this pass and
// the unread area is still rectangular
bufferLimit /= 2;
if (cols == colsRemaining)
{
bufferSize.Y = 0;
break;
}
else
{
// some areas have been read. This could only happen when the number of columns
// to write is larger than bufferLimit. In that case, the algorithm reads one row
// at a time => bufferSize.Y == 1. Then, we can safely leave bufferSize.Y unchanged
// to retry with a smaller bufferSize.X.
Dbg.Assert(bufferSize.Y == 1, string.Format(CultureInfo.InvariantCulture, "bufferSize.Y should be 1, but is {0}", bufferSize.Y));
bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit);
continue;
}
}
}
colsRemaining -= bufferSize.X;
readRegion.Left += bufferSize.X;
if (colsRemaining > 0 && (bufferSize.Y == 1) &&
(contents[atContents.Bottom, atContents.Right].Character == ' '))
{
colsRemaining++;
readRegion.Left--;
}
bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit);
} // column iteration
rowsRemaining -= bufferSize.Y;
readRegion.Top += bufferSize.Y;
} // row iteration
// The following nested loop set the value of the empty cells in contents:
// character to ' '
// foreground color to console's foreground color
// background color to console's background color
int rowIndex = contents.GetLowerBound(0);
int rowEnd = contents.GetUpperBound(0);
int colBegin = contents.GetLowerBound(1);
int colEnd = contents.GetUpperBound(1);
CONSOLE_SCREEN_BUFFER_INFO bufferInfo =
GetConsoleScreenBufferInfo(consoleHandle);
ConsoleColor foreground = 0;
ConsoleColor background = 0;
WORDToColor(
bufferInfo.Attributes,
out foreground,
out background
);
while (rowIndex <= rowEnd)
{
int colIndex = colBegin;
while (true)
{
// if contents[rowIndex,colIndex] is in contentsRegion, hence a non-empty cell,
// move colIndex to one past the right end of contentsRegion
if (contentsRegion.Top <= rowIndex && rowIndex <= contentsRegion.Bottom &&
contentsRegion.Left <= colIndex && colIndex <= contentsRegion.Right)
{
colIndex = contentsRegion.Right + 1;
}
// colIndex past contents last column
if (colIndex > colEnd)
{
break;
}
contents[rowIndex, colIndex] = new BufferCell(
' ', foreground, background, BufferCellType.Complete);
colIndex++;
}
rowIndex++;
}
}
#endregion ReadConsoleOutput CJK
private static void ReadConsoleOutputPlain
(
ConsoleHandle consoleHandle,
Coordinates origin,
Rectangle contentsRegion,
ref BufferCell[,] contents
)
{
int rows = contentsRegion.Bottom - contentsRegion.Top + 1;
int cols = contentsRegion.Right - contentsRegion.Left + 1;
if ((rows <= 0) || cols <= 0)
{
tracer.WriteLine("invalid contents region");
return;
}
int bufferLimit = 2 * 1024; // Limit is 8K bytes as each CHAR_INFO takes 4 bytes
COORD bufferCoord;
bufferCoord.X = 0;
bufferCoord.Y = 0;
// keeps track of which screen area read
SMALL_RECT readRegion;
readRegion.Top = (short)origin.Y;
int rowsRemaining = rows;
while (rowsRemaining > 0)
{
// Iteration of columns is nested inside iteration of rows.
// If the size of contents exceeds the buffer limit, reading is
// done in blocks of size equal to the bufferlimit from left to right
// then top to bottom.
// For each iteration of rows,
// - readRegion.Left and bufferSize.X are reset
// - rowsRemaining, readRegion.Top, readRegion.Bottom, and bufferSize.Y
// are updated
// For each iteration of columns,
// - readRegion.Left, readRegion.Right and bufferSize.X are updated
readRegion.Left = (short)origin.X;
COORD bufferSize;
bufferSize.X = (short)Math.Min(cols, bufferLimit);
bufferSize.Y = (short)Math.Min
(
rowsRemaining,
bufferLimit / bufferSize.X
);
readRegion.Bottom = (short)(readRegion.Top + bufferSize.Y - 1);
// atContentsRow is at which row of contents a particular iteration is operating
int atContentsRow = rows - rowsRemaining + contentsRegion.Top;
// number of columns yet to be read
int colsRemaining = cols;
while (colsRemaining > 0)
{
readRegion.Right = (short)(readRegion.Left + bufferSize.X - 1);
// Now readRegion and bufferSize are updated.
// Call NativeMethods.ReadConsoleOutput
CHAR_INFO[] characterBuffer = new CHAR_INFO[bufferSize.Y * bufferSize.X];
bool result = NativeMethods.ReadConsoleOutput(
consoleHandle.DangerousGetHandle(),
characterBuffer,
bufferSize,
bufferCoord,
ref readRegion);
if (result == false)
{
// When WriteConsoleOutput fails, half bufferLimit
if (bufferLimit < 2)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "ReadConsoleOutput",
ErrorCategory.ReadError, ConsoleControlStrings.ReadConsoleOutputExceptionTemplate);
throw e;
}
// if cols == colsRemaining, nothing is guaranteed read in this pass and
// the unread area is still rectangular
bufferLimit /= 2;
if (cols == colsRemaining)
{
bufferSize.Y = 0;
break;
}
else
{
// some areas have been read. This could only happen when the number of columns
// to write is larger than bufferLimit. In that case, the algorithm reads one row
// at a time => bufferSize.Y == 1. Then, we can safely leave bufferSize.Y unchanged
// to retry with a smaller bufferSize.X.
Dbg.Assert(bufferSize.Y == 1, string.Format(CultureInfo.InvariantCulture, "bufferSize.Y should be 1, but is {0}", bufferSize.Y));
bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit);
continue;
}
}
// atContentsCol is at which column of contents a particular iteration is operating
int atContentsCol = cols - colsRemaining + contentsRegion.Left;
// copy characterBuffer to contents;
int characterBufferIndex = 0;
for (int r = atContentsRow; r < bufferSize.Y + atContentsRow; r++)
{
for (int c = atContentsCol; c < bufferSize.X + atContentsCol; c++, characterBufferIndex++)
{
contents[r, c].Character = (char)
characterBuffer[characterBufferIndex].UnicodeChar;
ConsoleColor fgColor, bgColor;
WORDToColor(characterBuffer[characterBufferIndex].Attributes,
out fgColor,
out bgColor);
contents[r, c].ForegroundColor = fgColor;
contents[r, c].BackgroundColor = bgColor;
}
}
colsRemaining -= bufferSize.X;
readRegion.Left += bufferSize.X;
bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit);
} // column iteration
rowsRemaining -= bufferSize.Y;
readRegion.Top += bufferSize.Y;
} // row iteration
// The following nested loop set the value of the empty cells in contents:
// character to ' '
// foreground color to console's foreground color
// background color to console's background color
int rowIndex = contents.GetLowerBound(0);
int rowEnd = contents.GetUpperBound(0);
int colBegin = contents.GetLowerBound(1);
int colEnd = contents.GetUpperBound(1);
CONSOLE_SCREEN_BUFFER_INFO bufferInfo =
GetConsoleScreenBufferInfo(consoleHandle);
ConsoleColor foreground = 0;
ConsoleColor background = 0;
WORDToColor(
bufferInfo.Attributes,
out foreground,
out background
);
while (rowIndex <= rowEnd)
{
int colIndex = colBegin;
while (true)
{
// if contents[rowIndex,colIndex] is in contentsRegion, hence a non-empty cell,
// move colIndex to one past the right end of contentsRegion
if (contentsRegion.Top <= rowIndex && rowIndex <= contentsRegion.Bottom &&
contentsRegion.Left <= colIndex && colIndex <= contentsRegion.Right)
{
colIndex = contentsRegion.Right + 1;
}
// colIndex past contents last column
if (colIndex > colEnd)
{
break;
}
contents[rowIndex, colIndex].Character = ' ';
contents[rowIndex, colIndex].ForegroundColor = foreground;
contents[rowIndex, colIndex].BackgroundColor = background;
colIndex++;
}
rowIndex++;
}
}
/// <summary>
/// Wraps Win32 FillConsoleOutputCharacter
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where output is filled
///
/// </param>
/// <param name="character">
///
/// character to fill the console output
///
/// </param>
/// <param name="numberToWrite">
///
/// number of times to write character
///
/// </param>
/// <param name="origin">
///
/// location on screen buffer where writing starts
///
/// </param>
/// <exception cref="HostException">
/// If Win32's FillConsoleOutputCharacter fails
/// </exception>
internal static void FillConsoleOutputCharacter
(
ConsoleHandle consoleHandle,
char character,
int numberToWrite,
Coordinates origin
)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
COORD c;
c.X = (short)origin.X;
c.Y = (short)origin.Y;
DWORD unused = 0;
bool result =
NativeMethods.FillConsoleOutputCharacter(
consoleHandle.DangerousGetHandle(),
character,
(DWORD)numberToWrite,
c,
out unused);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "FillConsoleOutputCharacter",
ErrorCategory.WriteError, ConsoleControlStrings.FillConsoleOutputCharacterExceptionTemplate);
throw e;
}
// we don't assert that the number actually written matches the number we asked for, as the function may clip if
// the number of cells to write extends past the end of the screen buffer.
}
/// <summary>
/// Wraps Win32 FillConsoleOutputAttribute
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where output is filled
///
/// </param>
/// <param name="attribute">
///
/// attribute to fill the console output
///
/// </param>
/// <param name="numberToWrite">
///
/// number of times to write attribute
///
/// </param>
/// <param name="origin">
///
/// location on screen buffer where writing starts
///
/// </param>
/// <exception cref="HostException">
/// If Win32's FillConsoleOutputAttribute fails
/// </exception>
internal static void FillConsoleOutputAttribute
(
ConsoleHandle consoleHandle,
WORD attribute,
int numberToWrite,
Coordinates origin
)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
COORD c;
c.X = (short)origin.X;
c.Y = (short)origin.Y;
DWORD unused = 0;
bool result =
NativeMethods.FillConsoleOutputAttribute(
consoleHandle.DangerousGetHandle(),
attribute,
(DWORD)numberToWrite,
c,
out unused);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "FillConsoleOutputAttribute",
ErrorCategory.WriteError, ConsoleControlStrings.FillConsoleOutputAttributeExceptionTemplate);
throw e;
}
}
/// <summary>
/// Wrap Win32 ScrollConsoleScreenBuffer
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where screen buffer is scrolled
///
/// </param>
/// <param name="scrollRectangle">
///
/// area to be scrolled
///
/// </param>
/// <param name="clipRectangle">
///
/// area to be updated after scrolling
///
/// </param>
/// <param name="destOrigin">
///
/// location to which the top left corner of scrollRectangle move
///
/// </param>
/// <param name="fill">
///
/// character and attribute to fill the area vacated by the scroll
///
/// </param>
/// <exception cref="HostException">
/// If Win32's ScrollConsoleScreenBuffer fails
/// </exception>
internal static void ScrollConsoleScreenBuffer
(
ConsoleHandle consoleHandle,
SMALL_RECT scrollRectangle,
SMALL_RECT clipRectangle,
COORD destOrigin, CHAR_INFO fill
)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
bool result =
NativeMethods.ScrollConsoleScreenBuffer(
consoleHandle.DangerousGetHandle(),
ref scrollRectangle,
ref clipRectangle,
destOrigin,
ref fill);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "ScrollConsoleScreenBuffer",
ErrorCategory.WriteError, ConsoleControlStrings.ScrollConsoleScreenBufferExceptionTemplate);
throw e;
}
}
#endregion Buffer
#region Window
/// <summary>
/// Wraps Win32 SetConsoleWindowInfo
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where window info is set
///
/// </param>
/// <param name="absolute">
///
/// If this parameter is TRUE, the coordinates specify the new upper-left and
/// lower-right corners of the window. If it is false, the coordinates are offsets
/// to the current window-corner coordinates
///
/// </param>
/// <param name="windowInfo">
///
/// specify the size and position of the console screen buffer's window
///
/// </param>
/// <exception cref="HostException">
/// If Win32's SetConsoleWindowInfo fails
/// </exception>
internal static void SetConsoleWindowInfo(ConsoleHandle consoleHandle, bool absolute, SMALL_RECT windowInfo)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
bool result = NativeMethods.SetConsoleWindowInfo(consoleHandle.DangerousGetHandle(), absolute, ref windowInfo);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "SetConsoleWindowInfo",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SetConsoleWindowInfoExceptionTemplate);
throw e;
}
}
/// <summary>
/// Wraps Win32 GetLargestConsoleWindowSize
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console for which the largest window size is obtained
///
/// </param>
/// <returns>
///
/// the largest window size
///
/// </returns>
/// <exception cref="HostException">
/// If Win32's GetLargestConsoleWindowSize fails
/// </exception>
internal static Size GetLargestConsoleWindowSize(ConsoleHandle consoleHandle)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
COORD result = NativeMethods.GetLargestConsoleWindowSize(consoleHandle.DangerousGetHandle());
if ((result.X == 0) && (result.Y == 0))
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "GetLargestConsoleWindowSize",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.GetLargestConsoleWindowSizeExceptionTemplate);
throw e;
}
return new Size(result.X, result.Y);
}
/// <summary>
/// Wraps Win32 GetConsoleTitle. 1K is the safe limit experimentally. The 64K limit
/// found in the docs is disregarded because it is essentially meaningless.
/// </summary>
/// <returns>
///
/// a string for the title of the window
///
/// </returns>
/// <exception cref="HostException">
/// If Win32's GetConsoleTitle fails
/// </exception>
internal static string GetConsoleWindowTitle()
{
const int MaxWindowTitleLength = 1024;
DWORD bufferSize = MaxWindowTitleLength;
DWORD result;
StringBuilder consoleTitle = new StringBuilder((int)bufferSize);
// Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
// get the error code.
#pragma warning disable 56523
result = NativeMethods.GetConsoleTitle(consoleTitle, bufferSize);
// If the result is zero, it may mean and error but it may also mean
// that the window title has been set to null. Since we can't tell the
// the difference, we'll just return the empty string every time.
if (result == 0)
{
return String.Empty;
}
return consoleTitle.ToString();
}
/// <summary>
/// Wraps Win32 SetConsoleTitle
/// </summary>
/// <param name="consoleTitle">
///
/// a string for the title of the window
///
/// </param>
/// <exception cref="HostException">
/// If Win32's SetConsoleTitle fails
/// </exception>
internal static void SetConsoleWindowTitle(string consoleTitle)
{
bool result = NativeMethods.SetConsoleTitle(consoleTitle);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "SetConsoleWindowTitle",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SetConsoleWindowTitleExceptionTemplate);
throw e;
}
}
#endregion Window
#if !CORECLR
#region Font Selection
/// <summary>
/// UpdateLocaleSpecificFont is a helper method used to update
/// the console font based on the locale.
/// The default font face used for Powershell Console is Lucida Console.
/// However certain locales dont support Lucida Console font. Hence for such
/// locales the console font is updated to Raster dynamically.
/// </summary>
internal static void UpdateLocaleSpecificFont()
{
// Default Powershell shortcut.lnk settings.
const string defaultFontFace = "Lucida Console";
// Default CJK locale shortcut.lnk settings.
// Font size is hard coded here to ensure we select a supported size.
// GDI does a poor job of selecting raster font size if the requested
// size is not supported.
const string CJKFontFace = "Terminal";
const int CJKFontFamily = 48;
const int CJKnFont = 6;
const int CJKFontWidth = 8;
const int CJKFontHeight = 12;
const int CKJFontWeight = 400;
uint currentLocaleCodePage = (uint)ConsoleControl.NativeMethods.GetConsoleCP();
ConsoleHandle handle = ConsoleControl.GetActiveScreenBufferHandle();
CONSOLE_FONT_INFO_EX fontInfo;
try
{
fontInfo = ConsoleControl.GetConsoleFontInfo(handle);
}
catch (Exception)
{
return;
}
bool isLucidaConsoleSupportedLocale = CodePageSupportsLucida(currentLocaleCodePage);
// The Raster font is updated for Japanese and Korean locales if the user has not manually
// altered the default settings. If the default settings are altered then the
// setting chosen by the user would be used.
if (!isLucidaConsoleSupportedLocale &&
fontInfo.FontFace.Equals(defaultFontFace, StringComparison.OrdinalIgnoreCase))
{
fontInfo.FontFace = CJKFontFace;
fontInfo.FontFamily = CJKFontFamily;
fontInfo.nFont = CJKnFont;
fontInfo.FontWidth = CJKFontWidth;
fontInfo.FontHeight = CJKFontHeight;
fontInfo.FontWeight = CKJFontWeight;
bool result = NativeMethods.SetCurrentConsoleFontEx(handle.DangerousGetHandle(), false, ref fontInfo);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err,
"SetConsoleFontInfo",
ErrorCategory.ResourceUnavailable,
ConsoleControlStrings.SetConsoleFontInfoExceptionTemplate);
throw e;
}
}
}
/// <summary>
/// Supported Lucida code pages obtained from Font group.
/// Contacts: alib, judysa, simonda
/// </summary>
private static HashSet<uint> LucidaSupportedCodePages = new HashSet<uint>()
{
1251, // Latin 1
1250, // Latin 2
1251, // Cyrillic
1253, // Greek
1254, // Turkish
869, // IBM Greek
866, // MS-DOS Russian
865, // MS-DOS Nordic
863, // MS-DOS Canadian French
861, // MS-DOS Icelandic
860, // MS-DOS Portuguese
857, // IBM Turkish
855, // IBM Cyrillic
852, // Latin 2
737, // Greek
850, // WE/Latin 1
437 // US
//936, // SimplifiedChinese - According to font team this is *not* supported.
//950 // TraditionalChinese - According to font team this is *not* supported.
};
private static bool CodePageSupportsLucida(uint currentLocaleCodePage)
{
return LucidaSupportedCodePages.Contains(currentLocaleCodePage);
}
#endregion
#endif
/// <summary>
///
/// Wrap Win32 WriteConsole
///
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where the string is written
///
/// </param>
/// <param name="output">
///
/// string that is written
///
/// </param>
/// <exception cref="HostException">
///
/// if the Win32's WriteConsole fails
///
/// </exception>
internal static void WriteConsole(ConsoleHandle consoleHandle, string output)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
if (String.IsNullOrEmpty(output))
return;
// Native WriteConsole doesn't support output buffer longer than 64K.
// We need to chop the output string if it is too long.
int cursor = 0; // This records the chopping position in output string
const int maxBufferSize = 16383; // this is 64K/4 - 1 to account for possible width of each character.
while (cursor < output.Length)
{
string outBuffer;
if (cursor + maxBufferSize < output.Length)
{
outBuffer = output.Substring(cursor, maxBufferSize);
cursor += maxBufferSize;
}
else
{
outBuffer = output.Substring(cursor);
cursor = output.Length;
}
DWORD charsWritten;
bool result =
NativeMethods.WriteConsole(
consoleHandle.DangerousGetHandle(),
outBuffer,
(DWORD)outBuffer.Length,
out charsWritten,
IntPtr.Zero);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "WriteConsole",
ErrorCategory.WriteError, ConsoleControlStrings.WriteConsoleExceptionTemplate);
throw e;
}
}
}
/// <summary>
/// Wraps Win32 SetConsoleTextAttribute
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where text attribute is set
///
/// </param>
/// <param name="attribute">
///
/// text attribute to set the console
///
/// </param>
/// <exception cref="HostException">
///
/// if the Win32's SetConsoleTextAttribute fails
///
/// </exception>
internal static void SetConsoleTextAttribute(ConsoleHandle consoleHandle, WORD attribute)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
bool result = NativeMethods.SetConsoleTextAttribute(consoleHandle.DangerousGetHandle(), attribute);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "SetConsoleTextAttribute",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SetConsoleTextAttributeExceptionTemplate);
throw e;
}
}
#region Dealing with CJK
/// <summary>
/// From IsConsoleFullWidth in \windows\core\ntcon\server\dbcs.c
/// Gets the CharSet for a code page
/// </summary>
/// <param name="codePage"></param>
/// <returns>The CharSet corresponding to the codePage; defaults to OEM_CHARSET (255)</returns>
private static uint CodePageToCharSet(uint codePage)
{
const uint OEM_CHARSET = 255;
// Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
// get the error code.
#pragma warning disable 56523
#if CORECLR // TranslateCharsetInfo exists in an extension API set 'ext-ms-win-gdi-font-l1-1-1.dll', which is not available in NanoServer.
return OEM_CHARSET;
#else
CHARSETINFO csi;
const DWORD TCI_SRCCODEPAGE = 2;
if (!NativeMethods.TranslateCharsetInfo((IntPtr)codePage, out csi, TCI_SRCCODEPAGE))
{
csi.ciCharset = OEM_CHARSET;
}
return csi.ciCharset;
#endif
}
// From \windows\core\ntcon\server\dbcs.c
private static bool IsAvailableFarEastCodePage(uint codePage)
{
uint charSet = CodePageToCharSet(codePage);
return IsAnyDBCSCharSet(charSet);
}
// From \windows\core\ntcon\server\dbcs.c
private static bool IsAnyDBCSCharSet(uint charSet)
{
const uint SHIFTJIS_CHARSET = 128;
const uint HANGEUL_CHARSET = 129;
const uint CHINESEBIG5_CHARSET = 136;
const uint GB2312_CHARSET = 134;
return charSet == SHIFTJIS_CHARSET || charSet == HANGEUL_CHARSET ||
charSet == CHINESEBIG5_CHARSET || charSet == GB2312_CHARSET;
}
/// <summary>
/// From IsConsoleFullWidth in \windows\core\ntcon\server\dbcs.c
/// Precondition: the current code page needs to be a Far East code page.
///
/// char F8F8 makes this function return 1 while in CHT, CHS, and KOR it takes 2 cells.
/// I don't think we should special-case this because that ought to be a bug outside of
/// this code.
/// </summary>
/// <param name="c"></param>
/// <param name="hwnd">window handle</param>
/// <param name="hDC">handle to DC; it is not released by this method</param>
/// <param name="istmInitialized"></param>
/// <param name="tm"></param>
/// <returns></returns>
private static int LengthInBufferCellsFE(char c, ref HWND hwnd, ref HDC hDC, ref bool istmInitialized, ref TEXTMETRIC tm)
{
if (0x20 <= c && c <= 0x7e)
{
/* ASCII */
return 1;
}
else if (0x3041 <= c && c <= 0x3094)
{
/* Hiragana */
return 2;
}
else if (0x30a1 <= c && c <= 0x30f6)
{
/* Katakana */
return 2;
}
else if (0x3105 <= c && c <= 0x312c)
{
/* Bopomofo */
return 2;
}
else if (0x3131 <= c && c <= 0x318e)
{
/* Hangul Elements */
return 2;
}
else if (0xac00 <= c && c <= 0xd7a3)
{
/* Korean Hangul Syllables */
return 2;
}
else if (0xff01 <= c && c <= 0xff5e)
{
/* Fullwidth ASCII variants */
return 2;
}
else if (0xff61 <= c && c <= 0xff9f)
{
/* Halfwidth Katakana variants */
return 1;
}
else if ((0xffa0 <= c && c <= 0xffbe) ||
(0xffc2 <= c && c <= 0xffc7) ||
(0xffca <= c && c <= 0xffcf) ||
(0xffd2 <= c && c <= 0xffd7) ||
(0xffda <= c && c <= 0xffdc))
{
/* Halfwidth Hangule variants */
return 1;
}
else if (0xffe0 <= c && c <= 0xffe6)
{
/* Fullwidth symbol variants */
return 2;
}
else if (0x4e00 <= c && c <= 0x9fa5)
{
/* Han Ideographic */
return 2;
}
else if (0xf900 <= c && c <= 0xfa2d)
{
/* Han Compatibility Ideographs */
return 2;
}
else
{
// GetTextMetrics / GetCharWidth32 exist in an extension API set 'ext-ms-win-gdi-font-l1-1-1.dll', which is not available in NanoServer.
#if !CORECLR
/* Unknown character: need to use GDI*/
if (hDC == (IntPtr)0)
{
hwnd = NativeMethods.GetConsoleWindow();
if ((IntPtr)0 == hwnd)
{
int err = Marshal.GetLastWin32Error();
//Don't throw exception so that output can continue
tracer.TraceError("Win32 Error 0x{0:X} occurred when getting the window handle to the console.",
err);
return 1;
}
hDC = NativeMethods.GetDC(hwnd);
if ((IntPtr)0 == hDC)
{
int err = Marshal.GetLastWin32Error();
//Don't throw exception so that output can continue
tracer.TraceError("Win32 Error 0x{0:X} occurred when getting the Device Context of the console window.",
err);
return 1;
}
}
bool result = true;
if (!istmInitialized)
{
result = NativeMethods.GetTextMetrics(hDC, out tm);
if (!result)
{
int err = Marshal.GetLastWin32Error();
//Don't throw exception so that output can continue
tracer.TraceError("Win32 Error 0x{0:X} occurred when getting the Text Metric of the console window's Device Context.",
err);
return 1;
}
istmInitialized = true;
}
int width;
result = NativeMethods.GetCharWidth32(hDC, (uint)c, (uint)c, out width);
if (!result)
{
int err = Marshal.GetLastWin32Error();
//Don't throw exception so that output can continue
tracer.TraceError("Win32 Error 0x{0:X} occurred when getting the width of a char.",
err);
return 1;
}
if (width >= tm.tmMaxCharWidth)
{
return 2;
}
#endif
}
tracer.WriteLine("failed to locate char {0}, return 1", (int)c);
return 1;
}
internal static int LengthInBufferCells(char c)
{
uint codePage = NativeMethods.GetConsoleOutputCP();
return LengthInBufferCells(c, codePage);
}
/// <summary>
/// From IsConsoleFullWidth in \windows\core\ntcon\server\dbcs.c
/// </summary>
/// <param name="c"></param>
/// <param name="codePage"></param>
/// <returns></returns>
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults",
MessageId = "Microsoft.PowerShell.ConsoleControl+NativeMethods.ReleaseDC(System.IntPtr,System.IntPtr)")]
private static int LengthInBufferCells(char c, uint codePage)
{
if (!IsAvailableFarEastCodePage(codePage))
{
return 1;
}
HWND hwnd = (HWND)0;
HDC hDC = (HDC)0;
bool istmInitialized = false;
TEXTMETRIC tm = new TEXTMETRIC(); ;
try
{
return LengthInBufferCellsFE(c, ref hwnd, ref hDC, ref istmInitialized, ref tm);
}
finally
{
if (hwnd != (IntPtr)0 && hDC != (IntPtr)0)
{
NativeMethods.ReleaseDC(hwnd, hDC);
}
}
}
// Return the length of a VT100 control sequence character in str starting
// at the given offset.
//
// This code only handles the most common formatting sequences, which are
// all of the pattern:
// ESC '[' digits+ (';' digits)* 'm'
//
// There are many other VT100 escape sequences, but this simple pattern
// is sufficient for our formatting system. We won't handle cursor movements
// or other attempts at animation.
//
// Note that offset is adjusted past the escape sequence.
internal static int ControlSequenceLength(string str, ref int offset)
{
var start = offset;
if (str[offset++] != (char)0x1B)
return 0;
if (offset >= str.Length || str[offset] != '[')
return 0;
offset += 1;
while (offset < str.Length)
{
var c = str[offset++];
if (c == 'm')
break;
if (char.IsDigit(c) || c == ';')
continue;
return 0;
}
return offset - start;
}
/// <summary>
/// From IsConsoleFullWidth in \windows\core\ntcon\server\dbcs.c
/// </summary>
/// <returns></returns>
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults",
MessageId = "Microsoft.PowerShell.ConsoleControl+NativeMethods.ReleaseDC(System.IntPtr,System.IntPtr)")]
internal static int LengthInBufferCells(string str, int offset, bool checkEscapeSequences)
{
Dbg.Assert(offset >= 0, "offset >= 0");
Dbg.Assert(string.IsNullOrEmpty(str) || (offset < str.Length), "offset < str.Length");
var escapeSequenceAdjustment = 0;
if (checkEscapeSequences)
{
int i = 0;
while (i < offset)
{
ControlSequenceLength(str, ref i);
}
// If offset != i, we're in the middle of a sequence, which the caller should avoid,
// but we'll tolerate.
while (i < str.Length)
{
escapeSequenceAdjustment += ControlSequenceLength(str, ref i);
}
}
uint codePage = NativeMethods.GetConsoleOutputCP();
if (!IsAvailableFarEastCodePage(codePage))
{
return str.Length - offset - escapeSequenceAdjustment;
}
HWND hwnd = (HWND)0;
HDC hDC = (HDC)0;
bool istmInitialized = false;
TEXTMETRIC tm = new TEXTMETRIC(); ;
int length = 0;
try
{
int n = str.Length;
for (int i = offset; i < n; i++)
{
char c = str[i];
length += LengthInBufferCellsFE(c, ref hwnd, ref hDC, ref istmInitialized, ref tm);
}
return length - escapeSequenceAdjustment;
}
finally
{
if (hwnd != (IntPtr)0 && hDC != (IntPtr)0)
{
NativeMethods.ReleaseDC(hwnd, hDC);
}
}
}
/// <summary>
/// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese
/// </summary>
/// <param name="codePage"></param>
/// <returns></returns>
internal static bool IsCJKOutputCodePage(out uint codePage)
{
codePage = NativeMethods.GetConsoleOutputCP();
return codePage == 932 || // Japanese
codePage == 936 || // Simplified Chinese
codePage == 949 || // Korean
codePage == 950; // Traditional Chinese
}
#endregion Dealing with CJK
#region Cursor
/// <summary>
/// Wraps Win32 SetConsoleCursorPosition
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where cursor position is set
///
/// </param>
/// <param name="cursorPosition">
///
/// location to which the cursor will be set
///
/// </param>
/// <exception cref="HostException">
/// If Win32's SetConsoleCursorPosition fails
/// </exception>
internal static void SetConsoleCursorPosition(ConsoleHandle consoleHandle, Coordinates cursorPosition)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
ConsoleControl.COORD c;
c.X = (short)cursorPosition.X;
c.Y = (short)cursorPosition.Y;
bool result = NativeMethods.SetConsoleCursorPosition(consoleHandle.DangerousGetHandle(), c);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "SetConsoleCursorPosition",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SetConsoleCursorPositionExceptionTemplate);
throw e;
}
}
/// <summary>
/// Wraps Win32 GetConsoleCursorInfo
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where cursor info is obtained
///
/// </param>
/// <returns>
///
/// cursor info
///
/// </returns>
/// <exception cref="HostException">
/// If Win32's GetConsoleCursorInfo fails
/// </exception>
internal static CONSOLE_CURSOR_INFO GetConsoleCursorInfo(ConsoleHandle consoleHandle)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
CONSOLE_CURSOR_INFO cursorInfo;
bool result = NativeMethods.GetConsoleCursorInfo(consoleHandle.DangerousGetHandle(), out cursorInfo);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "GetConsoleCursorInfo",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.GetConsoleCursorInfoExceptionTemplate);
throw e;
}
return cursorInfo;
}
internal static CONSOLE_FONT_INFO_EX GetConsoleFontInfo(ConsoleHandle consoleHandle)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
CONSOLE_FONT_INFO_EX fontInfo = new CONSOLE_FONT_INFO_EX();
fontInfo.cbSize = Marshal.SizeOf(fontInfo);
bool result = NativeMethods.GetCurrentConsoleFontEx(consoleHandle.DangerousGetHandle(), false, ref fontInfo);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "GetConsoleFontInfo",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.GetConsoleFontInfoExceptionTemplate);
throw e;
}
return fontInfo;
}
/// <summary>
/// Wraps Win32 SetConsoleCursorInfo
/// </summary>
/// <param name="consoleHandle">
///
/// handle for the console where cursor info is set
///
/// </param>
/// <param name="cursorInfo">
///
/// cursor info to set the cursor
///
/// </param>
/// <exception cref="HostException">
/// If Win32's SetConsoleCursorInfo fails
/// </exception>
internal static void SetConsoleCursorInfo(ConsoleHandle consoleHandle, CONSOLE_CURSOR_INFO cursorInfo)
{
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
bool result = NativeMethods.SetConsoleCursorInfo(consoleHandle.DangerousGetHandle(), ref cursorInfo);
if (result == false)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "SetConsoleCursorInfo",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SetConsoleCursorInfoExceptionTemplate);
throw e;
}
}
#endregion Cursor
#region helper
/// <summary>
/// Helper function to create the proper HostException
/// </summary>
/// <param name="win32Error"></param>
/// <param name="errorId"></param>
/// <param name="category"></param>
/// <param name="resourceStr"></param>
/// <returns></returns>
static private HostException CreateHostException(
int win32Error, string errorId, ErrorCategory category, string resourceStr)
{
Win32Exception innerException = new Win32Exception(win32Error);
string msg = StringUtil.Format(resourceStr, innerException.Message, win32Error);
HostException e = new HostException(msg, innerException, errorId, category);
return e;
}
#endregion helper
#region SendInput
internal static void MimicKeyPress(INPUT[] inputs)
{
Dbg.Assert(inputs != null && inputs.Length > 0, "inputs should not be null or empty");
var numberOfSuccessfulEvents = NativeMethods.SendInput((uint)inputs.Length, inputs, ClrFacade.SizeOf<INPUT>());
if (numberOfSuccessfulEvents == 0)
{
int err = Marshal.GetLastWin32Error();
HostException e = CreateHostException(err, "SendKeyPressInput",
ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SendKeyPressInputExceptionTemplate);
throw e;
}
}
#endregion SendInput
/// <summary>
///
/// Class to hold the Native Methods used in this file enclosing class.
///
/// </summary>
internal static class NativeMethods
{
internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); // WinBase.h
internal const int FontTypeMask = 0x06;
internal const int TrueTypeFont = 0x04;
#region CreateFile
[Flags]
internal enum AccessQualifiers : uint
{
// From winnt.h
GenericRead = 0x80000000,
GenericWrite = 0x40000000
}
[Flags]
internal enum ShareModes : uint
{
// From winnt.h
ShareRead = 0x00000001,
ShareWrite = 0x00000002
}
internal enum CreationDisposition : uint
{
// From winbase.h
CreateNew = 1,
CreateAlways = 2,
OpenExisting = 3,
OpenAlways = 4,
TruncateExisting = 5
}
[DllImport(PinvokeDllNames.CreateFileDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern NakedWin32Handle CreateFile
(
string fileName,
DWORD desiredAccess,
DWORD ShareModes,
IntPtr securityAttributes,
DWORD creationDisposition,
DWORD flagsAndAttributes,
NakedWin32Handle templateFileWin32Handle
);
#endregion CreateFile
#region Code Page
[DllImport(PinvokeDllNames.GetConsoleCPDllName, SetLastError = false, CharSet = CharSet.Unicode)]
internal static extern uint GetConsoleCP();
[DllImport(PinvokeDllNames.GetConsoleOutputCPDllName, SetLastError = false, CharSet = CharSet.Unicode)]
internal static extern uint GetConsoleOutputCP();
#endregion Code Page
[DllImport(PinvokeDllNames.GetConsoleWindowDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern HWND GetConsoleWindow();
[DllImport(PinvokeDllNames.GetDCDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern HDC GetDC(HWND hwnd);
[DllImport(PinvokeDllNames.ReleaseDCDllName, SetLastError = false, CharSet = CharSet.Unicode)]
internal static extern int ReleaseDC(HWND hwnd, HDC hdc);
[DllImport(PinvokeDllNames.TranslateCharsetInfoDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool TranslateCharsetInfo(IntPtr src, out CHARSETINFO Cs, DWORD options);
[DllImport(PinvokeDllNames.GetTextMetricsDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool GetTextMetrics(HDC hdc, out TEXTMETRIC tm);
[DllImport(PinvokeDllNames.GetCharWidth32DllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool GetCharWidth32(HDC hdc, uint first, uint last, out int width);
[DllImport(PinvokeDllNames.FlushConsoleInputBufferDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool FlushConsoleInputBuffer(NakedWin32Handle consoleInput);
[DllImport(PinvokeDllNames.FillConsoleOutputAttributeDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool FillConsoleOutputAttribute
(
NakedWin32Handle consoleOutput,
WORD attribute,
DWORD length,
COORD writeCoord,
out DWORD numberOfAttrsWritten
);
[DllImport(PinvokeDllNames.FillConsoleOutputCharacterDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool FillConsoleOutputCharacter
(
NakedWin32Handle consoleOutput,
Char character,
DWORD length,
COORD writeCoord,
out DWORD numberOfCharsWritten
);
[DllImport(PinvokeDllNames.WriteConsoleDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool WriteConsole
(
NakedWin32Handle consoleOutput,
string buffer,
DWORD numberOfCharsToWrite,
out DWORD numberOfCharsWritten,
IntPtr reserved
);
[DllImport(PinvokeDllNames.GetConsoleTitleDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern DWORD GetConsoleTitle(StringBuilder consoleTitle, DWORD size);
[DllImport(PinvokeDllNames.SetConsoleTitleDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetConsoleTitle(string consoleTitle);
[DllImport(PinvokeDllNames.GetConsoleModeDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool GetConsoleMode(NakedWin32Handle consoleHandle, out UInt32 mode);
[DllImport(PinvokeDllNames.GetConsoleScreenBufferInfoDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool GetConsoleScreenBufferInfo(NakedWin32Handle consoleHandle, out CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo);
internal enum FileType
{
Unknown,
Disk,
Char,
Pipe
};
[DllImport(PinvokeDllNames.GetFileTypeDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern FileType GetFileType(NakedWin32Handle fileHandle);
[DllImport(PinvokeDllNames.GetLargestConsoleWindowSizeDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern COORD GetLargestConsoleWindowSize(NakedWin32Handle consoleOutput);
[DllImport(PinvokeDllNames.ReadConsoleDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool ReadConsole
(
NakedWin32Handle consoleInput,
StringBuilder buffer,
DWORD numberOfCharsToRead,
out DWORD numberOfCharsRead,
// This magical parameter is not documented, but is the secret to tab-completion.
ref CONSOLE_READCONSOLE_CONTROL controlData
);
[DllImport(PinvokeDllNames.PeekConsoleInputDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool PeekConsoleInput
(
NakedWin32Handle consoleInput,
[Out] INPUT_RECORD[] buffer,
DWORD length,
out DWORD numberOfEventsRead
);
[DllImport(PinvokeDllNames.GetNumberOfConsoleInputEventsDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool GetNumberOfConsoleInputEvents(NakedWin32Handle consoleInput, out DWORD numberOfEvents);
[DllImport(PinvokeDllNames.SetConsoleCtrlHandlerDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetConsoleCtrlHandler(BreakHandler handlerRoutine, bool add);
[DllImport(PinvokeDllNames.SetConsoleCursorPositionDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetConsoleCursorPosition(NakedWin32Handle consoleOutput, COORD cursorPosition);
[DllImport(PinvokeDllNames.SetConsoleModeDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetConsoleMode(NakedWin32Handle consoleHandle, DWORD mode);
[DllImport(PinvokeDllNames.SetConsoleScreenBufferSizeDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetConsoleScreenBufferSize(NakedWin32Handle consoleOutput, COORD size);
[DllImport(PinvokeDllNames.SetConsoleTextAttributeDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetConsoleTextAttribute(NakedWin32Handle consoleOutput, WORD attributes);
[DllImport(PinvokeDllNames.SetConsoleWindowInfoDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetConsoleWindowInfo(NakedWin32Handle consoleHandle, bool absolute, ref SMALL_RECT windowInfo);
[DllImport(PinvokeDllNames.WriteConsoleOutputDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool WriteConsoleOutput
(
NakedWin32Handle consoleOutput,
CHAR_INFO[] buffer,
COORD bufferSize,
COORD bufferCoord,
ref SMALL_RECT writeRegion
);
[DllImport(PinvokeDllNames.ReadConsoleOutputDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool ReadConsoleOutput
(
NakedWin32Handle consoleOutput,
[Out] CHAR_INFO[] buffer,
COORD bufferSize,
COORD bufferCoord,
ref SMALL_RECT readRegion
);
[DllImport(PinvokeDllNames.ScrollConsoleScreenBufferDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool ScrollConsoleScreenBuffer
(
NakedWin32Handle consoleOutput,
ref SMALL_RECT scrollRectangle,
ref SMALL_RECT clipRectangle,
COORD destinationOrigin,
ref CHAR_INFO fill
);
[DllImport(PinvokeDllNames.SendInputDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern UInt32 SendInput(UInt32 inputNumbers, INPUT[] inputs, Int32 sizeOfInput);
// There is no GetCurrentConsoleFontEx on Core
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool GetCurrentConsoleFontEx(NakedWin32Handle consoleOutput, bool bMaximumWindow, ref CONSOLE_FONT_INFO_EX consoleFontInfo);
// There is no SetCurrentConsoleFontEx on Core
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetCurrentConsoleFontEx(NakedWin32Handle consoleOutput, bool bMaximumWindow, ref CONSOLE_FONT_INFO_EX consoleFontInfo);
[DllImport(PinvokeDllNames.GetConsoleCursorInfoDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool GetConsoleCursorInfo(NakedWin32Handle consoleOutput, out CONSOLE_CURSOR_INFO consoleCursorInfo);
[DllImport(PinvokeDllNames.SetConsoleCursorInfoDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetConsoleCursorInfo(NakedWin32Handle consoleOutput, ref CONSOLE_CURSOR_INFO consoleCursorInfo);
[DllImport(PinvokeDllNames.ReadConsoleInputDllName, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool ReadConsoleInput
(
NakedWin32Handle consoleInput,
[Out] INPUT_RECORD[] buffer,
DWORD length,
out DWORD numberOfEventsRead
);
internal enum CHAR_INFO_Attributes : uint
{
COMMON_LVB_LEADING_BYTE = 0x0100,
COMMON_LVB_TRAILING_BYTE = 0x0200
}
}
private const string StringsResourceBaseName = "ConsoleControlStrings";
/*private const string AddBreakHandlerTemplateResource = ConsoleControlStrings.AddBreakHandlerExceptionTemplate;
private const string RemoveBreakHandlerTemplateResource = ConsoleControlStrings.RemoveBreakHandlerExceptionTemplate;
private const string AttachToParentConsoleTemplateResource = ConsoleControlStrings.AttachToParentConsoleExceptionTemplate;
private const string DetachFromConsoleTemplateResource = ConsoleControlStrings.DetachFromConsoleExceptionTemplate;
private const string GetInputHandleTemplateResource = ConsoleControlStrings.GetInputModeExceptionTemplate;
private const string GetActiveScreenBufferHandleTemplateResource = ConsoleControlStrings.GetActiveScreenBufferHandleExceptionTemplate;
private const string GetModeTemplateResource = ConsoleControlStrings.GetModeExceptionTemplate;
private const string SetModeTemplateResource = ConsoleControlStrings.SetModeExceptionTemplate;
private const string ReadConsoleTemplateResource = ConsoleControlStrings.ReadConsoleExceptionTemplate;
private const string ReadConsoleInputTemplateResource = ConsoleControlStrings.ReadConsoleInputExceptionTemplate;
private const string PeekConsoleInputTemplateResource = ConsoleControlStrings.PeekConsoleInputExceptionTemplate;
private const string GetNumberOfConsoleInputEventsTemplateResource = ConsoleControlStrings.GetNumberOfConsoleInputEventsExceptionTemplate;
private const string FlushConsoleInputBufferTemplateResource = ConsoleControlStrings.FlushConsoleInputBufferExceptionTemplate;
private const string GetConsoleScreenBufferInfoTemplateResource = ConsoleControlStrings.GetConsoleScreenBufferInfoExceptionTemplate;
private const string SetConsoleScreenBufferSizeTemplateResource = ConsoleControlStrings.SetConsoleScreenBufferSizeExceptionTemplate;
private const string WriteConsoleOutputTemplateResource = ConsoleControlStrings.WriteConsoleOutputExceptionTemplate;
private const string ReadConsoleOutputTemplateResource = ConsoleControlStrings.ReadConsoleOutputExceptionTemplate;
private const string FillConsoleOutputCharacterTemplateResource = ConsoleControlStrings.FillConsoleOutputCharacterExceptionTemplate;
private const string FillConsoleOutputAttributeTemplateResource = ConsoleControlStrings.FillConsoleOutputAttributeExceptionTemplate;
private const string ScrollConsoleScreenBufferTemplateResource = ConsoleControlStrings.ScrollConsoleScreenBufferExceptionTemplate;
private const string SetConsoleWindowInfoTemplateResource = ConsoleControlStrings.SetConsoleWindowInfoExceptionTemplate;
private const string GetLargestConsoleWindowSizeTemplateResource = ConsoleControlStrings.GetLargestConsoleWindowSizeExceptionTemplate;
private const string SetConsoleWindowTitleTemplateResource = ConsoleControlStrings.SetConsoleWindowTitleExceptionTemplate;
private const string WriteConsoleTemplateResource = ConsoleControlStrings.WriteConsoleExceptionTemplate;
private const string SetConsoleTextAttributeTemplateResource = ConsoleControlStrings.SetConsoleTextAttributeExceptionTemplate;
private const string SetConsoleCursorPositionTemplateResource = ConsoleControlStrings.SetConsoleCursorPositionExceptionTemplate;
private const string GetConsoleCursorInfoTemplateResource = ConsoleControlStrings.GetConsoleCursorInfoExceptionTemplate;
private const string SetConsoleCursorInfoTemplateResource = ConsoleControlStrings.SetConsoleCursorInfoExceptionTemplate;*/
[TraceSourceAttribute("ConsoleControl", "Console control methods")]
private static PSTraceSource tracer = PSTraceSource.GetTracer("ConsoleControl", "Console control methods");
}
} // namespace