c748652c34
commit 8cec8f150da7583b7af5efbe2853efee0179750c
3611 lines
145 KiB
C#
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
|
|
|