mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
1120 lines
36 KiB
C#
1120 lines
36 KiB
C#
/********************************************************************++
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
--********************************************************************/
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using Microsoft.Win32.SafeHandles;
|
|
|
|
namespace Microsoft.PowerShell.Internal
|
|
{
|
|
#pragma warning disable 1591
|
|
|
|
internal static class NativeMethods
|
|
{
|
|
public const uint MAPVK_VK_TO_VSC = 0x00;
|
|
public const uint MAPVK_VSC_TO_VK = 0x01;
|
|
public const uint MAPVK_VK_TO_CHAR = 0x02;
|
|
|
|
public const byte VK_SHIFT = 0x10;
|
|
public const byte VK_CONTROL = 0x11;
|
|
public const byte VK_ALT = 0x12;
|
|
public const uint MENU_IS_ACTIVE = 0x01;
|
|
public const uint MENU_IS_INACTIVE = 0x00; // windows key
|
|
|
|
public const uint ENABLE_PROCESSED_INPUT = 0x0001;
|
|
public const uint ENABLE_LINE_INPUT = 0x0002;
|
|
public const uint ENABLE_WINDOW_INPUT = 0x0008;
|
|
public const uint ENABLE_MOUSE_INPUT = 0x0010;
|
|
|
|
public const int FontTypeMask = 0x06;
|
|
public const int TrueTypeFont = 0x04;
|
|
|
|
internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); // WinBase.h
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
public static extern IntPtr GetStdHandle(uint handleId);
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode);
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode);
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
public static extern bool ScrollConsoleScreenBuffer(IntPtr hConsoleOutput,
|
|
ref SMALL_RECT lpScrollRectangle,
|
|
IntPtr lpClipRectangle,
|
|
COORD dwDestinationOrigin,
|
|
ref CHAR_INFO lpFill);
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
public static extern bool WriteConsole(IntPtr hConsoleOutput, string lpBuffer, uint nNumberOfCharsToWrite, out uint lpNumberOfCharsWritten, IntPtr lpReserved);
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
public static extern bool SetConsoleCtrlHandler(BreakHandler handlerRoutine, bool add);
|
|
|
|
[DllImport("KERNEL32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
public static extern bool WriteConsoleOutput(IntPtr consoleOutput, CHAR_INFO[] buffer, COORD bufferSize, COORD bufferCoord, ref SMALL_RECT writeRegion);
|
|
|
|
[DllImport("KERNEL32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
public static extern bool ReadConsoleOutput(IntPtr consoleOutput, [Out] CHAR_INFO[] buffer, COORD bufferSize, COORD bufferCoord, ref SMALL_RECT readRegion);
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
public static extern uint MapVirtualKey(uint uCode, uint uMapType);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
|
public static extern int ToUnicode(uint uVirtKey, uint uScanCode, byte[] lpKeyState,
|
|
[MarshalAs(UnmanagedType.LPArray)] [Out] char[] chars, int charMaxCount, uint flags);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
public static extern short VkKeyScan(char @char);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)]
|
|
public static extern uint GetConsoleOutputCP();
|
|
|
|
[DllImport("User32.dll", SetLastError = false, CharSet = CharSet.Unicode)]
|
|
public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern IntPtr GetConsoleWindow();
|
|
|
|
[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern IntPtr GetDC(IntPtr hwnd);
|
|
|
|
[DllImport("GDI32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern bool TranslateCharsetInfo(IntPtr src, out CHARSETINFO Cs, uint options);
|
|
|
|
[DllImport("GDI32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern bool GetTextMetrics(IntPtr hdc, out TEXTMETRIC tm);
|
|
|
|
[DllImport("GDI32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern bool GetCharWidth32(IntPtr hdc, uint first, uint last, out int width);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern IntPtr CreateFile
|
|
(
|
|
string fileName,
|
|
uint desiredAccess,
|
|
uint ShareModes,
|
|
IntPtr securityAttributes,
|
|
uint creationDisposition,
|
|
uint flagsAndAttributes,
|
|
IntPtr templateFileWin32Handle
|
|
);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
internal static extern bool GetCurrentConsoleFontEx(IntPtr consoleOutput, bool bMaximumWindow, ref CONSOLE_FONT_INFO_EX consoleFontInfo);
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct COLORREF
|
|
{
|
|
internal uint ColorDWORD;
|
|
|
|
internal uint R
|
|
{
|
|
get { return ColorDWORD & 0xff; }
|
|
}
|
|
|
|
internal uint G
|
|
{
|
|
get { return (ColorDWORD >> 8) & 0xff; }
|
|
}
|
|
|
|
internal uint B
|
|
{
|
|
get { return (ColorDWORD >> 16) & 0xff; }
|
|
}
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct CONSOLE_SCREEN_BUFFER_INFO_EX
|
|
{
|
|
internal int cbSize;
|
|
internal COORD dwSize;
|
|
internal COORD dwCursorPosition;
|
|
internal ushort wAttributes;
|
|
internal SMALL_RECT srWindow;
|
|
internal COORD dwMaximumWindowSize;
|
|
internal ushort wPopupAttributes;
|
|
internal bool bFullscreenSupported;
|
|
internal COLORREF Black;
|
|
internal COLORREF DarkBlue;
|
|
internal COLORREF DarkGreen;
|
|
internal COLORREF DarkCyan;
|
|
internal COLORREF DarkRed;
|
|
internal COLORREF DarkMagenta;
|
|
internal COLORREF DarkYellow;
|
|
internal COLORREF Gray;
|
|
internal COLORREF DarkGray;
|
|
internal COLORREF Blue;
|
|
internal COLORREF Green;
|
|
internal COLORREF Cyan;
|
|
internal COLORREF Red;
|
|
internal COLORREF Magenta;
|
|
internal COLORREF Yellow;
|
|
internal COLORREF White;
|
|
}
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
internal static extern bool GetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput,
|
|
ref CONSOLE_SCREEN_BUFFER_INFO_EX csbe);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
internal static extern bool SetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput,
|
|
ref CONSOLE_SCREEN_BUFFER_INFO_EX csbe);
|
|
}
|
|
|
|
public delegate bool BreakHandler(ConsoleBreakSignal ConsoleBreakSignal);
|
|
|
|
public enum ConsoleBreakSignal : uint
|
|
{
|
|
CtrlC = 0,
|
|
CtrlBreak = 1,
|
|
Close = 2,
|
|
Logoff = 5,
|
|
Shutdown = 6,
|
|
None = 255,
|
|
}
|
|
|
|
internal enum CHAR_INFO_Attributes : ushort
|
|
{
|
|
COMMON_LVB_LEADING_BYTE = 0x0100,
|
|
COMMON_LVB_TRAILING_BYTE = 0x0200
|
|
}
|
|
|
|
public enum StandardHandleId : uint
|
|
{
|
|
Error = unchecked((uint)-12),
|
|
Output = unchecked((uint)-11),
|
|
Input = unchecked((uint)-10),
|
|
}
|
|
|
|
[Flags]
|
|
internal enum AccessQualifiers : uint
|
|
{
|
|
// From winnt.h
|
|
GenericRead = 0x80000000,
|
|
GenericWrite = 0x40000000
|
|
}
|
|
|
|
internal enum CreationDisposition : uint
|
|
{
|
|
// From winbase.h
|
|
CreateNew = 1,
|
|
CreateAlways = 2,
|
|
OpenExisting = 3,
|
|
OpenAlways = 4,
|
|
TruncateExisting = 5
|
|
}
|
|
|
|
[Flags]
|
|
internal enum ShareModes : uint
|
|
{
|
|
// From winnt.h
|
|
ShareRead = 0x00000001,
|
|
ShareWrite = 0x00000002
|
|
}
|
|
|
|
public struct SMALL_RECT
|
|
{
|
|
public short Left;
|
|
public short Top;
|
|
public short Right;
|
|
public short Bottom;
|
|
|
|
[ExcludeFromCodeCoverage]
|
|
public override string ToString()
|
|
{
|
|
return String.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", Left, Top, Right, Bottom);
|
|
}
|
|
}
|
|
|
|
internal struct COORD
|
|
{
|
|
public short X;
|
|
public short Y;
|
|
|
|
[ExcludeFromCodeCoverage]
|
|
public override string ToString()
|
|
{
|
|
return String.Format(CultureInfo.InvariantCulture, "{0},{1}", X, Y);
|
|
}
|
|
}
|
|
|
|
[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 uint fsUsb0;
|
|
internal uint fsUsb1;
|
|
internal uint fsUsb2;
|
|
internal uint fsUsb3;
|
|
// fsCsb*: A 64-bit, code-page bitfield (CPB) that identifies a specific character set or code page.
|
|
internal uint fsCsb0;
|
|
internal uint 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;
|
|
}
|
|
|
|
[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;
|
|
}
|
|
|
|
public struct BufferChar
|
|
{
|
|
public char UnicodeChar;
|
|
public ConsoleColor ForegroundColor;
|
|
public ConsoleColor BackgroundColor;
|
|
#if !UNIX
|
|
public bool IsLeadByte;
|
|
public bool IsTrailByte;
|
|
#endif
|
|
public bool Inverse;
|
|
|
|
#if !UNIX
|
|
public CHAR_INFO ToCharInfo()
|
|
{
|
|
int fg = (int) ForegroundColor;
|
|
int bg = (int) BackgroundColor;
|
|
|
|
if (fg < 0 || fg > 0xf || bg < 0 || bg > 0xf)
|
|
throw new InvalidOperationException();
|
|
|
|
if (Inverse)
|
|
{
|
|
// TODO: check $host.UI.SupportsVirtualTerminal and maybe set Inverse instead (it does look weird)
|
|
fg = fg ^ 7;
|
|
bg = bg ^ 7;
|
|
}
|
|
ushort attrs = (ushort)(fg | (bg << 4));
|
|
if (IsLeadByte)
|
|
attrs |= (ushort)CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE;
|
|
if (IsTrailByte)
|
|
attrs |= (ushort)CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE;
|
|
CHAR_INFO result = new CHAR_INFO
|
|
{
|
|
UnicodeChar = UnicodeChar,
|
|
Attributes = attrs,
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
public static BufferChar FromCharInfo(CHAR_INFO charInfo)
|
|
{
|
|
BufferChar result = new BufferChar
|
|
{
|
|
UnicodeChar = (char) charInfo.UnicodeChar,
|
|
ForegroundColor = (ConsoleColor)(charInfo.Attributes & 0xf),
|
|
BackgroundColor = (ConsoleColor)((charInfo.Attributes & 0x00f0) >> 4),
|
|
};
|
|
return result;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public struct CHAR_INFO
|
|
{
|
|
public ushort UnicodeChar;
|
|
public ushort Attributes;
|
|
}
|
|
|
|
internal static class ConsoleKeyInfoExtension
|
|
{
|
|
public static string ToGestureString(this ConsoleKeyInfo key)
|
|
{
|
|
var mods = key.Modifiers;
|
|
|
|
var sb = new StringBuilder();
|
|
if (key.Modifiers.HasFlag(ConsoleModifiers.Control))
|
|
{
|
|
sb.Append("Ctrl");
|
|
}
|
|
if (key.Modifiers.HasFlag(ConsoleModifiers.Alt))
|
|
{
|
|
if (sb.Length > 0)
|
|
sb.Append("+");
|
|
sb.Append("Alt");
|
|
}
|
|
|
|
#if UNIX
|
|
if (sb.Length > 0)
|
|
sb.Append("+");
|
|
// TODO: find better way to map these characters to something more friendly
|
|
if ((key.Key >= ConsoleKey.D0 && key.Key <= ConsoleKey.D9)
|
|
|| (key.Key >= ConsoleKey.Oem1 && key.Key <= ConsoleKey.Oem8))
|
|
{
|
|
sb.Append(key.KeyChar);
|
|
}
|
|
else
|
|
{
|
|
if (key.Modifiers.HasFlag(ConsoleModifiers.Shift))
|
|
{
|
|
sb.Append("Shift+");
|
|
}
|
|
sb.Append(key.Key);
|
|
}
|
|
#else
|
|
char c = ConsoleKeyChordConverter.GetCharFromConsoleKey(key.Key,
|
|
(mods & ConsoleModifiers.Shift) != 0 ? ConsoleModifiers.Shift : 0);
|
|
if (char.IsControl(c) || char.IsWhiteSpace(c))
|
|
{
|
|
if (key.Modifiers.HasFlag(ConsoleModifiers.Shift))
|
|
{
|
|
if (sb.Length > 0)
|
|
sb.Append("+");
|
|
sb.Append("Shift");
|
|
}
|
|
if (sb.Length > 0)
|
|
sb.Append("+");
|
|
sb.Append(key.Key);
|
|
}
|
|
else
|
|
{
|
|
if (sb.Length > 0)
|
|
sb.Append("+");
|
|
sb.Append(c);
|
|
}
|
|
#endif
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
#if !UNIX
|
|
internal class ConhostConsole : IConsole
|
|
{
|
|
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
|
|
private IntPtr _hwnd = (IntPtr)0;
|
|
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
|
|
private IntPtr _hDC = (IntPtr)0;
|
|
private uint _codePage;
|
|
private bool _istmInitialized = false;
|
|
private TEXTMETRIC _tm = new TEXTMETRIC();
|
|
private bool _trueTypeInUse = false;
|
|
|
|
private readonly Lazy<SafeFileHandle> _outputHandle = new Lazy<SafeFileHandle>(() =>
|
|
{
|
|
// We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
|
|
var handle = NativeMethods.CreateFile(
|
|
"CONOUT$",
|
|
(UInt32)(AccessQualifiers.GenericRead | AccessQualifiers.GenericWrite),
|
|
(UInt32)ShareModes.ShareWrite,
|
|
(IntPtr)0,
|
|
(UInt32)CreationDisposition.OpenExisting,
|
|
0,
|
|
(IntPtr)0);
|
|
|
|
if (handle == NativeMethods.INVALID_HANDLE_VALUE)
|
|
{
|
|
int err = Marshal.GetLastWin32Error();
|
|
Win32Exception innerException = new Win32Exception(err);
|
|
throw new Exception("Failed to retreive the input console handle.", innerException);
|
|
}
|
|
|
|
return new SafeFileHandle(handle, true);
|
|
}
|
|
);
|
|
|
|
private readonly Lazy<SafeFileHandle> _inputHandle = new Lazy<SafeFileHandle>(() =>
|
|
{
|
|
// We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
|
|
var handle = NativeMethods.CreateFile(
|
|
"CONIN$",
|
|
(UInt32)(AccessQualifiers.GenericRead | AccessQualifiers.GenericWrite),
|
|
(UInt32)ShareModes.ShareWrite,
|
|
(IntPtr)0,
|
|
(UInt32)CreationDisposition.OpenExisting,
|
|
0,
|
|
(IntPtr)0);
|
|
|
|
if (handle == NativeMethods.INVALID_HANDLE_VALUE)
|
|
{
|
|
int err = Marshal.GetLastWin32Error();
|
|
Win32Exception innerException = new Win32Exception(err);
|
|
throw new Exception("Failed to retreive the input console handle.", innerException);
|
|
}
|
|
|
|
return new SafeFileHandle(handle, true);
|
|
});
|
|
|
|
public object GetConsoleInputMode()
|
|
{
|
|
uint mode;
|
|
var handle = _inputHandle.Value.DangerousGetHandle();
|
|
NativeMethods.GetConsoleMode(handle, out mode);
|
|
return mode;
|
|
}
|
|
|
|
public void SetConsoleInputMode(object modeObj)
|
|
{
|
|
if (modeObj is uint)
|
|
{
|
|
// Clear a couple flags so we can actually receive certain keys:
|
|
// ENABLE_PROCESSED_INPUT - enables Ctrl+C
|
|
// ENABLE_LINE_INPUT - enables Ctrl+S
|
|
// Also clear a couple flags so we don't mask the input that we ignore:
|
|
// ENABLE_MOUSE_INPUT - mouse events
|
|
// ENABLE_WINDOW_INPUT - window resize events
|
|
var mode = (uint)modeObj &
|
|
~(NativeMethods.ENABLE_PROCESSED_INPUT |
|
|
NativeMethods.ENABLE_LINE_INPUT |
|
|
NativeMethods.ENABLE_WINDOW_INPUT |
|
|
NativeMethods.ENABLE_MOUSE_INPUT);
|
|
|
|
var handle = _inputHandle.Value.DangerousGetHandle();
|
|
NativeMethods.SetConsoleMode(handle, mode);
|
|
}
|
|
}
|
|
|
|
public void RestoreConsoleInputMode(object modeObj)
|
|
{
|
|
if (modeObj is uint)
|
|
{
|
|
var handle = _inputHandle.Value.DangerousGetHandle();
|
|
NativeMethods.SetConsoleMode(handle, (uint)modeObj);
|
|
}
|
|
}
|
|
|
|
public ConsoleKeyInfo ReadKey()
|
|
{
|
|
return Console.ReadKey(true);
|
|
}
|
|
|
|
public bool KeyAvailable
|
|
{
|
|
get { return Console.KeyAvailable; }
|
|
}
|
|
|
|
public int CursorLeft
|
|
{
|
|
get { return Console.CursorLeft; }
|
|
set { Console.CursorLeft = value; }
|
|
}
|
|
|
|
public int CursorTop
|
|
{
|
|
get { return Console.CursorTop; }
|
|
set { Console.CursorTop = value; }
|
|
}
|
|
|
|
public int CursorSize
|
|
{
|
|
get { return Console.CursorSize; }
|
|
set { Console.CursorSize = value; }
|
|
}
|
|
|
|
public int BufferWidth
|
|
{
|
|
get { return Console.BufferWidth; }
|
|
set { Console.BufferWidth = value; }
|
|
}
|
|
|
|
public int BufferHeight
|
|
{
|
|
get { return Console.BufferHeight; }
|
|
set { Console.BufferHeight = value; }
|
|
}
|
|
|
|
public int WindowWidth
|
|
{
|
|
get { return Console.WindowWidth; }
|
|
set { Console.WindowWidth = value; }
|
|
}
|
|
|
|
public int WindowHeight
|
|
{
|
|
get { return Console.WindowHeight; }
|
|
set { Console.WindowHeight = value; }
|
|
}
|
|
|
|
public int WindowTop
|
|
{
|
|
get { return Console.WindowTop; }
|
|
set { Console.WindowTop = value; }
|
|
}
|
|
|
|
public ConsoleColor BackgroundColor
|
|
{
|
|
get { return Console.BackgroundColor; }
|
|
set { Console.BackgroundColor = value; }
|
|
}
|
|
|
|
public ConsoleColor ForegroundColor
|
|
{
|
|
get { return Console.ForegroundColor; }
|
|
set { Console.ForegroundColor = value; }
|
|
}
|
|
|
|
public void SetWindowPosition(int left, int top)
|
|
{
|
|
Console.SetWindowPosition(left, top);
|
|
}
|
|
|
|
public void SetCursorPosition(int left, int top)
|
|
{
|
|
Console.SetCursorPosition(left, top);
|
|
}
|
|
|
|
public void Write(string value)
|
|
{
|
|
Console.Write(value);
|
|
}
|
|
|
|
public void WriteLine(string value)
|
|
{
|
|
Console.WriteLine(value);
|
|
}
|
|
|
|
public void WriteBufferLines(BufferChar[] buffer, ref int top)
|
|
{
|
|
WriteBufferLines(buffer, ref top, true);
|
|
}
|
|
|
|
public void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible)
|
|
{
|
|
int bufferWidth = Console.BufferWidth;
|
|
int bufferLineCount = buffer.Length / bufferWidth;
|
|
if ((top + bufferLineCount) > Console.BufferHeight)
|
|
{
|
|
var scrollCount = (top + bufferLineCount) - Console.BufferHeight;
|
|
ScrollBuffer(scrollCount);
|
|
top -= scrollCount;
|
|
}
|
|
|
|
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
|
|
var bufferSize = new COORD
|
|
{
|
|
X = (short) bufferWidth,
|
|
Y = (short) bufferLineCount
|
|
};
|
|
var bufferCoord = new COORD {X = 0, Y = 0};
|
|
var bottom = top + bufferLineCount - 1;
|
|
var writeRegion = new SMALL_RECT
|
|
{
|
|
Top = (short) top,
|
|
Left = 0,
|
|
Bottom = (short) bottom,
|
|
Right = (short) (bufferWidth - 1)
|
|
};
|
|
NativeMethods.WriteConsoleOutput(handle, ToCharInfoBuffer(buffer),
|
|
bufferSize, bufferCoord, ref writeRegion);
|
|
|
|
// Now make sure the bottom line is visible
|
|
if (ensureBottomLineVisible &&
|
|
(bottom >= (Console.WindowTop + Console.WindowHeight)))
|
|
{
|
|
Console.CursorTop = bottom;
|
|
}
|
|
}
|
|
|
|
public void ScrollBuffer(int lines)
|
|
{
|
|
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
|
|
|
|
var scrollRectangle = new SMALL_RECT
|
|
{
|
|
Top = (short) lines,
|
|
Left = 0,
|
|
Bottom = (short)(Console.BufferHeight - 1),
|
|
Right = (short)Console.BufferWidth
|
|
};
|
|
var destinationOrigin = new COORD {X = 0, Y = 0};
|
|
var fillChar = new CHAR_INFO
|
|
{
|
|
UnicodeChar = ' ',
|
|
Attributes = (ushort)((int)Console.ForegroundColor | ((int)Console.BackgroundColor << 4))
|
|
};
|
|
NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar);
|
|
}
|
|
|
|
public BufferChar[] ReadBufferLines(int top, int count)
|
|
{
|
|
var result = new CHAR_INFO[BufferWidth * count];
|
|
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
|
|
|
|
var readBufferSize = new COORD {
|
|
X = (short)BufferWidth,
|
|
Y = (short)count};
|
|
var readBufferCoord = new COORD {X = 0, Y = 0};
|
|
var readRegion = new SMALL_RECT
|
|
{
|
|
Top = (short)top,
|
|
Left = 0,
|
|
Bottom = (short)(top + count),
|
|
Right = (short)(BufferWidth - 1)
|
|
};
|
|
NativeMethods.ReadConsoleOutput(handle, result,
|
|
readBufferSize, readBufferCoord, ref readRegion);
|
|
|
|
return ToBufferCharBuffer(result);
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
|
|
Justification = "Then the API we pass the handle to will return an error if it is invalid. They are not exposed.")]
|
|
internal static CONSOLE_FONT_INFO_EX GetConsoleFontInfo(SafeFileHandle consoleHandle)
|
|
{
|
|
|
|
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();
|
|
Win32Exception innerException = new Win32Exception(err);
|
|
throw new Exception("Failed to get console font information.", innerException);
|
|
}
|
|
return fontInfo;
|
|
}
|
|
|
|
public int LengthInBufferCells(char c)
|
|
{
|
|
if (!IsCJKOutputCodePage() || !_trueTypeInUse)
|
|
return 1;
|
|
|
|
return LengthInBufferCellsFE(c);
|
|
}
|
|
|
|
internal 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;
|
|
}
|
|
|
|
internal uint CodePageToCharSet()
|
|
{
|
|
CHARSETINFO csi;
|
|
const uint TCI_SRCCODEPAGE = 2;
|
|
const uint OEM_CHARSET = 255;
|
|
if (!NativeMethods.TranslateCharsetInfo((IntPtr)_codePage, out csi, TCI_SRCCODEPAGE))
|
|
{
|
|
csi.ciCharset = OEM_CHARSET;
|
|
}
|
|
return csi.ciCharset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese
|
|
/// </summary>
|
|
/// <returns>true if it is CJK code page; otherwise, false.</returns>
|
|
internal bool IsCJKOutputCodePage()
|
|
{
|
|
return _codePage == 932 || // Japanese
|
|
_codePage == 936 || // Simplified Chinese
|
|
_codePage == 949 || // Korean
|
|
_codePage == 950; // Traditional Chinese
|
|
}
|
|
|
|
internal bool IsAvailableFarEastCodePage()
|
|
{
|
|
uint charSet = CodePageToCharSet();
|
|
return IsAnyDBCSCharSet(charSet);
|
|
}
|
|
|
|
internal int LengthInBufferCellsFE(char c)
|
|
{
|
|
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
|
|
{
|
|
/* Unknown character: need to use GDI*/
|
|
if (_hDC == (IntPtr)0)
|
|
{
|
|
_hwnd = NativeMethods.GetConsoleWindow();
|
|
if ((IntPtr)0 == _hwnd)
|
|
{
|
|
return 1;
|
|
}
|
|
_hDC = NativeMethods.GetDC(_hwnd);
|
|
if ((IntPtr)0 == _hDC)
|
|
{
|
|
//Don't throw exception so that output can continue
|
|
return 1;
|
|
}
|
|
}
|
|
bool result = true;
|
|
if (!_istmInitialized)
|
|
{
|
|
result = NativeMethods.GetTextMetrics(_hDC, out _tm);
|
|
if (!result)
|
|
{
|
|
return 1;
|
|
}
|
|
_istmInitialized = true;
|
|
}
|
|
int width;
|
|
result = NativeMethods.GetCharWidth32(_hDC, (uint)c, (uint)c, out width);
|
|
if (!result)
|
|
{
|
|
return 1;
|
|
}
|
|
if (width >= _tm.tmMaxCharWidth)
|
|
{
|
|
return 2;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
public void StartRender()
|
|
{
|
|
_codePage = NativeMethods.GetConsoleOutputCP();
|
|
_istmInitialized = false;
|
|
var consoleHandle = _outputHandle.Value;
|
|
CONSOLE_FONT_INFO_EX fontInfo = ConhostConsole.GetConsoleFontInfo(consoleHandle);
|
|
int fontType = fontInfo.FontFamily & NativeMethods.FontTypeMask;
|
|
_trueTypeInUse = (fontType & NativeMethods.TrueTypeFont) == NativeMethods.TrueTypeFont;
|
|
|
|
}
|
|
|
|
public void EndRender()
|
|
{
|
|
if (_hwnd != (IntPtr)0 && _hDC != (IntPtr)0)
|
|
{
|
|
NativeMethods.ReleaseDC(_hwnd, _hDC);
|
|
}
|
|
}
|
|
|
|
private CHAR_INFO[] cachedBuffer;
|
|
private CHAR_INFO[] ToCharInfoBuffer(BufferChar[] buffer)
|
|
{
|
|
if (cachedBuffer == null || cachedBuffer.Length != buffer.Length)
|
|
{
|
|
cachedBuffer = new CHAR_INFO[buffer.Length];
|
|
}
|
|
|
|
for (int i = 0; i < buffer.Length; i++)
|
|
{
|
|
cachedBuffer[i] = buffer[i].ToCharInfo();
|
|
}
|
|
|
|
return cachedBuffer;
|
|
}
|
|
|
|
private BufferChar[] ToBufferCharBuffer(CHAR_INFO[] buffer)
|
|
{
|
|
var result = new BufferChar[buffer.Length];
|
|
|
|
for (int i = 0; i < buffer.Length; i++)
|
|
{
|
|
result[i] = BufferChar.FromCharInfo(buffer[i]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
internal class TTYConsole : IConsole
|
|
{
|
|
public object GetConsoleInputMode()
|
|
{
|
|
return Console.TreatControlCAsInput;
|
|
}
|
|
|
|
public void SetConsoleInputMode(object modeObj)
|
|
{
|
|
Console.TreatControlCAsInput = true;
|
|
}
|
|
|
|
public void RestoreConsoleInputMode(object modeObj)
|
|
{
|
|
if (modeObj is bool)
|
|
{
|
|
Console.TreatControlCAsInput = (bool)modeObj;
|
|
}
|
|
}
|
|
|
|
public ConsoleKeyInfo ReadKey()
|
|
{
|
|
return Console.ReadKey(true);
|
|
}
|
|
|
|
public bool KeyAvailable
|
|
{
|
|
get { return Console.KeyAvailable; }
|
|
}
|
|
|
|
public int CursorLeft
|
|
{
|
|
get { return Console.CursorLeft; }
|
|
set { Console.CursorLeft = value; }
|
|
}
|
|
|
|
public int CursorTop
|
|
{
|
|
get { return Console.CursorTop; }
|
|
set { Console.CursorTop = value; }
|
|
}
|
|
|
|
public int CursorSize
|
|
{
|
|
get { return Console.CursorSize; }
|
|
set { Console.CursorSize = value; }
|
|
}
|
|
|
|
public int BufferWidth
|
|
{
|
|
get { return Console.BufferWidth; }
|
|
set { Console.BufferWidth = value; }
|
|
}
|
|
|
|
public int BufferHeight
|
|
{
|
|
get { return Console.BufferHeight; }
|
|
set { Console.BufferHeight = value; }
|
|
}
|
|
|
|
public int WindowWidth
|
|
{
|
|
get { return Console.WindowWidth; }
|
|
set { Console.WindowWidth = value; }
|
|
}
|
|
|
|
public int WindowHeight
|
|
{
|
|
get { return Console.WindowHeight; }
|
|
set { Console.WindowHeight = value; }
|
|
}
|
|
|
|
public int WindowTop
|
|
{
|
|
get { return Console.WindowTop; }
|
|
set { Console.WindowTop = value; }
|
|
}
|
|
|
|
public ConsoleColor BackgroundColor
|
|
{
|
|
get { return Console.BackgroundColor; }
|
|
set { Console.BackgroundColor = value; }
|
|
}
|
|
|
|
public ConsoleColor ForegroundColor
|
|
{
|
|
get { return Console.ForegroundColor; }
|
|
set { Console.ForegroundColor = value; }
|
|
}
|
|
|
|
public void SetWindowPosition(int left, int top)
|
|
{
|
|
Console.SetWindowPosition(left, top);
|
|
}
|
|
|
|
public void SetCursorPosition(int left, int top)
|
|
{
|
|
Console.SetCursorPosition(left, top);
|
|
}
|
|
|
|
public void Write(string value)
|
|
{
|
|
Console.Write(value);
|
|
}
|
|
|
|
public void WriteLine(string value)
|
|
{
|
|
Console.WriteLine(value);
|
|
}
|
|
|
|
public void WriteBufferLines(BufferChar[] buffer, ref int top)
|
|
{
|
|
WriteBufferLines(buffer, ref top, true);
|
|
}
|
|
|
|
public void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible)
|
|
{
|
|
int bufferWidth = Console.BufferWidth;
|
|
int bufferLineCount = buffer.Length / bufferWidth;
|
|
if ((top + bufferLineCount) > Console.BufferHeight)
|
|
{
|
|
var scrollCount = (top + bufferLineCount) - Console.BufferHeight;
|
|
ScrollBuffer(scrollCount);
|
|
top -= scrollCount;
|
|
}
|
|
|
|
ConsoleColor foregroundColor = Console.ForegroundColor;
|
|
ConsoleColor backgroundColor = Console.BackgroundColor;
|
|
|
|
Console.SetCursorPosition(0, (top>=0) ? top : 0);
|
|
|
|
for (int i = 0; i < buffer.Length; ++i)
|
|
{
|
|
// TODO: use escape sequences for better perf
|
|
Console.ForegroundColor = buffer[i].ForegroundColor;
|
|
Console.BackgroundColor = buffer[i].BackgroundColor;
|
|
|
|
Console.Write(buffer[i].UnicodeChar);
|
|
}
|
|
|
|
Console.BackgroundColor = backgroundColor;
|
|
Console.ForegroundColor = foregroundColor;
|
|
}
|
|
|
|
public void ScrollBuffer(int lines)
|
|
{
|
|
for (int i=0; i<lines; ++i)
|
|
{
|
|
Console.SetCursorPosition(Console.BufferWidth, Console.BufferHeight - 1);
|
|
Console.WriteLine();
|
|
}
|
|
}
|
|
|
|
public BufferChar[] ReadBufferLines(int top, int count)
|
|
{
|
|
var result = new BufferChar[BufferWidth * count];
|
|
for (int i=0; i<BufferWidth*count; ++i)
|
|
{
|
|
result[i].UnicodeChar = ' ';
|
|
result[i].ForegroundColor = Console.ForegroundColor;
|
|
result[i].BackgroundColor = Console.BackgroundColor;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public int LengthInBufferCells(char c)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
public void StartRender()
|
|
{
|
|
}
|
|
|
|
public void EndRender()
|
|
{
|
|
}
|
|
|
|
// TODO: this is not correct, PSReadline never clears the screen, it should only scroll
|
|
public void Clear()
|
|
{
|
|
Console.Clear();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if CORECLR // TODO: remove if CORECLR adds this attribute back
|
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
|
|
sealed class ExcludeFromCodeCoverageAttribute : Attribute
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#pragma warning restore 1591
|
|
}
|