#if !UNIX /********************************************************************++ 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 { /// /// /// 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. /// /// 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 { /// /// 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. /// internal Int32 X; /// /// 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. /// internal Int32 Y; /// /// 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. /// internal DWORD MouseData; /// /// 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) /// internal DWORD Flags; /// /// The time stamp for the event, in milliseconds. If this parameter is 0, the system will provide its own time stamp. /// internal DWORD Time; /// /// An additional value associated with the mouse event. An application calls GetMessageExtraInfo to obtain this extra information /// internal IntPtr ExtraInfo; } [StructLayout(LayoutKind.Sequential)] internal struct KeyboardInput { /// /// 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. /// internal WORD Vk; /// /// 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. /// internal WORD Scan; /// /// Specifies various aspects of a keystroke. /// This member can be certain combinations of the following values. /// internal DWORD Flags; /// /// The time stamp for the event, in milliseconds. /// If this parameter is zero, the system will provide its own time stamp. /// internal DWORD Time; /// /// An additional value associated with the keystroke. /// Use the GetMessageExtraInfo function to obtain this information. /// internal IntPtr ExtraInfo; } [StructLayout(LayoutKind.Sequential)] internal struct HardwareInput { /// /// The message generated by the input hardware. /// internal DWORD Msg; /// /// The low-order word of the lParam parameter for uMsg. /// internal WORD ParamL; /// /// The high-order word of the lParam parameter for uMsg. /// internal WORD ParamH; } internal enum VirtualKeyCode : ushort { /// /// LEFT ARROW key /// Left = 0x25, /// /// ENTER key /// Return = 0x0D, } /// /// Specify the type of the input /// internal enum InputType : uint { /// /// INPUT_MOUSE = 0x00 /// Mouse = 0, /// /// INPUT_KEYBOARD = 0x01 /// Keyboard = 1, /// /// INPUT_HARDWARE = 0x02 /// Hardware = 2, } internal enum KeyboardFlag : uint { /// /// If specified, the scan code was preceded by a prefix byte that has the value 0xE0 (224). /// ExtendedKey = 0x0001, /// /// If specified, the key is being released. If not specified, the key is being pressed. /// KeyUp = 0x0002, /// /// If specified, wScan identifies the key and wVk is ignored. /// Unicode = 0x0004, /// /// 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. /// 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 /// /// Code to control the display properties of the a window... /// /// The window to show... /// The command to do /// true it it was successful [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) /// /// /// Types of control ConsoleBreakSignals received by break Win32Handler delegates /// /// 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); /// /// Set the console's break handler /// /// /// /// If Win32's SetConsoleCtrlHandler fails /// 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; } } /// /// Set the console's break handler to null /// /// /// If Win32's SetConsoleCtrlHandler fails /// 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 private static readonly Lazy _keyboardInputHandle = new Lazy(() => { 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); } ); /// /// Returns a ConsoleHandle to the console (keyboard device) /// internal static ConsoleHandle GetConioDeviceHandle() { return _keyboardInputHandle.Value; } private static readonly Lazy _outputHandle = new Lazy(() => { // 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); } ); /// /// Returns a ConsoleHandle to the active screen buffer, even if that output has been redirected. /// /// /// /// If Win32's CreateFile fails /// internal static ConsoleHandle GetActiveScreenBufferHandle() { return _outputHandle.Value; } #endregion #region Mode /// /// /// flags used by ConsoleControl.GetMode and ConsoleControl.SetMode /// /// [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, } /// /// /// Returns a mask of ConsoleModes flags describing the current modality of the console /// /// /// /// If Win32's GetConsoleMode fails /// 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; } /// /// /// Sets the current mode of the console device /// /// /// /// /// Handle to the console device returned by GetInputHandle /// /// /// /// /// Mask of mode flags /// /// /// /// /// If Win32's SetConsoleMode fails /// /// 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 /// /// /// Reads input from the console device according to the mode in effect (see GetMode, SetMode) /// /// /// /// /// Handle to the console device returned by GetInputHandle /// /// /// /// Initial contents of the edit buffer, if any. charactersToRead should be at least as large as the length of this string. /// /// /// /// /// Number of characters to read from the device. /// /// /// /// /// true to allow the user to terminate input by hitting the tab or shift-tab key, in addition to the enter key /// /// /// /// /// bit mask indicating the state of the control/shift keys at the point input was terminated. /// /// /// /// /// /// If Win32's ReadConsole fails /// /// 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); } /// /// Wraps Win32 ReadConsoleInput. /// Returns the number of records read in buffer. /// /// /// /// handle for the console where input is read /// /// /// /// /// array where data read are stored /// /// /// /// /// actual number of input records read /// /// /// /// If Win32's ReadConsoleInput fails /// 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; } /// /// Wraps Win32 PeekConsoleInput /// /// /// /// handle for the console where input is peeked /// /// /// /// /// array where data read are stored /// /// /// /// /// actual number of input records peeked /// /// /// /// If Win32's PeekConsoleInput fails /// 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; } /// /// Wraps Win32 GetNumberOfConsoleInputEvents /// /// /// /// handle for the console where the number of console input events is obtained /// /// /// /// /// number of console input events /// /// /// /// If Win32's GetNumberOfConsoleInputEvents fails /// 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; } /// /// Wraps Win32 FlushConsoleInputBuffer /// /// /// /// handle for the console where the input buffer is flushed /// /// /// /// If Win32's FlushConsoleInputBuffer fails /// 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 /// /// Wraps Win32 GetConsoleScreenBufferInfo /// Returns Console Screen Buffer Info /// /// /// /// Handle for the console where the screen buffer info is obtained /// /// /// /// /// info about the screen buffer. See the definition of CONSOLE_SCREEN_BUFFER_INFO /// /// /// /// If Win32's GetConsoleScreenBufferInfo fails /// 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; } /// /// set the output buffer's size /// /// /// /// /// If Win32's SetConsoleScreenBufferSize fails /// 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; } /// /// Wrap32 WriteConsoleOutput. /// This wrapper is not limited to 64K or 8K CHAR_INFO to which Win32's WriteConsoleOutput /// is constrained. /// /// /// /// handle for the console where output is written /// /// /// /// /// location on screen buffer where writing starts /// /// /// /// /// 2D array of cells. Caller needs to ensure that the array is 2D. /// /// /// /// If Win32's GetConsoleScreenBufferInfo fails /// If there is not enough memory to complete calls to Win32's WriteConsoleOutput /// /// /// If is null /// /// /// If it is illegal to write to the output buffer /// 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 sameEdgeAreas = new List(); 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 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 } /// /// Check the existing screen columns left and right of areas to be written /// /// /// /// must be within the screen buffer /// /// /// /// /// /// /// If it is illegal to write at /// /// /// If there is not enough memory to complete calls to Win32's ReadConsoleOutput /// [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 because 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 } /// /// Wrap32 ReadConsoleOutput /// This wrapper is not limited to 64K or 8K CHAR_INFO to which Win32's ReadConsoleOutput /// is constrained. /// /// /// /// handle for the console where output is read /// /// /// /// /// location on screen buffer where reading begins /// /// /// /// /// indicates the area in where the data read /// is stored. /// /// /// /// /// this is ref because the bounds and size of the array are needed. /// /// /// /// If there is not enough memory to complete calls to Win32's ReadConsoleOutput /// 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 /// /// 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 /// /// /// /// /// 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; } /// /// Can handle reading CJK characters, but the left and right edges are not checked /// /// /// /// /// /// /// /// If there is not enough memory to complete calls to Win32's ReadConsoleOutput /// 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++; } } /// /// Wraps Win32 FillConsoleOutputCharacter /// /// /// /// handle for the console where output is filled /// /// /// /// /// character to fill the console output /// /// /// /// /// number of times to write character /// /// /// /// /// location on screen buffer where writing starts /// /// /// /// If Win32's FillConsoleOutputCharacter fails /// 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. } /// /// Wraps Win32 FillConsoleOutputAttribute /// /// /// /// handle for the console where output is filled /// /// /// /// /// attribute to fill the console output /// /// /// /// /// number of times to write attribute /// /// /// /// /// location on screen buffer where writing starts /// /// /// /// If Win32's FillConsoleOutputAttribute fails /// 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; } } /// /// Wrap Win32 ScrollConsoleScreenBuffer /// /// /// /// handle for the console where screen buffer is scrolled /// /// /// /// /// area to be scrolled /// /// /// /// /// area to be updated after scrolling /// /// /// /// /// location to which the top left corner of scrollRectangle move /// /// /// /// /// character and attribute to fill the area vacated by the scroll /// /// /// /// If Win32's ScrollConsoleScreenBuffer fails /// 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 /// /// Wraps Win32 SetConsoleWindowInfo /// /// /// /// handle for the console where window info is set /// /// /// /// /// 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 /// /// /// /// /// specify the size and position of the console screen buffer's window /// /// /// /// If Win32's SetConsoleWindowInfo fails /// 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; } } /// /// Wraps Win32 GetLargestConsoleWindowSize /// /// /// /// handle for the console for which the largest window size is obtained /// /// /// /// /// the largest window size /// /// /// /// If Win32's GetLargestConsoleWindowSize fails /// 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); } /// /// Wraps Win32 GetConsoleTitle. 1K is the safe limit experimentally. The 64K limit /// found in the docs is disregarded because it is essentially meaningless. /// /// /// /// a string for the title of the window /// /// /// /// If Win32's GetConsoleTitle fails /// 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(); } /// /// Wraps Win32 SetConsoleTitle /// /// /// /// a string for the title of the window /// /// /// /// If Win32's SetConsoleTitle fails /// 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 /// /// 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. /// 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; } } } /// /// Supported Lucida code pages obtained from Font group. /// Contacts: alib, judysa, simonda /// private static HashSet LucidaSupportedCodePages = new HashSet() { 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 /// /// /// Wrap Win32 WriteConsole /// /// /// /// /// handle for the console where the string is written /// /// /// /// /// string that is written /// /// /// /// /// if the Win32's WriteConsole fails /// /// 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; } } } /// /// Wraps Win32 SetConsoleTextAttribute /// /// /// /// handle for the console where text attribute is set /// /// /// /// /// text attribute to set the console /// /// /// /// /// if the Win32's SetConsoleTextAttribute fails /// /// 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 /// /// From IsConsoleFullWidth in \windows\core\ntcon\server\dbcs.c /// Gets the CharSet for a code page /// /// /// The CharSet corresponding to the codePage; defaults to OEM_CHARSET (255) 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; } /// /// 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. /// /// /// window handle /// handle to DC; it is not released by this method /// /// /// 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); } /// /// From IsConsoleFullWidth in \windows\core\ntcon\server\dbcs.c /// /// /// /// [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; } /// /// From IsConsoleFullWidth in \windows\core\ntcon\server\dbcs.c /// /// [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); } } } /// /// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese /// /// /// 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 /// /// Wraps Win32 SetConsoleCursorPosition /// /// /// /// handle for the console where cursor position is set /// /// /// /// /// location to which the cursor will be set /// /// /// /// If Win32's SetConsoleCursorPosition fails /// 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; } } /// /// Wraps Win32 GetConsoleCursorInfo /// /// /// /// handle for the console where cursor info is obtained /// /// /// /// /// cursor info /// /// /// /// If Win32's GetConsoleCursorInfo fails /// 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; } /// /// Wraps Win32 SetConsoleCursorInfo /// /// /// /// handle for the console where cursor info is set /// /// /// /// /// cursor info to set the cursor /// /// /// /// If Win32's SetConsoleCursorInfo fails /// 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 /// /// Helper function to create the proper HostException /// /// /// /// /// /// private static 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()); if (numberOfSuccessfulEvents == 0) { int err = Marshal.GetLastWin32Error(); HostException e = CreateHostException(err, "SendKeyPressInput", ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SendKeyPressInputExceptionTemplate); throw e; } } #endregion SendInput /// /// /// Class to hold the Native Methods used in this file enclosing class. /// /// 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 #endif