diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..8b82efec1d --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ +This bug-tracker is monitored by Windows Console development team and other technical types. **We like detail!** + +If you have a feature request, please post to [the UserVoice](https://wpdev.uservoice.com/forums/266908). + +> **Important: When reporting BSODs or security issues, DO NOT attach memory dumps, logs, or traces to Github issues**. Instead, send dumps/traces to secure@microsoft.com, referencing this GitHub issue. + +Please use this form and describe your issue, concisely but precisely, with as much detail as possible + +* Your Windows build number: (Type `ver` at a Windows Command Prompt) + +* What you're doing and what's happening: (Copy & paste specific commands and their output, or include screen shots) + +* What's wrong / what should be happening instead: diff --git a/.gitignore b/.gitignore index 4071eab240..53e32d75ed 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ build/ bld/ [Bb]in/ [Oo]bj/ +[Ll]og/ objfre/ objchk/ @@ -79,14 +80,18 @@ _Chutzpah* ipch/ *.aps *.ncb +*.opendb *.opensdf *.sdf *.cachefile +*.VC.db +*.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx +*.sap # TFS 2012 Local Workspace $tf/ @@ -111,6 +116,7 @@ _TeamCity* # NCrunch _NCrunch_* .*crunch*.local.xml +nCrunchTemp_* # MightyMoose *.mm.* @@ -138,13 +144,16 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -## TODO: Comment the next line if you want to checkin your -## web deploy settings but do note that will include unencrypted -## passwords -#*.pubxml - +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml *.publishproj +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore @@ -153,13 +162,23 @@ publish/ !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets -# Windows Azure Build Output +# Microsoft Azure Build Output csx/ *.build.csdef -# Windows Store app package directory +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored @@ -211,12 +230,25 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt -# LightSwitch generated files -GeneratedArtifacts/ -_Pvt_Extensions/ -ModelManifest.xml -*.opendb -*.db +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml +*.exe # Windows Build System files build*.dbb diff --git a/README-ColorTool.md b/README-ColorTool.md new file mode 100644 index 0000000000..34c06827ea --- /dev/null +++ b/README-ColorTool.md @@ -0,0 +1,28 @@ +# Welcome to the official Windows Console issues & samples repo! + +## Issues + +This repo is monitored by the Windows Console engineering team, and provides a best-effort, informal support option for the community. Your patience is appreciated! + +The Windows Console engineering team greatly appreciate issues containing concise, detailed issues containing repro-steps, and screenshots where appropriate :) + +We also appreciate not +1-ing issues with no additional or actionable information. Please use a reaction to show your support of an existing comment on the thread and/or subscribe to notifications using the button in the sidebar in lieu of providing a low-value comment. + +### Code of Conduct +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact opencode@microsoft.com with any additional questions or comments. + +In addition, the team reserve the right to actively manage issues, closing duplicates or resolved issues, etc., and would appreciate it if you would avoid creating duplicates of existing items by searching issues _before_ filing a new issue. + +## Tools & Samples +You'll also find assorted Console tools, samples, including the following: + +* [ColorTool](https://github.com/Microsoft/Console/tree/master/src/tools/ColorTool) - A tool for changing the color scheme of the Windows console. +* [EchoCon](https://github.com/Microsoft/console/tree/master/samples/ConPTY/EchoCon) - A C++ sample application that illustrates how to use the new Win32 Pseudo Console (ConPTY). +* [MiniTerm](https://github.com/Microsoft/console/tree/master/samples/ConPTY/MiniTerm) - A C# sample terminal that illustrates how to use ConPTY. +* [ReadConsoleInputStream](https://github.com/Microsoft/console/tree/master/samples/ReadConsoleInputStream) - A C# sample console application that shows how to use the console APIs to stream stdin while asynchronously watching for console events (buffer size, viewport size, mouse input etc.) + +### Tool Build Status + +Project|Build Status +---|--- +src/tools/ColorTool|![](https://microsoft.visualstudio.com/_apis/public/build/definitions/c93e867a-8815-43c1-92c4-e7dd5404f1e1/17023/badge) diff --git a/samples/ConPTY/EchoCon/EchoCon.sln b/samples/ConPTY/EchoCon/EchoCon.sln new file mode 100644 index 0000000000..d198128887 --- /dev/null +++ b/samples/ConPTY/EchoCon/EchoCon.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2026 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EchoCon", "EchoCon\EchoCon.vcxproj", "{96274800-9574-423E-892A-909FBE2AC8BE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{556CAA54-33E0-4F99-95C8-0DFD6E8F6C6B}" + ProjectSection(SolutionItems) = preProject + readme.md = readme.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {96274800-9574-423E-892A-909FBE2AC8BE}.Debug|x64.ActiveCfg = Debug|x64 + {96274800-9574-423E-892A-909FBE2AC8BE}.Debug|x64.Build.0 = Debug|x64 + {96274800-9574-423E-892A-909FBE2AC8BE}.Debug|x86.ActiveCfg = Debug|Win32 + {96274800-9574-423E-892A-909FBE2AC8BE}.Debug|x86.Build.0 = Debug|Win32 + {96274800-9574-423E-892A-909FBE2AC8BE}.Release|x64.ActiveCfg = Release|x64 + {96274800-9574-423E-892A-909FBE2AC8BE}.Release|x64.Build.0 = Release|x64 + {96274800-9574-423E-892A-909FBE2AC8BE}.Release|x86.ActiveCfg = Release|Win32 + {96274800-9574-423E-892A-909FBE2AC8BE}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B27C5007-61E2-4080-965D-8C934367BA4F} + EndGlobalSection +EndGlobal diff --git a/samples/ConPTY/EchoCon/EchoCon/EchoCon.cpp b/samples/ConPTY/EchoCon/EchoCon/EchoCon.cpp new file mode 100644 index 0000000000..904ea5b890 --- /dev/null +++ b/samples/ConPTY/EchoCon/EchoCon/EchoCon.cpp @@ -0,0 +1,189 @@ +// EchoCon.cpp : Entry point for the EchoCon Pseudo-Consle sample application. +// Copyright 2018, Microsoft + +#include "stdafx.h" +#include +#include + +// Forward declarations +HRESULT CreatePseudoConsoleAndPipes(HPCON*, HANDLE*, HANDLE*); +HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX*, HPCON); +void __cdecl PipeListener(LPVOID); + +int main() +{ + wchar_t szCommand[]{ L"ping localhost" }; + HRESULT hr{ E_UNEXPECTED }; + HANDLE hConsole = { GetStdHandle(STD_OUTPUT_HANDLE) }; + + // Enable Console VT Processing + DWORD consoleMode{}; + GetConsoleMode(hConsole, &consoleMode); + hr = SetConsoleMode(hConsole, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + ? S_OK + : GetLastError(); + if (S_OK == hr) + { + HPCON hPC{ INVALID_HANDLE_VALUE }; + + // Create the Pseudo Console and pipes to it + HANDLE hPipeIn{ INVALID_HANDLE_VALUE }; + HANDLE hPipeOut{ INVALID_HANDLE_VALUE }; + hr = CreatePseudoConsoleAndPipes(&hPC, &hPipeIn, &hPipeOut); + if (S_OK == hr) + { + // Create & start thread to listen to the incoming pipe + // Note: Using CRT-safe _beginthread() rather than CreateThread() + HANDLE hPipeListenerThread{ reinterpret_cast(_beginthread(PipeListener, 0, hPipeIn)) }; + + // Initialize the necessary startup info struct + STARTUPINFOEX startupInfo{}; + if (S_OK == InitializeStartupInfoAttachedToPseudoConsole(&startupInfo, hPC)) + { + // Launch ping to emit some text back via the pipe + PROCESS_INFORMATION piClient{}; + hr = CreateProcess( + NULL, // No module name - use Command Line + szCommand, // Command Line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Inherit handles + EXTENDED_STARTUPINFO_PRESENT, // Creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &startupInfo.StartupInfo, // Pointer to STARTUPINFO + &piClient) // Pointer to PROCESS_INFORMATION + ? S_OK + : GetLastError(); + + if (S_OK == hr) + { + // Wait up to 10s for ping process to complete + WaitForSingleObject(piClient.hThread, 10 * 1000); + + // Allow listening thread to catch-up with final output! + Sleep(500); + } + + // --- CLOSEDOWN --- + + // Now safe to clean-up client app's process-info & thread + CloseHandle(piClient.hThread); + CloseHandle(piClient.hProcess); + + // Cleanup attribute list + DeleteProcThreadAttributeList(startupInfo.lpAttributeList); + free(startupInfo.lpAttributeList); + } + + // Close ConPTY - this will terminate client process if running + ClosePseudoConsole(hPC); + + // Clean-up the pipes + if (INVALID_HANDLE_VALUE != hPipeOut) CloseHandle(hPipeOut); + if (INVALID_HANDLE_VALUE != hPipeIn) CloseHandle(hPipeIn); + } + } + + return S_OK == hr ? EXIT_SUCCESS : EXIT_FAILURE; +} + +HRESULT CreatePseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut) +{ + HRESULT hr{ E_UNEXPECTED }; + HANDLE hPipePTYIn{ INVALID_HANDLE_VALUE }; + HANDLE hPipePTYOut{ INVALID_HANDLE_VALUE }; + + // Create the pipes to which the ConPTY will connect + if (CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) && + CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0)) + { + // Determine required size of Pseudo Console + COORD consoleSize{}; + CONSOLE_SCREEN_BUFFER_INFO csbi{}; + HANDLE hConsole{ GetStdHandle(STD_OUTPUT_HANDLE) }; + if (GetConsoleScreenBufferInfo(hConsole, &csbi)) + { + consoleSize.X = csbi.srWindow.Right - csbi.srWindow.Left + 1; + consoleSize.Y = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + } + + // Create the Pseudo Console of the required size, attached to the PTY-end of the pipes + hr = CreatePseudoConsole(consoleSize, hPipePTYIn, hPipePTYOut, 0, phPC); + + // Note: We can close the handles to the PTY-end of the pipes here + // because the handles are dup'ed into the ConHost and will be released + // when the ConPTY is destroyed. + if (INVALID_HANDLE_VALUE != hPipePTYOut) CloseHandle(hPipePTYOut); + if (INVALID_HANDLE_VALUE != hPipePTYIn) CloseHandle(hPipePTYIn); + } + + return hr; +} + +// Initializes the specified startup info struct with the required properties and +// updates its thread attribute list with the specified ConPTY handle +HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX* pStartupInfo, HPCON hPC) +{ + HRESULT hr{ E_UNEXPECTED }; + + if (pStartupInfo) + { + size_t attrListSize{}; + + pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX); + + // Get the size of the thread attribute list. + InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize); + + // Allocate a thread attribute list of the correct size + pStartupInfo->lpAttributeList = + reinterpret_cast(malloc(attrListSize)); + + // Initialize thread attribute list + if (pStartupInfo->lpAttributeList + && InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize)) + { + // Set Pseudo Console attribute + hr = UpdateProcThreadAttribute( + pStartupInfo->lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + hPC, + sizeof(HPCON), + NULL, + NULL) + ? S_OK + : HRESULT_FROM_WIN32(GetLastError()); + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + } + return hr; +} + +void __cdecl PipeListener(LPVOID pipe) +{ + HANDLE hPipe{ pipe }; + HANDLE hConsole{ GetStdHandle(STD_OUTPUT_HANDLE) }; + + const DWORD BUFF_SIZE{ 512 }; + char szBuffer[BUFF_SIZE]{}; + + DWORD dwBytesWritten{}; + DWORD dwBytesRead{}; + BOOL fRead{ FALSE }; + do + { + // Read from the pipe + fRead = ReadFile(hPipe, szBuffer, BUFF_SIZE, &dwBytesRead, NULL); + + // Write received text to the Console + // Note: Write to the Console using WriteFile(hConsole...), not printf()/puts() to + // prevent partially-read VT sequences from corrupting output + WriteFile(hConsole, szBuffer, dwBytesRead, &dwBytesWritten, NULL); + + } while (fRead && dwBytesRead >= 0); +} diff --git a/samples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj b/samples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj new file mode 100644 index 0000000000..1f96a0ac46 --- /dev/null +++ b/samples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {96274800-9574-423E-892A-909FBE2AC8BE} + Win32Proj + EchoCon + 10.0.17763.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/samples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters b/samples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters new file mode 100644 index 0000000000..69e0ecfed1 --- /dev/null +++ b/samples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/samples/ConPTY/EchoCon/EchoCon/stdafx.cpp b/samples/ConPTY/EchoCon/EchoCon/stdafx.cpp new file mode 100644 index 0000000000..dec485bab3 --- /dev/null +++ b/samples/ConPTY/EchoCon/EchoCon/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// EchoCon.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file \ No newline at end of file diff --git a/samples/ConPTY/EchoCon/EchoCon/stdafx.h b/samples/ConPTY/EchoCon/EchoCon/stdafx.h new file mode 100644 index 0000000000..1fa6ee20bf --- /dev/null +++ b/samples/ConPTY/EchoCon/EchoCon/stdafx.h @@ -0,0 +1,13 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include +#include + +// TODO: reference additional headers your program requires here diff --git a/samples/ConPTY/EchoCon/EchoCon/targetver.h b/samples/ConPTY/EchoCon/EchoCon/targetver.h new file mode 100644 index 0000000000..87c0086de7 --- /dev/null +++ b/samples/ConPTY/EchoCon/EchoCon/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/samples/ConPTY/EchoCon/readme.md b/samples/ConPTY/EchoCon/readme.md new file mode 100644 index 0000000000..7d1f3572b1 --- /dev/null +++ b/samples/ConPTY/EchoCon/readme.md @@ -0,0 +1,34 @@ +# "EchoCon" ConPTY Sample App +This is a very simple sample application that illustrates how to use the new Win32 Pseudo Console +(ConPTY) by: + +1. Creating an input and an output pipe +1. Calling `CreatePseudoConsole()` to create a ConPTY instance attached to the other end of the pipes +1. Spawning an instance of `ping.exe` connected to the ConPTY +1. Running a thread that listens for output from ping.exe, writing received text to the Console + +# Pre-Requirements +To build and run this sample, you must install: +* Windows 10 Insider build 17733 or later +* [Latest Windows 10 Insider SDK](https://www.microsoft.com/en-us/software-download/windowsinsiderpreviewSDK) + +# Running the sample +Once successfully built, running EchoCon should clear the screen and display the results of the +echo command: + +``` +Pinging Rincewind [::1] with 32 bytes of data: +Reply from ::1: time<1ms +Reply from ::1: time<1ms +Reply from ::1: time<1ms +Reply from ::1: time<1ms + +Ping statistics for ::1: + Packets: Sent = 4, Received = 4, Lost = 0 (0% loss), +Approximate round trip times in milli-seconds: + Minimum = 0ms, Maximum = 0ms, Average = 0ms +``` + +# Resources +For more information on the new Pseudo Console infrastructure and API, please review +[this blog post](https://blogs.msdn.microsoft.com/commandline/2018/08/02/windows-command-line-introducing-the-windows-pseudo-console-conpty/) diff --git a/samples/ConPTY/MiniTerm/MiniTerm.sln b/samples/ConPTY/MiniTerm/MiniTerm.sln new file mode 100644 index 0000000000..898f6e28b7 --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2035 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniTerm", "MiniTerm\MiniTerm.csproj", "{121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EFB13BDE-C952-4311-9FE7-35EFDAC8F021} + EndGlobalSection +EndGlobal diff --git a/samples/ConPTY/MiniTerm/MiniTerm/App.config b/samples/ConPTY/MiniTerm/MiniTerm/App.config new file mode 100644 index 0000000000..56efbc7b5f --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/ConPTY/MiniTerm/MiniTerm/MiniTerm.csproj b/samples/ConPTY/MiniTerm/MiniTerm/MiniTerm.csproj new file mode 100644 index 0000000000..e901ac9b42 --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/MiniTerm.csproj @@ -0,0 +1,60 @@ + + + + + Debug + AnyCPU + {121D4818-BD57-433B-8AD5-C4E1ACE7E7C0} + Exe + MiniTerm + MiniTerm + v4.7.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Native/ConsoleApi.cs b/samples/ConPTY/MiniTerm/MiniTerm/Native/ConsoleApi.cs new file mode 100644 index 0000000000..1e82176b56 --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/Native/ConsoleApi.cs @@ -0,0 +1,38 @@ +using Microsoft.Win32.SafeHandles; +using System.Runtime.InteropServices; + +namespace MiniTerm.Native +{ + /// + /// PInvoke signatures for win32 console api + /// + static class ConsoleApi + { + internal const int STD_OUTPUT_HANDLE = -11; + internal const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; + internal const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008; + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern SafeFileHandle GetStdHandle(int nStdHandle); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool SetConsoleMode(SafeFileHandle hConsoleHandle, uint mode); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool GetConsoleMode(SafeFileHandle handle, out uint mode); + + internal delegate bool ConsoleEventDelegate(CtrlTypes ctrlType); + + internal enum CtrlTypes : uint + { + CTRL_C_EVENT = 0, + CTRL_BREAK_EVENT, + CTRL_CLOSE_EVENT, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT + } + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add); + } +} diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Native/ProcessApi.cs b/samples/ConPTY/MiniTerm/MiniTerm/Native/ProcessApi.cs new file mode 100644 index 0000000000..0bf1f6c9cb --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/Native/ProcessApi.cs @@ -0,0 +1,86 @@ +using System; +using System.Runtime.InteropServices; + +namespace MiniTerm.Native +{ + /// + /// PInvoke signatures for win32 process api + /// + static class ProcessApi + { + internal const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct STARTUPINFOEX + { + public STARTUPINFO StartupInfo; + public IntPtr lpAttributeList; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct STARTUPINFO + { + public Int32 cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public Int32 dwX; + public Int32 dwY; + public Int32 dwXSize; + public Int32 dwYSize; + public Int32 dwXCountChars; + public Int32 dwYCountChars; + public Int32 dwFillAttribute; + public Int32 dwFlags; + public Int16 wShowWindow; + public Int16 cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SECURITY_ATTRIBUTES + { + public int nLength; + public IntPtr lpSecurityDescriptor; + public int bInheritHandle; + } + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool InitializeProcThreadAttributeList( + IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool UpdateProcThreadAttribute( + IntPtr lpAttributeList, uint dwFlags, IntPtr attribute, IntPtr lpValue, + IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize); + + [DllImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CreateProcess( + string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, + ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, + IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo, + out PROCESS_INFORMATION lpProcessInformation); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool CloseHandle(IntPtr hObject); + } +} diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Native/PseudoConsoleApi.cs b/samples/ConPTY/MiniTerm/MiniTerm/Native/PseudoConsoleApi.cs new file mode 100644 index 0000000000..8ad317550c --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/Native/PseudoConsoleApi.cs @@ -0,0 +1,33 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.Runtime.InteropServices; + +namespace MiniTerm.Native +{ + /// + /// PInvoke signatures for win32 pseudo console api + /// + static class PseudoConsoleApi + { + internal const uint PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016; + + [StructLayout(LayoutKind.Sequential)] + internal struct COORD + { + public short X; + public short Y; + } + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern int CreatePseudoConsole(COORD size, SafeFileHandle hInput, SafeFileHandle hOutput, uint dwFlags, out IntPtr phPC); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern int ResizePseudoConsole(IntPtr hPC, COORD size); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern int ClosePseudoConsole(IntPtr hPC); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, IntPtr lpPipeAttributes, int nSize); + } +} diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Processes/Process.cs b/samples/ConPTY/MiniTerm/MiniTerm/Processes/Process.cs new file mode 100644 index 0000000000..a1b5228fed --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/Processes/Process.cs @@ -0,0 +1,74 @@ +using System; +using System.Runtime.InteropServices; +using static MiniTerm.Native.ProcessApi; + +namespace MiniTerm +{ + /// + /// Represents an instance of a process. + /// + internal sealed class Process : IDisposable + { + public Process(STARTUPINFOEX startupInfo, PROCESS_INFORMATION processInfo) + { + StartupInfo = startupInfo; + ProcessInfo = processInfo; + } + + public STARTUPINFOEX StartupInfo { get; } + public PROCESS_INFORMATION ProcessInfo { get; } + + #region IDisposable Support + + private bool disposedValue = false; // To detect redundant calls + + void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // dispose managed state (managed objects). + } + + // dispose unmanaged state + + // Free the attribute list + if (StartupInfo.lpAttributeList != IntPtr.Zero) + { + DeleteProcThreadAttributeList(StartupInfo.lpAttributeList); + Marshal.FreeHGlobal(StartupInfo.lpAttributeList); + } + + // Close process and thread handles + if (ProcessInfo.hProcess != IntPtr.Zero) + { + CloseHandle(ProcessInfo.hProcess); + } + if (ProcessInfo.hThread != IntPtr.Zero) + { + CloseHandle(ProcessInfo.hThread); + } + + disposedValue = true; + } + } + + ~Process() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(false); + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // use the following line if the finalizer is overridden above. + GC.SuppressFinalize(this); + } + + #endregion + } +} diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Processes/ProcessFactory.cs b/samples/ConPTY/MiniTerm/MiniTerm/Processes/ProcessFactory.cs new file mode 100644 index 0000000000..f54805ba34 --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/Processes/ProcessFactory.cs @@ -0,0 +1,98 @@ +using System; +using System.Runtime.InteropServices; +using static MiniTerm.Native.ProcessApi; + +namespace MiniTerm +{ + /// + /// Support for starting and configuring processes. + /// + /// + /// Possible to replace with managed code? The key is being able to provide the PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE attribute + /// + static class ProcessFactory + { + /// + /// Start and configure a process. The return value represents the process and should be disposed. + /// + internal static Process Start(string command, IntPtr attributes, IntPtr hPC) + { + var startupInfo = ConfigureProcessThread(hPC, attributes); + var processInfo = RunProcess(ref startupInfo, "cmd.exe"); + return new Process(startupInfo, processInfo); + } + + private static STARTUPINFOEX ConfigureProcessThread(IntPtr hPC, IntPtr attributes) + { + // this method implements the behavior described in https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#preparing-for-creation-of-the-child-process + + var lpSize = IntPtr.Zero; + var success = InitializeProcThreadAttributeList( + lpAttributeList: IntPtr.Zero, + dwAttributeCount: 1, + dwFlags: 0, + lpSize: ref lpSize + ); + if (success || lpSize == IntPtr.Zero) // we're not expecting `success` here, we just want to get the calculated lpSize + { + throw new InvalidOperationException("Could not calculate the number of bytes for the attribute list. " + Marshal.GetLastWin32Error()); + } + + var startupInfo = new STARTUPINFOEX(); + startupInfo.StartupInfo.cb = Marshal.SizeOf(); + startupInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize); + + success = InitializeProcThreadAttributeList( + lpAttributeList: startupInfo.lpAttributeList, + dwAttributeCount: 1, + dwFlags: 0, + lpSize: ref lpSize + ); + if (!success) + { + throw new InvalidOperationException("Could not set up attribute list. " + Marshal.GetLastWin32Error()); + } + + success = UpdateProcThreadAttribute( + lpAttributeList: startupInfo.lpAttributeList, + dwFlags: 0, + attribute: attributes, + lpValue: hPC, + cbSize: (IntPtr)IntPtr.Size, + lpPreviousValue: IntPtr.Zero, + lpReturnSize: IntPtr.Zero + ); + if (!success) + { + throw new InvalidOperationException("Could not set pseudoconsole thread attribute. " + Marshal.GetLastWin32Error()); + } + + return startupInfo; + } + + private static PROCESS_INFORMATION RunProcess(ref STARTUPINFOEX sInfoEx, string commandLine) + { + int securityAttributeSize = Marshal.SizeOf(); + var pSec = new SECURITY_ATTRIBUTES { nLength = securityAttributeSize }; + var tSec = new SECURITY_ATTRIBUTES { nLength = securityAttributeSize }; + var success = CreateProcess( + lpApplicationName: null, + lpCommandLine: commandLine, + lpProcessAttributes: ref pSec, + lpThreadAttributes: ref tSec, + bInheritHandles: false, + dwCreationFlags: EXTENDED_STARTUPINFO_PRESENT, + lpEnvironment: IntPtr.Zero, + lpCurrentDirectory: null, + lpStartupInfo: ref sInfoEx, + lpProcessInformation: out PROCESS_INFORMATION pInfo + ); + if (!success) + { + throw new InvalidOperationException("Could not create process. " + Marshal.GetLastWin32Error()); + } + + return pInfo; + } + } +} diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Program.cs b/samples/ConPTY/MiniTerm/MiniTerm/Program.cs new file mode 100644 index 0000000000..6af24169f3 --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/Program.cs @@ -0,0 +1,35 @@ +using System; + +namespace MiniTerm +{ + /// + /// C# version of: + /// https://blogs.msdn.microsoft.com/commandline/2018/08/02/windows-command-line-introducing-the-windows-pseudo-console-conpty/ + /// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session + /// + /// System Requirements: + /// As of September 2018, requires Windows 10 with the "Windows Insider Program" installed for Redstone 5. + /// Also requires the Windows Insider Preview SDK: https://www.microsoft.com/en-us/software-download/windowsinsiderpreviewSDK + /// + /// + /// Basic design is: + /// Terminal UI starts the PseudoConsole, and controls it using a pair of PseudoConsolePipes + /// Terminal UI will run the Process (cmd.exe) and associate it with the PseudoConsole. + /// + static class Program + { + static void Main(string[] args) + { + try + { + var terminal = new Terminal(); + terminal.Run("cmd.exe"); + } + catch (InvalidOperationException e) + { + Console.Error.WriteLine(e.Message); + throw; + } + } + } +} diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Properties/AssemblyInfo.cs b/samples/ConPTY/MiniTerm/MiniTerm/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b38d4e859a --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MiniPty")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MiniPty")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("121d4818-bd57-433b-8ad5-c4e1ace7e7c0")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/ConPTY/MiniTerm/MiniTerm/PseudoConsole.cs b/samples/ConPTY/MiniTerm/MiniTerm/PseudoConsole.cs new file mode 100644 index 0000000000..b0072033d4 --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/PseudoConsole.cs @@ -0,0 +1,39 @@ +using Microsoft.Win32.SafeHandles; +using System; +using static MiniTerm.Native.PseudoConsoleApi; + +namespace MiniTerm +{ + /// + /// Utility functions around the new Pseudo Console APIs + /// + internal sealed class PseudoConsole : IDisposable + { + public static readonly IntPtr PseudoConsoleThreadAttribute = (IntPtr)PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE; + + public IntPtr Handle { get; } + + private PseudoConsole(IntPtr handle) + { + this.Handle = handle; + } + + internal static PseudoConsole Create(SafeFileHandle inputReadSide, SafeFileHandle outputWriteSide, int width, int height) + { + var createResult = CreatePseudoConsole( + new COORD { X = (short)width, Y = (short)height }, + inputReadSide, outputWriteSide, + 0, out IntPtr hPC); + if(createResult != 0) + { + throw new InvalidOperationException("Could not create psuedo console. Error Code " + createResult); + } + return new PseudoConsole(hPC); + } + + public void Dispose() + { + ClosePseudoConsole(Handle); + } + } +} diff --git a/samples/ConPTY/MiniTerm/MiniTerm/PseudoConsolePipe.cs b/samples/ConPTY/MiniTerm/MiniTerm/PseudoConsolePipe.cs new file mode 100644 index 0000000000..c5a8898a4b --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/PseudoConsolePipe.cs @@ -0,0 +1,46 @@ +using Microsoft.Win32.SafeHandles; +using System; +using static MiniTerm.Native.PseudoConsoleApi; + +namespace MiniTerm +{ + /// + /// A pipe used to talk to the pseudoconsole, as described in: + /// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session + /// + /// + /// We'll have two instances of this class, one for input and one for output. + /// + internal sealed class PseudoConsolePipe : IDisposable + { + public readonly SafeFileHandle ReadSide; + public readonly SafeFileHandle WriteSide; + + public PseudoConsolePipe() + { + if (!CreatePipe(out ReadSide, out WriteSide, IntPtr.Zero, 0)) + { + throw new InvalidOperationException("failed to create pipe"); + } + } + + #region IDisposable + + void Dispose(bool disposing) + { + if (disposing) + { + ReadSide?.Dispose(); + WriteSide?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + } +} diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs b/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs new file mode 100644 index 0000000000..df4bb21734 --- /dev/null +++ b/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs @@ -0,0 +1,145 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using static MiniTerm.Native.ConsoleApi; + +namespace MiniTerm +{ + /// + /// The UI of the terminal. It's just a normal console window, but we're managing the input/output. + /// In a "real" project this could be some other UI. + /// + internal sealed class Terminal + { + private const string ExitCommand = "exit\r"; + private const string CtrlC_Command = "\x3"; + + public Terminal() + { + EnableVirtualTerminalSequenceProcessing(); + } + + /// + /// Newer versions of the windows console support interpreting virtual terminal sequences, we just have to opt-in + /// + private static void EnableVirtualTerminalSequenceProcessing() + { + var hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (!GetConsoleMode(hStdOut, out uint outConsoleMode)) + { + throw new InvalidOperationException("Could not get console mode"); + } + + outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + if (!SetConsoleMode(hStdOut, outConsoleMode)) + { + throw new InvalidOperationException("Could not enable virtual terminal processing"); + } + } + + /// + /// Start the psuedoconsole and run the process as shown in + /// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-pseudoconsole + /// + /// the command to run, e.g. cmd.exe + public void Run(string command) + { + using (var inputPipe = new PseudoConsolePipe()) + using (var outputPipe = new PseudoConsolePipe()) + using (var pseudoConsole = PseudoConsole.Create(inputPipe.ReadSide, outputPipe.WriteSide, (short)Console.WindowWidth, (short)Console.WindowHeight)) + using (var process = ProcessFactory.Start(command, PseudoConsole.PseudoConsoleThreadAttribute, pseudoConsole.Handle)) + { + // copy all pseudoconsole output to stdout + Task.Run(() => CopyPipeToOutput(outputPipe.ReadSide)); + // prompt for stdin input and send the result to the pseudoconsole + Task.Run(() => CopyInputToPipe(inputPipe.WriteSide)); + // free resources in case the console is ungracefully closed (e.g. by the 'x' in the window titlebar) + OnClose(() => DisposeResources(process, pseudoConsole, outputPipe, inputPipe)); + + WaitForExit(process).WaitOne(Timeout.Infinite); + } + } + + /// + /// Reads terminal input and copies it to the PseudoConsole + /// + /// the "write" side of the pseudo console input pipe + private static void CopyInputToPipe(SafeFileHandle inputWriteSide) + { + using (var writer = new StreamWriter(new FileStream(inputWriteSide, FileAccess.Write))) + { + ForwardCtrlC(writer); + writer.AutoFlush = true; + writer.WriteLine(@"cd \"); + + while (true) + { + // send input character-by-character to the pipe + char key = Console.ReadKey(intercept: true).KeyChar; + writer.Write(key); + } + } + } + + /// + /// Don't let ctrl-c kill the terminal, it should be sent to the process in the terminal. + /// + private static void ForwardCtrlC(StreamWriter writer) + { + Console.CancelKeyPress += (sender, e) => + { + e.Cancel = true; + writer.Write(CtrlC_Command); + }; + } + + /// + /// Reads PseudoConsole output and copies it to the terminal's standard out. + /// + /// the "read" side of the pseudo console output pipe + private static void CopyPipeToOutput(SafeFileHandle outputReadSide) + { + using (var terminalOutput = Console.OpenStandardOutput()) + using (var pseudoConsoleOutput = new FileStream(outputReadSide, FileAccess.Read)) + { + pseudoConsoleOutput.CopyTo(terminalOutput); + } + } + + /// + /// Get an AutoResetEvent that signals when the process exits + /// + private static AutoResetEvent WaitForExit(Process process) => + new AutoResetEvent(false) + { + SafeWaitHandle = new SafeWaitHandle(process.ProcessInfo.hProcess, ownsHandle: false) + }; + + /// + /// Set a callback for when the terminal is closed (e.g. via the "X" window decoration button). + /// Intended for resource cleanup logic. + /// + private static void OnClose(Action handler) + { + SetConsoleCtrlHandler(eventType => + { + if(eventType == CtrlTypes.CTRL_CLOSE_EVENT) + { + handler(); + } + return false; + }, true); + } + + private void DisposeResources(params IDisposable[] disposables) + { + foreach (var disposable in disposables) + { + disposable.Dispose(); + } + } + } +} diff --git a/samples/ConPTY/MiniTerm/README.md b/samples/ConPTY/MiniTerm/README.md new file mode 100644 index 0000000000..780202feb9 --- /dev/null +++ b/samples/ConPTY/MiniTerm/README.md @@ -0,0 +1,19 @@ +# MiniTerm + +Experimental terminal using the new PTY APIs from Microsoft. Written in C#, and heavily based on the native code examples. + +## Status + +Demonstrates the basic API calls required, but not intended for "real-world" usage. + +## Resources + +- [Introductory blog post from Microsoft](https://blogs.msdn.microsoft.com/commandline/2018/08/02/windows-command-line-introducing-the-windows-pseudo-console-conpty/) +- [MSDN Documentation](https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session) + +## System Requirements + +See the Introductory blog post from Microsoft for the full setup instructions. This project has been tested with: + +- OS: Windows 10 Pro Build 17744 +- SDK: Windows_InsiderPreview_SDK_en-us_17749 diff --git a/samples/ReadConsoleInputStream/ConcurrentBoundedQueue.cs b/samples/ReadConsoleInputStream/ConcurrentBoundedQueue.cs new file mode 100644 index 0000000000..cad81034fc --- /dev/null +++ b/samples/ReadConsoleInputStream/ConcurrentBoundedQueue.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Samples.Terminal +{ + /// + /// Implements a bounded queue that won't block on overflow; instead the oldest item is discarded. + /// + /// + public class ConcurrentBoundedQueue : ConcurrentQueue + { + public ConcurrentBoundedQueue(int capacity) + { + Capacity = GetAlignedCapacity(capacity); + } + + public ConcurrentBoundedQueue(IEnumerable collection, int capacity) : base(collection) + { + Capacity = GetAlignedCapacity(capacity); + } + + private int GetAlignedCapacity(int n) + { + if (n < 2) + { + throw new ArgumentException("Capacity must be at least 2"); + } + + var f = Math.Log(n, 2); + var p = Math.Ceiling(f); + + return (int) Math.Pow(2, p); + } + + public new void Enqueue(T item) + { + // if we're about to overflow, dump oldest item + if (Count >= Capacity) + { + lock (this) + { + while (Count >= Capacity) + { + TryDequeue(out _); + } + } + } + + base.Enqueue(item); + } + + public int Capacity + { + get; private set; + } + } +} diff --git a/samples/ReadConsoleInputStream/NativeMethods.cs b/samples/ReadConsoleInputStream/NativeMethods.cs new file mode 100644 index 0000000000..3cae186345 --- /dev/null +++ b/samples/ReadConsoleInputStream/NativeMethods.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Samples.Terminal +{ + internal static class NativeMethods + { + private static int MakeHRFromErrorCode(int errorCode) + { + // Don't convert it if it is already an HRESULT + if ((0xFFFF0000 & errorCode) != 0) + { + Debug.Assert(false, "errorCode is already HRESULT"); + return errorCode; + } + + return unchecked(((int)0x80070000) | errorCode); + } + + internal static Exception GetExceptionForWin32Error(int errorCode) + { + return Marshal.GetExceptionForHR(MakeHRFromErrorCode(errorCode)); + } + + internal static Exception GetExceptionForLastWin32Error() + { + return GetExceptionForWin32Error(Marshal.GetLastWin32Error()); + } + } +} \ No newline at end of file diff --git a/samples/ReadConsoleInputStream/Program.cs b/samples/ReadConsoleInputStream/Program.cs new file mode 100644 index 0000000000..ef1192d27a --- /dev/null +++ b/samples/ReadConsoleInputStream/Program.cs @@ -0,0 +1,147 @@ +/* + * This is a demo that shows how we can have a stream-oriented view of characters from the console + * while also listening to console events like mouse, menu, focus, buffer/viewport(1) resize events. + * + * This has always been tricky to do because ReadConsoleW/A doesn't allow retrieving events. + * Only ReadConsoleInputW/A returns events, but isn't stream-oriented. Using both doesn't work because + * ReadConsoleW/A flushes the input queue, meaning calls to ReadConsoleInputW/A will wait forever. + * + * I do this by deriving a new Stream class which wraps ReadConsoleInputW and accepts a provider/consumer + * implementation of BlockingCollection. This allows asynchronous monitoring of + * console events while simultaneously streaming the character input. I also use Mark Gravell's great + * System.IO.Pipelines utility classes (2) and David Hall's excellent P/Invoke wrappers (3) to make this + * demo cleaner to read; both are pulled from NuGet. + * + * (1) in versions of windows 10 prior to 1809, the buffer resize event only fires for enlarging + * the viewport, as this would cause the buffer to be enlarged too. Now it fires even when + * shrinking the viewport, which won't change the buffer size. + * + * (2) https://github.com/mgravell/Pipelines.Sockets.Unofficial + * https://www.nuget.org/packages/Pipelines.Sockets.Unofficial + * + * (3) https://github.com/dahall/Vanara + * https://www.nuget.org/packages/Vanara.Pinvoke.Kernel32 + * + * Oisin Grehan - 2019/4/21 + * + * https://twitter.com/oising + * https://github.com/oising + */ + +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Pipelines.Sockets.Unofficial; +using Vanara.PInvoke; + +namespace Samples.Terminal +{ + internal class Program + { + private static async Task Main(string[] args) + { + // run for 90 seconds + var timeout = TimeSpan.FromSeconds(90); + + // in reality this will likely never be reached, but it is useful to guard against + // conditions where the queue isn't drained, or not drained fast enough. + const int maxNonKeyEventRetention = 128; + + var source = new CancellationTokenSource(timeout); + var token = source.Token; + var handle = Kernel32.GetStdHandle(Kernel32.StdHandleType.STD_INPUT_HANDLE); + + if (!Kernel32.GetConsoleMode(handle, out Kernel32.CONSOLE_INPUT_MODE mode)) + throw NativeMethods.GetExceptionForWin32Error(Marshal.GetLastWin32Error()); + + mode |= Kernel32.CONSOLE_INPUT_MODE.ENABLE_WINDOW_INPUT; + mode |= Kernel32.CONSOLE_INPUT_MODE.ENABLE_VIRTUAL_TERMINAL_INPUT; + mode &= ~Kernel32.CONSOLE_INPUT_MODE.ENABLE_ECHO_INPUT; + mode &= ~Kernel32.CONSOLE_INPUT_MODE.ENABLE_LINE_INPUT; + + if (!Kernel32.SetConsoleMode(handle, mode)) + throw NativeMethods.GetExceptionForLastWin32Error(); + + // base our provider/consumer on a bounded queue to keep memory usage under control + var events = new BlockingCollection( + new ConcurrentBoundedQueue(maxNonKeyEventRetention)); + + // Task that will consume non-key events asynchronously + var consumeEvents = Task.Run(() => + { + Console.WriteLine("consumeEvents started"); + + try + { + while (!events.IsCompleted) + { + // blocking call + var record = events.Take(token); + + Console.WriteLine("record: {0}", + Enum.GetName(typeof(Kernel32.EVENT_TYPE), record.EventType)); + } + } + catch (OperationCanceledException) + { + // timeout + } + + Console.WriteLine("consumeEvents ended"); + }, token); + + // Task that will watch for key events while feeding non-key events into our provider/consumer collection + var readInputAndProduceEvents = Task.Run(async () => + { + //So, this is the key point - we cannot use the following or we lose all non-key events: + // Stream stdin = Console.OpenStandardInput(); + + // get a unicode character stream over console input + Stream stdin = new ReadConsoleInputStream(handle, events); + + // wrap in a System.IO.Pipelines.PipeReader to get clean async and span/memory usage + var reader = StreamConnection.GetReader(stdin); + + while (!token.IsCancellationRequested) + { + // blocking call + var result = await reader.ReadAsync(token); + + if (result.IsCanceled) + break; + + var sequence = result.Buffer; + var segment = sequence.Start; + + while (sequence.TryGet(ref segment, out var mem)) + { + // decode back from unicode + var datum = Encoding.Unicode.GetString(mem.Span); + Console.Write(datum); + } + + reader.AdvanceTo(sequence.End); + } + }, token); + + Console.WriteLine("Running"); + + try + { + await Task.WhenAll(consumeEvents, readInputAndProduceEvents); + } + catch (OperationCanceledException) + { + // timeout + } + + Console.WriteLine("press any key..."); + Console.ReadKey(true); + } + } +} \ No newline at end of file diff --git a/samples/ReadConsoleInputStream/README.md b/samples/ReadConsoleInputStream/README.md new file mode 100644 index 0000000000..c6ce6e616a --- /dev/null +++ b/samples/ReadConsoleInputStream/README.md @@ -0,0 +1,16 @@ +# ReadConsoleInputStream Demo + +This is a demo that shows how we can have a stream-oriented view of characters from the console while also listening to console events like mouse, menu, focus, buffer/viewport resize events. This is partcularly useful when working with VT100 streams and ConPTY. + +This has always been tricky to do because ReadConsoleW/A doesn't allow retrieving events. Only ReadConsoleInputW/A returns events, but isn't stream-oriented. Using both doesn't work because ReadConsoleW/A flushes the input queue, meaning calls to ReadConsoleInputW/A will wait forever. + +I do this by deriving a new Stream class which wraps ReadConsoleInputW and accepts a provider/consumer implementation of BlockingCollection. This allows asynchronous monitoring of console events while simultaneously streaming the character input. I also use Mark Gravell's great System.IO.Pipelines utility classes and David Hall's excellent P/Invoke wrappers to make this demo cleaner to read; both are pulled from NuGet. + +**Note:** + +In versions of windows 10 prior to 1809, the buffer resize event only fires for enlarging the viewport, as this would cause the buffer to be enlarged too. Now it fires even when shrinking the viewport, which won't change the buffer size. + +NuGet packages used (GitHub links): + +* [Pipelines.Sockets.Unofficial](https://github.com/mgravell/Pipelines.Sockets.Unofficial) +* [Vanara P/Invoke](https://github.com/dahall/Vanara) \ No newline at end of file diff --git a/samples/ReadConsoleInputStream/ReadConsoleInputStream.cs b/samples/ReadConsoleInputStream/ReadConsoleInputStream.cs new file mode 100644 index 0000000000..0d2526766b --- /dev/null +++ b/samples/ReadConsoleInputStream/ReadConsoleInputStream.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.IO; + +using Vanara.PInvoke; + +namespace Samples.Terminal +{ + /// + /// Provides a Stream-oriented view over the console's input buffer key events + /// while also collecting out of band events like buffer resize, menu etc in + /// a caller-provided BlockingCollection. + /// + /// The buffer contains unicode chars, not 8 bit CP encoded chars as we rely on ReadConsoleInputW. + public sealed class ReadConsoleInputStream : Stream + { + private const int BufferSize = 256; + private const int BytesPerWChar = 2; + private readonly BlockingCollection _nonKeyEvents; + private IntPtr _handle; + + /// + /// Creates an instance of ReadConsoleInputStream over the standard handle for StdIn. + /// + /// A BlockingCollection provider/consumer collection for collecting non key events. + public ReadConsoleInputStream(BlockingCollection nonKeyEvents) : + this(Kernel32.GetStdHandle(Kernel32.StdHandleType.STD_INPUT_HANDLE), nonKeyEvents) + { + } + + /// + /// Creates an instance of ReadConsoleInputStream over a caller-provided standard handle for stdin. + /// + /// A HFILE handle representing StdIn + /// A BlockingCollection provider/consumer collection for collecting non key events. + internal ReadConsoleInputStream(HFILE handle, + BlockingCollection nonKeyEvents) + { + Debug.Assert(handle.IsInvalid == false, "handle.IsInvalid == false"); + + _handle = handle.DangerousGetHandle(); + _nonKeyEvents = nonKeyEvents; + } + + public override bool CanRead { get; } = true; + + public override bool CanWrite => false; + + public override bool CanSeek => false; + + public override long Length => throw new NotSupportedException("Seek not supported."); + + public override long Position + { + get => throw new NotSupportedException("Seek not supported."); + set => throw new NotSupportedException("Seek not supported."); + } + + protected override void Dispose(bool disposing) + { + _handle = IntPtr.Zero; + + base.Dispose(disposing); + } + + public override int Read(byte[] buffer, int offset, int count) + { + ValidateRead(buffer, offset, count); + + Debug.Assert(offset >= 0, "offset >= 0"); + Debug.Assert(count >= 0, "count >= 0"); + Debug.Assert(buffer != null, "bytes != null"); + + // Don't corrupt memory when multiple threads are erroneously writing + // to this stream simultaneously. + if (buffer.Length - offset < count) + throw new IndexOutOfRangeException("IndexOutOfRange_IORaceCondition"); + + int bytesRead; + int ret; + + if (buffer.Length == 0) + { + bytesRead = 0; + ret = Win32Error.ERROR_SUCCESS; + } + else + { + var charsRead = 0; + bytesRead = 0; + + var records = new Kernel32.INPUT_RECORD[BufferSize]; + + // begin input loop + do + { + var readSuccess = Kernel32.ReadConsoleInput(_handle, records, BufferSize, out var recordsRead); + Debug.WriteLine("Read {0} input record(s)", recordsRead); + + // some of the arithmetic here is deliberately more explicit than it needs to be + // in order to show how 16-bit unicode WCHARs are packed into the buffer. The console + // subsystem is one of the last bastions of UCS-2, so until UTF-16 is fully adopted + // the two-byte character assumptions below will hold. + if (readSuccess && recordsRead > 0) + { + for (var index = 0; index < recordsRead; index++) + { + var record = records[index]; + + if (record.EventType == Kernel32.EVENT_TYPE.KEY_EVENT) + { + // skip key up events - if not, every key will be duped in the stream + if (record.Event.KeyEvent.bKeyDown == false) continue; + + // pack ucs-2/utf-16le/unicode chars into position in our byte[] buffer. + var glyph = (ushort) record.Event.KeyEvent.uChar; + + var lsb = (byte) (glyph & 0xFFu); + var msb = (byte) ((glyph >> 8) & 0xFFu); + + // ensure we accommodate key repeat counts + for (var n = 0; n < record.Event.KeyEvent.wRepeatCount; n++) + { + buffer[offset + charsRead * BytesPerWChar] = lsb; + buffer[offset + charsRead * BytesPerWChar + 1] = msb; + + charsRead++; + } + } + else + { + // ignore focus events; not doing so makes debugging absolutely hilarious + // when breakpoints repeatedly cause focus events to occur as your view toggles + // between IDE and console. + if (record.EventType != Kernel32.EVENT_TYPE.FOCUS_EVENT) + { + // I assume success adding records - this is not so critical + // if it is critical to you, loop on this with a miniscule delay + _nonKeyEvents.TryAdd(record); + } + } + } + bytesRead = charsRead * BytesPerWChar; + } + else + { + Debug.Assert(bytesRead == 0, "bytesRead == 0"); + } + + } while (bytesRead == 0); + + Debug.WriteLine("Read {0} character(s)", charsRead); + + ret = Win32Error.ERROR_SUCCESS; + } + + var errCode = ret; + if (Win32Error.ERROR_SUCCESS != errCode) + throw NativeMethods.GetExceptionForWin32Error(errCode); + + return bytesRead; + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException("Write operations not implemented."); + } + + public override void Flush() + { + throw new NotSupportedException("Flush/Write not supported."); + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Seek not supported."); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seek not supported."); + } + + private void ValidateRead(byte[] buffer, int offset, int count) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0 || count < 0) + throw new ArgumentOutOfRangeException(offset < 0 ? nameof(offset) : nameof(count), + "offset or count cannot be negative numbers."); + if (buffer.Length - offset < count) + throw new ArgumentException("invalid offset length."); + + if (!CanRead) throw new NotSupportedException("Get read not supported."); + } + } +} \ No newline at end of file diff --git a/samples/ReadConsoleInputStream/ReadConsoleInputStreamDemo.csproj b/samples/ReadConsoleInputStream/ReadConsoleInputStreamDemo.csproj new file mode 100644 index 0000000000..a0cfb7f3e7 --- /dev/null +++ b/samples/ReadConsoleInputStream/ReadConsoleInputStreamDemo.csproj @@ -0,0 +1,15 @@ + + + + Exe + netcoreapp2.2 + latest + Samples.Terminal + + + + + + + + diff --git a/samples/ReadConsoleInputStream/ReadConsoleInputStreamDemo.sln b/samples/ReadConsoleInputStream/ReadConsoleInputStreamDemo.sln new file mode 100644 index 0000000000..cc2566a4c7 --- /dev/null +++ b/samples/ReadConsoleInputStream/ReadConsoleInputStreamDemo.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.156 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadConsoleInputStreamDemo", "ReadConsoleInputStreamDemo.csproj", "{62F500DE-4F06-4B46-B7AF-02AF21296F00}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {62F500DE-4F06-4B46-B7AF-02AF21296F00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62F500DE-4F06-4B46-B7AF-02AF21296F00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62F500DE-4F06-4B46-B7AF-02AF21296F00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62F500DE-4F06-4B46-B7AF-02AF21296F00}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {55A9793B-D717-4A6E-A8FE-ABC6CD3B17BA} + EndGlobalSection +EndGlobal diff --git a/src/tools/ColorTool/ColorTool.sln b/src/tools/ColorTool/ColorTool.sln new file mode 100644 index 0000000000..4e8aa959d5 --- /dev/null +++ b/src/tools/ColorTool/ColorTool.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorTool", "ColorTool\ColorTool.csproj", "{97F4550F-5775-4E40-8ECF-A03479884120}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {97F4550F-5775-4E40-8ECF-A03479884120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97F4550F-5775-4E40-8ECF-A03479884120}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97F4550F-5775-4E40-8ECF-A03479884120}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97F4550F-5775-4E40-8ECF-A03479884120}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/tools/ColorTool/ColorTool/App.config b/src/tools/ColorTool/ColorTool/App.config new file mode 100644 index 0000000000..9d2c7adf3b --- /dev/null +++ b/src/tools/ColorTool/ColorTool/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/tools/ColorTool/ColorTool/ColorScheme.cs b/src/tools/ColorTool/ColorTool/ColorScheme.cs new file mode 100644 index 0000000000..62c0eb47d1 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/ColorScheme.cs @@ -0,0 +1,134 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using System; +using System.Drawing; +using System.Linq; + +namespace ColorTool +{ + /// + /// Represents a colorscheme that can be applied to a console. + /// + public class ColorScheme + { + public ColorScheme(uint[] colorTable, ConsoleAttributes consoleAttributes) + { + ColorTable = colorTable; + ConsoleAttributes = consoleAttributes; + } + + public uint[] ColorTable { get; } + public ConsoleAttributes ConsoleAttributes { get; } + + public ushort? ScreenColorAttributes => + CalculateBackgroundForegroundAttributes( + this.ConsoleAttributes.Background, + this.ConsoleAttributes.Foreground + ); + + public ushort? PopupColorAttributes => + CalculateBackgroundForegroundAttributes( + this.ConsoleAttributes.PopupBackground, + this.ConsoleAttributes.PopupForeground + ); + + public Color this[int index] => UIntToColor(ColorTable[index]); + + private static Color UIntToColor(uint color) + { + byte r = (byte)(color >> 0); + byte g = (byte)(color >> 8); + byte b = (byte)(color >> 16); + return Color.FromArgb(r, g, b); + } + + private ushort? CalculateBackgroundForegroundAttributes(uint? background, uint? foreground) + { + if(!(background.HasValue && foreground.HasValue)) + { + return null; + } + int fgidx = this.CalculateIndex(foreground.Value); + int bgidx = this.CalculateIndex(background.Value); + var attributes = (ushort)(fgidx | (bgidx << 4)); + return attributes; + } + + public int CalculateIndex(uint value) => + ColorTable.Select((color, idx) => Tuple.Create(color, idx)) + .OrderBy(Difference(value)) + .First().Item2; + + private static Func, double> Difference(uint c1) => + // heuristic 1: nearest neighbor in RGB space + // tup => Distance(RGB(c1), RGB(tup.Item1)); + // heuristic 2: nearest neighbor in RGB space + // tup => Distance(HSV(c1), HSV(tup.Item1)); + // heuristic 3: weighted RGB L2 distance + tup => WeightedRGBSimilarity(c1, tup.Item1); + + private static double WeightedRGBSimilarity(uint c1, uint c2) + { + var rgb1 = RGB(c1); + var rgb2 = RGB(c2); + var dist = rgb1.Zip(rgb2, (a, b) => Math.Pow((int)a - (int)b, 2)).ToArray(); + var rbar = (rgb1[0] + rgb1[0]) / 2.0; + return Math.Sqrt(dist[0] * (2 + rbar / 256.0) + dist[1] * 4 + dist[2] * (2 + (255 - rbar) / 256.0)); + } + + internal static uint[] RGB(uint c) => new[] { c & 0xFF, (c >> 8) & 0xFF, (c >> 16) & 0xFF }; + + internal static uint[] HSV(uint c) + { + var rgb = RGB(c).Select(_ => (int)_).ToArray(); + int max = rgb.Max(); + int min = rgb.Min(); + + int d = max - min; + int h = 0; + int s = (int)(255 * ((max == 0) ? 0 : d / (double)max)); + int v = max; + + if (d != 0) + { + double dh; + if (rgb[0] == max) dh = ((rgb[1] - rgb[2]) / (double)d); + else if (rgb[1] == max) dh = 2.0 + ((rgb[2] - rgb[0]) / (double)d); + else /* if (rgb[2] == max) */ dh = 4.0 + ((rgb[0] - rgb[1]) / (double)d); + dh *= 60; + if (dh < 0) dh += 360.0; + h = (int)(dh * 255.0 / 360.0); + } + + return new[] { (uint)h, (uint)s, (uint)v }; + } + + internal void Dump() + { + Action _dump = (str, c) => + { + var rgb = RGB(c); + var hsv = HSV(c); + Console.WriteLine($"{str} =\tRGB({rgb[0]}, {rgb[1]}, {rgb[2]}),\tHSV({hsv[0]}, {hsv[1]}, {hsv[2]})"); + }; + + for (int i = 0; i < 16; ++i) + { + _dump($"Color[{i}]", ColorTable[i]); + } + + if (ConsoleAttributes.Foreground != null) + { + _dump("FG ", ConsoleAttributes.Foreground.Value); + } + + if (ConsoleAttributes.Background != null) + { + _dump("BG ", ConsoleAttributes.Background.Value); + } + } + } +} diff --git a/src/tools/ColorTool/ColorTool/ColorTable.cs b/src/tools/ColorTool/ColorTool/ColorTable.cs new file mode 100644 index 0000000000..29470ac112 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/ColorTable.cs @@ -0,0 +1,205 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using System; +using System.Collections.Generic; + +namespace ColorTool +{ + /// + /// Displays the color table that demonstrates the current colorscheme. + /// + static class ColorTable + { + private const int DarkBlack = 0; + private const int DarkBlue = 1; + private const int DarkGreen = 2; + private const int DarkCyan = 3; + private const int DarkRed = 4; + private const int DarkMagenta = 5; + private const int DarkYellow = 6; + private const int DarkWhite = 7; + private const int BrightBlack = 8; + private const int BrightBlue = 9; + private const int BrightGreen = 10; + private const int BrightCyan = 11; + private const int BrightRed = 12; + private const int BrightMagenta = 13; + private const int BrightYellow = 14; + private const int BrightWhite = 15; + + // This is the order of colors when output by the table. + private static readonly IReadOnlyList Foregrounds = new[] + { + BrightWhite, + DarkBlack, + BrightBlack, + DarkRed, + BrightRed, + DarkGreen, + BrightGreen, + DarkYellow, + BrightYellow, + DarkBlue, + BrightBlue, + DarkMagenta, + BrightMagenta, + DarkCyan, + BrightCyan, + DarkWhite, + BrightWhite + }; + + private static readonly IReadOnlyList Backgrounds = new[] + { + DarkBlack, + DarkRed, + DarkGreen, + DarkYellow, + DarkBlue, + DarkMagenta, + DarkCyan, + DarkWhite + }; + + private const string TestText = " gYw "; + + private static readonly IReadOnlyList AnsiForegroundSequences = new[] + { + "m", + "1m", + "30m", + "1;30m", + "31m", + "1;31m", + "32m", + "1;32m", + "33m", + "1;33m", + "34m", + "1;34m", + "35m", + "1;35m", + "36m", + "1;36m", + "37m", + "1;37m" + }; + + private static readonly IReadOnlyList AnsiBackgroundSequences = new[] + { + "m", + "40m", + "41m", + "42m", + "43m", + "44m", + "45m", + "46m", + "47m" + }; + + public static void PrintTable() + { + ConsoleColor[] colors = (ConsoleColor[])ConsoleColor.GetValues(typeof(ConsoleColor)); + // Save the current background and foreground colors. + ConsoleColor currentBackground = Console.BackgroundColor; + ConsoleColor currentForeground = Console.ForegroundColor; + + Console.Write("\t"); + for (int bg = 0; bg < AnsiBackgroundSequences.Count; bg++) + { + if (bg > 0) Console.Write(" "); + Console.Write(" "); + Console.Write(bg == 0 ? " " : AnsiBackgroundSequences[bg]); + Console.Write(" "); + } + Console.WriteLine(); + + for (int fg = 0; fg < AnsiForegroundSequences.Count; fg++) + { + Console.ForegroundColor = currentForeground; + Console.BackgroundColor = currentBackground; + + if (fg >= 0) Console.Write(AnsiForegroundSequences[fg] + "\t"); + + if (fg == 0) Console.ForegroundColor = currentForeground; + else Console.ForegroundColor = colors[Foregrounds[fg - 1]]; + + for (int bg = 0; bg < AnsiBackgroundSequences.Count; bg++) + { + if (bg > 0) Console.Write(" "); + if (bg == 0) + Console.BackgroundColor = currentBackground; + else Console.BackgroundColor = colors[Backgrounds[bg - 1]]; + Console.Write(TestText); + Console.BackgroundColor = currentBackground; + } + Console.Write("\n"); + } + Console.Write("\n"); + + // Reset foreground and background colors + Console.ForegroundColor = currentForeground; + Console.BackgroundColor = currentBackground; + } + + public static void PrintTableWithVt() + { + Console.Write("\t"); + for (int bg = 0; bg < AnsiBackgroundSequences.Count; bg++) + { + if (bg > 0) Console.Write(" "); + Console.Write(" "); + Console.Write(bg == 0 ? " " : AnsiBackgroundSequences[bg]); + Console.Write(" "); + } + Console.WriteLine(); + + for (int fg = 0; fg < AnsiForegroundSequences.Count; fg++) + { + Console.Write("\x1b[m"); + + if (fg >= 0) + { + Console.Write(AnsiForegroundSequences[fg] + "\t"); + } + + if (fg == 0) + { + Console.Write("\x1b[39m"); + } + else + { + Console.Write("\x1b[" + AnsiForegroundSequences[fg]); + } + + for (int bg = 0; bg < AnsiBackgroundSequences.Count; bg++) + { + if (bg > 0) + { + Console.Write(" "); + } + if (bg == 0) + { + Console.Write("\x1b[49m"); + } + else + { + Console.Write("\x1b[" + AnsiBackgroundSequences[bg]); + } + + Console.Write(TestText); + Console.Write("\x1b[49m"); + } + Console.Write("\n"); + } + Console.Write("\n"); + + // Reset foreground and background colors + Console.Write("\x1b[m"); + } + } +} diff --git a/src/tools/ColorTool/ColorTool/ColorTool.csproj b/src/tools/ColorTool/ColorTool/ColorTool.csproj new file mode 100644 index 0000000000..a1df3abb45 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/ColorTool.csproj @@ -0,0 +1,9 @@ + + + + Exe + net461 + false + latest + + diff --git a/src/tools/ColorTool/ColorTool/ConsoleAPI.cs b/src/tools/ColorTool/ColorTool/ConsoleAPI.cs new file mode 100644 index 0000000000..8df31f64dc --- /dev/null +++ b/src/tools/ColorTool/ColorTool/ConsoleAPI.cs @@ -0,0 +1,94 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using System; +using System.Runtime.InteropServices; + +namespace ColorTool +{ + static class ConsoleAPI + { + private const int StdOutputHandle = -11; + public const int ColorTableSize = 16; + + [StructLayout(LayoutKind.Sequential)] + public struct COORD + { + public short X; + public short Y; + } + + public struct SMALL_RECT + { + public short Left; + public short Top; + public short Right; + public short Bottom; + } + + [Flags] + public enum ConsoleOutputModes : uint + { + ENABLE_PROCESSED_OUTPUT = 0x0001, + ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002, + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004, + DISABLE_NEWLINE_AUTO_RETURN = 0x0008, + ENABLE_LVB_GRID_WORLDWIDE = 0x0010, + } + + [StructLayout(LayoutKind.Sequential)] + public struct CONSOLE_SCREEN_BUFFER_INFO_EX + { + public uint cbSize; + public COORD dwSize; + public COORD dwCursorPosition; + public ushort wAttributes; + public SMALL_RECT srWindow; + public COORD dwMaximumWindowSize; + + public ushort wPopupAttributes; + public bool bFullscreenSupported; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public uint[] ColorTable; + + public static CONSOLE_SCREEN_BUFFER_INFO_EX Create() + { + return new CONSOLE_SCREEN_BUFFER_INFO_EX { cbSize = 96 }; + } + } + + public static IntPtr GetStdOutputHandle() => GetStdHandle(StdOutputHandle); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GetStdHandle(int nStdHandle); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFO_EX ConsoleScreenBufferInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool SetConsoleScreenBufferInfoEx(IntPtr ConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFO_EX ConsoleScreenBufferInfoEx); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool WriteConsole( + IntPtr hConsoleOutput, + string lpBuffer, + uint nNumberOfCharsToWrite, + out uint lpNumberOfCharsWritten, + IntPtr lpReserved + ); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); + + public static uint RGB(int r, int g, int b) + { + return (uint)r + (((uint)g) << 8) + (((uint)b) << 16); + } + } +} diff --git a/src/tools/ColorTool/ColorTool/ConsoleAttributes.cs b/src/tools/ColorTool/ColorTool/ConsoleAttributes.cs new file mode 100644 index 0000000000..40234528fc --- /dev/null +++ b/src/tools/ColorTool/ColorTool/ConsoleAttributes.cs @@ -0,0 +1,26 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +namespace ColorTool +{ + /// + /// Keeps track of the color table indices for the background/foreground in a colorscheme. + /// + public readonly struct ConsoleAttributes + { + public ConsoleAttributes(uint? background, uint? foreground, uint? popupBackground, uint? popupForeground) + { + Background = background; + Foreground = foreground; + PopupBackground = popupBackground; + PopupForeground = popupForeground; + } + + public uint? Foreground { get; } + public uint? Background { get; } + public uint? PopupForeground { get; } + public uint? PopupBackground { get; } + } +} diff --git a/src/tools/ColorTool/ColorTool/ConsoleTargets/CurrentConsoleTarget.cs b/src/tools/ColorTool/ColorTool/ConsoleTargets/CurrentConsoleTarget.cs new file mode 100644 index 0000000000..f116d75cd1 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/ConsoleTargets/CurrentConsoleTarget.cs @@ -0,0 +1,47 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using System; +using static ColorTool.ConsoleAPI; + +namespace ColorTool.ConsoleTargets +{ + /// + /// A console target that writes to the currently open console. + /// + class CurrentConsoleTarget : IConsoleTarget + { + public void ApplyColorScheme(ColorScheme colorScheme, bool quietMode) + { + CONSOLE_SCREEN_BUFFER_INFO_EX csbiex = CONSOLE_SCREEN_BUFFER_INFO_EX.Create(); + IntPtr hOut = GetStdOutputHandle(); + bool success = GetConsoleScreenBufferInfoEx(hOut, ref csbiex); + if (!success) + { + throw new InvalidOperationException("Could not obtain console screen buffer"); + } + + csbiex.srWindow.Bottom++; + for (int i = 0; i < 16; i++) + { + csbiex.ColorTable[i] = colorScheme.ColorTable[i]; + } + if(colorScheme.ScreenColorAttributes is ushort wAttrs) + { + csbiex.wAttributes = wAttrs; + } + if(colorScheme.PopupColorAttributes is ushort wPopupAttrs) + { + csbiex.wPopupAttributes = wPopupAttrs; + } + SetConsoleScreenBufferInfoEx(hOut, ref csbiex); + + if (!quietMode) + { + ColorTable.PrintTable(); + } + } + } +} diff --git a/src/tools/ColorTool/ColorTool/ConsoleTargets/DefaultConsoleTarget.cs b/src/tools/ColorTool/ColorTool/ConsoleTargets/DefaultConsoleTarget.cs new file mode 100644 index 0000000000..1948078119 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/ConsoleTargets/DefaultConsoleTarget.cs @@ -0,0 +1,36 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using Microsoft.Win32; +using System; + +namespace ColorTool.ConsoleTargets +{ + /// + /// A console target that writes to the Windows registry to modify system defaults + /// + class DefaultConsoleTarget : IConsoleTarget + { + public void ApplyColorScheme(ColorScheme colorScheme, bool quietMode) + { + RegistryKey consoleKey = Registry.CurrentUser.OpenSubKey("Console", true); + for (int i = 0; i < colorScheme.ColorTable.Length; i++) + { + string valueName = "ColorTable" + (i < 10 ? "0" : "") + i; + consoleKey.SetValue(valueName, colorScheme.ColorTable[i], RegistryValueKind.DWord); + } + if(colorScheme.ScreenColorAttributes is ushort screenColors) + { + consoleKey.SetValue("ScreenColors", screenColors, RegistryValueKind.DWord); + } + if(colorScheme.PopupColorAttributes is ushort popupColors) + { + consoleKey.SetValue("PopupColors", popupColors, RegistryValueKind.DWord); + } + + Console.WriteLine(Resources.WroteToDefaults); + } + } +} diff --git a/src/tools/ColorTool/ColorTool/ConsoleTargets/IConsoleTarget.cs b/src/tools/ColorTool/ColorTool/ConsoleTargets/IConsoleTarget.cs new file mode 100644 index 0000000000..a1a2b9d2a8 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/ConsoleTargets/IConsoleTarget.cs @@ -0,0 +1,15 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +namespace ColorTool.ConsoleTargets +{ + /// + /// A console that can have a color scheme applied to it. + /// + interface IConsoleTarget + { + void ApplyColorScheme(ColorScheme colorScheme, bool quietMode); + } +} diff --git a/src/tools/ColorTool/ColorTool/ConsoleTargets/VirtualTerminalConsoleTarget.cs b/src/tools/ColorTool/ColorTool/ConsoleTargets/VirtualTerminalConsoleTarget.cs new file mode 100644 index 0000000000..bbc24d96b0 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/ConsoleTargets/VirtualTerminalConsoleTarget.cs @@ -0,0 +1,69 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using static ColorTool.ConsoleAPI; + +namespace ColorTool.ConsoleTargets +{ + /// + /// A console target that writes to the currently open console, using VT sequences. + /// + class VirtualTerminalConsoleTarget : IConsoleTarget + { + // Use a Console index in to get a VT index out. + public static readonly IReadOnlyList VirtualTerminalIndices = new[] + { + 0, // Dark Black + 4, // Dark Blue + 2, // Dark Green + 6, // Dark Cyan + 1, // Dark Red + 5, // Dark Magenta + 3, // Dark Yellow + 7, // Dark White + 8+0, // Bright Black + 8+4, // Bright Blue + 8+2, // Bright Green + 8+6, // Bright Cyan + 8+1, // Bright Red + 8+5, // Bright Magenta + 8+3, // Bright Yellow + 8+7, // Bright White + }; + + public void ApplyColorScheme(ColorScheme colorScheme, bool quietMode) + { + IntPtr hOut = GetStdOutputHandle(); + uint originalMode; + uint requestedMode; + bool succeeded = GetConsoleMode(hOut, out originalMode); + if (succeeded) + { + requestedMode = originalMode | (uint)ConsoleAPI.ConsoleOutputModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hOut, requestedMode); + } + + for (int i = 0; i < colorScheme.ColorTable.Length; i++) + { + int vtIndex = VirtualTerminalIndices[i]; + Color color = colorScheme[i]; + string s = $"\x1b]4;{vtIndex};rgb:{color.R:X}/{color.G:X}/{color.B:X}\x7"; + Console.Write(s); + } + if (!quietMode) + { + ColorTable.PrintTableWithVt(); + } + + if (succeeded) + { + SetConsoleMode(hOut, originalMode); + } + } + } +} diff --git a/src/tools/ColorTool/ColorTool/Program.cs b/src/tools/ColorTool/ColorTool/Program.cs new file mode 100644 index 0000000000..b9d12f4785 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/Program.cs @@ -0,0 +1,150 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using ColorTool.ConsoleTargets; +using ColorTool.SchemeWriters; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ColorTool +{ + static class Program + { + private static bool quietMode = false; + private static bool reportErrors = false; + private static bool setDefaults = false; + private static bool setProperties = true; + private static bool setUnixStyle = false; + + public static void Main(string[] args) + { + if (args.Length < 1) + { + Usage(); + return; + } + for (int i = 0; i < args.Length; i++) + { + string arg = args[i]; + switch (arg) + { + case "-c": + case "--current": + ColorTable.PrintTable(); + return; + case "-e": + case "--errors": + reportErrors = true; + break; + case "-q": + case "--quiet": + quietMode = true; + break; + case "-d": + case "--defaults": + setDefaults = true; + setProperties = false; + break; + case "-b": + case "--both": + setDefaults = true; + setProperties = true; + break; + case "-?": + case "--help": + Usage(); + return; + case "-v": + case "--version": + Version(); + return; + case "-l": + case "--location": + SchemeManager.PrintSchemesDirectory(); + return; + case "-x": + case "--xterm": + setUnixStyle = true; + setProperties = true; + break; + case "-o": + case "--output": + if (i + 1 < args.Length) + { + new IniSchemeWriter().ExportCurrentAsIni(args[i + 1]); + } + else + { + OutputUsage(); + } + return; + case "-s": + case "--schemes": + SchemeManager.PrintSchemes(); + return; + default: + break; + } + } + + string schemeName = args[args.Length - 1]; + + ColorScheme colorScheme = SchemeManager.GetScheme(schemeName, reportErrors); + + if (colorScheme == null) + { + Console.WriteLine(string.Format(Resources.SchemeNotFound, schemeName)); + return; + } + + foreach (var target in GetConsoleTargets()) + { + target.ApplyColorScheme(colorScheme, quietMode); + } + } + + private static void Usage() + { + Console.WriteLine(Resources.Usage, + string.Join($"{Environment.NewLine} ", SchemeManager.GetParsers().Select(p => p.Name))); + } + + private static void OutputUsage() + { + Console.WriteLine(Resources.OutputUsage); + } + + private static void Version() + { + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + var info = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location); + Console.WriteLine($"colortool v{info.FileVersion}"); + } + + /// + /// Returns an enumerable of consoles that we want to apply the colorscheme to. + /// The contents of this enumerable depends on the user's provided command line flags. + /// + private static IEnumerable GetConsoleTargets() + { + if (setDefaults) + { + yield return new DefaultConsoleTarget(); + } + if (setProperties) + { + if (setUnixStyle) + { + yield return new VirtualTerminalConsoleTarget(); + } + else + { + yield return new CurrentConsoleTarget(); + } + } + } + } +} diff --git a/src/tools/ColorTool/ColorTool/Properties/launchSettings.json b/src/tools/ColorTool/ColorTool/Properties/launchSettings.json new file mode 100644 index 0000000000..5183ca3e35 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "ColorTool": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/src/tools/ColorTool/ColorTool/Resources.Designer.cs b/src/tools/ColorTool/ColorTool/Resources.Designer.cs new file mode 100644 index 0000000000..820f2a57d3 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/Resources.Designer.cs @@ -0,0 +1,143 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ColorTool { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ColorTool.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Error loading ini file "{0}". + /// + public static string IniLoadError { + get { + return ResourceManager.GetString("IniLoadError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error loading ini file "{0}" + /// for key "{1}" + /// the value "{2}" is invalid. + /// + public static string IniParseError { + get { + return ResourceManager.GetString("IniParseError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid Color. + /// + public static string InvalidColor { + get { + return ResourceManager.GetString("InvalidColor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid scheme - did not find 16 colors. + /// + public static string InvalidNumberOfColors { + get { + return ResourceManager.GetString("InvalidNumberOfColors", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Usage: colortool -o <filename>. + /// + public static string OutputUsage { + get { + return ResourceManager.GetString("OutputUsage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find or load "{0}". + /// + public static string SchemeNotFound { + get { + return ResourceManager.GetString("SchemeNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Usage: + /// colortool.exe [options] <schemename> + ///ColorTool is a utility for helping to set the color palette of the Windows Console. + ///By default, applies the colors in the specified .itermcolors or .ini file to the current console window. + ///This does NOT save the properties automatically. For that, you'll need to open the properties sheet and hit "Ok". + ///Included should be a `schemes/` directory with a selection of schemes of both formats for examples. + ///Feel free to add your own preferred scheme to that directory. [rest of string was truncated]";. + /// + public static string Usage { + get { + return ResourceManager.GetString("Usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wrote selected scheme to the defaults.. + /// + public static string WroteToDefaults { + get { + return ResourceManager.GetString("WroteToDefaults", resourceCulture); + } + } + } +} diff --git a/src/tools/ColorTool/ColorTool/Resources.resx b/src/tools/ColorTool/ColorTool/Resources.resx new file mode 100644 index 0000000000..59d8d19c49 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/Resources.resx @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Error loading ini file "{0}" + + + Error loading ini file "{0}" + for key "{1}" + the value "{2}" is invalid + + + Invalid Color + + + Invalid scheme - did not find 16 colors + + + Usage: colortool -o <filename> + + + Could not find or load "{0}" + + + Usage: + colortool.exe [options] <schemename> +ColorTool is a utility for helping to set the color palette of the Windows Console. +By default, applies the colors in the specified .itermcolors, .json or .ini file to the current console window. +This does NOT save the properties automatically. For that, you'll need to open the properties sheet and hit "Ok". +Included should be a `schemes/` directory with a selection of schemes of both formats for examples. +Feel free to add your own preferred scheme to that directory. +Arguments: + <schemename>: The name of a color scheme. ct will try to first load it as an .ini file color scheme + If that fails, it will look for it as a .json file color scheme + If that fails, it will look for it as an .itermcolors file color scheme. +Options: + -?, --help : Display this help message + -c, --current : Print the color table for the currently applied scheme + -q, --quiet : Don't print the color table after applying + -e, --errors : Report scheme parsing errors on the console + -d, --defaults : Apply the scheme to only the defaults in the registry + -b, --both : Apply the scheme to both the current console and the defaults. + -x, --xterm : Set the colors using VT sequences. Used for setting the colors in WSL. + Only works in Windows versions >= 17048. + -s, --schemes : Displays all available schemes + -l, --location : Displays the full path to the schemes directory + -v, --version : Display the version number + -o, --output <filename> : output the current color table to an file (in .ini format) + +Available importers: + {0} + + + Wrote selected scheme to the defaults. + + \ No newline at end of file diff --git a/src/tools/ColorTool/ColorTool/SchemeManager.cs b/src/tools/ColorTool/ColorTool/SchemeManager.cs new file mode 100644 index 0000000000..1f84a74601 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/SchemeManager.cs @@ -0,0 +1,106 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using ColorTool.SchemeParsers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using static ColorTool.ConsoleAPI; + +namespace ColorTool +{ + static class SchemeManager + { + public static IEnumerable GetSearchPaths(string schemeName, string extension) + { + // Search order, for argument "name", where 'exe' is the dir of the exe. + // 1. ./name + // 2. ./name.ext + // 3. ./schemes/name + // 4. ./schemes/name.ext + // 5. exe/schemes/name + // 6. exe/schemes/name.ext + // 7. name (as an absolute path) + + string cwd = "./"; + yield return cwd + schemeName; + + string filename = schemeName + extension; + yield return cwd + filename; + + string cwdSchemes = "./schemes/"; + yield return cwdSchemes + schemeName; + yield return cwdSchemes + filename; + + string exeDir = Directory.GetParent(System.Reflection.Assembly.GetEntryAssembly().Location).FullName; + string exeSchemes = exeDir + "/schemes/"; + yield return exeSchemes + schemeName; + yield return exeSchemes + filename; + yield return schemeName; + } + + public static void PrintSchemesDirectory() + { + string schemeDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "schemes"); + Console.WriteLine(schemeDirectory); + } + + public static void PrintSchemes() + { + var schemeDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "schemes"); + + if (Directory.Exists(schemeDirectory)) + { + IntPtr handle = GetStdOutputHandle(); + GetConsoleMode(handle, out var mode); + SetConsoleMode(handle, mode | 0x4); + + int consoleWidth = Console.WindowWidth; + string fgText = " gYw "; + foreach (string schemeName in Directory.GetFiles(schemeDirectory).Select(Path.GetFileName)) + { + ColorScheme colorScheme = GetScheme(schemeName, false); + if (colorScheme != null) + { + string colors = string.Empty; + for (var index = 0; index < 8; index++) + { + var color = colorScheme[index]; + // Set the background color to the color in the scheme, plus some text to show how it looks + colors += $"\x1b[48;2;{color.R};{color.G};{color.B}m{fgText}"; + } + // Align scheme colors right, or on newline if it doesn't fit + int schemeTextLength = fgText.Length * 8; + int bufferLength = consoleWidth - (schemeName.Length + schemeTextLength); + + string bufferString = bufferLength >= 0 + ? new string(' ', bufferLength) + : "\n" + new string(' ', consoleWidth - schemeTextLength); + + string outputString = schemeName + bufferString + colors; + Console.WriteLine(outputString); + Console.ResetColor(); + } + } + } + } + + public static ColorScheme GetScheme(string schemeName, bool reportErrors = false) + { + return GetParsers() + .Where(parser => parser.CanParse(schemeName)) + .Select(parser => parser.ParseScheme(schemeName, reportErrors)) + .FirstOrDefault(); + } + + public static IEnumerable GetParsers() + { + return typeof(Program).Assembly.GetTypes() + .Where(t => !t.IsAbstract && typeof(ISchemeParser).IsAssignableFrom(t)) + .Select(t => (ISchemeParser)Activator.CreateInstance(t)); + } + } +} \ No newline at end of file diff --git a/src/tools/ColorTool/ColorTool/SchemeParsers/ISchemeParser.cs b/src/tools/ColorTool/ColorTool/SchemeParsers/ISchemeParser.cs new file mode 100644 index 0000000000..cb4ebce6af --- /dev/null +++ b/src/tools/ColorTool/ColorTool/SchemeParsers/ISchemeParser.cs @@ -0,0 +1,14 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +namespace ColorTool.SchemeParsers +{ + interface ISchemeParser + { + string Name { get; } + bool CanParse(string schemeName); + ColorScheme ParseScheme(string schemeName, bool reportErrors = false); + } +} diff --git a/src/tools/ColorTool/ColorTool/SchemeParsers/IniSchemeParser.cs b/src/tools/ColorTool/ColorTool/SchemeParsers/IniSchemeParser.cs new file mode 100644 index 0000000000..e4e1add824 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/SchemeParsers/IniSchemeParser.cs @@ -0,0 +1,193 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using System; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using System.IO; +using System.Collections.Generic; +using static ColorTool.ConsoleAPI; + +namespace ColorTool.SchemeParsers +{ + class IniSchemeParser : ISchemeParser + { + [DllImport("kernel32")] + private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath); + + private const string FileExtension = ".ini"; + + // These are in Windows Color table order - BRG, not RGB. + internal static readonly IReadOnlyList ColorNames = new[] + { + "DARK_BLACK", + "DARK_BLUE", + "DARK_GREEN", + "DARK_CYAN", + "DARK_RED", + "DARK_MAGENTA", + "DARK_YELLOW", + "DARK_WHITE", + "BRIGHT_BLACK", + "BRIGHT_BLUE", + "BRIGHT_GREEN", + "BRIGHT_CYAN", + "BRIGHT_RED", + "BRIGHT_MAGENTA", + "BRIGHT_YELLOW", + "BRIGHT_WHITE" + }; + + public string Name { get; } = "INI File Parser"; + + public bool CanParse(string schemeName) => + string.Equals(Path.GetExtension(schemeName), FileExtension, StringComparison.OrdinalIgnoreCase); + + public ColorScheme ParseScheme(string schemeName, bool reportErrors = false) + { + bool success = true; + + string filename = FindIniScheme(schemeName); + if (filename == null) return null; + + string[] tableStrings = new string[ColorTableSize]; + uint[] colorTable = null; + uint? foregroundColor = null; + uint? backgroundColor = null; + uint? popupForegroundColor = null; + uint? popupBackgroundColor = null; + + for (int i = 0; i < ColorTableSize; i++) + { + string name = ColorNames[i]; + StringBuilder buffer = new StringBuilder(512); + GetPrivateProfileString("table", name, null, buffer, 512, filename); + + tableStrings[i] = buffer.ToString(); + if (tableStrings[i].Length <= 0) + { + success = false; + if (reportErrors) + { + Console.WriteLine(string.Format(Resources.IniParseError, filename, name, tableStrings[i])); + } + break; + } + } + + if (success) + { + try + { + colorTable = new uint[ColorTableSize]; + for (int i = 0; i < ColorTableSize; i++) + { + colorTable[i] = ParseColor(tableStrings[i]); + } + + if (ReadAttributes("popup", out var foreground, out var background)) + { + var foregroundIndex = (ColorNames as IList).IndexOf(foreground); + var backgroundIndex = (ColorNames as IList).IndexOf(background); + if (foregroundIndex != -1 && backgroundIndex != -1) + { + popupForegroundColor = colorTable[foregroundIndex]; + popupBackgroundColor = colorTable[backgroundIndex]; + } + } + + if (ReadAttributes("screen", out foreground, out background)) + { + var foregroundIndex = (ColorNames as IList).IndexOf(foreground); + var backgroundIndex = (ColorNames as IList).IndexOf(background); + if (foregroundIndex != -1 && backgroundIndex != -1) + { + foregroundColor = colorTable[foregroundIndex]; + backgroundColor = colorTable[backgroundIndex]; + } + } + } + catch (Exception /*e*/) + { + if (reportErrors) + { + Console.WriteLine(string.Format(Resources.IniLoadError, filename)); + } + + colorTable = null; + } + } + + if (colorTable != null) + { + var consoleAttributes = new ConsoleAttributes(backgroundColor, foregroundColor, popupBackgroundColor, popupForegroundColor); + return new ColorScheme(colorTable, consoleAttributes); + } + else + { + return null; + } + + bool ReadAttributes(string section, out string foreground, out string background) + { + foreground = null; + background = null; + + StringBuilder buffer = new StringBuilder(512); + GetPrivateProfileString(section, "FOREGROUND", null, buffer, 512, filename); + foreground = buffer.ToString(); + if (!ColorNames.Contains(foreground)) + return false; + + + buffer = new StringBuilder(512); + GetPrivateProfileString(section, "BACKGROUND", null, buffer, 512, filename); + background = buffer.ToString(); + if (!ColorNames.Contains(background)) + return false; + + return true; + } + } + + private static uint ParseHex(string arg) + { + System.Drawing.Color col = System.Drawing.ColorTranslator.FromHtml(arg); + return RGB(col.R, col.G, col.B); + } + + private static uint ParseRgb(string arg) + { + int[] components = { 0, 0, 0 }; + string[] args = arg.Split(','); + if (args.Length != components.Length) throw new Exception("Invalid color format \"" + arg + "\""); + if (args.Length != 3) throw new Exception("Invalid color format \"" + arg + "\""); + for (int i = 0; i < args.Length; i++) + { + components[i] = Int32.Parse(args[i]); + } + + return RGB(components[0], components[1], components[2]); + } + + private static uint ParseColor(string arg) + { + if (arg[0] == '#') + { + return ParseHex(arg.Substring(1)); + } + else + { + return ParseRgb(arg); + } + } + + private static string FindIniScheme(string schemeName) + { + return SchemeManager.GetSearchPaths(schemeName, FileExtension).FirstOrDefault(File.Exists); + } + } +} diff --git a/src/tools/ColorTool/ColorTool/SchemeParsers/JsonParser.cs b/src/tools/ColorTool/ColorTool/SchemeParsers/JsonParser.cs new file mode 100644 index 0000000000..37218ba18a --- /dev/null +++ b/src/tools/ColorTool/ColorTool/SchemeParsers/JsonParser.cs @@ -0,0 +1,141 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Json; +using System.Xml; +using static ColorTool.ConsoleAPI; + +namespace ColorTool.SchemeParsers +{ + class JsonParser : ISchemeParser + { + private const string FileExtension = ".json"; + private static readonly IReadOnlyList ConcfgColorNames = new[] + { + "black", // Dark Black + "dark_blue", // Dark Blue + "dark_green", // Dark Green + "dark_cyan", // Dark Cyan + "dark_red", // Dark Red + "dark_magenta", // Dark Magenta + "dark_yellow", // Dark Yellow + "gray", // Dark White + "dark_gray", // Bright Black + "blue", // Bright Blue + "green", // Bright Green + "cyan", // Bright Cyan + "red", // Bright Red + "magenta", // Bright Magenta + "yellow", // Bright Yellow + "white" // Bright White + }; + + public string Name { get; } = "concfg Parser"; + + public bool CanParse(string schemeName) => + string.Equals(Path.GetExtension(schemeName), FileExtension, StringComparison.OrdinalIgnoreCase); + + public ColorScheme ParseScheme(string schemeName, bool reportErrors = false) + { + XmlDocument xmlDoc = LoadJsonFile(schemeName); + if (xmlDoc == null) return null; + + try + { + XmlNode root = xmlDoc.DocumentElement; + XmlNodeList children = root.ChildNodes; + uint[] colorTable = new uint[ColorTableSize]; ; + for (int i = 0; i < ColorTableSize; i++) + { + string name = ConcfgColorNames[i]; + var node = children.OfType().Where(n => n.Name == name).Single(); + colorTable[i] = ParseColor(node.InnerText); + } + + + uint? popupForeground = null; + uint? popupBackground = null; + + var popupNode = children.OfType().Where(n => n.Name == "popup_colors").SingleOrDefault(); + if (popupNode != null) + { + var parts = popupNode.InnerText.Split(','); + if (parts.Length == 2) + { + var foregroundIndex = (ConcfgColorNames as IList).IndexOf(parts[0]); + var backgroundIndex = (ConcfgColorNames as IList).IndexOf(parts[1]); + if (foregroundIndex != -1 && backgroundIndex != -1) + { + popupForeground = colorTable[foregroundIndex]; + popupBackground = colorTable[backgroundIndex]; + } + } + } + + uint? screenForeground = null; + uint? screenBackground = null; + + var screenNode = children.OfType().Where(n => n.Name == "screen_colors").SingleOrDefault(); + if (screenNode != null) + { + var parts = screenNode.InnerText.Split(','); + if (parts.Length == 2) + { + var foregroundIndex = (ConcfgColorNames as IList).IndexOf(parts[0]); + var backgroundIndex = (ConcfgColorNames as IList).IndexOf(parts[1]); + if (foregroundIndex != -1 && backgroundIndex != -1) + { + screenForeground = colorTable[foregroundIndex]; + screenBackground = colorTable[backgroundIndex]; + } + } + } + + var consoleAttributes = new ConsoleAttributes(screenBackground, screenForeground, popupBackground, popupForeground); + return new ColorScheme(colorTable, consoleAttributes); + } + catch (Exception /*e*/) + { + if (reportErrors) + { + Console.WriteLine("failed to load json scheme"); + } + + return null; + } + } + + private static uint ParseColor(string arg) + { + System.Drawing.Color col = System.Drawing.ColorTranslator.FromHtml(arg); + return RGB(col.R, col.G, col.B); + } + + private static XmlDocument LoadJsonFile(string schemeName) + { + XmlDocument xmlDoc = new XmlDocument(); + foreach (string path in SchemeManager.GetSearchPaths(schemeName, FileExtension) + .Where(File.Exists)) + { + try + { + var data = File.ReadAllBytes(path); + var reader = JsonReaderWriterFactory.CreateJsonReader(data, System.Xml.XmlDictionaryReaderQuotas.Max); + xmlDoc.Load(reader); + return xmlDoc; + } + catch (XmlException /*e*/) { /* failed to parse */ } + catch (IOException /*e*/) { /* failed to find */ } + catch (UnauthorizedAccessException /*e*/) { /* unauthorized */ } + } + + return null; + } + } +} diff --git a/src/tools/ColorTool/ColorTool/SchemeParsers/XmlSchemeParser.cs b/src/tools/ColorTool/ColorTool/SchemeParsers/XmlSchemeParser.cs new file mode 100644 index 0000000000..4747fd0157 --- /dev/null +++ b/src/tools/ColorTool/ColorTool/SchemeParsers/XmlSchemeParser.cs @@ -0,0 +1,147 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Xml; +using static ColorTool.ConsoleAPI; + +namespace ColorTool.SchemeParsers +{ + class XmlSchemeParser : ISchemeParser + { + // In Windows Color Table order + private static readonly string[] PListColorNames = + { + "Ansi 0 Color", // Dark Black + "Ansi 4 Color", // Dark Blue + "Ansi 2 Color", // Dark Green + "Ansi 6 Color", // Dark Cyan + "Ansi 1 Color", // Dark Red + "Ansi 5 Color", // Dark Magenta + "Ansi 3 Color", // Dark Yellow + "Ansi 7 Color", // Dark White + "Ansi 8 Color", // Bright Black + "Ansi 12 Color", // Bright Blue + "Ansi 10 Color", // Bright Green + "Ansi 14 Color", // Bright Cyan + "Ansi 9 Color", // Bright Red + "Ansi 13 Color", // Bright Magenta + "Ansi 11 Color", // Bright Yellow + "Ansi 15 Color" // Bright White + }; + + private const string ForegroundKey = "Foreground Color"; + private const string BackgroundKey = "Background Color"; + private const string RedKey = "Red Component"; + private const string GreenKey = "Green Component"; + private const string BlueKey = "Blue Component"; + private const string FileExtension = ".itermcolors"; + + public string Name { get; } = "iTerm Parser"; + + public bool CanParse(string schemeName) => + string.Equals(Path.GetExtension(schemeName), FileExtension, StringComparison.OrdinalIgnoreCase); + + public ColorScheme ParseScheme(string schemeName, bool reportErrors = false) + { + XmlDocument xmlDoc = LoadXmlScheme(schemeName); // Create an XML document object + if (xmlDoc == null) return null; + XmlNode root = xmlDoc.GetElementsByTagName("dict")[0]; + XmlNodeList children = root.ChildNodes; + + uint[] colorTable = new uint[ColorTableSize]; + uint? fgColor = null, bgColor = null; + int colorsFound = 0; + bool success = false; + foreach (var tableEntry in children.OfType().Where(_ => _.Name == "key")) + { + uint rgb = 0; + int index = -1; + XmlNode components = tableEntry.NextSibling; + success = ParseRgbFromXml(components, ref rgb); + if (!success) { break; } + else if (tableEntry.InnerText == ForegroundKey) { fgColor = rgb; } + else if (tableEntry.InnerText == BackgroundKey) { bgColor = rgb; } + else if (-1 != (index = Array.IndexOf(PListColorNames, tableEntry.InnerText))) + { colorTable[index] = rgb; colorsFound++; } + } + if (colorsFound < ColorTableSize) + { + if (reportErrors) + { + Console.WriteLine(Resources.InvalidNumberOfColors); + } + success = false; + } + if (!success) + { + return null; + } + + var consoleAttributes = new ConsoleAttributes(bgColor, fgColor, null, null); + return new ColorScheme(colorTable, consoleAttributes); + } + + private static bool ParseRgbFromXml(XmlNode components, ref uint rgb) + { + int r = -1; + int g = -1; + int b = -1; + + foreach (XmlNode c in components.ChildNodes) + { + if (c.Name == "key") + { + if (c.InnerText == RedKey) + { + r = (int)(255 * Convert.ToDouble(c.NextSibling.InnerText, CultureInfo.InvariantCulture)); + } + else if (c.InnerText == GreenKey) + { + g = (int)(255 * Convert.ToDouble(c.NextSibling.InnerText, CultureInfo.InvariantCulture)); + } + else if (c.InnerText == BlueKey) + { + b = (int)(255 * Convert.ToDouble(c.NextSibling.InnerText, CultureInfo.InvariantCulture)); + } + else + { + continue; + } + } + } + if (r < 0 || g < 0 || b < 0) + { + Console.WriteLine(Resources.InvalidColor); + return false; + } + rgb = RGB(r, g, b); + return true; + } + + private static XmlDocument LoadXmlScheme(string schemeName) + { + XmlDocument xmlDoc = new XmlDocument(); // Create an XML document object + foreach (string path in SchemeManager.GetSearchPaths(schemeName, FileExtension) + .Where(File.Exists)) + { + try + { + xmlDoc.Load(path); + return xmlDoc; + } + catch (XmlException /*e*/) { /* failed to parse */ } + catch (IOException /*e*/) { /* failed to find */ } + catch (UnauthorizedAccessException /*e*/) { /* unauthorized */ } + } + + return null; + } + } +} diff --git a/src/tools/ColorTool/ColorTool/SchemeWriters/IniSchemeWriter.cs b/src/tools/ColorTool/ColorTool/SchemeWriters/IniSchemeWriter.cs new file mode 100644 index 0000000000..e72a35fe0c --- /dev/null +++ b/src/tools/ColorTool/ColorTool/SchemeWriters/IniSchemeWriter.cs @@ -0,0 +1,66 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +using ColorTool.SchemeParsers; +using System; +using static ColorTool.ConsoleAPI; + +namespace ColorTool.SchemeWriters +{ + class IniSchemeWriter + { + public bool ExportCurrentAsIni(string outputPath) + { + CONSOLE_SCREEN_BUFFER_INFO_EX csbiex = CONSOLE_SCREEN_BUFFER_INFO_EX.Create(); + IntPtr hOut = GetStdOutputHandle(); + bool success = GetConsoleScreenBufferInfoEx(hOut, ref csbiex); + if (success) + { + try + { + // StreamWriter can fail for a variety of file system reasons so catch exceptions and print message. + using (System.IO.StreamWriter file = new System.IO.StreamWriter(outputPath)) + { + file.WriteLine("[table]"); + for (int i = 0; i < 16; i++) + { + string line = IniSchemeParser.ColorNames[i]; + line += " = "; + uint color = csbiex.ColorTable[i]; + uint r = color & (0x000000ff); + uint g = (color & (0x0000ff00)) >> 8; + uint b = (color & (0x00ff0000)) >> 16; + line += r + "," + g + "," + b; + file.WriteLine(line); + } + + file.WriteLine(); + file.WriteLine("[screen]"); + var forgroundIndex = csbiex.wAttributes & 0xF; + var backgroundIndex = csbiex.wAttributes >> 4; + file.WriteLine($"FOREGROUND = {IniSchemeParser.ColorNames[forgroundIndex]}"); + file.WriteLine($"BACKGROUND = {IniSchemeParser.ColorNames[backgroundIndex]}"); + + file.WriteLine(); + file.WriteLine("[popup]"); + forgroundIndex = csbiex.wPopupAttributes & 0xF; + backgroundIndex = csbiex.wPopupAttributes >> 4; + file.WriteLine($"FOREGROUND = {IniSchemeParser.ColorNames[forgroundIndex]}"); + file.WriteLine($"BACKGROUND = {IniSchemeParser.ColorNames[backgroundIndex]}"); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + else + { + Console.WriteLine("Failed to get console information."); + } + return success; + } + } +} diff --git a/src/tools/ColorTool/LICENSE b/src/tools/ColorTool/LICENSE new file mode 100644 index 0000000000..21071075c2 --- /dev/null +++ b/src/tools/ColorTool/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/src/tools/ColorTool/README.md b/src/tools/ColorTool/README.md new file mode 100644 index 0000000000..ab55b240c9 --- /dev/null +++ b/src/tools/ColorTool/README.md @@ -0,0 +1,54 @@ +# ColorTool + +ColorTool makes it easy to change the Windows console to your desired scheme. Includes support for iTerm themes! + +``` +Usage: + colortool.exe [options] +ColorTool is a utility for helping to set the color palette of the Windows Console. +By default, applies the colors in the specified .itermcolors or .ini file to the current console window. +This does NOT save the properties automatically. For that, you'll need to open the properties sheet and hit "Ok". +Included should be a `schemes/` directory with a selection of schemes of both formats for examples. +Feel free to add your own preferred scheme to that directory. +Arguments: + : The name of a color scheme. ct will try to first load it as an .itermcolors color scheme. + If that fails, it will look for it as an .ini file color scheme. +Options: + -?, --help : Display this help message + -c, --current : Print the color table for the currently applied scheme + -q, --quiet : Don't print the color table after applying + -d, --defaults : Apply the scheme to only the defaults in the registry + -b, --both : Apply the scheme to both the current console and the defaults. + -s, --schemes : Display all available schemes + -v, --version : Display the version number +``` + +## Included Schemes + + Included are two important color schemes in .ini file format - `cmd-legacy` and `campbell`. + + * `cmd-legacy` is the legacy color scheme of the Windows Console, before July 2017 + + * `campbell` is the new default scheme used by the Windows Console Host, as of the Fall Creator's Update. + + There are a few other schemes in that directory in both .ini format and .itermcolors. + +## Adding Schemes + + You can also add color schemes to the colortool easily. Take any existing scheme in `.itermcolors` format, and paste it in the `schemes/` directory. Or just cd into a directory containing `*.itermcolors` files before running the colortool. + + I recommend the excellent [iTerm2-Color-Schemes](https://github.com/mbadolato/iTerm2-Color-Schemes) repo, which has TONS of schemes to choose from, and previews. + + You can also easily visually edit `.itermcolors` color schemes using [terminal.sexy](https://terminal.sexy). Use the **Import** and **Export** tabs with `iTerm2` as the format. + +## Installing + +Just [download the latest colortool release](https://github.com/Microsoft/console/releases) and extract the zip file. + +## Building + + Either build with Visual Studio, or use the included `build.bat` from the command line to try and auto-detect your msbuild version. + +## Contributing + + This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/src/tools/ColorTool/ThirdPartyNotices.md b/src/tools/ColorTool/ThirdPartyNotices.md new file mode 100644 index 0000000000..8c02c37428 --- /dev/null +++ b/src/tools/ColorTool/ThirdPartyNotices.md @@ -0,0 +1,51 @@ +Third Party Notices +=================== +This software is based on or incorporates material from the projects listed below (collectively, “Third-Party Code”). Please Note: Microsoft is not the original author of the Third-Party Code. The original copyright notice and license text under which Microsoft received the Third-Party Code are set forth below. Such licenses and notices are provided solely for your information. Unless otherwise noted below, Microsoft, not the third party, licenses this Third-Party Code to you under the terms by which you received the Microsoft software or the service. Microsoft reserves all other rights not expressly granted, whether by implication, estoppel or otherwise. + +Do Not Translate or Localize + + +One Half Dark and One Half Light Schemes +---------------------------------------- + +Copyright (c) 2016 Son A. Pham + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Solarized Dark and Light Schemes +-------------------------------- +Copyright (c) 2011 Ethan Schoonover + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/src/tools/ColorTool/all.bat b/src/tools/ColorTool/all.bat new file mode 100644 index 0000000000..8161cba5ed --- /dev/null +++ b/src/tools/ColorTool/all.bat @@ -0,0 +1,14 @@ +@echo off + +rem all.bat +rem This tool can be used to iterate over all the schemes you have installed +rem To help find one that you like. Simply press Ctrl+C when you get to one you like. +rem Note: You will likely destroy your current console window's history. +rem Only the most recent theme is visible in the console. +rem All of the previously viewed tables will display the current scheme's colors. + +for %%i in (schemes\*) do ( + echo %%i + .\colortool.exe "%%i" + pause +) diff --git a/src/tools/ColorTool/build.bat b/src/tools/ColorTool/build.bat new file mode 100644 index 0000000000..3a6a28e017 --- /dev/null +++ b/src/tools/ColorTool/build.bat @@ -0,0 +1,79 @@ +@echo off + +rem Add path to MSBuild Binaries +set MSBUILD=() +for /f "usebackq tokens=*" %%f in (`where.exe msbuild.exe 2^>nul`) do ( + set MSBUILD="%%~ff" + goto :FOUND_MSBUILD +) +if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" ( + set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" + goto :FOUND_MSBUILD +) +if exist "%ProgramFiles%\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" ( + set MSBUILD="%ProgramFiles%\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" + goto :FOUND_MSBUILD +) +if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\msbuild.exe" ( + set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\msbuild.exe" + goto :FOUND_MSBUILD +) +if exist "%ProgramFiles%\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\msbuild.exe" ( + set MSBUILD="%ProgramFiles%\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\msbuild.exe" + goto :FOUND_MSBUILD +) +if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\msbuild.exe" ( + set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\msbuild.exe" + goto :FOUND_MSBUILD +) +if exist "%ProgramFiles%\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\msbuild.exe" ( + set MSBUILD="%ProgramFiles%\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\msbuild.exe" + goto :FOUND_MSBUILD +) +if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe" ( + set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe" + goto :FOUND_MSBUILD +) +if exist "%ProgramFiles(x86)%\MSBuild\14.0\bin" ( + set MSBUILD="%ProgramFiles(x86)%\MSBuild\14.0\bin\msbuild.exe" + goto :FOUND_MSBUILD +) +if exist "%ProgramFiles%\MSBuild\14.0\bin" ( + set MSBUILD="%ProgramFiles%\MSBuild\14.0\bin\msbuild.exe" + goto :FOUND_MSBUILD +) + +if %MSBUILD%==() ( + echo "I couldn't find MSBuild on your PC. Make sure it's installed somewhere, and if it's not in the above if statements (in build.bat), add it." + goto :EXIT +) +:FOUND_MSBUILD +set _MSBUILD_TARGET=Build +set _MSBUILD_CONFIG=Debug + +:ARGS_LOOP +if (%1) == () goto :POST_ARGS_LOOP +if (%1) == (clean) ( + set _MSBUILD_TARGET=Clean,Build +) +if (%1) == (rel) ( + set _MSBUILD_CONFIG=Release +) +shift +goto :ARGS_LOOP + +:POST_ARGS_LOOP +%MSBUILD% %~dp0ColorTool.sln /t:%_MSBUILD_TARGET% /m /nr:true /p:Configuration=%_MSBUILD_CONFIG% + +if (%ERRORLEVEL%) == (0) ( + echo. + echo Created exe in: + echo %~dp0ColorTool\bin\%_MSBUILD_CONFIG%\colortool.exe + echo. + echo Copying exe to root of project... + copy %~dp0ColorTool\bin\%_MSBUILD_CONFIG%\colortool.exe %~dp0colortool.exe + echo Done. + echo. +) + +:EXIT \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/OneHalfDark.itermcolors b/src/tools/ColorTool/schemes/OneHalfDark.itermcolors new file mode 100644 index 0000000000..3dc69aca6a --- /dev/null +++ b/src/tools/ColorTool/schemes/OneHalfDark.itermcolors @@ -0,0 +1,376 @@ + + + + + + Ansi 0 Color + + Alpha Component + 1 + Blue Component + 0.203921568627 + Color Space + Calibrated + Green Component + 0.172549019608 + Red Component + 0.156862745098 + + Ansi 1 Color + + Alpha Component + 1 + Blue Component + 0.458823529412 + Color Space + Calibrated + Green Component + 0.423529411765 + Red Component + 0.878431372549 + + Ansi 10 Color + + Alpha Component + 1 + Blue Component + 0.474509803922 + Color Space + Calibrated + Green Component + 0.764705882353 + Red Component + 0.596078431373 + + Ansi 11 Color + + Alpha Component + 1 + Blue Component + 0.482352941176 + Color Space + Calibrated + Green Component + 0.752941176471 + Red Component + 0.898039215686 + + Ansi 12 Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Ansi 13 Color + + Alpha Component + 1 + Blue Component + 0.866666666667 + Color Space + Calibrated + Green Component + 0.470588235294 + Red Component + 0.776470588235 + + Ansi 14 Color + + Alpha Component + 1 + Blue Component + 0.760784313725 + Color Space + Calibrated + Green Component + 0.713725490196 + Red Component + 0.337254901961 + + Ansi 15 Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Ansi 2 Color + + Alpha Component + 1 + Blue Component + 0.474509803922 + Color Space + Calibrated + Green Component + 0.764705882353 + Red Component + 0.596078431373 + + Ansi 3 Color + + Alpha Component + 1 + Blue Component + 0.482352941176 + Color Space + Calibrated + Green Component + 0.752941176471 + Red Component + 0.898039215686 + + Ansi 4 Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Ansi 5 Color + + Alpha Component + 1 + Blue Component + 0.866666666667 + Color Space + Calibrated + Green Component + 0.470588235294 + Red Component + 0.776470588235 + + Ansi 6 Color + + Alpha Component + 1 + Blue Component + 0.760784313725 + Color Space + Calibrated + Green Component + 0.713725490196 + Red Component + 0.337254901961 + + Ansi 7 Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Ansi 8 Color + + Alpha Component + 1 + Blue Component + 0.4549019607843137 + Color Space + Calibrated + Green Component + 0.38823529411764707 + Red Component + 0.35294117647058826 + + Ansi 9 Color + + Alpha Component + 1 + Blue Component + 0.458823529412 + Color Space + Calibrated + Green Component + 0.423529411765 + Red Component + 0.878431372549 + + Background Color + + Alpha Component + 1 + Blue Component + 0.203921568627 + Color Space + Calibrated + Green Component + 0.172549019608 + Red Component + 0.156862745098 + + Badge Color + + Alpha Component + 0.5 + Blue Component + 0.0 + Color Space + Calibrated + Green Component + 0.0 + Red Component + 1 + + Bold Color + + Alpha Component + 1 + Blue Component + 0.74901962280273438 + Color Space + Calibrated + Green Component + 0.69803923368453979 + Red Component + 0.67058825492858887 + + Cursor Color + + Alpha Component + 1 + Blue Component + 0.8 + Color Space + Calibrated + Green Component + 0.701960784314 + Red Component + 0.639215686275 + + Cursor Guide Color + + Alpha Component + 0.25 + Blue Component + 0.250980392157 + Color Space + Calibrated + Green Component + 0.211764705882 + Red Component + 0.192156862745 + + Cursor Text Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Foreground Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Link Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Selected Text Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Selection Color + + Alpha Component + 1 + Blue Component + 0.364705882353 + Color Space + Calibrated + Green Component + 0.305882352941 + Red Component + 0.278431372549 + + + \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/OneHalfLight.itermcolors b/src/tools/ColorTool/schemes/OneHalfLight.itermcolors new file mode 100644 index 0000000000..5039196027 --- /dev/null +++ b/src/tools/ColorTool/schemes/OneHalfLight.itermcolors @@ -0,0 +1,376 @@ + + + + + + Ansi 0 Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Ansi 1 Color + + Alpha Component + 1 + Blue Component + 0.286274509804 + Color Space + Calibrated + Green Component + 0.337254901961 + Red Component + 0.894117647059 + + Ansi 10 Color + + Alpha Component + 1 + Blue Component + 0.474509803922 + Color Space + Calibrated + Green Component + 0.764705882353 + Red Component + 0.596078431373 + + Ansi 11 Color + + Alpha Component + 1 + Blue Component + 0.482352941176 + Color Space + Calibrated + Green Component + 0.752941176471 + Red Component + 0.898039215686 + + Ansi 12 Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Ansi 13 Color + + Alpha Component + 1 + Blue Component + 0.866666666667 + Color Space + Calibrated + Green Component + 0.470588235294 + Red Component + 0.776470588235 + + Ansi 14 Color + + Alpha Component + 1 + Blue Component + 0.760784313725 + Color Space + Calibrated + Green Component + 0.713725490196 + Red Component + 0.337254901961 + + Ansi 15 Color + + Alpha Component + 1 + Blue Component + 1.0 + Color Space + Calibrated + Green Component + 1.0 + Red Component + 1.0 + + Ansi 2 Color + + Alpha Component + 1 + Blue Component + 0.309803921569 + Color Space + Calibrated + Green Component + 0.63137254902 + Red Component + 0.313725490196 + + Ansi 3 Color + + Alpha Component + 1 + Blue Component + 0.00392156862745 + Color Space + Calibrated + Green Component + 0.517647058824 + Red Component + 0.756862745098 + + Ansi 4 Color + + Alpha Component + 1 + Blue Component + 0.737254901961 + Color Space + Calibrated + Green Component + 0.517647058824 + Red Component + 0.00392156862745 + + Ansi 5 Color + + Alpha Component + 1 + Blue Component + 0.643137254902 + Color Space + Calibrated + Green Component + 0.149019607843 + Red Component + 0.650980392157 + + Ansi 6 Color + + Alpha Component + 1 + Blue Component + 0.701960784314 + Color Space + Calibrated + Green Component + 0.592156862745 + Red Component + 0.0352941176471 + + Ansi 7 Color + + Alpha Component + 1 + Blue Component + 0.980392156863 + Color Space + Calibrated + Green Component + 0.980392156863 + Red Component + 0.980392156863 + + Ansi 8 Color + + Alpha Component + 1 + Blue Component + 0.36862745098 + Color Space + Calibrated + Green Component + 0.321568627451 + Red Component + 0.309803921569 + + Ansi 9 Color + + Alpha Component + 1 + Blue Component + 0.458823529412 + Color Space + Calibrated + Green Component + 0.423529411765 + Red Component + 0.878431372549 + + Background Color + + Alpha Component + 1 + Blue Component + 0.980392156863 + Color Space + Calibrated + Green Component + 0.980392156863 + Red Component + 0.980392156863 + + Badge Color + + Alpha Component + 0.5 + Blue Component + 0.0 + Color Space + Calibrated + Green Component + 0.0 + Red Component + 1 + + Bold Color + + Alpha Component + 1 + Blue Component + 0.74901962280273438 + Color Space + Calibrated + Green Component + 0.69803923368453979 + Red Component + 0.67058825492858887 + + Cursor Color + + Alpha Component + 1 + Blue Component + 1.0 + Color Space + Calibrated + Green Component + 0.807843137255 + Red Component + 0.749019607843 + + Cursor Guide Color + + Alpha Component + 0.25 + Blue Component + 0.941176470588 + Color Space + Calibrated + Green Component + 0.941176470588 + Red Component + 0.941176470588 + + Cursor Text Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Foreground Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Link Color + + Alpha Component + 1 + Blue Component + 0.737254901961 + Color Space + Calibrated + Green Component + 0.517647058824 + Red Component + 0.00392156862745 + + Selected Text Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Selection Color + + Alpha Component + 1 + Blue Component + 1.0 + Color Space + Calibrated + Green Component + 0.807843137255 + Red Component + 0.749019607843 + + + \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/campbell-legacy.ini b/src/tools/ColorTool/schemes/campbell-legacy.ini new file mode 100644 index 0000000000..0374bbb4e9 --- /dev/null +++ b/src/tools/ColorTool/schemes/campbell-legacy.ini @@ -0,0 +1,21 @@ +[table] +DARK_BLACK = 19,17,23 +DARK_BLUE = 6,54,222 +DARK_GREEN = 57,151,50 +DARK_CYAN = 48,151,168 +DARK_RED = 185,0,5 +DARK_MAGENTA = 141,2,180 +DARK_YELLOW = 187,182,0 +DARK_WHITE = 192,190,197 +BRIGHT_BLACK = 85,82,92 +BRIGHT_BLUE = 62,109,253 +BRIGHT_GREEN = 11,213,0 +BRIGHT_CYAN = 128,205,253 +BRIGHT_RED = 237,57,96 +BRIGHT_MAGENTA = 198,2,253 +BRIGHT_YELLOW = 255,247,149 +BRIGHT_WHITE = 240,239,241 + +[info] +name = Campbell-Legacy +author = paulcam \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/campbell.ini b/src/tools/ColorTool/schemes/campbell.ini new file mode 100644 index 0000000000..6ce99f6f3b --- /dev/null +++ b/src/tools/ColorTool/schemes/campbell.ini @@ -0,0 +1,21 @@ +[table] +DARK_BLACK = 12,12,12 +DARK_BLUE = 0,55,218 +DARK_GREEN = 19,161,14 +DARK_CYAN = 58,150,221 +DARK_RED = 197,15,31 +DARK_MAGENTA = 136,23,152 +DARK_YELLOW = 193,156,0 +DARK_WHITE = 204,204,204 +BRIGHT_BLACK = 118,118,118 +BRIGHT_BLUE = 59,120,255 +BRIGHT_GREEN = 22,198,12 +BRIGHT_CYAN = 97,214,214 +BRIGHT_RED = 231,72,86 +BRIGHT_MAGENTA = 180,0,158 +BRIGHT_YELLOW = 249,241,165 +BRIGHT_WHITE = 242,242,242 + +[info] +name = Campbell +author = crloew \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/cmd-legacy.ini b/src/tools/ColorTool/schemes/cmd-legacy.ini new file mode 100644 index 0000000000..39ee2f4de0 --- /dev/null +++ b/src/tools/ColorTool/schemes/cmd-legacy.ini @@ -0,0 +1,21 @@ +[table] +DARK_BLACK = 0, 0, 0 +DARK_BLUE = 0, 0, 128 +DARK_GREEN = 0, 128, 0 +DARK_CYAN = 0, 128, 128 +DARK_RED = 128, 0, 0 +DARK_MAGENTA = 128, 0, 128 +DARK_YELLOW = 128, 128, 0 +DARK_WHITE = 192, 192, 192 +BRIGHT_BLACK = 128, 128, 128 +BRIGHT_BLUE = 0, 0, 255 +BRIGHT_GREEN = 0, 255, 0 +BRIGHT_CYAN = 0, 255, 255 +BRIGHT_RED = 255, 0, 0 +BRIGHT_MAGENTA = 255, 0, 255 +BRIGHT_YELLOW = 255, 255, 0 +BRIGHT_WHITE = 255, 255, 255 + +[info] +name = cmd-legacy +author = unknown \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/deuteranopia.itermcolors b/src/tools/ColorTool/schemes/deuteranopia.itermcolors new file mode 100644 index 0000000000..beb56369fb --- /dev/null +++ b/src/tools/ColorTool/schemes/deuteranopia.itermcolors @@ -0,0 +1,293 @@ + + + + + + Ansi 0 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Ansi 1 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.6 + Red Component + 0.6 + + Ansi 10 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 0 + Red Component + 0 + + Ansi 11 Color + + Color Space + sRGB + Blue Component + 0.7490196078431373 + Green Component + 1 + Red Component + 1 + + Ansi 12 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 0.5019607843137255 + Red Component + 0.5019607843137255 + + Ansi 13 Color + + Color Space + sRGB + Blue Component + 0.5019607843137255 + Green Component + 1 + Red Component + 1 + + Ansi 14 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 0.7490196078431373 + Red Component + 0.7490196078431373 + + Ansi 15 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Ansi 2 Color + + Color Space + sRGB + Blue Component + 0.6 + Green Component + 0 + Red Component + 0 + + Ansi 3 Color + + Color Space + sRGB + Blue Component + 0.45098039215686275 + Green Component + 0.6 + Red Component + 0.6 + + Ansi 4 Color + + Color Space + sRGB + Blue Component + 0.6 + Green Component + 0.30196078431372547 + Red Component + 0.30196078431372547 + + Ansi 5 Color + + Color Space + sRGB + Blue Component + 0.30196078431372547 + Green Component + 0.6 + Red Component + 0.6 + + Ansi 6 Color + + Color Space + sRGB + Blue Component + 0.6 + Green Component + 0.45098039215686275 + Red Component + 0.45098039215686275 + + Ansi 7 Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Ansi 8 Color + + Color Space + sRGB + Blue Component + 0.4235294117647059 + Green Component + 0.4235294117647059 + Red Component + 0.4235294117647059 + + Ansi 9 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 1 + Red Component + 1 + + Background Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Bold Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Cursor Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Cursor Text Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Foreground Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Selected Text Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Selection Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + + diff --git a/src/tools/ColorTool/schemes/solarized_dark.itermcolors b/src/tools/ColorTool/schemes/solarized_dark.itermcolors new file mode 100644 index 0000000000..24c76f5819 --- /dev/null +++ b/src/tools/ColorTool/schemes/solarized_dark.itermcolors @@ -0,0 +1,243 @@ + + + + + + Ansi 0 Color + + Blue Component + 0.19370138645172119 + Green Component + 0.15575926005840302 + Red Component + 0.0 + + Ansi 1 Color + + Blue Component + 0.14145714044570923 + Green Component + 0.10840655118227005 + Red Component + 0.81926977634429932 + + Ansi 10 Color + + Blue Component + 0.38298487663269043 + Green Component + 0.35665956139564514 + Red Component + 0.27671992778778076 + + Ansi 11 Color + + Blue Component + 0.43850564956665039 + Green Component + 0.40717673301696777 + Red Component + 0.32436618208885193 + + Ansi 12 Color + + Blue Component + 0.51685798168182373 + Green Component + 0.50962930917739868 + Red Component + 0.44058024883270264 + + Ansi 13 Color + + Blue Component + 0.72908437252044678 + Green Component + 0.33896297216415405 + Red Component + 0.34798634052276611 + + Ansi 14 Color + + Blue Component + 0.56363654136657715 + Green Component + 0.56485837697982788 + Red Component + 0.50599193572998047 + + Ansi 15 Color + + Blue Component + 0.86405980587005615 + Green Component + 0.95794391632080078 + Red Component + 0.98943418264389038 + + Ansi 2 Color + + Blue Component + 0.020208755508065224 + Green Component + 0.54115492105484009 + Red Component + 0.44977453351020813 + + Ansi 3 Color + + Blue Component + 0.023484811186790466 + Green Component + 0.46751424670219421 + Red Component + 0.64746475219726562 + + Ansi 4 Color + + Blue Component + 0.78231418132781982 + Green Component + 0.46265947818756104 + Red Component + 0.12754884362220764 + + Ansi 5 Color + + Blue Component + 0.43516635894775391 + Green Component + 0.10802463442087173 + Red Component + 0.77738940715789795 + + Ansi 6 Color + + Blue Component + 0.52502274513244629 + Green Component + 0.57082360982894897 + Red Component + 0.14679534733295441 + + Ansi 7 Color + + Blue Component + 0.79781103134155273 + Green Component + 0.89001238346099854 + Red Component + 0.91611063480377197 + + Ansi 8 Color + + Blue Component + 0.39215686274509803 + Green Component + 0.30196078431372547 + Red Component + 0.0 + + Ansi 9 Color + + Blue Component + 0.073530435562133789 + Green Component + 0.21325300633907318 + Red Component + 0.74176257848739624 + + Background Color + + Blue Component + 0.15170273184776306 + Green Component + 0.11783610284328461 + Red Component + 0.0 + + Bold Color + + Blue Component + 0.56363654136657715 + Green Component + 0.56485837697982788 + Red Component + 0.50599193572998047 + + Cursor Color + + Blue Component + 0.51685798168182373 + Green Component + 0.50962930917739868 + Red Component + 0.44058024883270264 + + Cursor Text Color + + Blue Component + 0.19370138645172119 + Green Component + 0.15575926005840302 + Red Component + 0.0 + + Foreground Color + + Blue Component + 0.51685798168182373 + Green Component + 0.50962930917739868 + Red Component + 0.44058024883270264 + + Selected Text Color + + Blue Component + 0.56363654136657715 + Green Component + 0.56485837697982788 + Red Component + 0.50599193572998047 + + Selection Color + + Blue Component + 0.19370138645172119 + Green Component + 0.15575926005840302 + Red Component + 0.0 + + + \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/solarized_light.itermcolors b/src/tools/ColorTool/schemes/solarized_light.itermcolors new file mode 100644 index 0000000000..b39cc7714c --- /dev/null +++ b/src/tools/ColorTool/schemes/solarized_light.itermcolors @@ -0,0 +1,289 @@ + + + + + + Ansi 0 Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + Ansi 1 Color + + Color Space + sRGB + Blue Component + 0.1843137254901961 + Green Component + 0.19607843137254902 + Red Component + 0.8627450980392157 + + Ansi 10 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.6 + Red Component + 0.5215686274509804 + + Ansi 11 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.5372549019607843 + Red Component + 0.7098039215686275 + + Ansi 12 Color + + Color Space + sRGB + Blue Component + 0.8235294117647058 + Green Component + 0.5450980392156862 + Red Component + 0.14901960784313725 + + Ansi 13 Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.44313725490196076 + Red Component + 0.4235294117647059 + + Ansi 14 Color + + Color Space + sRGB + Blue Component + 0.596078431372549 + Green Component + 0.6313725490196078 + Red Component + 0.16470588235294117 + + Ansi 15 Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Ansi 2 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.6 + Red Component + 0.5215686274509804 + + Ansi 3 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.5372549019607843 + Red Component + 0.7098039215686275 + + Ansi 4 Color + + Color Space + sRGB + Blue Component + 0.8235294117647058 + Green Component + 0.5450980392156862 + Red Component + 0.14901960784313725 + + Ansi 5 Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.44313725490196076 + Red Component + 0.4235294117647059 + + Ansi 6 Color + + Color Space + sRGB + Blue Component + 0.596078431372549 + Green Component + 0.6313725490196078 + Red Component + 0.16470588235294117 + + Ansi 7 Color + + Color Space + sRGB + Blue Component + 0.6313725490196078 + Green Component + 0.6313725490196078 + Red Component + 0.5764705882352941 + + Ansi 8 Color + + Color Space + sRGB + Blue Component + 0.5137254901960784 + Green Component + 0.4823529411764706 + Red Component + 0.396078431372549 + + Ansi 9 Color + + Color Space + sRGB + Blue Component + 0.1843137254901961 + Green Component + 0.19607843137254902 + Red Component + 0.8627450980392157 + + Background Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + Bold Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Cursor Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Cursor Text Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + Foreground Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Selected Text Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Selection Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + + diff --git a/src/tools/ColorTool/signing/CopySignFiles.cmd b/src/tools/ColorTool/signing/CopySignFiles.cmd new file mode 100644 index 0000000000..51fda9ec8f --- /dev/null +++ b/src/tools/ColorTool/signing/CopySignFiles.cmd @@ -0,0 +1,37 @@ +REM @echo off +REM This script copies the files to have different paths to be signed and copies them back + +if "%2" == "" goto :usage +if "%1" == "sign" goto :sign +if "%1" == "afterSign" goto :afterSign + +goto :usage + +:sign +pushd "%2" +mkdir tosign +call :checkedCopy ..\b\Release\anycpu\ColorTool.exe tosign\ColorTool.exe + +popd +goto :end + +:afterSign +pushd "%2" +call :checkedCopy signed\ColorTool.exe ..\b\Release\anycpu\ColorTool.exe + +popd +goto :end + +:checkedCopy +copy %1 %2 +if %errorlevel% NEQ 0 ( + popd + exit 1 +) +exit /b + +:usage +echo "Usage: CopySignFiles " +echo "Will copy the binary release from \Release to be sent to signed" + +:end \ No newline at end of file diff --git a/src/tools/ColorTool/signing/SignConfig.xml b/src/tools/ColorTool/signing/SignConfig.xml new file mode 100644 index 0000000000..43b707e06a --- /dev/null +++ b/src/tools/ColorTool/signing/SignConfig.xml @@ -0,0 +1,6 @@ + + + + + +