Merged PR 3300188: Merge GitHub changes up to 82e75ce3
Related work items: #21439265
|
@ -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}
|
||||
|
|
24
README.md
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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/)
|
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
40
samples/ConPTY/GUIConsole/GUIConsole.ConPTY/PseudoConsole.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
113
samples/ConPTY/GUIConsole/GUIConsole.ConPTY/Terminal.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
samples/ConPTY/GUIConsole/GUIConsole.WPF/App.config
Normal 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>
|
8
samples/ConPTY/GUIConsole/GUIConsole.WPF/App.xaml
Normal 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>
|
15
samples/ConPTY/GUIConsole/GUIConsole.WPF/App.xaml.cs
Normal 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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
105
samples/ConPTY/GUIConsole/GUIConsole.WPF/GUIConsole.WPF.csproj
Normal 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>
|
84
samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml
Normal 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=""/>
|
||||
<!--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=""
|
||||
Style="{StaticResource TitleBarButtonStyle}"/>
|
||||
<Button x:Name="MaximizeRestoreButton"
|
||||
Click="MaximizeRestoreButton_Click"
|
||||
Content=""
|
||||
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>
|
121
samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")]
|
63
samples/ConPTY/GUIConsole/GUIConsole.WPF/Properties/Resources.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
26
samples/ConPTY/GUIConsole/GUIConsole.WPF/Properties/Settings.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
61
samples/ConPTY/GUIConsole/GUIConsole.sln
Normal 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
|
9
samples/ConPTY/GUIConsole/README.md
Normal 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.
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
After Width: | Height: | Size: 243 B |
After Width: | Height: | Size: 641 B |
After Width: | Height: | Size: 737 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 616 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 755 B |
After Width: | Height: | Size: 1.6 KiB |
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
249
src/cascadia/TerminalApp/KeyChordSerialization.cpp
Normal 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 };
|
||||
}
|
12
src/cascadia/TerminalApp/KeyChordSerialization.h
Normal 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);
|
||||
};
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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; };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
136
src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp
Normal 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");
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -26,15 +26,15 @@
|
|||
<PostBuildEvent Condition="'$(Platform)'!='Win32'">
|
||||
<Command>
|
||||
echo OutDir=$(OutDir)
|
||||
(echo f | xcopy /y "$(OutDir)$(ProjectName).dll" "$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).dll" )
|
||||
(echo f | xcopy /y "$(OutDir)$(ProjectName).pdb" "$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).pdb" )
|
||||
(xcopy /Y "$(OutDir)$(ProjectName).dll" "$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).dll*" )
|
||||
(xcopy /Y "$(OutDir)$(ProjectName).pdb" "$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).pdb*" )
|
||||
</Command>
|
||||
</PostBuildEvent>
|
||||
<PostBuildEvent Condition="'$(Platform)'=='Win32'">
|
||||
<Command>
|
||||
echo OutDir=$(OutDir)
|
||||
(echo f | xcopy /y "$(OutDir)$(ProjectName).dll" "$(OpenConsoleDir)$(Configuration)\$(ProjectName).dll" )
|
||||
(echo f | xcopy /y "$(OutDir)$(ProjectName).pdb" "$(OpenConsoleDir)$(Configuration)\$(ProjectName).pdb" )
|
||||
(xcopy /Y "$(OutDir)$(ProjectName).dll" "$(OpenConsoleDir)$(Configuration)\$(ProjectName).dll*" )
|
||||
(xcopy /Y "$(OutDir)$(ProjectName).pdb" "$(OpenConsoleDir)$(Configuration)\$(ProjectName).pdb*" )
|
||||
</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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 :
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -505,5 +505,5 @@ void Utf8ToWideCharParser::_Reset()
|
|||
{
|
||||
_currentState = _State::Ready;
|
||||
_bytesStored = 0;
|
||||
_convertedWideChars.release();
|
||||
_convertedWideChars.reset(nullptr);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -18,7 +18,7 @@ HANDLE ConsoleInputThread::Start()
|
|||
|
||||
hThread = CreateThread(nullptr,
|
||||
0,
|
||||
(LPTHREAD_START_ROUTINE)ConsoleInputThreadProcWin32,
|
||||
ConsoleInputThreadProcWin32,
|
||||
nullptr,
|
||||
0,
|
||||
&dwThreadId);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -979,6 +979,7 @@ Return Value:
|
|||
/* ----- Preview routines ----- */
|
||||
|
||||
LRESULT
|
||||
CALLBACK
|
||||
FontPreviewWndProc(
|
||||
HWND hWnd,
|
||||
UINT wMessage,
|
||||
|
|
|
@ -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,
|
||||
|
|