Merged PR 3300188: Merge GitHub changes up to 82e75ce3

Related work items: #21439265
This commit is contained in:
Dustin Howett 2019-05-23 22:35:14 +00:00
commit 45350b49ad
157 changed files with 3634 additions and 1367 deletions

View file

@ -228,6 +228,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Buffer", "Buffer", "{1E4A06
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{89CDCC5C-9F53-4054-97A4-639D99F169CD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Types.Unit.Tests", "src\types\ut_types\Types.Unit.Tests.vcxproj", "{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
AuditMode|ARM64 = AuditMode|ARM64
@ -1099,6 +1101,24 @@ Global
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x64.Build.0 = Release|x64
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x86.ActiveCfg = Release|Win32
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x86.Build.0 = Release|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x64.ActiveCfg = AuditMode|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x64.Build.0 = AuditMode|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x86.ActiveCfg = AuditMode|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x86.Build.0 = AuditMode|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM64.ActiveCfg = Debug|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM64.Build.0 = Debug|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x64.ActiveCfg = Debug|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x64.Build.0 = Debug|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x86.ActiveCfg = Debug|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x86.Build.0 = Debug|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|ARM64.ActiveCfg = Release|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|ARM64.Build.0 = Release|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x64.ActiveCfg = Release|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x64.Build.0 = Release|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x86.ActiveCfg = Release|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1157,6 +1177,7 @@ Global
{F1995847-4AE5-479A-BBAF-382E51A63532} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
{05500DEF-2294-41E3-AF9A-24E580B82836} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
{1E4A062E-293B-4817-B20D-BF16B979E350} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}

View file

@ -63,7 +63,7 @@ Otherwise, you'll need to wait until Mid-June for an official preview build to d
## I built and ran the new Terminal, but I just get a blank window app!
Make sure your are building for your computer's architecture. If your box has a 64-bit Windows change your Solution Platform to x64.
Make sure you are building for your computer's architecture. If your box has a 64-bit Windows change your Solution Platform to x64.
To check your OS architecture go to Settings -> System -> About (or Win+X -> System) and under `Device specifications` check for the `System type`
## I built and ran the new Terminal, but it looks just like the old console! What gives?
@ -82,14 +82,10 @@ Secondly, try pressing <kbd>Ctrl</kbd> + <kbd>T</kbd>. The tabs are hidden when
## Prerequisites
* You must be running Windows 1903 (build >= 10.0.18362.0) or above in order to run Windows Terminal
- **As of May 2019** this build is only available through Windows Insider Program. You may register and configure Insider Program through your device's system settings.
* You must have the [1903 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) (build 10.0.18362.0) installed
* You must have at least [VS 2017](https://visualstudio.microsoft.com/downloads/) installed
* You must install the following Workloads via the VS Installer:
* You must have at least [VS 2017](https://visualstudio.microsoft.com/downloads/) installed.
* You must install the following Workloads via the VS Installer. If you're running VS 2019, opening the solution will [prompt you to install missing components automatically](https://devblogs.microsoft.com/setup/configure-visual-studio-across-your-organization-with-vsconfig/).
- Desktop Development with C++
- If you're running VS2019, you'll also need to install the following Individual Components:
- MSVC v141 - VS 2017 C++ (x86 and x64) build tools
- C++ ATL for v141 build tools (x86 and x64)
- Universal Windows Platform Development
- Also install the following Individual Component:
- C++ (v141) Universal Windows Platform Tools
@ -112,7 +108,7 @@ We ask that **before you start work on a feature that you would like to contribu
## Documentation
All documentation is located in the `./docs` folder. If you would like to contribute to the documentation, please submit a pull request.
All documentation is located in the `./doc` folder. If you would like to contribute to the documentation, please submit a pull request.
## Communicating with the Team
@ -140,7 +136,17 @@ This repository uses [git submodules](https://git-scm.com/book/en/v2/Git-Tools-S
git submodule update --init --recursive
```
OpenConsole.sln may be built from within Visual Studio or from the command-line using MSBuild. To build from the command line:
OpenConsole.sln may be built from within Visual Studio or from the command-line using MSBuild. To build from the command line, find your shell below.
### PowerShell
```powershell
Import-Module .\tools\OpenConsole.psm1
Set-MsBuildDevEnvironment
Invoke-OpenConsoleBuild
```
### CMD
```shell
.\tools\razzle.cmd

View file

@ -26,3 +26,7 @@ jobs:
- template: ./templates/build-console-ci.yml
parameters:
platform: x86
- template: ./templates/build-console-ci.yml
parameters:
platform: ARM64

89
doc/bot.md Normal file
View file

@ -0,0 +1,89 @@
# Issue/PR Management Bot Information
## Overview
The goal here is to help us automate, manage, and narrow down what we actually need to focus on in this repository.
We'll be using tags, primarily, to help us understand what needs attention, what is sitting around and turning stale, etc.
### Quick-Guidance to Core Contributors
1. Look at `Needs-Attention` as top priority
1. Look at `Needs-Triage` during triage meetings to get a handle on what's new and sort it out
1. Look at `Needs-Tag-Fix` when you have a few minutes to fix up things tagged impoperly
1. Manually add `Needs-Author-Feedback` when there's something we need the author to follow up on and want attention if they return it or an auto-close for inactivity if it goes stale.
### Tagging/Process Details
1. When new issues arrive, or when issues are not properly tagged... we'll mark them as `Needs-Triage` automatically.
- The core contributor team will then come through and mark them up as appropriate. The goal is to have a tag that fits the `Product`, `Area`, and `Issue` category.
- The `Needs-Triage` tag will be removed manually by the core contributor team during a triage meeting. (Exception, triage may also be done offline by senior team members during high-volume times.)
- An issue may or may not be assigned to a contributor during triage. It is not necessary to assign someone to complete it.
- We're not focusing on Projects yet.
1. When core contributors need to ask something of the author, they will manually assign the `Needs-Author-Feedback` tag.
- This tag will automatically drop off when the author comes back around and applies activity to the thread.
- When this tag drops off, the bot will apply the `Needs-Attention` tag to get the core contribution team's attention again. If an author cares enough to be active, we will attempt to prioritize engaging with that author.
- If the author doesn't come back around in a while, this will become a `No-Recent-Activity` tag.
- If there's activity on an issue, the `No-Recent-Activity` tag will automatically drop.
- If the `No-Recent-Activity` stays, the issue will be closed as stale.
1. PRs will automatically get a `Needs-Author-Feedback` tag when reviewers wait on the author
- This follows a similar decay strategy to issues.
- If the author responds, the `Needs-Author-Feedback` tag will drop.
- If there is no activity in a while, the `No-Recent-Activity` tag will appear.
- If the `No-Recent-Activity` tag exists for a while, the PR will be closed as stale.
1. Issues manually marked as `Resolution-Duplicate` will be closed shortly after activity stops
1. Pull requests manually marked as `AutoMerge` will permit the bot to complete the PR and do cleanup when certain conditions are met. See details below.
## Rules
### Issue Management
#### Mark as Triage Needed
- When an issue doesn't meet triage criteria, applies `Needs-Triage` tag. Right now, this is just when it's opened.
#### Author Has Responded
- When an issue with `Needs-Author-Feedback` gets an author response, drops that tag in favor of `Needs-Attention` to flag core contributors to drop by.
#### Remove Activity Tag
- When an issue with `No-Recent-Activity` has activity, drops this tag
#### Close Stale
- Every hour, checks if there's an issue with `Needs-Author-Feedback` and `No-Recent-Activity` for 3 days. Closes as stale.
#### Tag as No Activity
- Every hour, checks if there's been no activity in 4 days on an issue that `Needs-Author-Feedback`. If it's been 4 days, mark `No-Recent-Activity` as well.
#### Close Duplicate Issues
- Every hour, checks if there's been a day since the last activity on an issue with tag `Resolution-Duplicate` and closes it if inactive.
#### Enforce tag system
- When an issue is opened or labels are changed in any way, we will check if the tagging matches the system. If not, it will get `Needs-Tag-Fix`. The system is to have an `Area-`, `Issue-`, and `Product-` tag for all open things, and also a `Resolution-` for closed ones.
- When the tags from appropriate categories are applied, it will auto-remove the `Needs-Tag-Fix` tag.
### PR Management
#### Codeflow Link *(Disabled)*
- Bumps a PR with a link to the Microsoft CodeFlow tool for reviewing PRs
#### Marks PR as Awaiting Author Feedback
- When a reviewer marks the PR as changes requested, the `Needs-Author-Feedback` tag will be applied
#### Removes Awaiting Author Feedback
- When the PR author updates the pull request, comments on it, or responds to a review, the `Needs-Author-Feedback` tag is removed.
#### Removes No Recent Activity
- When anyone touches the pull request, the `No-Recent-Activity` tag is removed.
#### Markup stale pull requests
- Every hour, if a pull request `Needs-Author-Feedback` and hasn't been touched in 7 days, it will get the `No-Recent-Activity` tag.
#### Close stale pull requests
- Every hour, if a pull request has `No-Recent-Activity` and hasn't been touched in a further 7 days, it will be closed.
#### Auto-Merge pull requests
- When a pull request has the `AutoMerge` label...
- If it has been at least 480 minutes and all the statuses pass, merge it in.
- Will use Squash merge stratgy
- Will attempt to delete branch after merge, if possible
- Will automatically remove the `AutoMerge` label if changes are pushed by someone *without* Write Access.
- More information on bot-logic that can be controlled with comments is [here](https://github.com/OfficeDev/office-ui-fabric-react/wiki/Advanced-auto-merge)
## Admin Panel
[Here](https://fabric-cp.azurewebsites.net/bot/)

View file

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,23 @@
using System.Runtime.InteropServices;
namespace GUIConsole.ConPTY.Native
{
/// <summary>
/// PInvoke signatures for Win32's Console API.
/// </summary>
static class ConsoleApi
{
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
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
}
}
}

View file

@ -0,0 +1,86 @@
using System;
using System.Runtime.InteropServices;
namespace GUIConsole.ConPTY.Native
{
/// <summary>
/// PInvoke signatures for Win32's Process API.
/// </summary>
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 int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short 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);
}
}

View file

@ -0,0 +1,30 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
namespace GUIConsole.ConPTY.Native
{
/// <summary>
/// PInvoke signatures for Win32's PseudoConsole API.
/// </summary>
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 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);
}
}

View file

@ -0,0 +1,70 @@
using System;
using System.Runtime.InteropServices;
using static GUIConsole.ConPTY.Native.ProcessApi;
namespace GUIConsole.ConPTY.Processes
{
/// <summary>
/// Represents an instance of a process.
/// </summary>
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()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View file

@ -0,0 +1,99 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using static GUIConsole.ConPTY.Native.ProcessApi;
namespace GUIConsole.ConPTY.Processes
{
/// <summary>
/// Support for starting and configuring processes.
/// </summary>
/// <remarks>
/// Possible to replace with managed code? The key is being able to provide the PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE attribute
/// </remarks>
static class ProcessFactory
{
/// <summary>
/// Start and configure a process. The return value represents the process and should be disposed.
/// </summary>
internal static Process Start(string command, IntPtr attributes, IntPtr hPC)
{
var startupInfo = ConfigureProcessThread(hPC, attributes);
var processInfo = RunProcess(ref startupInfo, command);
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 Win32Exception(Marshal.GetLastWin32Error(), "Could not calculate the number of bytes for the attribute list.");
}
var startupInfo = new STARTUPINFOEX();
startupInfo.StartupInfo.cb = Marshal.SizeOf<STARTUPINFOEX>();
startupInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize);
success = InitializeProcThreadAttributeList(
lpAttributeList: startupInfo.lpAttributeList,
dwAttributeCount: 1,
dwFlags: 0,
lpSize: ref lpSize
);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not set up attribute list.");
}
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 Win32Exception(Marshal.GetLastWin32Error(), "Could not set pseudoconsole thread attribute.");
}
return startupInfo;
}
private static PROCESS_INFORMATION RunProcess(ref STARTUPINFOEX sInfoEx, string commandLine)
{
int securityAttributeSize = Marshal.SizeOf<SECURITY_ATTRIBUTES>();
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 Win32Exception(Marshal.GetLastWin32Error(), "Could not create process.");
}
return pInfo;
}
}
}

View file

@ -0,0 +1,40 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using static GUIConsole.ConPTY.Native.PseudoConsoleApi;
namespace GUIConsole.ConPTY
{
/// <summary>
/// Utility functions around the new Pseudo Console APIs.
/// </summary>
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 Win32Exception(createResult, "Could not create pseudo console.");
}
return new PseudoConsole(hPC);
}
public void Dispose()
{
ClosePseudoConsole(Handle);
}
}
}

View file

@ -0,0 +1,48 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using static GUIConsole.ConPTY.Native.PseudoConsoleApi;
namespace GUIConsole.ConPTY
{
/// <summary>
/// A pipe used to talk to the pseudoconsole, as described in:
/// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session
/// </summary>
/// <remarks>
/// We'll have two instances of this class, one for input and one for output.
/// </remarks>
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 Win32Exception(Marshal.GetLastWin32Error(), "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
}
}

View file

@ -0,0 +1,113 @@
using GUIConsole.ConPTY.Processes;
using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Threading;
using static GUIConsole.ConPTY.Native.ConsoleApi;
namespace GUIConsole.ConPTY
{
/// <summary>
/// Class for managing communication with the underlying console, and communicating with its pseudoconsole.
/// </summary>
public sealed class Terminal
{
private const string ExitCommand = "exit\r";
private const string CtrlC_Command = "\x3";
private SafeFileHandle _consoleInputPipeWriteHandle;
private StreamWriter _consoleInputWriter;
/// <summary>
/// A stream of VT-100-enabled output from the console.
/// </summary>
public FileStream ConsoleOutStream { get; private set; }
/// <summary>
/// Fired once the console has been hooked up and is ready to receive input.
/// </summary>
public event EventHandler OutputReady;
public Terminal()
{
}
/// <summary>
/// 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
/// </summary>
/// <param name="command">the command to run, e.g. cmd.exe</param>
/// <param name="consoleHeight">The height (in characters) to start the pseudoconsole with. Defaults to 80.</param>
/// <param name="consoleWidth">The width (in characters) to start the pseudoconsole with. Defaults to 30.</param>
public void Start(string command, int consoleWidth = 80, int consoleHeight = 30)
{
using (var inputPipe = new PseudoConsolePipe())
using (var outputPipe = new PseudoConsolePipe())
using (var pseudoConsole = PseudoConsole.Create(inputPipe.ReadSide, outputPipe.WriteSide, consoleWidth, consoleHeight))
using (var process = ProcessFactory.Start(command, PseudoConsole.PseudoConsoleThreadAttribute, pseudoConsole.Handle))
{
// copy all pseudoconsole output to a FileStream and expose it to the rest of the app
ConsoleOutStream = new FileStream(outputPipe.ReadSide, FileAccess.Read);
OutputReady.Invoke(this, EventArgs.Empty);
// Store input pipe handle, and a writer for later reuse
_consoleInputPipeWriteHandle = inputPipe.WriteSide;
_consoleInputWriter = new StreamWriter(new FileStream(_consoleInputPipeWriteHandle, FileAccess.Write))
{
AutoFlush = true
};
// free resources in case the console is ungracefully closed (e.g. by the 'x' in the window titlebar)
OnClose(() => DisposeResources(process, pseudoConsole, outputPipe, inputPipe, _consoleInputWriter));
WaitForExit(process).WaitOne(Timeout.Infinite);
}
}
/// <summary>
/// Sends the given string to the anonymous pipe that writes to the active pseudoconsole.
/// </summary>
/// <param name="input">A string of characters to write to the console. Supports VT-100 codes.</param>
public void WriteToPseudoConsole(string input)
{
if (_consoleInputWriter == null)
{
throw new InvalidOperationException("There is no writer attached to a pseudoconsole. Have you called Start on this instance yet?");
}
_consoleInputWriter.Write(input);
}
/// <summary>
/// Get an AutoResetEvent that signals when the process exits
/// </summary>
private static AutoResetEvent WaitForExit(Process process) =>
new AutoResetEvent(false)
{
SafeWaitHandle = new SafeWaitHandle(process.ProcessInfo.hProcess, ownsHandle: false)
};
/// <summary>
/// Set a callback for when the terminal is closed (e.g. via the "X" window decoration button).
/// Intended for resource cleanup logic.
/// </summary>
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();
}
}
}
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
</startup>
</configuration>

View file

@ -0,0 +1,8 @@
<Application x:Class="GUIConsole.Wpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View file

@ -0,0 +1,15 @@
using System.Windows;
namespace GUIConsole.Wpf
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App()
{
}
}
}

View file

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{FD2109FE-F78A-4E31-8317-11D1B66B69AF}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>GUIConsole.Wpf</RootNamespace>
<AssemblyName>GUIConsole.Wpf</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GUIConsole.ConPTY\GUIConsole.ConPTY.csproj">
<Project>{96634c74-0c52-4381-9477-97e1d58fe5b5}</Project>
<Name>GUIConsole.ConPTY</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,84 @@
<Window x:Class="GUIConsole.Wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
Background="#C7000000"
AllowsTransparency="True"
WindowStyle="None"
MouseDown="Window_MouseDown"
BorderThickness="1"
BorderBrush="LightSlateGray"
KeyDown="Window_KeyDown"
Loaded="Window_Loaded">
<Window.Resources>
<Style x:Key="TitleBarButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="FontFamily" Value="Segoe MDL2 Assets"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Height" Value="32"/>
<Setter Property="Width" Value="46"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
<Style x:Key="CloseButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource TitleBarButtonStyle}">
<Setter Property="Content" Value="&#xE10A;"/>
<!--Remove the default Button template's Triggers, otherwise they'll override our trigger below.-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Button.Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="TitleBarTitle" Grid.Column="0" VerticalAlignment="Center" Padding="10 0" Foreground="White">
GUIConsole
</TextBlock>
<StackPanel x:Name="TitleBarButtons" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Top">
<Button x:Name="MinimizeButton"
Click="MinimizeButton_Click"
Content="&#xE108;"
Style="{StaticResource TitleBarButtonStyle}"/>
<Button x:Name="MaximizeRestoreButton"
Click="MaximizeRestoreButton_Click"
Content="&#xE922;"
FontSize="12"
Style="{StaticResource TitleBarButtonStyle}"/>
<Button x:Name="CloseButton"
Click="CloseButton_Click"
FontSize="12"
Style="{StaticResource CloseButtonStyle}"/>
</StackPanel>
</Grid>
<ScrollViewer x:Name="TerminalHistoryViewer" Grid.Row="1" ScrollChanged="ScrollViewer_ScrollChanged">
<TextBlock x:Name="TerminalHistoryBlock" FontFamily="Consolas" TextWrapping="Wrap" Foreground="White"/>
</ScrollViewer>
</Grid>
</Window>

View file

@ -0,0 +1,121 @@
using GUIConsole.ConPTY;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace GUIConsole.Wpf
{
public partial class MainWindow : Window
{
private Terminal _terminal;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Start up the console, and point it to cmd.exe.
_terminal = new Terminal();
Task.Run(() => _terminal.Start("powershell.exe"));
_terminal.OutputReady += Terminal_OutputReady;
}
private void Terminal_OutputReady(object sender, EventArgs e)
{
// Start a long-lived thread for the "read console" task, so that we don't use a standard thread pool thread.
Task.Factory.StartNew(() => CopyConsoleToWindow(), TaskCreationOptions.LongRunning);
Dispatcher.Invoke(() => { TitleBarTitle.Text = "GUIConsole - powershell.exe"; });
}
private void CopyConsoleToWindow()
{
using (StreamReader reader = new StreamReader(_terminal.ConsoleOutStream))
{
// Read the console's output 1 character at a time
int bytesRead;
char[] buf = new char[1];
while ((bytesRead = reader.ReadBlock(buf, 0, 1)) != 0)
{
// This is where you'd parse and tokenize the incoming VT100 text, most likely.
Dispatcher.Invoke(() =>
{
// ...and then you'd do something to render it.
// For now, just emit raw VT100 to the primary TextBlock.
TerminalHistoryBlock.Text += new string(buf.Take(bytesRead).ToArray());
});
}
}
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (!e.Handled)
{
// This is where you'd take the pressed key, and convert it to a
// VT100 code before sending it along. For now, we'll just send _something_.
_terminal.WriteToPseudoConsole(e.Key.ToString());
}
}
private bool _autoScroll = true;
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
// User scrolled...
if (e.ExtentHeightChange == 0)
{
//...down to the bottom. Re-engage autoscrolling.
if (TerminalHistoryViewer.VerticalOffset == TerminalHistoryViewer.ScrollableHeight)
{
_autoScroll = true;
}
//...elsewhere. Disengage autoscrolling.
else
{
_autoScroll = false;
}
// Autoscrolling is enabled, and content caused scrolling:
if (_autoScroll && e.ExtentHeightChange != 0)
{
TerminalHistoryViewer.ScrollToEnd();
}
}
}
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) { DragMove(); }
}
private void MaximizeRestoreButton_Click(object sender, RoutedEventArgs e)
{
if (WindowState == WindowState.Normal)
{
WindowState = WindowState.Maximized;
MaximizeRestoreButton.Content = "\uE923";
}
else if (WindowState == WindowState.Maximized)
{
WindowState = WindowState.Normal;
MaximizeRestoreButton.Content = "\uE922";
}
}
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}

View file

@ -0,0 +1,55 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// 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("GUIConsole.Wpf")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GUIConsole.Wpf")]
[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)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// 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")]

View file

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace GUIConsole.Wpf.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GUIConsole.Wpf.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View file

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace GUIConsole.Wpf.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View file

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View file

@ -0,0 +1,61 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GUIConsole.WPF", "GUIConsole.WPF\GUIConsole.WPF.csproj", "{FD2109FE-F78A-4E31-8317-11D1B66B69AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GUIConsole.ConPTY", "GUIConsole.ConPTY\GUIConsole.ConPTY.csproj", "{96634C74-0C52-4381-9477-97E1D58FE5B5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM = Debug|ARM
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|ARM = Release|ARM
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|ARM.ActiveCfg = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|ARM.Build.0 = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x64.ActiveCfg = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x64.Build.0 = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x86.ActiveCfg = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x86.Build.0 = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|Any CPU.Build.0 = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|ARM.ActiveCfg = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|ARM.Build.0 = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x64.ActiveCfg = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x64.Build.0 = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x86.ActiveCfg = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x86.Build.0 = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|ARM.ActiveCfg = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|ARM.Build.0 = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x64.ActiveCfg = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x64.Build.0 = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x86.ActiveCfg = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x86.Build.0 = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|Any CPU.Build.0 = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|ARM.ActiveCfg = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|ARM.Build.0 = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x64.ActiveCfg = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x64.Build.0 = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x86.ActiveCfg = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0066B3A2-194D-471B-A56D-E25BB5AC0EB4}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,9 @@
# GUIConsole
This is an example of what the skeleton of a custom WPF console might look like.
The `GUIConsole.WPF` project is a WPF application targeting .NET 4.6.1. It creates a single WPF `Window` that acts as the console, and keeps the underlying console visible.
The `GUIConsole.ConPTY` project is a .NET Standard 2.0 library that handles the creation of the console, and enables pseudoconsole behavior. `Terminal.cs` contains the publicly visible pieces that the WPF application will interact with. `Terminal.cs` exposes two things that allow reading from, and writing to, the console:
* `ConsoleOutStream`, a `FileStream` hooked up to the pseudoconsole's output pipe. This will output VT100.
* `WriteToPseudoConsole(string input)`, a method that will take the given string and write it to the pseudoconsole via its input pipe. This accepts VT100.

View file

@ -238,8 +238,8 @@ void TextAttribute::SetDefaultBackground() noexcept
}
// Method Description:
// - Returns true if this attribute indicates it's foreground is the "default"
// foreground. It's _rgbForeground will contain the actual value of the
// - Returns true if this attribute indicates its foreground is the "default"
// foreground. Its _rgbForeground will contain the actual value of the
// default foreground. If the default colors are ever changed, this method
// should be used to identify attributes with the default fg value, and
// update them accordingly.
@ -253,8 +253,8 @@ bool TextAttribute::ForegroundIsDefault() const noexcept
}
// Method Description:
// - Returns true if this attribute indicates it's background is the "default"
// background. It's _rgbBackground will contain the actual value of the
// - Returns true if this attribute indicates its background is the "default"
// background. Its _rgbBackground will contain the actual value of the
// default background. If the default colors are ever changed, this method
// should be used to identify attributes with the default bg value, and
// update them accordingly.

View file

@ -32,7 +32,7 @@ void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph)
}
// Routine Description:
// - erases key and it's associated data from the storage
// - erases key and its associated data from the storage
// Arguments:
// - key - the key to remove
void UnicodeStorage::Erase(const key_type key) noexcept

View file

@ -99,6 +99,14 @@
<Content Include="Images\Wide310x150Logo.scale-150.png" />
<Content Include="Images\Wide310x150Logo.scale-200.png" />
<Content Include="Images\Wide310x150Logo.scale-400.png" />
<Content Include="ProfileIcons\{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.scale-100.png" />
<Content Include="ProfileIcons\{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.scale-200.png" />
<Content Include="ProfileIcons\{574e775e-4f2a-5b96-ac1e-a2962a402336}.scale-100.png" />
<Content Include="ProfileIcons\{574e775e-4f2a-5b96-ac1e-a2962a402336}.scale-200.png" />
<Content Include="ProfileIcons\{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.scale-100.png" />
<Content Include="ProfileIcons\{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.scale-200.png" />
<Content Include="ProfileIcons\{9acb9455-ca41-5af7-950f-6bca1bc9722f}.scale-100.png" />
<Content Include="ProfileIcons\{9acb9455-ca41-5af7-950f-6bca1bc9722f}.scale-200.png" />
<PRIResource Include="Resources\en-US\Resources.resw" />
</ItemGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -215,11 +215,26 @@ namespace winrt::TerminalApp::implementation
void App::_CreateNewTabFlyout()
{
auto newTabFlyout = Controls::MenuFlyout{};
auto keyBindings = _settings->GetKeybindings();
for (int profileIndex = 0; profileIndex < _settings->GetProfiles().size(); profileIndex++)
{
const auto& profile = _settings->GetProfiles()[profileIndex];
auto profileMenuItem = Controls::MenuFlyoutItem{};
// add the keyboard shortcuts for the first 9 profiles
if (profileIndex < 9)
{
// enum value for ShortcutAction::NewTabProfileX; 0==NewTabProfile0
auto profileKeyChord = keyBindings.GetKeyBinding(static_cast<ShortcutAction>(profileIndex + static_cast<int>(ShortcutAction::NewTabProfile0)));
// make sure we find one to display
if (profileKeyChord)
{
_SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord);
}
}
auto profileName = profile.GetName();
winrt::hstring hName{ profileName };
profileMenuItem.Text(hName);
@ -254,6 +269,12 @@ namespace winrt::TerminalApp::implementation
settingsItem.Click({ this, &App::_SettingsButtonOnClick });
newTabFlyout.Items().Append(settingsItem);
auto settingsKeyChord = keyBindings.GetKeyBinding(ShortcutAction::OpenSettings);
if (settingsKeyChord)
{
_SetAcceleratorForMenuItem(settingsItem, settingsKeyChord);
}
// Create the feedback button.
auto feedbackFlyout = Controls::MenuFlyoutItem{};
feedbackFlyout.Text(L"Feedback");
@ -405,7 +426,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
void App::_ReloadSettings()
{
_settings = CascadiaSettings::LoadAll();
_settings = CascadiaSettings::LoadAll(false);
// Re-wire the keybindings to their handlers, as we'll have created a
// new AppKeyBindings object.
_HookupKeyBindings(_settings->GetKeybindings());
@ -853,18 +874,26 @@ namespace winrt::TerminalApp::implementation
}
uint32_t tabIndexFromControl = 0;
_tabView.Items().IndexOf(tabViewItem, tabIndexFromControl);
if (tabIndexFromControl == _GetFocusedTabIndex())
{
_tabView.SelectedIndex((tabIndexFromControl > 0) ? tabIndexFromControl - 1 : 1);
}
auto focusedTabIndex = _GetFocusedTabIndex();
// Removing the tab from the collection will destroy its control and disconnect its connection.
_tabs.erase(_tabs.begin() + tabIndexFromControl);
_tabView.Items().RemoveAt(tabIndexFromControl);
// ensure tabs and focus is sync
_tabView.SelectedIndex(tabIndexFromControl > 0 ? tabIndexFromControl - 1 : 0);
if (tabIndexFromControl == focusedTabIndex)
{
if (focusedTabIndex >= _tabs.size())
{
focusedTabIndex = _tabs.size() - 1;
}
if (focusedTabIndex < 0)
{
focusedTabIndex = 0;
}
_SelectTab(focusedTabIndex);
}
}
// Method Description:
@ -899,6 +928,40 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Takes a MenuFlyoutItem and a corresponding KeyChord value and creates the accelerator for UI display.
// Takes into account a special case for an error condition for a comma
// Arguments:
// - MenuFlyoutItem that will be displayed, and a KeyChord to map an accelerator
void App::_SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord)
{
// work around https://github.com/microsoft/microsoft-ui-xaml/issues/708 in case of VK_OEM_COMMA
if (keyChord.Vkey() != VK_OEM_COMMA)
{
// use the XAML shortcut to give us the automatic capabilities
auto menuShortcut = Windows::UI::Xaml::Input::KeyboardAccelerator{};
// TODO: Modify this when https://github.com/microsoft/terminal/issues/877 is resolved
menuShortcut.Key(static_cast<Windows::System::VirtualKey>(keyChord.Vkey()));
// inspect the modifiers from the KeyChord and set the flags int he XAML value
auto modifiers = AppKeyBindings::ConvertVKModifiers(keyChord.Modifiers());
// add the modifiers to the shortcut
menuShortcut.Modifiers(modifiers);
// add to the menu
menuItem.KeyboardAccelerators().Append(menuShortcut);
}
else // we've got a comma, so need to just use the alternate method
{
// extract the modifier and key to a nice format
auto overrideString = AppKeyBindings::FormatOverrideShortcutText(keyChord.Modifiers());
menuItem.KeyboardAcceleratorTextOverride(overrideString + L" ,");
}
}
// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.

View file

@ -64,6 +64,7 @@ namespace winrt::TerminalApp::implementation
std::vector<std::shared_ptr<Tab>> _tabs;
std::unique_ptr<::TerminalApp::CascadiaSettings> _settings;
std::unique_ptr<TerminalApp::AppKeyBindings> _keyBindings;
bool _loadedInitialSettings;
@ -111,6 +112,7 @@ namespace winrt::TerminalApp::implementation
void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme);
static Windows::UI::Xaml::Controls::IconElement _GetIconFromProfile(const ::TerminalApp::Profile& profile);
static void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord);
};
}

View file

@ -1,158 +1,409 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "AppKeyBindings.h"
using namespace winrt::Microsoft::Terminal;
namespace winrt::TerminalApp::implementation
{
void AppKeyBindings::SetKeyBinding(TerminalApp::ShortcutAction const& action,
Settings::KeyChord const& chord)
{
_keyShortcuts[chord] = action;
}
bool AppKeyBindings::TryKeyChord(Settings::KeyChord const& kc)
{
const auto keyIter = _keyShortcuts.find(kc);
if (keyIter != _keyShortcuts.end())
{
const auto action = keyIter->second;
return _DoAction(action);
}
return false;
}
bool AppKeyBindings::_DoAction(ShortcutAction action)
{
switch (action)
{
case ShortcutAction::CopyText:
_CopyTextHandlers();
return true;
case ShortcutAction::PasteText:
_PasteTextHandlers();
return true;
case ShortcutAction::NewTab:
_NewTabHandlers();
return true;
case ShortcutAction::OpenSettings:
_OpenSettingsHandlers();
return true;
case ShortcutAction::NewTabProfile0:
_NewTabWithProfileHandlers(0);
return true;
case ShortcutAction::NewTabProfile1:
_NewTabWithProfileHandlers(1);
return true;
case ShortcutAction::NewTabProfile2:
_NewTabWithProfileHandlers(2);
return true;
case ShortcutAction::NewTabProfile3:
_NewTabWithProfileHandlers(3);
return true;
case ShortcutAction::NewTabProfile4:
_NewTabWithProfileHandlers(4);
return true;
case ShortcutAction::NewTabProfile5:
_NewTabWithProfileHandlers(5);
return true;
case ShortcutAction::NewTabProfile6:
_NewTabWithProfileHandlers(6);
return true;
case ShortcutAction::NewTabProfile7:
_NewTabWithProfileHandlers(7);
return true;
case ShortcutAction::NewTabProfile8:
_NewTabWithProfileHandlers(8);
return true;
case ShortcutAction::NewTabProfile9:
_NewTabWithProfileHandlers(9);
return true;
case ShortcutAction::NewWindow:
_NewWindowHandlers();
return true;
case ShortcutAction::CloseWindow:
_CloseWindowHandlers();
return true;
case ShortcutAction::CloseTab:
_CloseTabHandlers();
return true;
case ShortcutAction::ScrollUp:
_ScrollUpHandlers();
return true;
case ShortcutAction::ScrollDown:
_ScrollDownHandlers();
return true;
case ShortcutAction::ScrollUpPage:
_ScrollUpPageHandlers();
return true;
case ShortcutAction::ScrollDownPage:
_ScrollDownPageHandlers();
return true;
case ShortcutAction::NextTab:
_NextTabHandlers();
return true;
case ShortcutAction::PrevTab:
_PrevTabHandlers();
return true;
case ShortcutAction::SwitchToTab0:
_SwitchToTabHandlers(0);
return true;
case ShortcutAction::SwitchToTab1:
_SwitchToTabHandlers(1);
return true;
case ShortcutAction::SwitchToTab2:
_SwitchToTabHandlers(2);
return true;
case ShortcutAction::SwitchToTab3:
_SwitchToTabHandlers(3);
return true;
case ShortcutAction::SwitchToTab4:
_SwitchToTabHandlers(4);
return true;
case ShortcutAction::SwitchToTab5:
_SwitchToTabHandlers(5);
return true;
case ShortcutAction::SwitchToTab6:
_SwitchToTabHandlers(6);
return true;
case ShortcutAction::SwitchToTab7:
_SwitchToTabHandlers(7);
return true;
case ShortcutAction::SwitchToTab8:
_SwitchToTabHandlers(8);
return true;
case ShortcutAction::SwitchToTab9:
_SwitchToTabHandlers(9);
return true;
}
return false;
}
// -------------------------------- Events ---------------------------------
DEFINE_EVENT(AppKeyBindings, CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs);
DEFINE_EVENT(AppKeyBindings, PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs);
DEFINE_EVENT(AppKeyBindings, NewTab, _NewTabHandlers, TerminalApp::NewTabEventArgs);
DEFINE_EVENT(AppKeyBindings, NewTabWithProfile, _NewTabWithProfileHandlers, TerminalApp::NewTabWithProfileEventArgs);
DEFINE_EVENT(AppKeyBindings, NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs);
DEFINE_EVENT(AppKeyBindings, CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs);
DEFINE_EVENT(AppKeyBindings, CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs);
DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs);
DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs);
DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs);
DEFINE_EVENT(AppKeyBindings, IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs);
DEFINE_EVENT(AppKeyBindings, DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "AppKeyBindings.h"
#include "KeyChordSerialization.h"
using namespace winrt::Microsoft::Terminal;
using namespace winrt::TerminalApp;
using namespace winrt::Windows::Data::Json;
static constexpr std::wstring_view KEYS_KEY{ L"keys" };
static constexpr std::wstring_view COMMAND_KEY{ L"command" };
static constexpr std::wstring_view COPYTEXT_KEY{ L"copy" };
static constexpr std::wstring_view PASTETEXT_KEY{ L"paste" };
static constexpr std::wstring_view NEWTAB_KEY{ L"newTab" };
static constexpr std::wstring_view NEWTABWITHPROFILE0_KEY{ L"newTabProfile0" };
static constexpr std::wstring_view NEWTABWITHPROFILE1_KEY{ L"newTabProfile1" };
static constexpr std::wstring_view NEWTABWITHPROFILE2_KEY{ L"newTabProfile2" };
static constexpr std::wstring_view NEWTABWITHPROFILE3_KEY{ L"newTabProfile3" };
static constexpr std::wstring_view NEWTABWITHPROFILE4_KEY{ L"newTabProfile4" };
static constexpr std::wstring_view NEWTABWITHPROFILE5_KEY{ L"newTabProfile5" };
static constexpr std::wstring_view NEWTABWITHPROFILE6_KEY{ L"newTabProfile6" };
static constexpr std::wstring_view NEWTABWITHPROFILE7_KEY{ L"newTabProfile7" };
static constexpr std::wstring_view NEWTABWITHPROFILE8_KEY{ L"newTabProfile8" };
static constexpr std::wstring_view NEWWINDOW_KEY{ L"newWindow" };
static constexpr std::wstring_view CLOSEWINDOW_KEY{ L"closeWindow" };
static constexpr std::wstring_view CLOSETAB_KEY{ L"closeTab" };
static constexpr std::wstring_view SWITCHTOTAB_KEY{ L"switchToTab" };
static constexpr std::wstring_view NEXTTAB_KEY{ L"nextTab" };
static constexpr std::wstring_view PREVTAB_KEY{ L"prevTab" };
static constexpr std::wstring_view INCREASEFONTSIZE_KEY{ L"increaseFontSize" };
static constexpr std::wstring_view DECREASEFONTSIZE_KEY{ L"decreaseFontSize" };
static constexpr std::wstring_view SCROLLUP_KEY{ L"scrollUp" };
static constexpr std::wstring_view SCROLLDOWN_KEY{ L"scrollDown" };
static constexpr std::wstring_view SCROLLUPPAGE_KEY{ L"scrollUpPage" };
static constexpr std::wstring_view SCROLLDOWNPAGE_KEY{ L"scrollDownPage" };
static constexpr std::wstring_view SWITCHTOTAB0_KEY{ L"switchToTab0" };
static constexpr std::wstring_view SWITCHTOTAB1_KEY{ L"switchToTab1" };
static constexpr std::wstring_view SWITCHTOTAB2_KEY{ L"switchToTab2" };
static constexpr std::wstring_view SWITCHTOTAB3_KEY{ L"switchToTab3" };
static constexpr std::wstring_view SWITCHTOTAB4_KEY{ L"switchToTab4" };
static constexpr std::wstring_view SWITCHTOTAB5_KEY{ L"switchToTab5" };
static constexpr std::wstring_view SWITCHTOTAB6_KEY{ L"switchToTab6" };
static constexpr std::wstring_view SWITCHTOTAB7_KEY{ L"switchToTab7" };
static constexpr std::wstring_view SWITCHTOTAB8_KEY{ L"switchToTab8" };
static constexpr std::wstring_view OPENSETTINGS_KEY{ L"openSettings" };
// Specifically use a map here over an unordered_map. We want to be able to
// iterate over these entries in-order when we're serializing the keybindings.
static const std::map<std::wstring_view, ShortcutAction> commandNames {
{ COPYTEXT_KEY, ShortcutAction::CopyText },
{ PASTETEXT_KEY, ShortcutAction::PasteText },
{ NEWTAB_KEY, ShortcutAction::NewTab },
{ NEWTABWITHPROFILE0_KEY, ShortcutAction::NewTabProfile0 },
{ NEWTABWITHPROFILE1_KEY, ShortcutAction::NewTabProfile1 },
{ NEWTABWITHPROFILE2_KEY, ShortcutAction::NewTabProfile2 },
{ NEWTABWITHPROFILE3_KEY, ShortcutAction::NewTabProfile3 },
{ NEWTABWITHPROFILE4_KEY, ShortcutAction::NewTabProfile4 },
{ NEWTABWITHPROFILE5_KEY, ShortcutAction::NewTabProfile5 },
{ NEWTABWITHPROFILE6_KEY, ShortcutAction::NewTabProfile6 },
{ NEWTABWITHPROFILE7_KEY, ShortcutAction::NewTabProfile7 },
{ NEWTABWITHPROFILE8_KEY, ShortcutAction::NewTabProfile8 },
{ NEWWINDOW_KEY, ShortcutAction::NewWindow },
{ CLOSEWINDOW_KEY, ShortcutAction::CloseWindow },
{ CLOSETAB_KEY, ShortcutAction::CloseTab },
{ NEXTTAB_KEY, ShortcutAction::NextTab },
{ PREVTAB_KEY, ShortcutAction::PrevTab },
{ INCREASEFONTSIZE_KEY, ShortcutAction::IncreaseFontSize },
{ DECREASEFONTSIZE_KEY, ShortcutAction::DecreaseFontSize },
{ SCROLLUP_KEY, ShortcutAction::ScrollUp },
{ SCROLLDOWN_KEY, ShortcutAction::ScrollDown },
{ SCROLLUPPAGE_KEY, ShortcutAction::ScrollUpPage },
{ SCROLLDOWNPAGE_KEY, ShortcutAction::ScrollDownPage },
{ SWITCHTOTAB0_KEY, ShortcutAction::SwitchToTab0 },
{ SWITCHTOTAB1_KEY, ShortcutAction::SwitchToTab1 },
{ SWITCHTOTAB2_KEY, ShortcutAction::SwitchToTab2 },
{ SWITCHTOTAB3_KEY, ShortcutAction::SwitchToTab3 },
{ SWITCHTOTAB4_KEY, ShortcutAction::SwitchToTab4 },
{ SWITCHTOTAB5_KEY, ShortcutAction::SwitchToTab5 },
{ SWITCHTOTAB6_KEY, ShortcutAction::SwitchToTab6 },
{ SWITCHTOTAB7_KEY, ShortcutAction::SwitchToTab7 },
{ SWITCHTOTAB8_KEY, ShortcutAction::SwitchToTab8 },
{ OPENSETTINGS_KEY, ShortcutAction::OpenSettings },
};
namespace winrt::TerminalApp::implementation
{
void AppKeyBindings::SetKeyBinding(const TerminalApp::ShortcutAction& action,
const Settings::KeyChord& chord)
{
_keyShortcuts[chord] = action;
}
Microsoft::Terminal::Settings::KeyChord AppKeyBindings::GetKeyBinding(TerminalApp::ShortcutAction const& action)
{
for (auto& kv : _keyShortcuts)
{
if (kv.second == action) return kv.first;
}
return { nullptr };
}
bool AppKeyBindings::TryKeyChord(const Settings::KeyChord& kc)
{
const auto keyIter = _keyShortcuts.find(kc);
if (keyIter != _keyShortcuts.end())
{
const auto action = keyIter->second;
return _DoAction(action);
}
return false;
}
bool AppKeyBindings::_DoAction(ShortcutAction action)
{
switch (action)
{
case ShortcutAction::CopyText:
_CopyTextHandlers();
return true;
case ShortcutAction::PasteText:
_PasteTextHandlers();
return true;
case ShortcutAction::NewTab:
_NewTabHandlers();
return true;
case ShortcutAction::OpenSettings:
_OpenSettingsHandlers();
return true;
case ShortcutAction::NewTabProfile0:
_NewTabWithProfileHandlers(0);
return true;
case ShortcutAction::NewTabProfile1:
_NewTabWithProfileHandlers(1);
return true;
case ShortcutAction::NewTabProfile2:
_NewTabWithProfileHandlers(2);
return true;
case ShortcutAction::NewTabProfile3:
_NewTabWithProfileHandlers(3);
return true;
case ShortcutAction::NewTabProfile4:
_NewTabWithProfileHandlers(4);
return true;
case ShortcutAction::NewTabProfile5:
_NewTabWithProfileHandlers(5);
return true;
case ShortcutAction::NewTabProfile6:
_NewTabWithProfileHandlers(6);
return true;
case ShortcutAction::NewTabProfile7:
_NewTabWithProfileHandlers(7);
return true;
case ShortcutAction::NewTabProfile8:
_NewTabWithProfileHandlers(8);
return true;
case ShortcutAction::NewWindow:
_NewWindowHandlers();
return true;
case ShortcutAction::CloseWindow:
_CloseWindowHandlers();
return true;
case ShortcutAction::CloseTab:
_CloseTabHandlers();
return true;
case ShortcutAction::ScrollUp:
_ScrollUpHandlers();
return true;
case ShortcutAction::ScrollDown:
_ScrollDownHandlers();
return true;
case ShortcutAction::ScrollUpPage:
_ScrollUpPageHandlers();
return true;
case ShortcutAction::ScrollDownPage:
_ScrollDownPageHandlers();
return true;
case ShortcutAction::NextTab:
_NextTabHandlers();
return true;
case ShortcutAction::PrevTab:
_PrevTabHandlers();
return true;
case ShortcutAction::SwitchToTab0:
_SwitchToTabHandlers(0);
return true;
case ShortcutAction::SwitchToTab1:
_SwitchToTabHandlers(1);
return true;
case ShortcutAction::SwitchToTab2:
_SwitchToTabHandlers(2);
return true;
case ShortcutAction::SwitchToTab3:
_SwitchToTabHandlers(3);
return true;
case ShortcutAction::SwitchToTab4:
_SwitchToTabHandlers(4);
return true;
case ShortcutAction::SwitchToTab5:
_SwitchToTabHandlers(5);
return true;
case ShortcutAction::SwitchToTab6:
_SwitchToTabHandlers(6);
return true;
case ShortcutAction::SwitchToTab7:
_SwitchToTabHandlers(7);
return true;
case ShortcutAction::SwitchToTab8:
_SwitchToTabHandlers(8);
return true;
default:
return false;
}
return false;
}
// -------------------------------- Events ---------------------------------
DEFINE_EVENT(AppKeyBindings, CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs);
DEFINE_EVENT(AppKeyBindings, PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs);
DEFINE_EVENT(AppKeyBindings, NewTab, _NewTabHandlers, TerminalApp::NewTabEventArgs);
DEFINE_EVENT(AppKeyBindings, NewTabWithProfile, _NewTabWithProfileHandlers, TerminalApp::NewTabWithProfileEventArgs);
DEFINE_EVENT(AppKeyBindings, NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs);
DEFINE_EVENT(AppKeyBindings, CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs);
DEFINE_EVENT(AppKeyBindings, CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs);
DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs);
DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs);
DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs);
DEFINE_EVENT(AppKeyBindings, IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs);
DEFINE_EVENT(AppKeyBindings, DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
// Method Description:
// - Deserialize an AppKeyBindings from the key mappings that are in the
// array `json`. The json array should contain an array of objects with
// both a `command` string and a `keys` array, where `command` is one of
// the names listed in `commandNames`, and `keys` is an array of
// keypresses. Currently, the array should contain a single string, which
// can be deserialized into a KeyChord.
// Arguments:
// - json: and array of JsonObject's to deserialize into our _keyShortcuts mapping.
// Return Value:
// - the newly constructed AppKeyBindings object.
TerminalApp::AppKeyBindings AppKeyBindings::FromJson(const JsonArray& json)
{
TerminalApp::AppKeyBindings newBindings{};
for (const auto& value : json)
{
if (value.ValueType() == JsonValueType::Object)
{
JsonObject obj = value.GetObjectW();
if (obj.HasKey(COMMAND_KEY) && obj.HasKey(KEYS_KEY))
{
const auto commandString = obj.GetNamedString(COMMAND_KEY);
const auto keys = obj.GetNamedArray(KEYS_KEY);
if (keys.Size() != 1)
{
continue;
}
const auto keyChordString = keys.GetAt(0).GetString();
ShortcutAction action;
// Try matching the command to one we have
auto found = commandNames.find(commandString);
if (found != commandNames.end())
{
action = found->second;
}
else
{
continue;
}
// Try parsing the chord
try
{
auto chord = KeyChordSerialization::FromString(keyChordString);
newBindings.SetKeyBinding(action, chord);
}
catch (...)
{
continue;
}
}
}
}
return newBindings;
}
// Function Description:
// - Small helper to insert a given KeyChord, ShortcutAction pair into the
// given json array
// Arguments:
// - bindingsArray: The JsonArray to insert the object into.
// - chord: The KeyChord to serailize and place in the json array
// - actionName: the name of the ShortcutAction to use with this KeyChord
static void _AddShortcutToJsonArray(const JsonArray& bindingsArray,
const Settings::KeyChord& chord,
const std::wstring_view& actionName)
{
const auto keyString = KeyChordSerialization::ToString(chord);
if (keyString == L"")
{
return;
}
winrt::Windows::Data::Json::JsonObject jsonObject;
winrt::Windows::Data::Json::JsonArray keysArray;
keysArray.Append(JsonValue::CreateStringValue(keyString));
jsonObject.Insert(KEYS_KEY, keysArray);
jsonObject.Insert(COMMAND_KEY, JsonValue::CreateStringValue(actionName));
bindingsArray.Append(jsonObject);
}
// Method Description:
// - Serialize this AppKeyBindings to a json array of objects. Each object
// in the array represents a single keybinding, mapping a KeyChord to a
// ShortcutAction.
// Return Value:
// - a JsonArray which is an equivalent serialization of this object.
Windows::Data::Json::JsonArray AppKeyBindings::ToJson()
{
winrt::Windows::Data::Json::JsonArray bindingsArray;
// Iterate over all the possible actions in the names list, and see if
// it has a binding.
for (auto& actionName : commandNames)
{
const auto searchedForName = actionName.first;
const auto searchedForAction = actionName.second;
for (const auto& kv : _keyShortcuts)
{
const auto chord = kv.first;
const auto command = kv.second;
if (command == searchedForAction)
{
_AddShortcutToJsonArray(bindingsArray, chord, searchedForName);
}
}
}
return bindingsArray;
}
// Method Description:
// - Takes the KeyModifier flags from Terminal and maps them to the WinRT types which are used by XAML
// Return Value:
// - a Windows::System::VirtualKeyModifiers object with the flags of which modifiers used.
Windows::System::VirtualKeyModifiers AppKeyBindings::ConvertVKModifiers(Settings::KeyModifiers modifiers)
{
Windows::System::VirtualKeyModifiers keyModifiers = Windows::System::VirtualKeyModifiers::None;
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Ctrl))
{
keyModifiers |= Windows::System::VirtualKeyModifiers::Control;
}
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Shift))
{
keyModifiers |= Windows::System::VirtualKeyModifiers::Shift;
}
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Alt))
{
// note: Menu is the Alt VK_MENU
keyModifiers |= Windows::System::VirtualKeyModifiers::Menu;
}
return keyModifiers;
}
// Method Description:
// - Handles the special case of providing a text override for the UI shortcut due to VK_OEM_COMMA issue.
// Looks at the flags from the KeyChord modifiers and provides a concatenated string value of all
// in the same order that XAML would put them as well.
// Return Value:
// - a WinRT hstring representation of the key modifiers for the shortcut
//NOTE: This needs to be localized with https://github.com/microsoft/terminal/issues/794 if XAML framework issue not resolved before then
winrt::hstring AppKeyBindings::FormatOverrideShortcutText(Settings::KeyModifiers modifiers)
{
std::wstring buffer{ L"" };
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Ctrl))
{
buffer += L"Ctrl+";
}
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Shift))
{
buffer += L"Shift+";
}
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Alt))
{
buffer += L"Alt+";
}
return winrt::hstring{ buffer };
}
}

View file

@ -32,8 +32,14 @@ namespace winrt::TerminalApp::implementation
{
AppKeyBindings() = default;
static TerminalApp::AppKeyBindings FromJson(Windows::Data::Json::JsonArray const& json);
Windows::Data::Json::JsonArray ToJson();
static Windows::System::VirtualKeyModifiers ConvertVKModifiers(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers);
static winrt::hstring FormatOverrideShortcutText(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers);
bool TryKeyChord(winrt::Microsoft::Terminal::Settings::KeyChord const& kc);
void SetKeyBinding(TerminalApp::ShortcutAction const& action, winrt::Microsoft::Terminal::Settings::KeyChord const& chord);
Microsoft::Terminal::Settings::KeyChord GetKeyBinding(TerminalApp::ShortcutAction const& action);
DECLARE_EVENT(CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs);
DECLARE_EVENT(PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs);

View file

@ -17,7 +17,6 @@ namespace TerminalApp
NewTabProfile6,
NewTabProfile7,
NewTabProfile8,
NewTabProfile9,
NewWindow,
CloseWindow,
CloseTab,
@ -32,7 +31,6 @@ namespace TerminalApp
SwitchToTab6,
SwitchToTab7,
SwitchToTab8,
SwitchToTab9,
IncreaseFontSize,
DecreaseFontSize,
ScrollUp,
@ -65,7 +63,11 @@ namespace TerminalApp
{
AppKeyBindings();
Windows.Data.Json.JsonArray ToJson();
static AppKeyBindings FromJson(Windows.Data.Json.JsonArray json);
void SetKeyBinding(ShortcutAction action, Microsoft.Terminal.Settings.KeyChord chord);
Microsoft.Terminal.Settings.KeyChord GetKeyBinding(ShortcutAction action);
event CopyTextEventArgs CopyText;
event PasteTextEventArgs PasteText;

View file

@ -12,6 +12,15 @@ using namespace winrt::Microsoft::Terminal::Settings;
using namespace ::TerminalApp;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::TerminalApp;
using namespace Microsoft::Console;
// {2bde4a90-d05f-401c-9492-e40884ead1d8}
// uuidv5 properties: name format is UTF-16LE bytes
static constexpr GUID TERMINAL_PROFILE_NAMESPACE_GUID =
{ 0x2bde4a90, 0xd05f, 0x401c, { 0x94, 0x92, 0xe4, 0x8, 0x84, 0xea, 0xd1, 0xd8 } };
static constexpr std::wstring_view PACKAGED_PROFILE_ICON_PATH{ L"ms-appx:///ProfileIcons/" };
static constexpr std::wstring_view PACKAGED_PROFILE_ICON_EXTENSION{ L".png" };
CascadiaSettings::CascadiaSettings() :
_globals{},
@ -32,8 +41,8 @@ ColorScheme _CreateCampbellScheme()
RGB(12, 12, 12) };
auto& campbellTable = campbellScheme.GetTable();
auto campbellSpan = gsl::span<COLORREF>(&campbellTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
Microsoft::Console::Utils::InitializeCampbellColorTable(campbellSpan);
Microsoft::Console::Utils::SetColorTableAlpha(campbellSpan, 0xff);
Utils::InitializeCampbellColorTable(campbellSpan);
Utils::SetColorTableAlpha(campbellSpan, 0xff);
return campbellScheme;
}
@ -64,7 +73,7 @@ ColorScheme _CreateOneHalfDarkScheme()
oneHalfDarkTable[13] = RGB(198, 120, 221); // magenta
oneHalfDarkTable[14] = RGB( 86, 182, 194); // cyan
oneHalfDarkTable[15] = RGB(220, 223, 228); // white
Microsoft::Console::Utils::SetColorTableAlpha(oneHalfDarkSpan, 0xff);
Utils::SetColorTableAlpha(oneHalfDarkSpan, 0xff);
return oneHalfDarkScheme;
}
@ -94,7 +103,7 @@ ColorScheme _CreateOneHalfLightScheme()
oneHalfLightTable[13] = RGB(197, 119, 221); // magenta
oneHalfLightTable[14] = RGB( 86, 181, 193); // cyan
oneHalfLightTable[15] = RGB(255, 255, 255); // white
Microsoft::Console::Utils::SetColorTableAlpha(oneHalfLightSpan, 0xff);
Utils::SetColorTableAlpha(oneHalfLightSpan, 0xff);
return oneHalfLightScheme;
}
@ -122,7 +131,7 @@ ColorScheme _CreateSolarizedDarkScheme()
solarizedDarkTable[13] = RGB(108, 113, 196);
solarizedDarkTable[14] = RGB(147, 161, 161);
solarizedDarkTable[15] = RGB(253, 246, 227);
Microsoft::Console::Utils::SetColorTableAlpha(solarizedDarkSpan, 0xff);
Utils::SetColorTableAlpha(solarizedDarkSpan, 0xff);
return solarizedDarkScheme;
}
@ -150,7 +159,7 @@ ColorScheme _CreateSolarizedLightScheme()
solarizedLightTable[13] = RGB(108, 113, 196);
solarizedLightTable[14] = RGB(147, 161, 161);
solarizedLightTable[15] = RGB(253, 246, 227);
Microsoft::Console::Utils::SetColorTableAlpha(solarizedLightSpan, 0xff);
Utils::SetColorTableAlpha(solarizedLightSpan, 0xff);
return solarizedLightScheme;
}
@ -182,16 +191,15 @@ void CascadiaSettings::_CreateDefaultSchemes()
// - <none>
void CascadiaSettings::_CreateDefaultProfiles()
{
Profile cmdProfile{};
auto cmdProfile{ _CreateDefaultProfile(L"cmd") };
cmdProfile.SetFontFace(L"Consolas");
cmdProfile.SetCommandline(L"cmd.exe");
cmdProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
cmdProfile.SetColorScheme({ L"Campbell" });
cmdProfile.SetAcrylicOpacity(0.75);
cmdProfile.SetUseAcrylic(true);
cmdProfile.SetName(L"cmd");
Profile powershellProfile{};
auto powershellProfile{ _CreateDefaultProfile(L"PowerShell") };
// If the user has installed PowerShell Core, we add PowerShell Core as a default.
// PowerShell Core default folder is "%PROGRAMFILES%\PowerShell\[Version]\".
std::wstring psCmdline = L"powershell.exe";
@ -210,7 +218,6 @@ void CascadiaSettings::_CreateDefaultProfiles()
powershellProfile.SetColorScheme({ L"Campbell" });
powershellProfile.SetDefaultBackground(RGB(1, 36, 86));
powershellProfile.SetUseAcrylic(false);
powershellProfile.SetName(L"PowerShell");
_profiles.emplace_back(powershellProfile);
_profiles.emplace_back(cmdProfile);
@ -278,9 +285,6 @@ void CascadiaSettings::_CreateDefaultKeybindings()
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile8,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('9') });
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile9,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('0') });
keyBindings.SetKeyBinding(ShortcutAction::ScrollUp,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
@ -321,9 +325,6 @@ void CascadiaSettings::_CreateDefaultKeybindings()
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab8,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('9') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab9,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('0') });
}
// Method Description:
@ -468,3 +469,26 @@ std::wstring CascadiaSettings::ExpandEnvironmentVariableString(std::wstring_view
result.resize(requiredSize-1);
return result;
}
// Method Description:
// - Helper function for creating a skeleton default profile with a pre-populated
// guid and name.
// Arguments:
// - name: the name of the new profile.
// Return Value:
// - A Profile, ready to be filled in
Profile CascadiaSettings::_CreateDefaultProfile(const std::wstring_view name)
{
auto profileGuid{ Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(name))) };
Profile newProfile{ profileGuid };
newProfile.SetName(static_cast<std::wstring>(name));
std::wstring iconPath{ PACKAGED_PROFILE_ICON_PATH };
iconPath.append(Utils::GuidToString(profileGuid));
iconPath.append(PACKAGED_PROFILE_ICON_EXTENSION);
newProfile.SetIconPath(iconPath);
return newProfile;
}

View file

@ -69,4 +69,5 @@ private:
static std::optional<winrt::hstring> _LoadAsUnpackagedApp();
static bool _IsPowerShellCoreInstalled(std::wstring_view programFileEnv, std::filesystem::path& cmdline);
static std::wstring ExpandEnvironmentVariableString(std::wstring_view source);
static Profile _CreateDefaultProfile(const std::wstring_view name);
};

View file

@ -18,12 +18,12 @@ using namespace winrt::Windows::Storage;
using namespace winrt::Windows::Storage::Streams;
using namespace ::Microsoft::Console;
static const std::wstring FILENAME { L"profiles.json" };
static const std::wstring SETTINGS_FOLDER_NAME{ L"\\Microsoft\\Windows Terminal\\" };
static constexpr std::wstring_view FILENAME { L"profiles.json" };
static constexpr std::wstring_view SETTINGS_FOLDER_NAME{ L"\\Microsoft\\Windows Terminal\\" };
static const std::wstring PROFILES_KEY{ L"profiles" };
static const std::wstring KEYBINDINGS_KEY{ L"keybindings" };
static const std::wstring SCHEMES_KEY{ L"schemes" };
static constexpr std::wstring_view PROFILES_KEY{ L"profiles" };
static constexpr std::wstring_view KEYBINDINGS_KEY{ L"keybindings" };
static constexpr std::wstring_view SCHEMES_KEY{ L"schemes" };
// Method Description:
// - Creates a CascadiaSettings from whatever's saved on disk, or instantiates
@ -137,6 +137,9 @@ JsonObject CascadiaSettings::ToJson() const
jsonObject.Insert(PROFILES_KEY, profilesArray);
jsonObject.Insert(SCHEMES_KEY, schemesArray);
jsonObject.Insert(KEYBINDINGS_KEY,
_globals.GetKeybindings().ToJson());
return jsonObject;
}
@ -190,9 +193,18 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::FromJson(JsonObject json)
}
}
// TODO:MSFT:20700157
// Load the keybindings from the file as well
resultPtr->_CreateDefaultKeybindings();
if (json.HasKey(KEYBINDINGS_KEY))
{
const auto keybindingsObj = json.GetNamedArray(KEYBINDINGS_KEY);
auto loadedBindings = AppKeyBindings::FromJson(keybindingsObj);
resultPtr->_globals.SetKeybindings(loadedBindings);
}
else
{
// Create the default keybindings if we couldn't find any keybindings.
resultPtr->_CreateDefaultKeybindings();
}
return resultPtr;
}
@ -366,7 +378,7 @@ std::optional<winrt::hstring> CascadiaSettings::_LoadAsUnpackagedApp()
// function Description:
// - Returns the full path to the settings file, either within the application
// package, or in it's unpackaged location.
// package, or in its unpackaged location.
// Arguments:
// - <none>
// Return Value:
@ -378,7 +390,7 @@ winrt::hstring CascadiaSettings::GetSettingsPath()
}
// Function Description:
// - Get the full path to settings file in it's packaged location.
// - Get the full path to settings file in its packaged location.
// Arguments:
// - <none>
// Return Value:

View file

@ -12,10 +12,10 @@ using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::TerminalApp;
using namespace winrt::Windows::Data::Json;
static const std::wstring NAME_KEY{ L"name" };
static const std::wstring TABLE_KEY{ L"colors" };
static const std::wstring FOREGROUND_KEY{ L"foreground" };
static const std::wstring BACKGROUND_KEY{ L"background" };
static constexpr std::wstring_view NAME_KEY{ L"name" };
static constexpr std::wstring_view TABLE_KEY{ L"colors" };
static constexpr std::wstring_view FOREGROUND_KEY{ L"foreground" };
static constexpr std::wstring_view BACKGROUND_KEY{ L"background" };
static const std::array<std::wstring, 16> TABLE_COLORS =
{
L"black",

View file

@ -13,18 +13,18 @@ using namespace winrt::Windows::Data::Json;
using namespace winrt::Windows::UI::Xaml;
using namespace ::Microsoft::Console;
static const std::wstring DEFAULTPROFILE_KEY{ L"defaultProfile" };
static const std::wstring ALWAYS_SHOW_TABS_KEY{ L"alwaysShowTabs" };
static const std::wstring INITIALROWS_KEY{ L"initialRows" };
static const std::wstring INITIALCOLS_KEY{ L"initialCols" };
static const std::wstring SHOW_TITLE_IN_TITLEBAR_KEY{ L"showTerminalTitleInTitlebar" };
static const std::wstring REQUESTED_THEME_KEY{ L"requestedTheme" };
static constexpr std::wstring_view DEFAULTPROFILE_KEY{ L"defaultProfile" };
static constexpr std::wstring_view ALWAYS_SHOW_TABS_KEY{ L"alwaysShowTabs" };
static constexpr std::wstring_view INITIALROWS_KEY{ L"initialRows" };
static constexpr std::wstring_view INITIALCOLS_KEY{ L"initialCols" };
static constexpr std::wstring_view SHOW_TITLE_IN_TITLEBAR_KEY{ L"showTerminalTitleInTitlebar" };
static constexpr std::wstring_view REQUESTED_THEME_KEY{ L"requestedTheme" };
static const std::wstring SHOW_TABS_IN_TITLEBAR_KEY{ L"experimental_showTabsInTitlebar" };
static constexpr std::wstring_view SHOW_TABS_IN_TITLEBAR_KEY{ L"experimental_showTabsInTitlebar" };
static const std::wstring LIGHT_THEME_VALUE{ L"light" };
static const std::wstring DARK_THEME_VALUE{ L"dark" };
static const std::wstring SYSTEM_THEME_VALUE{ L"system" };
static constexpr std::wstring_view LIGHT_THEME_VALUE{ L"light" };
static constexpr std::wstring_view DARK_THEME_VALUE{ L"dark" };
static constexpr std::wstring_view SYSTEM_THEME_VALUE{ L"system" };
GlobalAppSettings::GlobalAppSettings() :
_keybindings{},
@ -71,6 +71,11 @@ AppKeyBindings GlobalAppSettings::GetKeybindings() const noexcept
return _keybindings;
}
void GlobalAppSettings::SetKeybindings(winrt::TerminalApp::AppKeyBindings newBindings) noexcept
{
_keybindings = newBindings;
}
bool GlobalAppSettings::GetAlwaysShowTabs() const noexcept
{
return _alwaysShowTabs;
@ -96,6 +101,10 @@ ElementTheme GlobalAppSettings::GetRequestedTheme() const noexcept
return _requestedTheme;
}
void GlobalAppSettings::SetRequestedTheme(const ElementTheme requestedTheme) noexcept
{
_requestedTheme = requestedTheme;
}
#pragma region ExperimentalSettings
bool GlobalAppSettings::GetShowTabsInTitlebar() const noexcept
@ -147,11 +156,11 @@ JsonObject GlobalAppSettings::ToJson() const
jsonObject.Insert(SHOW_TABS_IN_TITLEBAR_KEY,
JsonValue::CreateBooleanValue(_showTabsInTitlebar));
if (_requestedTheme != ElementTheme::Default)
{
jsonObject.Insert(REQUESTED_THEME_KEY,
JsonValue::CreateStringValue(_SerializeTheme(_requestedTheme)));
}
jsonObject.Insert(REQUESTED_THEME_KEY,
JsonValue::CreateStringValue(_SerializeTheme(_requestedTheme)));
// We'll add the keybindings later in CascadiaSettings, because if we do it
// here, they'll appear before the profiles.
return jsonObject;
}
@ -227,13 +236,13 @@ ElementTheme GlobalAppSettings::_ParseTheme(const std::wstring& themeString) noe
}
// Method Description:
// - Helper function for converting a CursorStyle to it's corresponding string
// - Helper function for converting a CursorStyle to its corresponding string
// value.
// Arguments:
// - theme: The enum value to convert to a string.
// Return Value:
// - The string value for the given CursorStyle
std::wstring GlobalAppSettings::_SerializeTheme(const ElementTheme theme) noexcept
std::wstring_view GlobalAppSettings::_SerializeTheme(const ElementTheme theme) noexcept
{
switch (theme)
{

View file

@ -35,6 +35,7 @@ public:
GUID GetDefaultProfile() const noexcept;
winrt::TerminalApp::AppKeyBindings GetKeybindings() const noexcept;
void SetKeybindings(winrt::TerminalApp::AppKeyBindings newBindings) noexcept;
bool GetAlwaysShowTabs() const noexcept;
void SetAlwaysShowTabs(const bool showTabs) noexcept;
@ -42,6 +43,8 @@ public:
bool GetShowTitleInTitlebar() const noexcept;
void SetShowTitleInTitlebar(const bool showTitleInTitlebar) noexcept;
void SetRequestedTheme(const winrt::Windows::UI::Xaml::ElementTheme requestedTheme) noexcept;
bool GetShowTabsInTitlebar() const noexcept;
void SetShowTabsInTitlebar(const bool showTabsInTitlebar) noexcept;
@ -69,6 +72,6 @@ private:
winrt::Windows::UI::Xaml::ElementTheme _requestedTheme;
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
static std::wstring _SerializeTheme(const winrt::Windows::UI::Xaml::ElementTheme theme) noexcept;
static std::wstring_view _SerializeTheme(const winrt::Windows::UI::Xaml::ElementTheme theme) noexcept;
};

View file

@ -0,0 +1,249 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "KeyChordSerialization.h"
using namespace winrt::Microsoft::Terminal::Settings;
static constexpr std::wstring_view CTRL_KEY{ L"ctrl" };
static constexpr std::wstring_view SHIFT_KEY{ L"shift" };
static constexpr std::wstring_view ALT_KEY{ L"alt" };
static constexpr int MAX_CHORD_PARTS = 4;
static const std::unordered_map<int32_t, std::wstring_view> vkeyNamePairs {
{ VK_BACK , L"backspace"},
{ VK_TAB , L"tab"},
{ VK_RETURN , L"enter" },
{ VK_ESCAPE , L"esc" },
{ VK_SPACE , L"space" },
{ VK_PRIOR , L"pgup" },
{ VK_NEXT , L"pgdn" },
{ VK_END , L"end" },
{ VK_HOME , L"home" },
{ VK_LEFT , L"left" },
{ VK_UP , L"up" },
{ VK_RIGHT , L"right" },
{ VK_DOWN , L"down" },
{ VK_INSERT , L"insert" },
{ VK_DELETE , L"delete" },
{ VK_NUMPAD0 , L"numpad_0" },
{ VK_NUMPAD1 , L"numpad_1" },
{ VK_NUMPAD2 , L"numpad_2" },
{ VK_NUMPAD3 , L"numpad_3" },
{ VK_NUMPAD4 , L"numpad_4" },
{ VK_NUMPAD5 , L"numpad_5" },
{ VK_NUMPAD6 , L"numpad_6" },
{ VK_NUMPAD7 , L"numpad_7" },
{ VK_NUMPAD8 , L"numpad_8" },
{ VK_NUMPAD9 , L"numpad_9" },
{ VK_MULTIPLY , L"numpad_multiply" },
{ VK_ADD , L"numpad_plus" },
{ VK_SUBTRACT , L"numpad_minus" },
{ VK_DECIMAL , L"numpad_period" },
{ VK_DIVIDE , L"numpad_divide" },
{ VK_F1 , L"f1" },
{ VK_F2 , L"f2" },
{ VK_F3 , L"f3" },
{ VK_F4 , L"f4" },
{ VK_F5 , L"f5" },
{ VK_F6 , L"f6" },
{ VK_F7 , L"f7" },
{ VK_F8 , L"f8" },
{ VK_F9 , L"f9" },
{ VK_F10 , L"f10" },
{ VK_F11 , L"f11" },
{ VK_F12 , L"f12" },
{ VK_F13 , L"f13" },
{ VK_F14 , L"f14" },
{ VK_F15 , L"f15" },
{ VK_F16 , L"f16" },
{ VK_F17 , L"f17" },
{ VK_F18 , L"f18" },
{ VK_F19 , L"f19" },
{ VK_F20 , L"f20" },
{ VK_F21 , L"f21" },
{ VK_F22 , L"f22" },
{ VK_F23 , L"f23" },
{ VK_F24 , L"f24" },
{ VK_OEM_PLUS , L"plus" },
{ VK_OEM_COMMA , L"," },
{ VK_OEM_MINUS , L"-" },
{ VK_OEM_PERIOD , L"." }
// TODO:
// These all look like they'd be good keybindings, but change based on keyboard
// layout. How do we deal with that?
// #define VK_OEM_NEC_EQUAL 0x92 // '=' key on numpad
// #define VK_OEM_1 0xBA // ';:' for US
// #define VK_OEM_2 0xBF // '/?' for US
// #define VK_OEM_3 0xC0 // '`~' for US
// #define VK_OEM_4 0xDB // '[{' for US
// #define VK_OEM_5 0xDC // '\|' for US
// #define VK_OEM_6 0xDD // ']}' for US
// #define VK_OEM_7 0xDE // ''"' for US
};
// Function Description:
// - Deserializes the given string into a new KeyChord instance. If this
// fails to translate the string into a keychord, it will throw a
// hresult_invalid_argument exception.
// - The string should fit the format "[ctrl+][alt+][shift+]<keyName>",
// where each modifier is optional, and keyName is either one of the
// names listed in the vkeyNamePairs vector above, or is one of 0-9a-zA-Z.
// Arguments:
// - hstr: the string to parse into a keychord.
// Return Value:
// - a newly constructed KeyChord
winrt::Microsoft::Terminal::Settings::KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr)
{
std::wstring wstr{ hstr };
// Split the string on '+'
std::wstring temp;
std::vector<std::wstring> parts;
std::wstringstream wss(wstr);
while(std::getline(wss, temp, L'+'))
{
parts.push_back(temp);
// If we have > 4, something's wrong.
if (parts.size() > MAX_CHORD_PARTS)
{
throw winrt::hresult_invalid_argument();
}
}
KeyModifiers modifiers = KeyModifiers::None;
int32_t vkey = 0;
// Look for ctrl, shift, alt. Anything else might be a key
for (const auto& part : parts)
{
std::wstring lowercase = part;
std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), std::towlower);
if (lowercase == CTRL_KEY)
{
modifiers |= KeyModifiers::Ctrl;
}
else if (lowercase == ALT_KEY)
{
modifiers |= KeyModifiers::Alt;
}
else if (lowercase == SHIFT_KEY)
{
modifiers |= KeyModifiers::Shift;
}
else
{
bool foundKey = false;
// For potential keys, look through the pairs of strings and vkeys
if (part.size() == 1)
{
const wchar_t wch = part.at(0);
// Quick lookup: ranges of vkeys that correlate directly to a key.
if (wch >= L'0' && wch <= L'9')
{
vkey = static_cast<int32_t>(wch);
foundKey = true;
}
else if (wch >= L'a' && wch <= L'z')
{
// subtract 0x20 to shift to uppercase
vkey = static_cast<int32_t>(wch - 0x20);
foundKey = true;
}
else if (wch >= L'A' && wch <= L'Z')
{
vkey = static_cast<int32_t>(wch);
foundKey = true;
}
}
// If we didn't find the key with a quick lookup, search the
// table to see if we have a matching name.
if (!foundKey)
{
for (const auto& pair : vkeyNamePairs)
{
if (pair.second == part)
{
vkey = pair.first;
foundKey = true;
break;
}
}
}
// If we weren't able to find a match, throw an exception.
if (!foundKey)
{
throw winrt::hresult_invalid_argument();
}
}
}
return winrt::Microsoft::Terminal::Settings::KeyChord{ modifiers, vkey };
}
// Function Description:
// - Serialize this keychord into a string represenation.
// - The string will fit the format "[ctrl+][alt+][shift+]<keyName>",
// where each modifier is optional, and keyName is either one of the
// names listed in the vkeyNamePairs vector above, or is one of 0-9a-z.
// Return Value:
// - a string which is an equivalent serialization of this object.
winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord)
{
bool serializedSuccessfully = false;
const auto modifiers = chord.Modifiers();
const auto vkey = chord.Vkey();
std::wstring buffer{ L"" };
// Add modifiers
if (WI_IsFlagSet(modifiers, KeyModifiers::Ctrl))
{
buffer += CTRL_KEY;
buffer += L"+";
}
if (WI_IsFlagSet(modifiers, KeyModifiers::Alt))
{
buffer += ALT_KEY;
buffer += L"+";
}
if (WI_IsFlagSet(modifiers, KeyModifiers::Shift))
{
buffer += SHIFT_KEY;
buffer += L"+";
}
// Quick lookup: ranges of vkeys that correlate directly to a key.
if (vkey >= L'0' && vkey <= L'9')
{
buffer += std::wstring(1, static_cast<wchar_t>(vkey));
serializedSuccessfully = true;
}
else if (vkey >= L'A' && vkey <= L'Z')
{
// add 0x20 to shift to lowercase
buffer += std::wstring(1, static_cast<wchar_t>(vkey + 0x20));
serializedSuccessfully = true;
}
else
{
if (vkeyNamePairs.find(vkey) != vkeyNamePairs.end())
{
buffer += vkeyNamePairs.at(vkey);
serializedSuccessfully = true;
}
}
if (!serializedSuccessfully)
{
buffer = L"";
}
return winrt::hstring{ buffer };
}

View file

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <winrt/Microsoft.Terminal.Settings.h>
class KeyChordSerialization final
{
public:
static winrt::Microsoft::Terminal::Settings::KeyChord FromString(const winrt::hstring& str);
static winrt::hstring ToString(const winrt::Microsoft::Terminal::Settings::KeyChord& chord);
};

View file

@ -13,43 +13,48 @@ using namespace winrt::Windows::Data::Json;
using namespace ::Microsoft::Console;
static const std::wstring NAME_KEY{ L"name" };
static const std::wstring GUID_KEY{ L"guid" };
static const std::wstring COLORSCHEME_KEY{ L"colorscheme" };
static constexpr std::wstring_view NAME_KEY{ L"name" };
static constexpr std::wstring_view GUID_KEY{ L"guid" };
static constexpr std::wstring_view COLORSCHEME_KEY{ L"colorscheme" };
static const std::wstring FOREGROUND_KEY{ L"foreground" };
static const std::wstring BACKGROUND_KEY{ L"background" };
static const std::wstring COLORTABLE_KEY{ L"colorTable" };
static const std::wstring HISTORYSIZE_KEY{ L"historySize" };
static const std::wstring SNAPONINPUT_KEY{ L"snapOnInput" };
static const std::wstring CURSORCOLOR_KEY{ L"cursorColor" };
static const std::wstring CURSORSHAPE_KEY{ L"cursorShape" };
static const std::wstring CURSORHEIGHT_KEY{ L"cursorHeight" };
static constexpr std::wstring_view FOREGROUND_KEY{ L"foreground" };
static constexpr std::wstring_view BACKGROUND_KEY{ L"background" };
static constexpr std::wstring_view COLORTABLE_KEY{ L"colorTable" };
static constexpr std::wstring_view HISTORYSIZE_KEY{ L"historySize" };
static constexpr std::wstring_view SNAPONINPUT_KEY{ L"snapOnInput" };
static constexpr std::wstring_view CURSORCOLOR_KEY{ L"cursorColor" };
static constexpr std::wstring_view CURSORSHAPE_KEY{ L"cursorShape" };
static constexpr std::wstring_view CURSORHEIGHT_KEY{ L"cursorHeight" };
static const std::wstring COMMANDLINE_KEY{ L"commandline" };
static const std::wstring FONTFACE_KEY{ L"fontFace" };
static const std::wstring FONTSIZE_KEY{ L"fontSize" };
static const std::wstring ACRYLICTRANSPARENCY_KEY{ L"acrylicOpacity" };
static const std::wstring USEACRYLIC_KEY{ L"useAcrylic" };
static const std::wstring SCROLLBARSTATE_KEY{ L"scrollbarState" };
static const std::wstring CLOSEONEXIT_KEY{ L"closeOnExit" };
static const std::wstring PADDING_KEY{ L"padding" };
static const std::wstring STARTINGDIRECTORY_KEY{ L"startingDirectory" };
static const std::wstring ICON_KEY{ L"icon" };
static constexpr std::wstring_view COMMANDLINE_KEY{ L"commandline" };
static constexpr std::wstring_view FONTFACE_KEY{ L"fontFace" };
static constexpr std::wstring_view FONTSIZE_KEY{ L"fontSize" };
static constexpr std::wstring_view ACRYLICTRANSPARENCY_KEY{ L"acrylicOpacity" };
static constexpr std::wstring_view USEACRYLIC_KEY{ L"useAcrylic" };
static constexpr std::wstring_view SCROLLBARSTATE_KEY{ L"scrollbarState" };
static constexpr std::wstring_view CLOSEONEXIT_KEY{ L"closeOnExit" };
static constexpr std::wstring_view PADDING_KEY{ L"padding" };
static constexpr std::wstring_view STARTINGDIRECTORY_KEY{ L"startingDirectory" };
static constexpr std::wstring_view ICON_KEY{ L"icon" };
// Possible values for Scrollbar state
static const std::wstring ALWAYS_VISIBLE{ L"visible" };
static const std::wstring ALWAYS_HIDE{ L"hidden" };
static constexpr std::wstring_view ALWAYS_VISIBLE{ L"visible" };
static constexpr std::wstring_view ALWAYS_HIDE{ L"hidden" };
// Possible values for Cursor Shape
static const std::wstring CURSORSHAPE_VINTAGE{ L"vintage" };
static const std::wstring CURSORSHAPE_BAR{ L"bar" };
static const std::wstring CURSORSHAPE_UNDERSCORE{ L"underscore" };
static const std::wstring CURSORSHAPE_FILLEDBOX{ L"filledBox" };
static const std::wstring CURSORSHAPE_EMPTYBOX{ L"emptyBox" };
static constexpr std::wstring_view CURSORSHAPE_VINTAGE{ L"vintage" };
static constexpr std::wstring_view CURSORSHAPE_BAR{ L"bar" };
static constexpr std::wstring_view CURSORSHAPE_UNDERSCORE{ L"underscore" };
static constexpr std::wstring_view CURSORSHAPE_FILLEDBOX{ L"filledBox" };
static constexpr std::wstring_view CURSORSHAPE_EMPTYBOX{ L"emptyBox" };
Profile::Profile() :
_guid{},
Profile(Utils::CreateGuid())
{
}
Profile::Profile(const winrt::guid& guid):
_guid(guid),
_name{ L"Default" },
_schemeName{},
@ -73,7 +78,6 @@ Profile::Profile() :
_padding{ DEFAULT_PADDING },
_icon{ }
{
UuidCreate(&_guid);
}
Profile::~Profile()
@ -457,6 +461,15 @@ bool Profile::HasIcon() const noexcept
return _icon.has_value();
}
// Method Description:
// - Sets this profile's icon path.
// Arguments:
// - path: the path
void Profile::SetIconPath(std::wstring_view path) noexcept
{
_icon.emplace(path);
}
// Method Description:
// - Returns this profile's icon path, if one is set. Otherwise returns the empty string.
// Return Value:
@ -572,13 +585,13 @@ CursorStyle Profile::_ParseCursorShape(const std::wstring& cursorShapeString)
}
// Method Description:
// - Helper function for converting a CursorStyle to it's corresponding string
// - Helper function for converting a CursorStyle to its corresponding string
// value.
// Arguments:
// - cursorShape: The enum value to convert to a string.
// Return Value:
// - The string value for the given CursorStyle
std::wstring Profile::_SerializeCursorStyle(const CursorStyle cursorShape)
std::wstring_view Profile::_SerializeCursorStyle(const CursorStyle cursorShape)
{
switch (cursorShape)
{

View file

@ -25,7 +25,9 @@ class TerminalApp::Profile final
{
public:
Profile(const winrt::guid& guid);
Profile();
~Profile();
winrt::Microsoft::Terminal::Settings::TerminalSettings CreateTerminalSettings(const std::vector<::TerminalApp::ColorScheme>& schemes) const;
@ -48,6 +50,7 @@ public:
bool HasIcon() const noexcept;
std::wstring_view GetIconPath() const noexcept;
void SetIconPath(std::wstring_view path) noexcept;
bool GetCloseOnExit() const noexcept;
@ -57,7 +60,7 @@ private:
static winrt::Microsoft::Terminal::Settings::ScrollbarState ParseScrollbarState(const std::wstring& scrollbarState);
static winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString);
static std::wstring _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
static std::wstring_view _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
GUID _guid;
std::wstring _name;

View file

@ -20,7 +20,7 @@ Tab::~Tab()
{
// When we're destructed, winrt will automatically decrement the refcount
// of our terminalcontrol.
// Assuming that refcount hits 0, it'll destruct it on it's own, including
// Assuming that refcount hits 0, it'll destruct it on its own, including
// calling Close on the terminal and connection.
}

View file

@ -41,6 +41,7 @@
<ClInclude Include="GlobalAppSettings.h" />
<ClInclude Include="Profile.h" />
<ClInclude Include="CascadiaSettings.h" />
<ClInclude Include="KeyChordSerialization.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="AppKeyBindings.h">
<DependentUpon>AppKeyBindings.idl</DependentUpon>
@ -58,6 +59,7 @@
<ClCompile Include="Profile.cpp" />
<ClCompile Include="CascadiaSettings.cpp" />
<ClCompile Include="CascadiaSettingsSerialization.cpp" />
<ClCompile Include="KeyChordSerialization.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@ -128,4 +130,4 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.1.190405001-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.1.190405001-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
</Target>
</Project>
</Project>

View file

@ -12,27 +12,32 @@
#endif
#include <conpty-universal.h>
#include "../../types/inc/Utils.hpp"
using namespace ::Microsoft::Console;
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
ConhostConnection::ConhostConnection(hstring const& commandline,
hstring const& startingDirectory,
uint32_t initialRows,
uint32_t initialCols) :
_connected{ false },
_inPipe{ INVALID_HANDLE_VALUE },
_outPipe{ INVALID_HANDLE_VALUE },
_signalPipe{ INVALID_HANDLE_VALUE },
_outputThreadId{ 0 },
_hOutputThread{ INVALID_HANDLE_VALUE },
_piConhost{ 0 },
_closing{ false }
ConhostConnection::ConhostConnection(const hstring& commandline,
const hstring& startingDirectory,
const uint32_t initialRows,
const uint32_t initialCols,
const guid& initialGuid) :
_initialRows{ initialRows },
_initialCols{ initialCols },
_commandline{ commandline },
_startingDirectory{ startingDirectory },
_guid{ initialGuid }
{
_commandline = commandline;
_startingDirectory = startingDirectory;
_initialRows = initialRows;
_initialCols = initialCols;
if (_guid == guid())
{
_guid = Utils::CreateGuid();
}
}
winrt::guid ConhostConnection::Guid() const noexcept
{
return _guid;
}
winrt::event_token ConhostConnection::TerminalOutput(Microsoft::Terminal::TerminalConnection::TerminalOutputEventArgs const& handler)
@ -57,30 +62,44 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConhostConnection::Start()
{
std::wstring cmdline = _commandline.c_str();
std::wstring cmdline{ _commandline.c_str() };
std::optional<std::wstring> startingDirectory;
if (!_startingDirectory.empty())
{
startingDirectory = _startingDirectory;
}
CreateConPty(cmdline,
startingDirectory,
static_cast<short>(_initialCols),
static_cast<short>(_initialRows),
&_inPipe,
&_outPipe,
&_signalPipe,
&_piConhost);
EnvironmentVariableMapW extraEnvVars;
{
// Convert connection Guid to string and ignore the enclosing '{}'.
std::wstring wsGuid{ Utils::GuidToString(_guid) };
wsGuid.pop_back();
const wchar_t* const pwszGuid{ wsGuid.data() + 1 };
// Ensure every connection has the unique identifier in the environment.
extraEnvVars.emplace(L"WT_SESSION", pwszGuid);
}
THROW_IF_FAILED(
CreateConPty(cmdline,
startingDirectory,
static_cast<short>(_initialCols),
static_cast<short>(_initialRows),
&_inPipe,
&_outPipe,
&_signalPipe,
&_piConhost,
extraEnvVars));
_connected = true;
// Create our own output handling thread
// Each console needs to make sure to drain the output from it's backing host.
_outputThreadId = (DWORD)-1;
// Each console needs to make sure to drain the output from its backing host.
_outputThreadId = static_cast<DWORD>(-1);
_hOutputThread = CreateThread(nullptr,
0,
(LPTHREAD_START_ROUTINE)StaticOutputThreadProc,
StaticOutputThreadProc,
this,
0,
&_outputThreadId);
@ -131,7 +150,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
CloseHandle(_piConhost.hProcess);
}
DWORD ConhostConnection::StaticOutputThreadProc(LPVOID lpParameter)
DWORD WINAPI ConhostConnection::StaticOutputThreadProc(LPVOID lpParameter)
{
ConhostConnection* const pInstance = (ConhostConnection*)lpParameter;
return pInstance->_OutputThread();

View file

@ -9,7 +9,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
struct ConhostConnection : ConhostConnectionT<ConhostConnection>
{
ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, uint32_t rows, uint32_t cols);
ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, const uint32_t rows, const uint32_t cols, const guid& guid);
winrt::event_token TerminalOutput(TerminalConnection::TerminalOutputEventArgs const& handler);
void TerminalOutput(winrt::event_token const& token) noexcept;
@ -20,26 +20,28 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void Resize(uint32_t rows, uint32_t columns);
void Close();
winrt::guid Guid() const noexcept;
private:
winrt::event<TerminalConnection::TerminalOutputEventArgs> _outputHandlers;
winrt::event<TerminalConnection::TerminalDisconnectedEventArgs> _disconnectHandlers;
uint32_t _initialRows;
uint32_t _initialCols;
hstring _commandline;
hstring _startingDirectory;
uint32_t _initialRows{};
uint32_t _initialCols{};
hstring _commandline{};
hstring _startingDirectory{};
bool _connected;
HANDLE _inPipe; // The pipe for writing input to
HANDLE _outPipe; // The pipe for reading output from
HANDLE _signalPipe;
//HPCON _hPC;
DWORD _outputThreadId;
HANDLE _hOutputThread;
PROCESS_INFORMATION _piConhost;
bool _closing;
bool _connected{};
HANDLE _inPipe{ INVALID_HANDLE_VALUE }; // The pipe for writing input to
HANDLE _outPipe{ INVALID_HANDLE_VALUE }; // The pipe for reading output from
HANDLE _signalPipe{ INVALID_HANDLE_VALUE };
DWORD _outputThreadId{};
HANDLE _hOutputThread{ INVALID_HANDLE_VALUE };
PROCESS_INFORMATION _piConhost{};
guid _guid{}; // A "unique" session identifier for connected client
bool _closing{};
static DWORD StaticOutputThreadProc(LPVOID lpParameter);
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
DWORD _OutputThread();
};
}

View file

@ -8,7 +8,9 @@ namespace Microsoft.Terminal.TerminalConnection
[default_interface]
runtimeclass ConhostConnection : ITerminalConnection
{
ConhostConnection(String cmdline, String startingDirectory, UInt32 rows, UInt32 columns);
ConhostConnection(String cmdline, String startingDirectory, UInt32 rows, UInt32 columns, Guid guid);
Guid Guid { get; };
};
}

View file

@ -60,7 +60,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_outputThreadId = (DWORD)-1;
_hOutputThread = CreateThread(nullptr,
0,
(LPTHREAD_START_ROUTINE)StaticOutputThreadProc,
StaticOutputThreadProc,
this,
0,
&_outputThreadId);
@ -222,7 +222,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
DWORD ConptyConnection::StaticOutputThreadProc(LPVOID lpParameter)
DWORD WINAPI ConptyConnection::StaticOutputThreadProc(LPVOID lpParameter)
{
ConptyConnection* const pInstance = (ConptyConnection*)lpParameter;
return pInstance->_OutputThread();

View file

@ -39,7 +39,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
HANDLE _hOutputThread;
PROCESS_INFORMATION _piClient;
static DWORD StaticOutputThreadProc(LPVOID lpParameter);
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
void _CreatePseudoConsole();
DWORD _OutputThread();
};

View file

@ -52,6 +52,18 @@
</ItemDefinitionGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
<!--
the packaging project won't recurse through our dependencies, you have to
make sure that if you add a cppwinrt dependency to any of these projects,
you also update all the consumers
-->
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<!--
DON'T REDIRECT OUR OUTPUT.

View file

@ -7,5 +7,11 @@
#pragma once
#include <LibraryIncludes.h>
// Must be included before any WinRT headers.
#include <unknwn.h>
#include "winrt/Windows.Foundation.h"
#include <Windows.h>
#include <wil/result.h>

View file

@ -26,7 +26,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
TermControl::TermControl(Settings::IControlSettings settings) :
_connection{ TerminalConnection::ConhostConnection(winrt::to_hstring("cmd.exe"), winrt::hstring(), 30, 80) },
_connection{ TerminalConnection::ConhostConnection(winrt::to_hstring("cmd.exe"), winrt::hstring(), 30, 80, winrt::guid()) },
_initializedTerminal{ false },
_root{ nullptr },
_controlRoot{ nullptr },
@ -133,7 +133,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal,[this](){
// Update our control settings
_ApplyUISettings();
// Update the terminal core with it's new Core settings
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(_settings);
// Refresh our font with the renderer
@ -221,7 +221,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// directory.
void TermControl::_ApplyConnectionSettings()
{
_connection = TerminalConnection::ConhostConnection(_settings.Commandline(), _settings.StartingDirectory(), 30, 80);
_connection = TerminalConnection::ConhostConnection(_settings.Commandline(), _settings.StartingDirectory(), 30, 80, winrt::guid());
}
TermControl::~TermControl()
@ -278,14 +278,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto windowWidth = _swapChainPanel.ActualWidth(); // Width() and Height() are NaN?
const auto windowHeight = _swapChainPanel.ActualHeight();
_terminal = new ::Microsoft::Terminal::Core::Terminal();
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
// First create the render thread.
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
// Stash a local pointer to the render thread, so we can enable it after
// we hand off ownership to the renderer.
auto* const localPointerToThread = renderThread.get();
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal, nullptr, 0, std::move(renderThread));
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
// Set up the DX Engine
@ -293,7 +293,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_renderer->AddRenderEngine(dxEngine.get());
// Set up the renderer to be used to calculate the width of a glyph,
// should we be unable to figure out it's width another way.
// should we be unable to figure out its width another way.
auto pfn = std::bind(&::Microsoft::Console::Render::Renderer::IsGlyphWideByFont, _renderer.get(), std::placeholders::_1);
SetGlyphWidthFallback(pfn);
@ -415,9 +415,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_cursorTimer = std::make_optional(DispatcherTimer());
_cursorTimer.value().Interval(std::chrono::milliseconds(blinkTime));
_cursorTimer.value().Tick({ this, &TermControl::_BlinkCursor });
_controlRoot.GotFocus({ this, &TermControl::_GotFocusHandler });
_controlRoot.LostFocus({ this, &TermControl::_LostFocusHandler });
}
else
{
@ -425,6 +422,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_cursorTimer = std::nullopt;
}
_controlRoot.GotFocus({ this, &TermControl::_GotFocusHandler });
_controlRoot.LostFocus({ this, &TermControl::_LostFocusHandler });
// Focus the control here. If we do it up above (in _Create_), then the
// focus won't actually get passed to us. I believe this is because
// we're not technically a part of the UI tree yet, so focusing us
@ -549,6 +549,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse)
{
// Ignore mouse events while the terminal does not have focus.
// This prevents the user from selecting and copying text if they
// click inside the current tab to refocus the terminal window.
if (!_focused)
{
args.Handled(true);
return;
}
const auto modifiers = args.KeyModifiers();
const auto altEnabled = WI_IsFlagSet(modifiers, VirtualKeyModifiers::Menu);
const auto shiftEnabled = WI_IsFlagSet(modifiers, VirtualKeyModifiers::Shift);
@ -556,13 +565,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (point.Properties().IsLeftButtonPressed())
{
const auto cursorPosition = point.Position();
const auto fontSize = _actualFont.GetSize();
const COORD terminalPosition = {
static_cast<SHORT>(cursorPosition.X / fontSize.X),
static_cast<SHORT>(cursorPosition.Y / fontSize.Y)
};
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
// save location before rendering
_terminal->SetSelectionAnchor(terminalPosition);
@ -618,13 +621,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (point.Properties().IsLeftButtonPressed())
{
const auto cursorPosition = point.Position();
const auto fontSize = _actualFont.GetSize();
const COORD terminalPosition = {
static_cast<SHORT>(cursorPosition.X / fontSize.X),
static_cast<SHORT>(cursorPosition.Y / fontSize.Y)
};
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
// save location (for rendering) + render
_terminal->SetEndSelectionPosition(terminalPosition);
@ -828,6 +825,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_GotFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
_focused = true;
if (_cursorTimer.has_value())
_cursorTimer.value().Start();
}
@ -838,6 +837,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_LostFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
_focused = false;
if (_cursorTimer.has_value())
{
_cursorTimer.value().Stop();
@ -1077,8 +1078,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
FontInfo actualFont = { fontFace, 0, 10, { 0, fontHeight }, CP_UTF8, false };
FontInfoDesired desiredFont = { actualFont };
const auto cols = settings.InitialCols();
const auto rows = settings.InitialRows();
// If the settings have negative or zero row or column counts, ignore those counts.
// (The lower TerminalCore layer also has upper bounds as well, but at this layer
// we may eventually impose different ones depending on how many pixels we can address.)
const auto cols = std::max(settings.InitialCols(), 1);
const auto rows = std::max(settings.InitialRows(), 1);
// Create a DX engine and initialize it with our font and DPI. We'll
// then use it to measure how much space the requested rows and columns
@ -1199,6 +1203,35 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
(shift ? Settings::KeyModifiers::Shift : Settings::KeyModifiers::None) };
}
// Method Description:
// - Gets the corresponding viewport terminal position for the cursor
// by excluding the padding and normalizing with the font size.
// This is used for selection.
// Arguments:
// - cursorPosition: the (x,y) position of a given cursor (i.e.: mouse cursor).
// NOTE: origin (0,0) is top-left.
// Return Value:
// - the corresponding viewport terminal position for the given Point parameter
const COORD TermControl::_GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition)
{
// Exclude padding from cursor position calculation
COORD terminalPosition =
{
static_cast<SHORT>(cursorPosition.X - _root.Padding().Left),
static_cast<SHORT>(cursorPosition.Y - _root.Padding().Top)
};
const auto fontSize = _actualFont.GetSize();
FAIL_FAST_IF(fontSize.X == 0);
FAIL_FAST_IF(fontSize.Y == 0);
// Normalize to terminal coordinates by using font size
terminalPosition.X /= fontSize.X;
terminalPosition.Y /= fontSize.Y;
return terminalPosition;
}
// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.

View file

@ -69,12 +69,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
Windows::UI::Xaml::Controls::Primitives::ScrollBar _scrollBar;
event_token _connectionOutputEventToken;
::Microsoft::Terminal::Core::Terminal* _terminal;
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer;
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
Settings::IControlSettings _settings;
bool _focused;
bool _closing;
FontInfoDesired _desiredFont;
@ -123,6 +124,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
Settings::KeyModifiers _GetPressedModifierKeys() const;
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
};
}

View file

@ -18,7 +18,12 @@ using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::VirtualTerminal;
std::wstring _KeyEventsToText(std::deque<std::unique_ptr<IInputEvent>>& inEventsToWrite)
static constexpr short _ClampToShortMax(int value, short min)
{
return static_cast<short>(std::clamp(value, static_cast<int>(min), SHRT_MAX));
}
static std::wstring _KeyEventsToText(std::deque<std::unique_ptr<IInputEvent>>& inEventsToWrite)
{
std::wstring wstr = L"";
for(auto& ev : inEventsToWrite)
@ -65,9 +70,9 @@ void Terminal::Create(COORD viewportSize, SHORT scrollbackLines, IRenderTarget&
{
_mutableViewport = Viewport::FromDimensions({ 0,0 }, viewportSize);
_scrollbackLines = scrollbackLines;
COORD bufferSize { viewportSize.X, viewportSize.Y + scrollbackLines };
TextAttribute attr{};
UINT cursorSize = 12;
const COORD bufferSize { viewportSize.X, _ClampToShortMax(viewportSize.Y + scrollbackLines, 1) };
const TextAttribute attr{};
const UINT cursorSize = 12;
_buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, renderTarget);
}
@ -79,9 +84,9 @@ void Terminal::Create(COORD viewportSize, SHORT scrollbackLines, IRenderTarget&
void Terminal::CreateFromSettings(winrt::Microsoft::Terminal::Settings::ICoreSettings settings,
Microsoft::Console::Render::IRenderTarget& renderTarget)
{
const COORD viewportSize{ static_cast<short>(settings.InitialCols()), static_cast<short>(settings.InitialRows()) };
const COORD viewportSize{ _ClampToShortMax(settings.InitialCols(), 1), _ClampToShortMax(settings.InitialRows(), 1) };
// TODO:MSFT:20642297 - Support infinite scrollback here, if HistorySize is -1
Create(viewportSize, static_cast<short>(settings.HistorySize()), renderTarget);
Create(viewportSize, _ClampToShortMax(settings.HistorySize(), 0), renderTarget);
UpdateSettings(settings);
}

View file

@ -0,0 +1,136 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <WexTestClass.h>
#include "DefaultSettings.h"
#include "../cascadia/TerminalCore/Terminal.hpp"
#include "../renderer/inc/DummyRenderTarget.hpp"
#include "consoletaeftemplates.hpp"
#include "winrt/Microsoft.Terminal.Settings.h"
using namespace winrt::Microsoft::Terminal::Settings;
using namespace Microsoft::Terminal::Core;
namespace TerminalCoreUnitTests
{
class MockTermSettings : public winrt::implements<MockTermSettings, ICoreSettings>
{
public:
MockTermSettings(int32_t historySize, int32_t initialRows, int32_t initialCols) :
_historySize(historySize),
_initialRows(initialRows),
_initialCols(initialCols)
{ }
// property getters - all implemented
int32_t HistorySize() { return _historySize; }
int32_t InitialRows() { return _initialRows; }
int32_t InitialCols() { return _initialCols; }
uint32_t DefaultForeground() { return COLOR_WHITE; }
uint32_t DefaultBackground() { return COLOR_BLACK; }
bool SnapOnInput() { return false; }
uint32_t CursorColor() { return COLOR_WHITE; }
CursorStyle CursorShape() const noexcept { return CursorStyle::Vintage; }
uint32_t CursorHeight() { return 42UL; }
// other implemented methods
uint32_t GetColorTableEntry(int32_t) const { return 123; }
// property setters - all unimplemented
void HistorySize(int32_t) { }
void InitialRows(int32_t) { }
void InitialCols(int32_t) { }
void DefaultForeground(uint32_t) { }
void DefaultBackground(uint32_t) { }
void SnapOnInput(bool) { }
void CursorColor(uint32_t) { }
void CursorShape(CursorStyle const&) noexcept { }
void CursorHeight(uint32_t) { }
// other unimplemented methods
void SetColorTableEntry(int32_t /* index */, uint32_t /* value */) { }
private:
int32_t _historySize;
int32_t _initialRows;
int32_t _initialCols;
};
#define WCS(x) WCSHELPER(x)
#define WCSHELPER(x) L#x
class ScreenSizeLimitsTest
{
TEST_CLASS(ScreenSizeLimitsTest);
TEST_METHOD(ScreenWidthAndHeightAreClampedToBounds)
{
DummyRenderTarget emptyRenderTarget;
// Negative values for initial visible row count or column count
// are clamped to 1. Too-large positive values are clamped to SHRT_MAX.
auto negativeColumnsSettings = winrt::make<MockTermSettings>(10000, 9999999, -1234);
Terminal negativeColumnsTerminal;
negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, emptyRenderTarget);
COORD actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, SHRT_MAX, L"Row count clamped to SHRT_MAX == " WCS(SHRT_MAX));
VERIFY_ARE_EQUAL(actualDimensions.X, 1, L"Column count clamped to 1");
// Zero values are clamped to 1 as well.
auto zeroRowsSettings = winrt::make<MockTermSettings>(10000, 0, 9999999);
Terminal zeroRowsTerminal;
zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, emptyRenderTarget);
actualDimensions = zeroRowsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, 1, L"Row count clamped to 1");
VERIFY_ARE_EQUAL(actualDimensions.X, SHRT_MAX, L"Column count clamped to SHRT_MAX == " WCS(SHRT_MAX));
}
TEST_METHOD(ScrollbackHistorySizeIsClampedToBounds)
{
// What is actually clamped is the number of rows in the internal history buffer,
// which is the *sum* of the history size plus the number of rows
// actually visible on screen at the moment.
const unsigned int visibleRowCount = 100;
DummyRenderTarget emptyRenderTarget;
// Zero history size is acceptable.
auto noHistorySettings = winrt::make<MockTermSettings>(0, visibleRowCount, 100);
Terminal noHistoryTerminal;
noHistoryTerminal.CreateFromSettings(noHistorySettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount,
L"History size of 0 is accepted");
// Negative history sizes are clamped to zero.
auto negativeHistorySizeSettings = winrt::make<MockTermSettings>(-100, visibleRowCount, 100);
Terminal negativeHistorySizeTerminal;
negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount,
L"Negative history size is clamped to 0");
// History size + initial visible rows == SHRT_MAX is acceptable.
auto maxHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount, visibleRowCount, 100);
Terminal maxHistorySizeTerminal;
maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX),
L"History size == SHRT_MAX - initial row count is accepted");
// History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly.
auto justTooBigHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100);
Terminal justTooBigHistorySizeTerminal;
justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX),
L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count");
// Ridiculously large history sizes are also clamped.
auto farTooBigHistorySizeSettings = winrt::make<MockTermSettings>(99999999, visibleRowCount, 100);
Terminal farTooBigHistorySizeTerminal;
farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX),
L"History size that is far too large is clamped to SHRT_MAX - initial row count");
}
};
}

View file

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(SolutionDir)\src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="ScreenSizeLimitsTest.cpp" />
<ClCompile Include="SelectionTest.cpp" />
<ClCompile Include="precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
@ -36,7 +38,7 @@
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettings\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
</ClCompile>
<Link>

View file

@ -89,7 +89,6 @@ public:
}
case CM_UPDATE_TITLE:
{
SetWindowTextW(_window, _title.c_str());
break;
}
@ -115,30 +114,67 @@ public:
SWP_NOZORDER | SWP_NOACTIVATE);
_currentDpi = uDpi;
NewScale(uDpi);
}
_inDpiChange = false;
return 0;
}
virtual void NewScale(UINT dpi) = 0;
virtual void OnResize(const UINT width, const UINT height) = 0;
virtual void OnMinimize() = 0;
virtual void OnRestore() = 0;
RECT GetWindowRect() const
RECT GetWindowRect() const noexcept
{
RECT rc = { 0 };
::GetWindowRect(_window, &rc);
return rc;
}
HWND GetHandle() noexcept
HWND GetHandle() const noexcept
{
return _window;
};
float GetCurrentDpiScale() const noexcept
{
const auto dpi = ::GetDpiForWindow(_window);
const auto scale = static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
return scale;
}
//// Gets the physical size of the client area of the HWND in _window
SIZE GetPhysicalSize() const noexcept
{
RECT rect = {};
GetClientRect(_window, &rect);
const auto windowsWidth = rect.right - rect.left;
const auto windowsHeight = rect.bottom - rect.top;
return SIZE{ windowsWidth, windowsHeight };
}
//// Gets the logical (in DIPs) size of a physical size specified by the parameter physicalSize
//// Remarks:
//// XAML coordinate system is always in Display Indepenent Pixels (a.k.a DIPs or Logical). However Win32 GDI (because of legacy reasons)
//// in DPI mode "Per-Monitor and Per-Monitor (V2) DPI Awareness" is always in physical pixels.
//// The formula to transform is:
//// logical = (physical / dpi) + 0.5 // 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75
//// See also:
//// https://docs.microsoft.com/en-us/windows/desktop/LearnWin32/dpi-and-device-independent-pixels
//// https://docs.microsoft.com/en-us/windows/desktop/hidpi/high-dpi-desktop-application-development-on-windows#per-monitor-and-per-monitor-v2-dpi-awareness
winrt::Windows::Foundation::Size GetLogicalSize(const SIZE physicalSize) const noexcept
{
const auto dpi = GetCurrentDpiScale();
// 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75
const auto logicalWidth = (physicalSize.cx / dpi) + 0.5f;
const auto logicalHeigth = (physicalSize.cy / dpi) + 0.5f;
return winrt::Windows::Foundation::Size(logicalWidth, logicalHeigth);
}
winrt::Windows::Foundation::Size GetLogicalSize() const noexcept
{
return GetLogicalSize(GetPhysicalSize());
}
// Method Description:
// - Sends a message to our message loop to update the title of the window.
// Arguments:

View file

@ -17,10 +17,7 @@ using namespace ::Microsoft::Console::Types;
#define XAML_HOSTING_WINDOW_CLASS_NAME L"CASCADIA_HOSTING_WINDOW_CLASS"
IslandWindow::IslandWindow() noexcept :
_currentWidth{ 0 },
_currentHeight{ 0 },
_interopWindowHandle{ nullptr },
_scale{ nullptr },
_rootGrid{ nullptr },
_source{ nullptr },
_pfnCreateCallback{ nullptr }
@ -49,7 +46,7 @@ void IslandWindow::MakeWindow() noexcept
WINRT_ASSERT(!_window);
// Create the window with the default size here - During the creation of the
// window, the system will give us a chance to set it's size in WM_CREATE.
// window, the system will give us a chance to set its size in WM_CREATE.
// WM_CREATE will be handled synchronously, before CreateWindow returns.
WINRT_VERIFY(CreateWindow(wc.lpszClassName,
L"Windows Terminal",
@ -126,40 +123,25 @@ void IslandWindow::Initialize()
// stash the child interop handle so we can resize it when the main hwnd is resized
interop->get_WindowHandle(&_interopWindowHandle);
if (!initialized)
{
_InitXamlContent();
}
_rootGrid = winrt::Windows::UI::Xaml::Controls::Grid();
_source.Content(_rootGrid);
// Do a quick resize to force the island to paint
OnSize();
}
void IslandWindow::_InitXamlContent()
{
// setup a root grid that will be used to apply DPI scaling
winrt::Windows::UI::Xaml::Media::ScaleTransform dpiScaleTransform;
winrt::Windows::UI::Xaml::Controls::Grid dpiAdjustmentGrid;
const auto dpi = GetDpiForWindow(_window);
const double scale = double(dpi) / double(USER_DEFAULT_SCREEN_DPI);
_rootGrid = dpiAdjustmentGrid;
_scale = dpiScaleTransform;
_scale.ScaleX(scale);
_scale.ScaleY(scale);
}
void IslandWindow::OnSize()
{
const auto physicalSize = GetPhysicalSize();
// update the interop window size
SetWindowPos(_interopWindowHandle, 0, 0, 0, _currentWidth, _currentHeight, SWP_SHOWWINDOW);
_rootGrid.Width(_currentWidth);
_rootGrid.Height(_currentHeight);
SetWindowPos(_interopWindowHandle, 0, 0, 0, physicalSize.cx, physicalSize.cy, SWP_SHOWWINDOW);
if (_rootGrid)
{
const auto size = GetLogicalSize();
_rootGrid.Width(size.Width);
_rootGrid.Height(size.Height);
}
}
LRESULT IslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
@ -186,63 +168,6 @@ LRESULT IslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LP
return base_type::MessageHandler(message, wparam, lparam);
}
// Method Description:
// - Called when the DPI of this window changes. Updates the XAML content sizing to match the client area of our window.
// Arguments:
// - dpi: new DPI to use. The default is 96, as defined by USER_DEFAULT_SCREEN_DPI.
// Return Value:
// - <none>
void IslandWindow::NewScale(UINT dpi)
{
const double scaleFactor = static_cast<double>(dpi) / static_cast<double>(USER_DEFAULT_SCREEN_DPI);
if (_scale != nullptr)
{
_scale.ScaleX(scaleFactor);
_scale.ScaleY(scaleFactor);
}
ApplyCorrection(scaleFactor);
}
// Method Description:
// - This method updates the padding that exists off the edge of the window to
// make sure to keep the XAML content size the same as the actual window size.
// Arguments:
// - scaleFactor: the DPI scaling multiplier to use. for a dpi of 96, this would
// be 1, for 144, this would be 1.5.
// Return Value:
// - <none>
void IslandWindow::ApplyCorrection(double scaleFactor)
{
// Get the dimensions of the XAML content grid.
const auto realWidth = _rootGrid.Width();
const auto realHeight = _rootGrid.Height();
// Scale those dimensions by our dpi scaling. This is how big the XAML
// content thinks it should be.
const auto dpiAwareWidth = realWidth * scaleFactor;
const auto dpiAwareHeight = realHeight * scaleFactor;
// Get the difference between what xaml thinks and the actual client area
// of our window.
const auto deltaX = dpiAwareWidth - realWidth;
const auto deltaY = dpiAwareHeight - realHeight;
// correct for the scaling we applied above
const auto dividedDeltaX = deltaX / scaleFactor;
const auto dividedDeltaY = deltaY / scaleFactor;
const double rightCorrection = dividedDeltaX;
const double bottomCorrection = dividedDeltaY;
// Apply padding to the root grid, so that it's content is the same size as
// our actual window size.
// Without this, XAML content will seem to spill off the side/bottom of the window
_rootGrid.Padding(Xaml::ThicknessHelper::FromLengths(0, 0, rightCorrection, bottomCorrection));
}
// Method Description:
// - Called when the window has been resized (or maximized)
// Arguments:
@ -250,13 +175,7 @@ void IslandWindow::ApplyCorrection(double scaleFactor)
// - height: the new height of the window _in pixels_
void IslandWindow::OnResize(const UINT width, const UINT height)
{
_currentWidth = width;
_currentHeight = height;
if (nullptr != _rootGrid)
{
OnSize();
ApplyCorrection(_scale.ScaleX());
}
OnSize();
}
// Method Description:
@ -276,6 +195,5 @@ void IslandWindow::OnRestore()
void IslandWindow::SetRootContent(winrt::Windows::UI::Xaml::UIElement content)
{
_rootGrid.Children().Clear();
ApplyCorrection(_scale.ScaleX());
_rootGrid.Children().Append(content);
}

View file

@ -17,8 +17,6 @@ public:
virtual void OnSize();
virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override;
void ApplyCorrection(double scaleFactor);
void NewScale(UINT dpi) override;
void OnResize(const UINT width, const UINT height) override;
void OnMinimize() override;
void OnRestore() override;
@ -29,18 +27,13 @@ public:
void SetCreateCallback(std::function<void(const HWND, const RECT)> pfn) noexcept;
protected:
unsigned int _currentWidth;
unsigned int _currentHeight;
HWND _interopWindowHandle;
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _source;
winrt::Windows::UI::Xaml::Media::ScaleTransform _scale;
winrt::Windows::UI::Xaml::Controls::Grid _rootGrid;
std::function<void(const HWND, const RECT)> _pfnCreateCallback;
void _InitXamlContent();
void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept;
};

View file

@ -68,7 +68,6 @@ void NonClientIslandWindow::Initialize()
void NonClientIslandWindow::SetNonClientContent(winrt::Windows::UI::Xaml::UIElement content)
{
_nonClientRootGrid.Children().Clear();
ApplyCorrection(_scale.ScaleX());
_nonClientRootGrid.Children().Append(content);
}
@ -91,13 +90,15 @@ void NonClientIslandWindow::SetNonClientHeight(const int contentHeight) noexcept
// content, in window coordinates.
Viewport NonClientIslandWindow::GetTitlebarContentArea() const noexcept
{
const auto dpi = GetDpiForWindow(_window);
const double scale = static_cast<double>(dpi) / static_cast<double>(USER_DEFAULT_SCREEN_DPI);
const auto scale = GetCurrentDpiScale();
const auto titlebarContentHeight = _titlebarUnscaledContentHeight * scale;
const auto titlebarMarginRight = _titlebarMarginRight;
auto titlebarWidth = _currentWidth - (_windowMarginSides + titlebarMarginRight);
const auto physicalSize = GetPhysicalSize();
const auto clientWidth = physicalSize.cx;
auto titlebarWidth = clientWidth - (_windowMarginSides + titlebarMarginRight);
// Adjust for maximized margins
titlebarWidth -= (_maximizedMargins.cxLeftWidth + _maximizedMargins.cxRightWidth);
@ -133,8 +134,9 @@ Viewport NonClientIslandWindow::GetClientContentArea() const noexcept
COORD clientOrigin = { static_cast<short>(margins.cxLeftWidth),
static_cast<short>(margins.cyTopHeight) };
auto clientWidth = _currentWidth;
auto clientHeight = _currentHeight;
const auto physicalSize = GetPhysicalSize();
auto clientWidth = physicalSize.cx;
auto clientHeight = physicalSize.cy;
// If we're maximized, we don't want to use the frame as our margins,
// instead we want to use the margins from the maximization. If we included
@ -177,8 +179,13 @@ void NonClientIslandWindow::OnSize()
clientArea.Height(),
SWP_SHOWWINDOW);
_rootGrid.Width(clientArea.Width());
_rootGrid.Height(clientArea.Height());
if (_rootGrid)
{
const SIZE physicalSize{ clientArea.Width(), clientArea.Height() };
const auto logicalSize = GetLogicalSize(physicalSize);
_rootGrid.Width(logicalSize.Width);
_rootGrid.Height(logicalSize.Height);
}
// update the interop window size
SetWindowPos(_nonClientInteropWindowHandle, 0,

View file

@ -6,7 +6,7 @@
</ClCompile>
<Link>
<ProgramDatabaseFile>$(OutDir)$(TargetName)FullPDB.pdb</ProgramDatabaseFile>
<AdditionalDependencies>dwrite.lib;dxgi.lib;d2d1.lib;d3d11.lib;shcore.lib;uxtheme.lib;dwmapi.lib;winmm.lib;pathcch.lib;propsys.lib;uiautomationcore.lib;Shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>onecore_apiset.lib;dwrite.lib;dxgi.lib;d2d1.lib;d3d11.lib;shcore.lib;uxtheme.lib;dwmapi.lib;winmm.lib;pathcch.lib;propsys.lib;uiautomationcore.lib;Shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<!--
There's a property that dictates which libraries are linked by default: MinimalCoreWin.
When it's enabled, only a sparing few libraries are injected into Link.AdditionalDependencies.

View file

@ -26,15 +26,15 @@
<PostBuildEvent Condition="'$(Platform)'!='Win32'">
<Command>
echo OutDir=$(OutDir)
(echo f | xcopy /y &quot;$(OutDir)$(ProjectName).dll&quot; &quot;$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).dll&quot; )
(echo f | xcopy /y &quot;$(OutDir)$(ProjectName).pdb&quot; &quot;$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).pdb&quot; )
(xcopy /Y &quot;$(OutDir)$(ProjectName).dll&quot; &quot;$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).dll*&quot; )
(xcopy /Y &quot;$(OutDir)$(ProjectName).pdb&quot; &quot;$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).pdb*&quot; )
</Command>
</PostBuildEvent>
<PostBuildEvent Condition="'$(Platform)'=='Win32'">
<Command>
echo OutDir=$(OutDir)
(echo f | xcopy /y &quot;$(OutDir)$(ProjectName).dll&quot; &quot;$(OpenConsoleDir)$(Configuration)\$(ProjectName).dll&quot; )
(echo f | xcopy /y &quot;$(OutDir)$(ProjectName).pdb&quot; &quot;$(OpenConsoleDir)$(Configuration)\$(ProjectName).pdb&quot; )
(xcopy /Y &quot;$(OutDir)$(ProjectName).dll&quot; &quot;$(OpenConsoleDir)$(Configuration)\$(ProjectName).dll*&quot; )
(xcopy /Y &quot;$(OutDir)$(ProjectName).pdb&quot; &quot;$(OpenConsoleDir)$(Configuration)\$(ProjectName).pdb*&quot; )
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
@ -42,7 +42,7 @@
<!--
For whatever reason, GENERATEPROJECTPRIFILE is incapable of creating this
directory on it's own, it just assumes it exists.
directory on its own, it just assumes it exists.
Also make the directory w/o the platform, because the x86 build in CI will
try to place the pri files into

View file

@ -7,19 +7,19 @@
#include <shellapi.h>
using namespace Microsoft::Console::Utils;
const std::wstring ConsoleArguments::VT_MODE_ARG = L"--vtmode";
const std::wstring ConsoleArguments::HEADLESS_ARG = L"--headless";
const std::wstring ConsoleArguments::SERVER_HANDLE_ARG = L"--server";
const std::wstring ConsoleArguments::SIGNAL_HANDLE_ARG = L"--signal";
const std::wstring ConsoleArguments::HANDLE_PREFIX = L"0x";
const std::wstring ConsoleArguments::CLIENT_COMMANDLINE_ARG = L"--";
const std::wstring ConsoleArguments::FORCE_V1_ARG = L"-ForceV1";
const std::wstring ConsoleArguments::FILEPATH_LEADER_PREFIX = L"\\??\\";
const std::wstring ConsoleArguments::WIDTH_ARG = L"--width";
const std::wstring ConsoleArguments::HEIGHT_ARG = L"--height";
const std::wstring ConsoleArguments::INHERIT_CURSOR_ARG = L"--inheritcursor";
const std::wstring ConsoleArguments::FEATURE_ARG = L"--feature";
const std::wstring ConsoleArguments::FEATURE_PTY_ARG = L"pty";
const std::wstring_view ConsoleArguments::VT_MODE_ARG = L"--vtmode";
const std::wstring_view ConsoleArguments::HEADLESS_ARG = L"--headless";
const std::wstring_view ConsoleArguments::SERVER_HANDLE_ARG = L"--server";
const std::wstring_view ConsoleArguments::SIGNAL_HANDLE_ARG = L"--signal";
const std::wstring_view ConsoleArguments::HANDLE_PREFIX = L"0x";
const std::wstring_view ConsoleArguments::CLIENT_COMMANDLINE_ARG = L"--";
const std::wstring_view ConsoleArguments::FORCE_V1_ARG = L"-ForceV1";
const std::wstring_view ConsoleArguments::FILEPATH_LEADER_PREFIX = L"\\??\\";
const std::wstring_view ConsoleArguments::WIDTH_ARG = L"--width";
const std::wstring_view ConsoleArguments::HEIGHT_ARG = L"--height";
const std::wstring_view ConsoleArguments::INHERIT_CURSOR_ARG = L"--inheritcursor";
const std::wstring_view ConsoleArguments::FEATURE_ARG = L"--feature";
const std::wstring_view ConsoleArguments::FEATURE_PTY_ARG = L"pty";
ConsoleArguments::ConsoleArguments(const std::wstring& commandline,
const HANDLE hStdIn,
@ -86,7 +86,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector<std::wstring>& args, _In
// Routine Description:
// Given the commandline of tokens `args`, tries to find the argument at
// index+1, and places it's value into pSetting.
// index+1, and places its value into pSetting.
// If there aren't enough args, then returns E_INVALIDARG.
// If we found a value, then we take the elements at both index and index+1 out
// of args. We'll also decrement index, so that a caller who is using index
@ -157,7 +157,7 @@ HRESULT ConsoleArguments::s_HandleFeatureValue(_Inout_ std::vector<std::wstring>
// Method Description:
// Routine Description:
// Given the commandline of tokens `args`, tries to find the argument at
// index+1, and places it's value into pSetting. See above for examples.
// index+1, and places its value into pSetting. See above for examples.
// This implementation attempts to parse a short from the argument.
// Arguments:
// args: A collection of wstrings representing command-line arguments

View file

@ -55,19 +55,19 @@ public:
void SetExpectedSize(COORD dimensions) noexcept;
static const std::wstring VT_MODE_ARG;
static const std::wstring HEADLESS_ARG;
static const std::wstring SERVER_HANDLE_ARG;
static const std::wstring SIGNAL_HANDLE_ARG;
static const std::wstring HANDLE_PREFIX;
static const std::wstring CLIENT_COMMANDLINE_ARG;
static const std::wstring FORCE_V1_ARG;
static const std::wstring FILEPATH_LEADER_PREFIX;
static const std::wstring WIDTH_ARG;
static const std::wstring HEIGHT_ARG;
static const std::wstring INHERIT_CURSOR_ARG;
static const std::wstring FEATURE_ARG;
static const std::wstring FEATURE_PTY_ARG;
static const std::wstring_view VT_MODE_ARG;
static const std::wstring_view HEADLESS_ARG;
static const std::wstring_view SERVER_HANDLE_ARG;
static const std::wstring_view SIGNAL_HANDLE_ARG;
static const std::wstring_view HANDLE_PREFIX;
static const std::wstring_view CLIENT_COMMANDLINE_ARG;
static const std::wstring_view FORCE_V1_ARG;
static const std::wstring_view FILEPATH_LEADER_PREFIX;
static const std::wstring_view WIDTH_ARG;
static const std::wstring_view HEIGHT_ARG;
static const std::wstring_view INHERIT_CURSOR_ARG;
static const std::wstring_view FEATURE_ARG;
static const std::wstring_view FEATURE_PTY_ARG;
private:
#ifdef UNIT_TESTING

View file

@ -129,7 +129,7 @@ DoScroll:
Scrolling::s_ScrollIfNecessary(ScreenInfo);
}
void CALLBACK CursorTimerRoutineWrapper(_In_ PVOID /* lpParam */, _In_ BOOL /* TimerOrWaitFired */)
void CALLBACK CursorTimerRoutineWrapper(_In_ PVOID /* lpParam */, _In_ BOOLEAN /* TimerOrWaitFired */)
{
// Suppose the following sequence of events takes place:
//
@ -192,7 +192,7 @@ void CursorBlinker::SetCaretTimer()
bRet = CreateTimerQueueTimer(&_hCaretBlinkTimer,
_hCaretBlinkTimerQueue,
(WAITORTIMERCALLBACKFUNC)CursorTimerRoutineWrapper,
CursorTimerRoutineWrapper,
this,
dwEffectivePeriod,
dwEffectivePeriod,

View file

@ -51,7 +51,7 @@ PtySignalInputThread::~PtySignalInputThread()
// - lpParameter - A pointer to the PtySignalInputTHread instance that should be called.
// Return Value:
// - The return value of the underlying instance's _InputThread
DWORD PtySignalInputThread::StaticThreadProc(_In_ LPVOID lpParameter)
DWORD WINAPI PtySignalInputThread::StaticThreadProc(_In_ LPVOID lpParameter)
{
PtySignalInputThread* const pInstance = reinterpret_cast<PtySignalInputThread*>(lpParameter);
return pInstance->_InputThread();
@ -175,7 +175,7 @@ HRESULT PtySignalInputThread::Start() noexcept
hThread = CreateThread(nullptr,
0,
(LPTHREAD_START_ROUTINE)PtySignalInputThread::StaticThreadProc,
PtySignalInputThread::StaticThreadProc,
this,
0,
&dwThreadId);

View file

@ -25,7 +25,7 @@ namespace Microsoft::Console
[[nodiscard]]
HRESULT Start() noexcept;
static DWORD StaticThreadProc(_In_ LPVOID lpParameter);
static DWORD WINAPI StaticThreadProc(_In_ LPVOID lpParameter);
// Prevent copying and assignment.
PtySignalInputThread(const PtySignalInputThread&) = delete;

View file

@ -90,7 +90,7 @@ HRESULT VtInputThread::_HandleRunInput(_In_reads_(cch) const byte* const charBuf
// - lpParameter - A pointer to the VtInputThread instance that should be called.
// Return Value:
// - The return value of the underlying instance's _InputThread
DWORD VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter)
DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter)
{
VtInputThread* const pInstance = reinterpret_cast<VtInputThread*>(lpParameter);
return pInstance->_InputThread();
@ -167,7 +167,7 @@ HRESULT VtInputThread::Start()
hThread = CreateThread(nullptr,
0,
(LPTHREAD_START_ROUTINE)VtInputThread::StaticVtInputThreadProc,
VtInputThread::StaticVtInputThreadProc,
this,
0,
&dwThreadId);

View file

@ -26,7 +26,7 @@ namespace Microsoft::Console
[[nodiscard]]
HRESULT Start();
static DWORD StaticVtInputThreadProc(_In_ LPVOID lpParameter);
static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter);
void DoReadInput(const bool throwOnFail);
private:

View file

@ -367,7 +367,7 @@ void VtIo::CloseOutput()
// owned by the paint.
// Instead we're releasing the Engine here. A pointer to it has already been
// given to the Renderer, so we don't want the unique_ptr to delete it. The
// Renderer will own it's lifetime now.
// Renderer will own its lifetime now.
_pVtRenderEngine.release();
g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(nullptr);

View file

@ -724,14 +724,14 @@ void SCREEN_INFORMATION::SetViewportSize(const COORD* const pcoordSize)
// Method Description:
// - Update the origin of the buffer's viewport. You can either move the
// viewport with a delta relative to it's current location, or set it's
// viewport with a delta relative to its current location, or set its
// absolute origin. Either way leaves the dimensions of the viewport
// unchanged. Also potentially updates our "virtual bottom", the last real
// location of the viewport in the buffer.
// Also notifies the window implementation to update it's scrollbars.
// Also notifies the window implementation to update its scrollbars.
// Arguments:
// - fAbsolute: If true, coordWindowOrigin is the absolute location of the origin of the new viewport.
// If false, coordWindowOrigin is a delta to move the viewport relative to it's current position.
// If false, coordWindowOrigin is a delta to move the viewport relative to its current position.
// - coordWindowOrigin: Either the new absolute position of the origin of the
// viewport, or a delta to add to the current viewport location.
// - updateBottom: If true, update our virtual bottom position. This should be
@ -1820,7 +1820,7 @@ void SCREEN_INFORMATION::SetCursorType(const CursorType Type, const bool setMain
// Routine Description:
// - This routine sets a flag saying whether the cursor should be displayed
// with it's default size or it should be modified to indicate the
// with its default size or it should be modified to indicate the
// insert/overtype mode has changed.
// Arguments:
// - ScreenInfo - pointer to screen info structure.
@ -2120,7 +2120,7 @@ void SCREEN_INFORMATION::UseMainScreenBuffer()
SCREEN_INFORMATION* psiAlt = psiMain->_psiAlternateBuffer;
psiMain->_psiAlternateBuffer = nullptr;
s_RemoveScreenBuffer(psiAlt); // this will also delete the alt buffer
// deleting the alt buffer will give the GetSet back to it's main
// deleting the alt buffer will give the GetSet back to its main
// Tell the VT MouseInput handler that we're in the main buffer now
gci.terminalMouseInput.UseMainScreenBuffer();
@ -2823,7 +2823,7 @@ void SCREEN_INFORMATION::MoveToBottom()
}
// Method Description:
// - Returns the "virtual" Viewport - the viewport with it's bottom at
// - Returns the "virtual" Viewport - the viewport with its bottom at
// `_virtualBottom`. For VT operations, this is essentially the mutable
// section of the buffer.
// Arguments:

View file

@ -769,7 +769,7 @@ WORD Settings::GenerateLegacyAttributes(const TextAttribute attributes) const
if (attributes.IsRgb())
{
// If the attribute doesn't have a "default" colored *ground, look up
// the nearest color table value for it's *ground.
// the nearest color table value for its *ground.
const COLORREF rgbForeground = LookupForegroundColor(attributes);
fgIndex = attributes.ForegroundIsDefault() ?
fgIndex :

View file

@ -214,7 +214,7 @@ NTSTATUS RemoveConsole(_In_ ConsoleProcessHandle* ProcessData)
return Status;
}
DWORD ConsoleIoThread();
DWORD WINAPI ConsoleIoThread(LPVOID lpParameter);
void ConsoleCheckDebug()
{
@ -258,7 +258,7 @@ HRESULT ConsoleCreateIoThreadLegacy(_In_ HANDLE Server, const ConsoleArguments*
ServerInformation.InputAvailableEvent = ServiceLocator::LocateGlobals().hInputEvent.get();
RETURN_IF_FAILED(g.pDeviceComm->SetServerInformation(&ServerInformation));
HANDLE const hThread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)ConsoleIoThread, 0, 0, nullptr);
HANDLE const hThread = CreateThread(nullptr, 0, ConsoleIoThread, 0, 0, nullptr);
RETURN_HR_IF(E_HANDLE, hThread == nullptr);
LOG_IF_WIN32_BOOL_FALSE(CloseHandle(hThread)); // The thread will run on its own and close itself. Free the associated handle.
@ -517,7 +517,7 @@ NTSTATUS ConsoleAllocateConsole(PCONSOLE_API_CONNECTINFO p)
auto renderThread = std::make_unique<RenderThread>();
// stash a local pointer to the thread here -
// We're going to give ownership of the thread to the Renderer,
// but the thread also need to be told who it's renderer is,
// but the thread also need to be told who its renderer is,
// and we can't do that until the renderer is constructed.
auto* const localPointerToThread = renderThread.get();
@ -529,7 +529,7 @@ NTSTATUS ConsoleAllocateConsole(PCONSOLE_API_CONNECTINFO p)
g.pRender->EnablePainting();
// Set up the renderer to be used to calculate the width of a glyph,
// should we be unable to figure out it's width another way.
// should we be unable to figure out its width another way.
auto pfn = std::bind(&Renderer::IsGlyphWideByFont, static_cast<Renderer*>(g.pRender), std::placeholders::_1);
SetGlyphWidthFallback(pfn);
@ -638,7 +638,7 @@ NTSTATUS ConsoleAllocateConsole(PCONSOLE_API_CONNECTINFO p)
// - <none>
// Return Value:
// - This routine never returns. The process exits when no more references or clients exist.
DWORD ConsoleIoThread()
DWORD WINAPI ConsoleIoThread(LPVOID /*lpParameter*/)
{
auto& globals = ServiceLocator::LocateGlobals();

View file

@ -10,9 +10,9 @@
using namespace WEX::Logging;
static const std::wstring emoji = L"\xD83E\xDD22"; // U+1F922 nauseated face
static constexpr std::wstring_view emoji = L"\xD83E\xDD22"; // U+1F922 nauseated face
static const std::wstring ambiguous = L"\x414"; // U+0414 cyrillic capital de
static constexpr std::wstring_view ambiguous = L"\x414"; // U+0414 cyrillic capital de
// codepoint and utf16 encoded string
static const std::vector<std::tuple<unsigned int, std::wstring, CodepointWidth>> testData =

View file

@ -505,5 +505,5 @@ void Utf8ToWideCharParser::_Reset()
{
_currentState = _State::Ready;
_bytesStored = 0;
_convertedWideChars.release();
_convertedWideChars.reset(nullptr);
}

View file

@ -9,6 +9,7 @@
#pragma warning(disable: ALL_CPPCORECHECK_WARNINGS)
// C
#include <climits>
#include <cwchar>
#include <cwctype>
@ -48,6 +49,7 @@
#include <wil/Result.h>
#include <wil/resource.h>
#include <wil/wistd_memory.h>
#include <wil/com.h>
// GSL
// Block GSL Multi Span include because it both has C++17 deprecated iterators

View file

@ -30,18 +30,166 @@
const unsigned int PTY_SIGNAL_RESIZE_WINDOW = 8u;
HRESULT CreateConPty(const std::wstring& cmdline, // _In_
const unsigned short w, // _In_
const unsigned short h, // _In_
HANDLE* const hInput, // _Out_
HANDLE* const hOutput, // _Out_
HANDLE* const hSignal, // _Out_
PROCESS_INFORMATION* const piPty); // _Out_
// A case-insensitive wide-character map is used to store environment variables
// due to documented requirements:
//
// "All strings in the environment block must be sorted alphabetically by name.
// The sort is case-insensitive, Unicode order, without regard to locale.
// Because the equal sign is a separator, it must not be used in the name of
// an environment variable."
// https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables
//
struct WStringCaseInsensitiveCompare
{
[[nodiscard]]
bool operator()(const std::wstring& lhs, const std::wstring& rhs) const noexcept
{
return (::_wcsicmp(lhs.c_str(), rhs.c_str()) < 0);
}
};
using EnvironmentVariableMapW = std::map<std::wstring, std::wstring, WStringCaseInsensitiveCompare>;
[[nodiscard]]
HRESULT CreateConPty(const std::wstring& cmdline,
const unsigned short w,
const unsigned short h,
HANDLE* const hInput,
HANDLE* const hOutput,
HANDLE* const hSignal,
PROCESS_INFORMATION* const piPty,
const EnvironmentVariableMapW& extraEnvVars = {}) noexcept;
bool SignalResizeWindow(const HANDLE hSignal,
const unsigned short w,
const unsigned short h);
[[nodiscard]]
HRESULT UpdateEnvironmentMapW(EnvironmentVariableMapW& map) noexcept;
[[nodiscard]]
HRESULT EnvironmentMapToEnvironmentStringsW(EnvironmentVariableMapW& map, std::vector<wchar_t>& newEnvVars) noexcept;
// Function Description:
// - Updates an EnvironmentVariableMapW with the current process's unicode
// environment variables ignoring ones already set in the provided map.
// Arguments:
// - map: The map to populate with the current processes's environment variables.
// Return Value:
// - S_OK if we succeeded, or an appropriate HRESULT for failing
[[nodiscard]]
__declspec(noinline) inline
HRESULT UpdateEnvironmentMapW(EnvironmentVariableMapW& map) noexcept
{
LPWCH currentEnvVars{};
auto freeCurrentEnv = wil::scope_exit([&] {
if (currentEnvVars)
{
(void)FreeEnvironmentStringsW(currentEnvVars);
currentEnvVars = nullptr;
}
});
RETURN_IF_NULL_ALLOC(currentEnvVars = ::GetEnvironmentStringsW());
// Each entry is NULL-terminated; block is guaranteed to be double-NULL terminated at a minimum.
for (wchar_t const* lastCh{ currentEnvVars }; *lastCh != '\0'; ++lastCh)
{
// Copy current entry into temporary map.
const size_t cchEntry{ ::wcslen(lastCh) };
const std::wstring_view entry{ lastCh, cchEntry };
// Every entry is of the form "name=value\0".
auto pos = entry.find_first_of(L"=", 0, 1);
RETURN_HR_IF(E_UNEXPECTED, pos == std::wstring::npos);
std::wstring name{ entry.substr(0, pos) }; // portion before '='
std::wstring value{ entry.substr(pos + 1) }; // portion after '='
// Don't replace entries that already exist.
map.try_emplace(std::move(name), std::move(value));
lastCh += cchEntry;
}
return S_OK;
}
// Function Description:
// - Creates a new environment block using the provided vector as appropriate
// (resizing if needed) based on the provided environment variable map
// matching the format of GetEnvironmentStringsW.
// Arguments:
// - map: The map to populate the new environment block vector with.
// - newEnvVars: The vector that will be used to create the new environment block.
// Return Value:
// - S_OK if we succeeded, or an appropriate HRESULT for failing
[[nodiscard]]
__declspec(noinline) inline
HRESULT EnvironmentMapToEnvironmentStringsW(EnvironmentVariableMapW& map, std::vector<wchar_t>& newEnvVars) noexcept
{
// Clear environment block before use.
constexpr size_t cbChar{ sizeof(decltype(newEnvVars.begin())::value_type) };
if (!newEnvVars.empty())
{
::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar);
}
// Resize environment block to fit map.
size_t cchEnv{ 2 }; // For the block's double NULL-terminator.
for (const auto&[name, value] : map)
{
// Final form of "name=value\0".
cchEnv += name.size() + 1 + value.size() + 1;
}
newEnvVars.resize(cchEnv);
// Ensure new block is wiped if we exit due to failure.
auto zeroNewEnv = wil::scope_exit([&] {
::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar);
});
// Transform each map entry and copy it into the new environment block.
LPWCH pEnvVars{ newEnvVars.data() };
size_t cbRemaining{ cchEnv * cbChar };
for (const auto&[name, value] : map)
{
// Final form of "name=value\0" for every entry.
{
const size_t cchSrc{ name.size() };
const size_t cbSrc{ cchSrc * cbChar };
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, name.c_str(), cbSrc) != 0);
pEnvVars += cchSrc;
cbRemaining -= cbSrc;
}
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"=", cbChar) != 0);
++pEnvVars;
cbRemaining -= cbChar;
{
const size_t cchSrc{ value.size() };
const size_t cbSrc{ cchSrc * cbChar };
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, value.c_str(), cbSrc) != 0);
pEnvVars += cchSrc;
cbRemaining -= cbSrc;
}
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0", cbChar) != 0);
++pEnvVars;
cbRemaining -= cbChar;
}
// Environment block only has to be NULL-terminated, but double NULL-terminate anyway.
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0\0", cbChar * 2) != 0);
cbRemaining -= cbChar * 2;
RETURN_HR_IF(E_UNEXPECTED, cbRemaining != 0);
zeroNewEnv.release(); // success; don't wipe new environment block on exit
return S_OK;
}
// Function Description:
// - Creates a headless conhost in "pty mode" and launches the given commandline
@ -64,9 +212,13 @@ bool SignalResizeWindow(const HANDLE hSignal,
// - hSignal: A handle to the pipe for writing signal messages to the pty.
// - piPty: The PROCESS_INFORMATION of the pty process. NOTE: This is *not* the
// PROCESS_INFORMATION of the process that's created as a result the cmdline.
// - extraEnvVars : A map of pairs of (Name, Value) representing additional
// environment variable strings and values to be set in the client process
// environment. May override any already present in parent process.
// Return Value:
// - S_OK if we succeeded, or an appropriate HRESULT for failing format the
// commandline or failing to launch the conhost
[[nodiscard]]
__declspec(noinline) inline
HRESULT CreateConPty(const std::wstring& cmdline,
std::optional<std::wstring> startingDirectory,
@ -75,7 +227,8 @@ HRESULT CreateConPty(const std::wstring& cmdline,
HANDLE* const hInput,
HANDLE* const hOutput,
HANDLE* const hSignal,
PROCESS_INFORMATION* const piPty)
PROCESS_INFORMATION* const piPty,
const EnvironmentVariableMapW& extraEnvVars = {}) noexcept
{
// Create some anon pipes so we can pass handles down and into the console.
// IMPORTANT NOTE:
@ -142,14 +295,42 @@ HRESULT CreateConPty(const std::wstring& cmdline,
LPCWSTR lpCurrentDirectory = startingDirectory.has_value() ? startingDirectory.value().c_str() : nullptr;
std::vector<wchar_t> newEnvVars;
auto zeroNewEnv = wil::scope_exit([&] {
::SecureZeroMemory(newEnvVars.data(),
newEnvVars.size() * sizeof(decltype(newEnvVars.begin())::value_type));
});
DWORD dwCreationFlags = 0;
if (!extraEnvVars.empty())
{
EnvironmentVariableMapW tempEnvMap{ extraEnvVars };
auto zeroEnvMap = wil::scope_exit([&] {
// Can't zero the keys, but at least we can zero the values.
for (auto&[name, value] : tempEnvMap)
{
::SecureZeroMemory(value.data(), value.size() * sizeof(decltype(value.begin())::value_type));
}
tempEnvMap.clear();
});
RETURN_IF_FAILED(UpdateEnvironmentMapW(tempEnvMap));
RETURN_IF_FAILED(EnvironmentMapToEnvironmentStringsW(tempEnvMap, newEnvVars));
// Required when using a unicode environment block.
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
}
LPWCH lpEnvironment = newEnvVars.empty() ? nullptr : newEnvVars.data();
bool fSuccess = !!CreateProcessW(
nullptr,
mutableCommandline.get(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
true, // bInheritHandles
0, // dwCreationFlags
nullptr, // lpEnvironment
dwCreationFlags, // dwCreationFlags
lpEnvironment, // lpEnvironment
lpCurrentDirectory, // lpCurrentDirectory
&si, // lpStartupInfo
piPty // lpProcessInformation

View file

@ -16,7 +16,7 @@
using namespace Microsoft::Console::Interactivity::OneCore;
DWORD ConsoleInputThreadProcOneCore(LPVOID /*lpParam*/)
DWORD WINAPI ConsoleInputThreadProcOneCore(LPVOID /*lpParam*/)
{
Globals& globals = ServiceLocator::LocateGlobals();
ConIoSrvComm * const Server = ServiceLocator::LocateInputServices<ConIoSrvComm>();
@ -108,7 +108,7 @@ HANDLE ConsoleInputThread::Start()
hThread = CreateThread(nullptr,
0,
(LPTHREAD_START_ROUTINE)ConsoleInputThreadProcOneCore,
ConsoleInputThreadProcOneCore,
_pConIoSrvComm,
0,
&dwThreadId);

View file

@ -18,7 +18,7 @@ HANDLE ConsoleInputThread::Start()
hThread = CreateThread(nullptr,
0,
(LPTHREAD_START_ROUTINE)ConsoleInputThreadProcWin32,
ConsoleInputThreadProcWin32,
nullptr,
0,
&dwThreadId);

View file

@ -15,7 +15,7 @@
#pragma hdrstop
INT_PTR FindDialogProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
INT_PTR CALLBACK FindDialogProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// This bool is used to track which option - up or down - was used to perform the last search. That way, the next time the
@ -92,7 +92,7 @@ void DoFind()
HWND const hwnd = pWindow->GetWindowHandle();
++g.uiDialogBoxCount;
DialogBoxParamW(g.hInstance, MAKEINTRESOURCE(ID_CONSOLE_FINDDLG), hwnd, (DLGPROC)FindDialogProc, (LPARAM) nullptr);
DialogBoxParamW(g.hInstance, MAKEINTRESOURCE(ID_CONSOLE_FINDDLG), hwnd, FindDialogProc, (LPARAM) nullptr);
--g.uiDialogBoxCount;
}
}

View file

@ -622,7 +622,7 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
// We need to try and have the virtual terminal handle the mouse's position in viewport coordinates,
// not in screen buffer coordinates. It expects the top left to always be 0,0
// (the TerminalMouseInput object will add (1,1) to convert to VT coords on it's own.)
// (the TerminalMouseInput object will add (1,1) to convert to VT coords on its own.)
// Mouse events with shift pressed will ignore this and fall through to the default handler.
// This is in line with PuTTY's behavior and vim's own documentation:
// "The xterm handling of the mouse buttons can still be used by keeping the shift key pressed." - `:help 'mouse'`, vim.
@ -937,7 +937,7 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
// Routine Description:
// - This routine gets called to filter input to console dialogs so that we can do the special processing that StoreKeyInfo does.
LRESULT DialogHookProc(int nCode, WPARAM /*wParam*/, LPARAM lParam)
LRESULT CALLBACK DialogHookProc(int nCode, WPARAM /*wParam*/, LPARAM lParam)
{
MSG msg = *((PMSG)lParam);
@ -979,7 +979,7 @@ NTSTATUS InitWindowsSubsystem(_Out_ HHOOK * phhook)
// We intentionally ignore the return value of SetWindowsHookEx. There are mixed LUID cases where this call will fail but in the past this call
// was special cased (for CSRSS) to always succeed. Thus, we ignore failure for app compat (as not having the hook isn't fatal).
*phhook = SetWindowsHookExW(WH_MSGFILTER, (HOOKPROC)DialogHookProc, nullptr, GetCurrentThreadId());
*phhook = SetWindowsHookExW(WH_MSGFILTER, DialogHookProc, nullptr, GetCurrentThreadId());
SetConsoleWindowOwner(ServiceLocator::LocateConsoleWindow()->GetWindowHandle(), ProcessData);
@ -995,7 +995,7 @@ NTSTATUS InitWindowsSubsystem(_Out_ HHOOK * phhook)
// (for a window)
// ----------------------------
DWORD ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/)
DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/)
{
InitEnvironmentVariables();

View file

@ -25,4 +25,4 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
const LPARAM lParam);
VOID SetConsoleWindowOwner(const HWND hwnd, _Inout_opt_ ConsoleProcessHandle* pProcessData);
DWORD ConsoleInputThreadProcWin32(LPVOID lpParameter);
DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID lpParameter);

View file

@ -42,7 +42,7 @@ void SimpleColorDoPaint(const HWND hColor, PAINTSTRUCT& ps, const int ColorId)
// Routine Description:
// - Window proc for the color buttons
LRESULT SimpleColorControlProc(const HWND hColor, const UINT wMsg, const WPARAM wParam, const LPARAM lParam)
LRESULT CALLBACK SimpleColorControlProc(const HWND hColor, const UINT wMsg, const WPARAM wParam, const LPARAM lParam)
{
PAINTSTRUCT ps;
int ColorId;

View file

@ -14,5 +14,5 @@ Author(s):
#pragma once
LRESULT SimpleColorControlProc(HWND hColor, UINT wMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK SimpleColorControlProc(HWND hColor, UINT wMsg, WPARAM wParam, LPARAM lParam);
void SimpleColorDoPaint(HWND hColor, PAINTSTRUCT& ps, int ColorId);

View file

@ -10,7 +10,7 @@ static int iColor;
// Routine Description:
// - Window proc for the color buttons
LRESULT ColorTableControlProc(HWND hColor, UINT wMsg, WPARAM wParam, LPARAM lParam)
LRESULT CALLBACK ColorTableControlProc(HWND hColor, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
int ColorId;

View file

@ -18,4 +18,4 @@ void ToggleV2ColorControls(__in const HWND hDlg);
INT_PTR WINAPI ColorDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam);
void SetOpacitySlider(__in HWND hDlg);
void PreviewOpacity(HWND hDlg, BYTE bOpacity);
LRESULT ColorTableControlProc(HWND hColor, UINT wMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK ColorTableControlProc(HWND hColor, UINT wMsg, WPARAM wParam, LPARAM lParam);

View file

@ -506,7 +506,7 @@ BOOL PopulatePropSheetPageArray(_Out_writes_(cPsps) PROPSHEETPAGE *pPsp, const s
pFontPage->dwSize = sizeof(PROPSHEETPAGE);
pFontPage->hInstance = ghInstance;
pFontPage->pszTemplate = MAKEINTRESOURCE(DID_FONTDLG);
pFontPage->pfnDlgProc = (DLGPROC) FontDlgProc;
pFontPage->pfnDlgProc = FontDlgProc;
pFontPage->lParam = FONT_PAGE_INDEX;
pOptionsPage->dwFlags = PSP_DEFAULT;
@ -660,7 +660,7 @@ void RegisterClasses(HINSTANCE hModule)
WNDCLASS wc;
wc.lpszClassName = TEXT("SimpleColor");
wc.hInstance = hModule;
wc.lpfnWndProc = (WNDPROC) SimpleColorControlProc;
wc.lpfnWndProc = SimpleColorControlProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = NULL;
wc.lpszMenuName = NULL;
@ -672,7 +672,7 @@ void RegisterClasses(HINSTANCE hModule)
wc.lpszClassName = TEXT("ColorTableColor");
wc.hInstance = hModule;
wc.lpfnWndProc = (WNDPROC) ColorTableControlProc;
wc.lpfnWndProc = ColorTableControlProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = NULL;
wc.lpszMenuName = NULL;
@ -683,13 +683,13 @@ void RegisterClasses(HINSTANCE hModule)
RegisterClass(&wc);
wc.lpszClassName = TEXT("WOAWinPreview");
wc.lpfnWndProc = (WNDPROC) PreviewWndProc;
wc.lpfnWndProc = PreviewWndProc;
wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND + 1);
wc.style = 0;
RegisterClass(&wc);
wc.lpszClassName = TEXT("WOAFontPreview");
wc.lpfnWndProc = (WNDPROC) FontPreviewWndProc;
wc.lpfnWndProc = FontPreviewWndProc;
wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
wc.style = 0;
RegisterClass(&wc);

View file

@ -173,13 +173,13 @@ VOID SetRegistryValues(
PCONSOLE_STATE_INFO InitStateValues(
HWND hwnd);
LRESULT FontPreviewWndProc(
LRESULT CALLBACK FontPreviewWndProc(
HWND hWnd,
UINT wMsg,
WPARAM wParam,
LPARAM lParam);
LRESULT PreviewWndProc(
LRESULT CALLBACK PreviewWndProc(
HWND hWnd,
UINT wMsg,
WPARAM wParam,

View file

@ -979,6 +979,7 @@ Return Value:
/* ----- Preview routines ----- */
LRESULT
CALLBACK
FontPreviewWndProc(
HWND hWnd,
UINT wMessage,

View file

@ -420,7 +420,7 @@ DestroyFonts(
* Is called exactly once by GDI for each font in the system. This
* routine is used to store the FONT_INFO structure.
*/
int FontEnumForV2Console(ENUMLOGFONT *pelf, NEWTEXTMETRIC *pntm, int nFontType, LPARAM lParam)
int CALLBACK FontEnumForV2Console(ENUMLOGFONT *pelf, NEWTEXTMETRIC *pntm, int nFontType, LPARAM lParam)
{
FAIL_FAST_IF(!(ShouldAllowAllMonoTTFonts()));
UINT i;
@ -546,6 +546,7 @@ int FontEnumForV2Console(ENUMLOGFONT *pelf, NEWTEXTMETRIC *pntm, int nFontType,
* routine is used to store the FONT_INFO structure.
*/
int
CALLBACK
FontEnum(
ENUMLOGFONT *pelf,
NEWTEXTMETRIC *pntm,

Some files were not shown because too many files have changed in this diff Show more