Merged PR 4770883: Migrate OSS changes up to f90f3bf99

Dustin Howett (1):
      Merge remote-tracking branch 'openconsole/inbox' into HEAD

James Holderness (1):
      Improve support for VT character sets (CC-4496)

Related work items: MSFT:26791619
This commit is contained in:
Dustin Howett 2020-06-05 21:40:03 +00:00
commit 935702cde9
59 changed files with 2228 additions and 367 deletions

View File

@ -97786,6 +97786,7 @@ dalesman
dalesmen
dalespeople
daleswoman
dalet
daleth
daleths
Daleville
@ -107561,6 +107562,7 @@ dialystelic
dialystely
dialytic
dialytically
dialytika
dialyzability
dialyzable
dialyzate
@ -114004,6 +114006,7 @@ djalmaite
Djambi
djasakid
djave
dje
djebel
djebels
djehad
@ -120418,11 +120421,13 @@ DZ
dz
dz.
Dzaudzhikau
dze
dzeren
dzerin
dzeron
Dzerzhinsk
Dzhambul
dzhe
Dzhugashvili
dziggetai
dzo
@ -158966,6 +158971,7 @@ Ghaznevid
Ghazzah
Ghazzali
ghbor
ghe
Gheber
gheber
ghebeta
@ -160166,6 +160172,7 @@ gizzards
gizzen
gizzened
gizzern
gje
gjedost
Gjellerup
gjetost
@ -212347,6 +212354,7 @@ Kizilbash
Kizzee
Kizzie
kJ
kje
Kjeldahl
kjeldahlization
kjeldahlize
@ -224856,6 +224864,7 @@ lizzie
Lizzy
LJ
LJBF
lje
Ljod
Ljoka
Ljubljana
@ -261607,6 +261616,7 @@ N.J.
NJ
nj
njave
nje
Njord
Njorth
NKGB
@ -274785,6 +274795,7 @@ Ogma
ogmic
Ogmios
OGO
ogonek
ogonium
Ogor
O'Gowan
@ -329834,6 +329845,7 @@ QN
qn
QNP
QNS
qof
Qoheleth
Qom
qoph
@ -371408,6 +371420,7 @@ Shaysite
shazam
Shazar
SHCD
shcha
Shcheglovsk
Shcherbakov
she
@ -420973,6 +420986,7 @@ tonometry
Tonopah
tonophant
tonoplast
tonos
tonoscope
tonotactic
tonotaxis
@ -428676,6 +428690,7 @@ tsetses
TSF
TSgt
TSH
tshe
Tshi
tshi
Tshiluba
@ -477068,6 +477083,7 @@ Yermo
yern
yertchuk
yerth
yeru
yerva
Yerwa-Maiduguri
Yerxa
@ -478235,6 +478251,7 @@ Z-bar
ZBB
ZBR
ZD
ze
Zea
zea
zeal
@ -478604,6 +478621,7 @@ ZG
ZGS
Zhang
Zhdanov
zhe
Zhitomir
Zhivkov
Zhmud

View File

@ -1,3 +1,6 @@
ACLs
DACL
DACLs
LKG
mfcribbon
microsoft
@ -5,6 +8,7 @@ microsoftonline
osgvsowi
powerrename
powershell
SACLs
tdbuildteamid
vcruntime
visualstudio

View File

@ -508,8 +508,8 @@ DECAUPSS
DECAWM
DECCKM
DECCOLM
decf
DECEKBD
decf
DECKPAM
DECKPM
DECKPNM
@ -906,6 +906,7 @@ grep
Greyscale
gridline
groupbox
gset
gsl
GTP
guc
@ -1425,6 +1426,7 @@ Mul
multiline
munged
mutex
mutexes
muxes
myapplet
mydir
@ -1530,6 +1532,7 @@ NOYIELD
NOZORDER
NPM
npos
NRCS
NSTATUS
ntapi
ntcon
@ -1920,6 +1923,7 @@ RELBINPATH
remoting
renderengine
rendersize
reparent
reparenting
replatformed
Replymessage
@ -2030,6 +2034,7 @@ SCROLLSCALE
SCROLLSCREENBUFFER
Scrollup
Scrolluppage
SCS
scursor
sddl
sdeleted
@ -2098,8 +2103,8 @@ SHIFTJIS
Shl
shlguid
shlobj
shobjidl
shlwapi
shobjidl
SHORTPATH
SHOWCURSOR
SHOWMAXIMIZED
@ -2260,6 +2265,7 @@ tcommandline
tcommands
tcon
TDP
tearoff
Teb
techcommunity
technet
@ -2419,6 +2425,7 @@ unexpand
Unfocus
unfocuses
unhighlighting
unhosted
unicode
UNICODESTRING
UNICODETEXT
@ -2444,6 +2451,7 @@ untimes
UPDATEDISPLAY
UPDOWN
UPKEY
UPSS
upvote
uri
url
@ -2608,7 +2616,6 @@ wincontypes
WINCORE
windbg
WINDEF
winget
WINDIR
windll
WINDOWALPHA
@ -2637,6 +2644,7 @@ WINDOWTITLE
winevent
winfx
wingdi
winget
WINIDE
winioctl
winmd

View File

@ -1,11 +1,13 @@
https://(?:(?:[-a-zA-Z0-9?&=]*\.|)microsoft\.com)/[-a-zA-Z0-9?&=_#\/.]*
https://aka\.ms/[-a-zA-Z0-9?&=\/_]*
https://www\.itscj\.ipsj\.or\.jp/iso-ir/[-0-9]+\.pdf
https://www\.vt100\.net/docs/[-a-zA-Z0-9#_\/.]*
https://www.w3.org/[-a-zA-Z0-9?&=\/_#]*
https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
https://[a-z-]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
[Pp]ublicKeyToken="?[0-9a-fA-F]{16}"?
(?:[{"]|UniqueIdentifier>)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|</UniqueIdentifier)
(?:0[Xx]|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]?\b
(?:0[Xx]|\\x|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]{0,2}\b
microsoft/cascadia-code\@[0-9a-fA-F]{40}
\d+x\d+Logo
Scro\&ll

View File

@ -67,12 +67,12 @@ To update the version of a given package, use the following snippet
where:
- `$PackageName` is the name of the package, e.g. Microsoft.UI.Xaml
- `$OldVersionNumber` is the version number currently used, e.g. 2.3.191217003-prerelease
- `$OldVersionNumber` is the version number currently used, e.g. 2.4.2-prerelease.200604001
- `$NewVersionNumber` is the version number you want to migrate to, e.g. 2.4.200117003-prerelease
Example usage:
`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.3.191217003-prerelease/2.4.200117003-prerelease/g'`
`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.4.2-prerelease.200604001/2.4.200117003-prerelease/g'`
## Using .nupkg files instead of downloaded Nuget packages
If you want to use .nupkg files instead of the downloaded Nuget package, you can do this with the following steps:

View File

@ -19,7 +19,7 @@ Properties listed below affect the entire window, regardless of the profile sett
| `showTerminalTitleInTitlebar` | _Required_ | Boolean | `true` | When set to `true`, titlebar displays the title of the selected tab. When set to `false`, titlebar displays "Windows Terminal". |
| `showTabsInTitlebar` | Optional | Boolean | `true` | When set to `true`, the tabs are moved into the titlebar and the titlebar disappears. When set to `false`, the titlebar sits above the tabs. |
| `snapToGridOnResize` | Optional | Boolean | `false` | When set to `true`, the window will snap to the nearest character boundary on resize. When `false`, the window will resize "smoothly" |
| `tabWidthMode` | Optional | String | `equal` | Sets the width of the tabs. Possible values: `"equal"`, `"titleLength"` |
| `tabWidthMode` | Optional | String | `equal` | Sets the width of the tabs. Possible values: <br><ul><li>`"equal"`: sizes each tab to the same width</li><li>`"titleLength"`: sizes each tab to the length of its title</li><li>`"compact"`: sizes each tab to the length of its title when focused, and shrinks to the size of only the icon when the tab is unfocused.</li></ul> |
| `wordDelimiters` | Optional | String | <code>&nbsp;&#x2f;&#x5c;&#x28;&#x29;&#x22;&#x27;&#x2d;&#x3a;&#x2c;&#x2e;&#x3b;&#x3c;&#x3e;&#x7e;&#x21;&#x40;&#x23;&#x24;&#x25;&#x5e;&#x26;&#x2a;&#x7c;&#x2b;&#x3d;&#x5b;&#x5d;&#x7b;&#x7d;&#x7e;&#x3f;</code><br>_(`│` is `U+2502 BOX DRAWINGS LIGHT VERTICAL`)_ | Determines the delimiters used in a double click selection. |
| `confirmCloseAllTabs` | Optional | Boolean | `true` | When set to `true` closing a window with multiple tabs open WILL require confirmation. When set to `false` closing a window with multiple tabs open WILL NOT require confirmation. |
| `startOnUserLogin` | Optional | Boolean | `false` | When set to `true` enables the launch of Windows Terminal at startup. Setting to `false` will disable the startup task entry. Note: if the Windows Terminal startup task entry is disabled either by org policy or by user action this setting will have no effect. |
@ -59,6 +59,7 @@ Properties listed below are specific to each unique profile.
| `scrollbarState` | Optional | String | `"visible"` | Defines the visibility of the scrollbar. Possible values: `"visible"`, `"hidden"` |
| `selectionBackground` | Optional | String | | Sets the selection background color of the profile. Overrides `selectionBackground` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
| `snapOnInput` | Optional | Boolean | `true` | When set to `true`, the window will scroll to the command input line when typing. When set to `false`, the window will not scroll when you start typing. |
| `altGrAliasing` | Optional | Boolean | `true` | By default Windows treats Ctrl+Alt as an alias for AltGr. When altGrAliasing is set to false, this behavior will be disabled. |
| `source` | Optional | String | | Stores the name of the profile generator that originated this profile. _There are no discoverable values for this field._ |
| `startingDirectory` | Optional | String | `%USERPROFILE%` | The directory the shell starts in when it is loaded. |
| `suppressApplicationTitle` | Optional | Boolean | `false` | When set to `true`, `tabTitle` overrides the default title of the tab and any title change messages from the application will be suppressed. When set to `false`, `tabTitle` behaves as normal. |

View File

@ -376,8 +376,9 @@
},
"tabWidthMode": {
"default": "equal",
"description": "Sets the width of the tabs. Possible values include:\n -\"equal\" sizes each tab to the same width\n -\"titleLength\" sizes each tab to the length of its title",
"description": "Sets the width of the tabs. Possible values include:\n -\"equal\" sizes each tab to the same width\n -\"titleLength\" sizes each tab to the length of its title\n -\"compact\" sizes each tab to the length of its title when focused, and shrinks to the size of only the icon when the tab is unfocused.",
"enum": [
"compact",
"equal",
"titleLength"
],
@ -615,6 +616,11 @@
"description": "When set to true, the window will scroll to the command input line when typing. When set to false, the window will not scroll when you start typing.",
"type": "boolean"
},
"altGrAliasing": {
"default": true,
"description": "By default Windows treats Ctrl+Alt as an alias for AltGr. When altGrAliasing is set to false, this behavior will be disabled.",
"type": "boolean"
},
"source": {
"description": "Stores the name of the profile generator that originated this profile.",
"type": ["string", "null"]

View File

@ -0,0 +1,319 @@
---
author: Michael Niksa @miniksa/miniksa@microsoft.com
created on: 2019-07-24
last updated: 2019-07-24
issue id: #1256
---
# Tab Tearoff/Merge & Default App IPC
## Abstract
This spec describes the sort of interprocess communications that will be required to support features like tab tearoff and merge. It goes through some of the considerations that became apparent when I tried to prototype passing connections between `conhost` and `wt`.
## Inspiration
Two main drivers:
1. We want the ability to tear off a tab from one Windows Terminal instance and send it to another Windows Terminal instance
2. We want the ability for a launch of a command-line application to trigger a hosting environment that isn't the stock in-box `conhost.exe`.
Both of these concerns will require there to exist some sort of interprocess communication manager that can send/receive the system handles representing connections between client applications and the hosting environment.
I spent some time during the Microsoft Hackathon in July 2019 investigating these avenues with a branch I pushed and linked at the bottom. The work resulted in me finding more questions than answers and ultimately deciding that a Hackathon is good enough for exploration of the mechanisms and ideas behind this, but not a good time for a full implementation.
## Solution Design
### Common Pieces
There are several common pieces needed for both the tab tear-off scenario and the default application scenario.
#### Manager
We need some sort of server/manager code that sits there waiting for connections from `wt.exe` processes and potentially `conhost.exe` processes such that it can broker a connection between the processes. It either needs to run in its own process or it needs to run in one of the existing `wt.exe`s that is chosen as the primary manager at the time. It should create communication channels and a global mutex at the time of creation.
All other `wt.exe` processes starting after the primary should detect the existence of the server manager process and wait on the mutex handle. When the primary disappears, the OS scheduler should choose one of the others to wake up first on the mutex. It can take the lock and then set up the primary management channel.
Alternatively, if the manager process is completely isolated and we expect all `wt.exe`s to have to remain connected at all times, we can make it such that when the connections are broken between the individual processes and the manager that they all shut down. I would prefer that it is resilient (the previous option) over this one, but browsers must have a good reason for preferring this way.
I attempted one particular way in a prototype of communicating between processes by setting up a Multithreaded Pipe Server using a Message-type configuration. This is visible in the branch I linked at the bottom. However, ultimately I think we would want to formalize around something more structured, tested, and inherently secured like a COM server interface.
#### Connection details
There are several parameters to a connection and several different modes. In short, they summarize to the ability to pass kernel handles between two processes and/or the ability to pass arbitrary length structured information about paths and settings. Both tab tear off and default application will likely need both functionalities.
##### Fresh Start
For an application that is being freshly started, the information required to begin the session is one of three things:
1. A server (and maybe reference) handle that describes the driver connection between the console server and the command-line client process. A `conhost.exe` can wrap this and turn it into a PTY. This may also contain LNK file (shortcut file) preferences for the running session.
1. A command-line string and working directory that describes which command-line client process we want to start. A `conhost.exe` can start this up and create the server and reference handles along the way and then turn it into a PTY.
1. A PTY session with its read, write, and signal handles.
When transiting a connection, we need to be aware of all three of these modes and relay them to the destination `wt.exe`.
For system handles, we can use the manager to broker a request to the destination process to find its `PID` and tell the source process. We can then use the `PID` with the `OpenProcess` method and the `PROCESS_DUP_HANDLE` right to get a handle to `DuplicateHandle` any of the above handle types into the destination process. The act of opening and duplicating the handles already requires the OS to check our access tokens and rights to interfere with another process, so that should automatically handle some level of the security checking for us.
For command-line string and working directory, we can pass all of this information along to the destination `wt.exe` and let it attempt to start a new ConPTY normally as if someone had chosen to start an option from the dropdown menu. A minor trick here is that we may need to attempt to match the command-line string with one of the user profiles to line up the icon and user-preferences for how the session should launch.
Lastly, for things started from an LNK, a user might expect that a window launched inside `wt.exe` from an old shortcut that they had would still apply even if that shortcut's properties technically apply to `conhost.exe` preferences and not to `wt.exe` preferences. The behavior here would likely to be to transit the LNK file information along to the `wt.exe` process by the same mechanism as a command-line string or working directory and let `wt.exe` use the shortcut parsing shared libraries to extract this information and migrate it into a `Settings` preference. Whether we would store that `Settings` preference or not for future use in the drop down might be an option or a prompt.
##### Already Running
For an application that is already running, we will need to send several pieces of information to successfully migrate to a new tab location:
1. The ConPTY handles for read, write, and signal
1. The scroll-back history that is stored inside `wt.exe` but isn't actually a part of what the underlying PTY-mode `conhost.exe` re-renders at any given time
1. The user preferences and session information related to `Settings`.
We would send all of this over to the destination by whatever IPC mechanism and then let it stand up a new tab with all of the same parameters as the tab on the other end.
**ALTERNATIVELY**
If we move everything to an isolated process model where the individual tabs/panes have a process and their UI is hosted in another frame/shell process and then there's a manager process, we will presumably already have to architect a solution that allows the UIs to be remoted onto other interfaces (Component UI?). If this is true, then all we need to relay for an active session is the information required to redirect the drawing/input targets for a given tab/pane to a different shell. This may ultimately be easier and more reliable than moving and rebuilding all the pieces of what fundamentally makes a session to the other side.
### Separate Pieces
#### For Tab Tear-off
We add a handler to the on-drag for the tab bar. We also likely need to implement a drag and drop handler. Drag and drop handlers use OLE (COM) so this might be another reason why we should implement the entire manager as COM. Note, I have never used this before so this is a theoretical low-knowledge design that would have to be explored...
Presumably the tab control from WinUI will update to support reordering the tabs through its own drag/drop. But we would likely want to create some sort of drag source with the session GUID when a drag operation starts.
Then we can let the OS handle the drop operation with the session GUID information. If the drop handler drops onto another wt.exe, it can use the session GUID in the drop payload in order to convey connection information between the processes. If it drops somewhere else, presumably we can be made aware of that in the source of the drag/drop operation and instead spawn a new `wt.exe` with arguments that specify that it should start up doing the "drop" portion of the operation to the session GUID with the manager instead of launching the default tab.
#### For Default Application
For default application launches, `conhost.exe` will have to attempt to transfer the incoming connection to the registered terminal handler instead of launching its own UI window.
If the registered handler fails to start the connection, there is no registered handler, or any part of this mechanism fails. The `conhost.exe` needs to fall back to doing whatever it would have done prior to this development (launching a window if necessary, being hidden, etc.)
##### Interactive vs. Not
We would have to be able to detect the difference between an interactive and non-interactive mode here.
- Interactive is defined as the end-user is attempting to launch a command-line application with a visible window to see the output and enter input.
- Non-interactive is defined as tools, utilities, and services attempting to launch a command-line application with no visible window (and possibly some redirected handles).
We do not want to capture non-interactive sessions as compilers, scripts, and utilities run command-line tools all the time. These should not trigger the overhead of being transitioned into the terminal as they will not need output or display.
Additionally, we may need to identify ConPTYs being started and ensure that they don't accidentally attempt to hand off in an infinite loop.
The biggest trick here is that we don't know whether it is going to be interactive or not until we begin to accept the connection from the server handle. We have two choices here:
##### Inbox conhost handles it
The inbox `conhost.exe` can just accept the connection from the server handle, assure itself that a `wt.exe` could take over the UI hosting of the session, and then switch itself into `ConPTY` mode and give those handles over to `wt.exe` and remain invisible in the background in PTY mode (much the same as if `wt.exe` had started the connection itself).
The upside here is that most of the startup connection flow happens normally, the `conhost.exe` that was given the server handle is the one that will continue to service it for the lifetime of the command-line application session. I can then discard any concerns about how the driver reacts and how the applications grovel for the relationship between processes as it will be normal.
The downside here is that launching command-line applications from shortcuts, the shell, or the run box (as is what triggers the default application scenario) will be using an old version of the PTY. It is possible and even probable that we will make improvements to the PTY that we would want to leverage if they're on the system already inside the app package. However, if we try to transit the server connection to the PTY in the package, we will have to deal with:
1. Potentially leaving the original conhost.exe open until the other one exits in case someone is waiting on the process
1. Coming up with some sort of dance to have the delegated PTY conhost inside the package determine the interactivity on starting the connection **OR** having the outside conhost start the connection and passing the connection off part way through if it's interactive **OR** something of that ilk.
##### Conhost in the Terminal package handles it
We could just send the server connection from the `conhost.exe` in System32 into the one inside the package and let it deal with it. We can connect to the broker and pass along the server handle and let `wt.exe` create a `conhost.exe` in PTY mode with that specific server handle.
The upsides/downsides here are exactly opposite of those above, so I won't restate.
##### Making default app work on current and downlevel OS
There's a few areas to study here.
1. Replacing conhost.exe in system32 at install time
- The OS (via the code for `ConsoleInitialize` inside `kernelbase.dll`) will launch `C:\windows\system32\conhost.exe` to start a default application session with the server handle. We can technically replace this binary in `system32` with an `OpenConsole.exe` named `conhost.exe` to make newer code run on older OS (presuming that we have the CRTs installed, build against the in-OS-CRT, and otherwise have conditional feature detection properly performed for all APIs/references not accessible downlevel). This is how we test/develop locally inside Windows without a full nightly build, so we know it works to some degree. Replacing a binary in `system32` is a bit of a problem, though, because the OS actively works to defend against this through ACLs (Windows File Protection which detected and restored changes here is gone, I believe). Additionally, it works for us because we're using internal builds and signing our binaries with test certificates for which our machines have the root certificate installed. Not going to cut it outside. We probably also can't sign it officially with the app signing mechanism and have it work because I'm not sure the root certificates for app signing will be trusted the same way as the certificates for OS signing. Also, we can't build outside of Windows against the in-box CRT. So we'd have to have the MSVCRT redist, which is also gross.
2. Updating kernelbase.dll to look up the launch preference and/or to launch a console host via a protocol handler
- To make this work anywhere but the most recent OS build, we'd have to service downlevel. Given `kernelbase.dll` is fundamental to literally everything, there's virtually no chance that we would be allowed to service it backwards in time for the sake of adding a feature. It's too risky by any stretch of the imagination. It's even risky to change `kernelbase.dll` for an upcoming release edition given how fundamental it is. End of thought experiment.
3. Updating conhost.exe to look up the launch preference and/or to launch another console host via a protocol handler
- This would allow the `C:\windows\system32\conhost.exe` to effectively delegate the session to another `conhost.exe` that is hopefully newer than the inbox one. Given that the driver protocol in the box doesn't change and hasn't changed and we don't intend to change it, the forward/backward compatibility story is great here. Additionally, if for whatever reason the delegated `conhost.exe` fails to launch, we can just fall back and launch the old one like we would have prior to the change. It is significantly more likely, but still challenging, to argue for servicing `conhost.exe` back several versions in Windows to make this light up better for all folks. It might be especially more possible if it is a very targeted code snippet that can drop in to all the old versions of the `conhost.exe` code. We would still have the argument about spending resources developing for OS versions that are supposed to be dropped in favor of latest, but it's still a lesser argument than upending all of `kernelbase.dll`.
- A protocol handler is also well understood and relatively well handled/tested in Windows. Old apps can handle protocols. New apps can handle protocols. Protocol handlers can take arguments. We don't have to lean on any other team to get them to help change the way the rest of the OS works.
#### Communicating the launch
For the parameters passing, I see a few options:
1. `conhost.exe` can look up the package registration for `wt.exe` and call an entrypoint with arguments. This could be adapted to instead look up which package is registered as the default one instead of `wt.exe` for third party hosts. We would have to build provisions into the OS to select this, or use some sort of publically documented registry key mechanism. Somewhat gross.
1. `conhost.exe` can call the execution alias with parameters. WSL distro launchers use this.
1. We can define a protocol handler for these sorts of connections and let `wt.exe` register for it. Protocol handlers are already well supported and understood both by classic applications and by packaged/modern applications on Windows. They must have provisions to communicate at least some semblance of argument data as well. This is the route I'd probably prefer. `ms-term://incoming/<session-id>` or something like that. The receiving `wt.exe` can contact the manager process (or set one up if it is the first) and negotiate receiving the session that was specified into a new tab.
## UI/UX Design
### For Tab Tear-off
#### Ideal World
The UX would be just as one might expect from a browser application.
- Mouse down and drag on a tab should provide some visual indication that it is being dragged.
- Dragging left/right should provide a visual indicator of the tabs reordering on the bar and otherwise not involve the IPC manager service.
- Dragging up/down to break free from the tab bar should launch a new instance of `wt.exe` passing in the state of the dragging tab as the initial launch point (ignoring other default launch aspects). The drag/mouse-down would be passed to that new instance which would chase the mouse.
- Continuing to drag the loose tab onto the tab bar of another running instance of `wt.exe` would merge the tab with that copy of the application. The interim new/loose frame instance of `wt.exe` would close when it transferred out the last tab to the drop location.
#### Simplified V1
To simplify this for a first iteration, we could just make it so the transfer does not happen live.
- Mouse down and drag on a tab should provide a visual indication that it is being dragged by changing the cursor (or something of that ilk)
- Nothing would actually happen in terms of transitioning the tab until it is released
- If released onto the same `wt.exe` instance in a different spot on the tab bar, we reorder the tabs in the tab control
- If released onto a different `wt.exe` instance, we relay the communications channel and details through the IPC manager to the other instance. It opens the tab on the destination instance; we close the tab on the source instance.
- If released onto anything that isn't a `wt.exe` instance, we create a new `wt.exe` instance and send in the connection as the default startup parameter.
#### Component UI
It is also theoretically possible that if we could find a Component UI style solution (where the tab/panes live in their own process and just remote the UI/input into the shell) that it would be easy and even trivial to change out which shell/frame host is holding that element at any given time.
### For Default Application
The UX would make it look exactly like the user had started `wt.exe` from a shortcut or launch tile, but would launch the first tab differently than the defaults.
#### No WT already started
If no `wt.exe` is already started, the `conhost.exe` triggered by the system to host the client application would find the installed `wt.exe` package and launch it with parameters to use as its first connection (in lieu of launching the default tab). `conhost.exe` wouldn't show a window, it would drop into ConPTY mode and only the new `wt.exe` and its tab would be visible.
#### WT already started
If a `wt.exe` is already started, `conhost.exe` would find the running instance and just add a new tab at the end of the tab bar by the same mechanism.
#### Multiple WTs already started
If multiple `wt.exe`s are already started, `conhost.exe` would have to find the foreground one, the active one, or the primary/manager one and send the tab there. I'm not sure how other tabbing things to do this. We could research/study.
## Capabilities
### Accessibility
I don't believe it changes anything for accessibility. The only concern I'd have to call out is the knowledge I have that the UIA framework makes its connections and some of its logic/reasoning based on PIDs, HWNDs, and the hierarchy thereof. Playing with these might impact the ability of screen reading applications to get the UIA tree when tabs have been shuffled around.
### Security
This particular feature will have to go through a security review/audit. It is unclear what level of control we will need over the IPC communication channels. A few things come to mind:
1. We need to ensure that the mutexes/pipes/communications are restricted inside of one particular session to one particular user. If another user is also running WT in their session, it should involve a completely different manager process and system objects.
1. We MAY have to enforce a scenario where we inhibit cross-integrity-level connections from being passed around. Generally speaking, processes at a higher integrity level have the authority to perform actions on those with a lower integrity level. This means that an elevated `wt.exe` could theoretically send a tab to a standard level `wt.exe`. We may be required to inhibit/prohibit this. We may also need to have one manager per integrity level.
1. I'm not sure what sorts of ACL/DACL/SACLs we would need to apply to all the kernel objects involved.
1. My initial prototype here used message-passing type pipes with a custom rolled protocol. If I make my own protocol, it needs to be fuzzed. And I'm probably missing something. Many/most of these concerns for security are probably eliminated if we use a well-known mechanism for this sort of IPC. My thoughts go to a COM server. More complicated to implement than message pipes, but probably brings a lot of security benefits and eliminates the need to fuzz the protocol (probably).
### Reliability
In the simple implementation, it will decrease reliability. We'll be shuffling connections back and forth between application instances. By default, that's more risky than leaving things alone. The only reason it is worth it is the user experience.
We might be able to mitigate some of the reliability concerns here or even improve reliability by going a step further with the process/containerization model like browsers do and standing up each individual tab as its own process host.
```
wt.exe - Manager Mode
|- wt.exe - Frame Host Mode
| |- wt.exe - Tab Host Mode
| | |- conhost.exe - ConPTY mode
| | |- pwsh.exe - Client application
| |- wt.exe - Tab Host Mode
| |- conhost.exe - ConPTY mode
| |- cmd.exe - Client application
|- wt.exe - Frame Host Mode
|- wt.exe - Tab Host Mode
|- conhost.exe - ConPTY mode
|- pwsh.exe - Client application
```
The current structure of `wt.exe` has everything hosted within the one process. To improve reliability, we would likely have to make `wt.exe` run in three modes.
1. Manager Mode - no UI, just sits there as a broker to hold the kernel objects for a given window station/session and integrity level, accepts protocol handler routines, helps relay connections between various frame hosts when tabs move and determines where to instantiate new default-app tabs
1. Frame Host Mode - The complete outer shell of the application outside of an individual tab. Hosts the tab bar, settings drop downs, title bar, etc.
1. Tab Host Mode - The inner shell of an individual tab including the rendering area, scroll bar, inputs, etc.
1. Pane Host Mode - Now that panes are a thing, we might need to go even one level deeper. Or maybe it's just a recursion on Tab Host mode.
How these connect to each other is unexplored at this time.
### Compatibility
There are a few compatibility concerns here, primarily related to how client applications or outside utilities detect the relationship between a command-line client application and its console hosting environment.
We're well aware that the process tree/hierarchy is one of the major methods used for understanding the relationship between the client and server application. However, in order to accomplish our goals here, it is inevitable that the original hosting `conhost.exe` (either started in ConPTY mode by a `wt.exe` or started by the operating system in response to an otherwise unhosted command-line application) will become orphaned or otherwise disassociated with the UI that is actually presenting it.
It is possible (but would need to be explored) that the APIs available to us to reorder the parenting of the processes to put the `conhost.exe` as the parent of the `cmd.exe` (despite the fact that `cmd.exe` usually starts first as the default application and the `ConsoleInitialize` routines inside `kernelbase.dll` create the `conhost.exe`) could be reused here to shuffle around the parent/child relationships. However, it could also introduce new problems. One prior example was that the UIA trees for accessibility do **NOT** tolerate the shuffling of the parent child relationship because their communication channel sessions are often tied to the relationships of HWNDs and PIDs.
#### Hierarchy Example between two Terminals (tab tearoff/merge)
In the one instance, we have this process hierarchy. Two instances of Windows Terminal exist. In Terminal A, the user has started a `cmd.exe` and a `pwsh.exe` tab. In the second instance, the user has started just one `cmd.exe` tab.
```
- wt.exe (Terminal Instance A)
|- conhost.exe (in PTY mode) - Hosted to A
| |- cmd.exe
|- conhost.exe (in PTY mode) - Hosted to A
|- pwsh.exe <-- I will be dragged out
- wt.exe (Terminal Instance B)
|- conhost.exe (in PTY mode) - Hosted to B
|- cmd.exe
```
When the `pwsh.exe` tab is torn off from Instance A and is dropped onto Instance B, the process hierarchy doesn't actually change. The connection details, preferences, and session metadata are passed via the IPC management channels, but to an outside observer, nothing has actually changed.
```
- wt.exe (Terminal Instance A)
|- conhost.exe (in PTY mode) - Hosted to A
| |- cmd.exe
|- conhost.exe (in PTY mode) - Hosted to B
|- pwsh.exe <-- I am hosted in B but I'm parented to A
- wt.exe (Terminal Instance B)
|- conhost.exe (in PTY mode) - Hosted to B
|- cmd.exe
```
I don't believe there are provisions in the Windows OS to reparent applications to a different process.
Additionally, this becomes more interesting when Terminal Instance A dies and B is still running:
```
- conhost.exe (in PTY mode) - Hosted to B
|- pwsh.exe <-- I am hosted in B but I'm parented to A
- wt.exe (Terminal Instance B)
|- conhost.exe (in PTY mode) - Hosted to B
|- cmd.exe
```
When instance A dies, the `conhost.exe` that was reparented keeps running and now just appears orphaned within the process hierarchy, reporting to the top level under utilities like Process Explorer.
I believe the action plan here would be to implement what we can, observe the state of the world, and correct going forward. We don't have a solid understanding of how many client applications might be impacted by this apparent change. It also might be perfectly OK because the client applications will always remain parented to the same `conhost.exe` even if those `conhost.exe`s don't report up to the correct `wt.exe`.
It is also unclear whether someone might want to write a utility from the outside to discover this hierarchy. I would be inclined to not provide a way to do this without a strong case otherwise because attempting to understand the local machine process hierarchy is a great way to box yourself in when attempting to expand later to encompass remote connections.
#### Hierarchy Example between Conhost and a Terminal (default application)
This looks very much like the previous section where Terminal Instance B died.
```
- conhost.exe (in PTY mode) - Hosted to A
|- pwsh.exe
- wt.exe (Terminal Instance A)
```
The `conhost.exe` was started in response to a `pwsh.exe` being started with no host. It then put itself into PTY mode and launched into a connection of `wt.exe` instance A.
**ALTERNATIVELY**
```
- conhost.exe - idling
- wt.exe (Terminal Instance A)
|- conhost.exe (in PTY mode)
|- pwsh.exe
```
The `conhost.exe` at the top was launched in response to `pwsh.exe` being started with no host. It identified that `wt.exe` was running and instead shuttled the incoming connection into that `wt.exe`. `wt.exe` stood up the `conhost.exe` in PTY mode beneath itself and the client `pwsh.exe` call below that. The PTY mode `conhost.exe` uses its reparenting commands on startup to make the tree look like the above. The orphaned (originally started) `conhost.exe` waits until the connection exits before exiting itself in case someone was waiting on it.
### Performance, Power, and Efficiency
This is obviously less efficient than not doing it as we have to stand up servers and protocols and handlers for shuffling things about.
But as long as we're creating threads and services that sleep most of the time and are only awakened on some kernel/system event, we shouldn't be wasting too much in terms of power and background resources.
Additionally, `wt.exe` is worse than `conhost.exe` alone in all efficiency categories simply because it not only requires more resources to display in a "pretty" manner, but it also requires a `conhost.exe` under it in PTY mode to adapt the API calls. This is generally acceptable for end users who care more about the experience than the total performance.
It is, however, not likely to be much if any worse than just choosing to use `wt.exe` anyway over `conhost.exe`.
## Potential Issues
I've listed most of the issues above in their individual sections. The primary highlights are:
1. Process tree layout - The processes in hierarchy may not make sense to someone inspecting them either visually with a tool or programmatically
1. Process and kernel object lifetime - Applications may be counting on a specific process or object lifetime in regards to their hosting window and we might be tampering with that in how we apply job objects or shuffle around ownership to make tabs happen
1. Default launch expectations - It is possible that test utilities or automation are counting on `conhost.exe` being the host application or that they're not ready to tolerate the potential for other applications to start. I think the interactive/non-interactive check mitigates this, but we'd have to remain concerned here.
1. `AttachConsole` and `DetachConsole` and `AllocConsole` - I don't have the slightest idea what happens for these APIs. We would have to explore. `AttachConsole` has restrictions based on the process hierarchy. It would likely behave in interesting ways with the strange parenting order and might be a driver to why we would have to adjust the parenting of the processes (or change the API under the hood). `DetachConsole` might create an issue where a tab disappears out of the terminal and the job object causes everything to die. `AttachConsole` wouldn't necessarily be guaranteed to go back into the same `wt.exe` or a `wt.exe` at all.
## Future considerations
This might unlock some sort of isolation for extensions as well. Extensions of some sort our on our own long term roadmap, but they're inherently risky to the stability and integrity of the application. If we have to go through a lot of gyrations to enable process containerization and an interprocess communication model for tab tear off and default application work, we might also be able to contain extensions the same way. This derives further from the idea of what browsers do.
## Resources
- [Manager Prototype](https://github.com/microsoft/terminal/blob/dev/miniksa/manager/src/types/Manager.cpp)
- [Pipe Server documentation](https://docs.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server)
- [OLE Drag and Drop](https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-registerdragdrop)
- [OpenProcess](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess)
- [DuplicateHandle](https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle)

View File

@ -0,0 +1,115 @@
---
author: Mike Griese @zadjii-msft
created on: 2019-05-31
last updated: 2019-05-31
issue id: #997
---
# Non-Terminal Panes
## Abstract
This spec hopes to cover the work necessary to enable panes to host non-terminal
content. It'll describe changes to the `Pane` class to support hosting arbitrary
controls in addition to terminals.
## Inspiration
The primary driver for this change is to enable testing of the pane code. If a
`Pane` can host an arbitrary class, then a use case for that would be the
hosting of a non-xaml test class that acts like a control. This test class could
be have its state queried, to make sure that the panes are properly delivering
focus to the correct pane content.
Additionally, non-terminal panes could be used to host a variety of other
content, such as browser panes, sticky notes, text editor scratch-pads, etc.
Some discussion of these ideas are in #644.
## Solution Design
We'll change the TermControl class to derive from the
`Windows.UI.Xaml.Controls.Control` runtime class.
* We may need to override its `FocusState` and `Focus` methods, and implement
them by plumbing them straight through to the fake Control the `TermControl`
hosts.
* Otherwise, it might be possible that we could just remove that fake control
entirely.
* We'll remove the `GetControl` method from the `TermControl`, as the
`TermControl` itself will now be used as the control.
We'll change the Pane class to accept a `Windows.UI.Xaml.Controls.Control`
instead of a `TermControl`.
We'll additionally change the `Pane` constructor to accept an `optional<GUID>`
as opposed to needing a GUID. For constructing panes with Terminals, we should
pass a GUID corresponding to that settings profile. For panes that aren't
hosting terminals however, we should pass `nullopt` as the GUID. For panes that
are leaf nodes (panes which are hosting a control, not another pair of panes),
if the pane has a GUID, we can assume that the control is a TermControl.
When we want to host other types of content, we'll simply pass any other control
to the Pane, and it'll render it just as it would the `TermControl`.
## UI/UX Design
Instead of a pane hosting a terminal, it could host _any arbitrary control_. The
control would still be subject to the sizing provided to it by the `Pane`, but
it could host any arbitrary content.
## Capabilities
### Security
I don't forsee this implementation by itself raising security concerns. This
feature is mostly concerned with adding support for arbitrary controls, not
actually implementing some arbitrary controls.
### Reliability
With more possible controls in a pane than just a terminal, it's possible that
crashes in those controls could impact the entire Terminal app's reliability.
This would largely be out of our control, as we only author the TermControl.
We may want to consider hosting each pane in it's own process, similar to how
moder browsers will host each tab in its own process, to help isolate tabs. This
is a bigger discussion than the feature at hand, however.
### Performance, Power, and Efficiency
decide to host a WebView in a pane, then it surely could impact these measures.
I don't believe this will have a noticable impact _on its own_. Should the user
However, I leave that discussion to the implementation of the actual alternative
pane content itself.
### Accessibility
When implementing the accessibility tree for Panes, we'll need to make sure that
for panes with arbitrary content, we properly activate their accessibility,
should the control provide some sort of accessibility pattern.
## Potential Issues
* [ ] It's entirely possible that panes with non-terminal content will not be
able to activate keybindings from the terminal application. For example, what
if the hosted control wants to use Ctrl+T for its own shortcut? The current
keybindings model has the `TermControl` call into the App layer to see if a
keystroke should be handled by the app first. We may want to make sure that
for non-terminal controls, we add a event handler to try and have the
`AppKeyBindings` handle the keypress if the control doesn't. This won't solve
the case where the control wants to use a keybinding that is mapped by the
Terminal App. In that case, non-terminal controls will actually behave
differently from the `TermControl`. The `TermControl` will give the app the
first chance to handle a keybinding, while for other controls, the app will
give the control the first chance to handle the keypress. This may be mildly
confusing to end users.
## Future considerations
I expect this to be a major part of our (eventual) extensibility model. By
allowing arbitrary controls to be hosted in a Tab/Pane, this will allow
extension authors to embed their own UI experiences alongside the terminal.
See #555 for more discussion on the extensibility/plugin subject.
## Resources
N/A

View File

@ -141,11 +141,11 @@
<!-- **END VC LIBS HACK** -->
<!-- This is required to get the package dependency in the AppXManifest. -->
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets'))" />
</Target>
</Project>

View File

@ -114,11 +114,11 @@
<!-- From Microsoft.UI.Xaml.targets -->
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
<_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot>
<_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot>
</PropertyGroup>
<!-- We actually can just straight up reference MUX here, it's fine -->
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
</Project>

View File

@ -121,7 +121,7 @@
</Reference>
</ItemGroup>
<Import Project="$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" />
<!-- Use this to auto-find all the dll's that TerminalConnection produces. We
don't roll these up automatically, so we'll need to copy them manually

View File

@ -27,8 +27,6 @@ static constexpr std::string_view InitialPositionKey{ "initialPosition" };
static constexpr std::string_view ShowTitleInTitlebarKey{ "showTerminalTitleInTitlebar" };
static constexpr std::string_view ThemeKey{ "theme" };
static constexpr std::string_view TabWidthModeKey{ "tabWidthMode" };
static constexpr std::wstring_view EqualTabWidthModeValue{ L"equal" };
static constexpr std::wstring_view TitleLengthTabWidthModeValue{ L"titleLength" };
static constexpr std::string_view ShowTabsInTitlebarKey{ "showTabsInTitlebar" };
static constexpr std::string_view WordDelimitersKey{ "wordDelimiters" };
static constexpr std::string_view CopyOnSelectKey{ "copyOnSelect" };
@ -36,18 +34,27 @@ static constexpr std::string_view CopyFormattingKey{ "copyFormatting" };
static constexpr std::string_view LaunchModeKey{ "launchMode" };
static constexpr std::string_view ConfirmCloseAllKey{ "confirmCloseAllTabs" };
static constexpr std::string_view SnapToGridOnResizeKey{ "snapToGridOnResize" };
static constexpr std::wstring_view DefaultLaunchModeValue{ L"default" };
static constexpr std::wstring_view MaximizedLaunchModeValue{ L"maximized" };
static constexpr std::wstring_view FullscreenLaunchModeValue{ L"fullscreen" };
static constexpr std::wstring_view LightThemeValue{ L"light" };
static constexpr std::wstring_view DarkThemeValue{ L"dark" };
static constexpr std::wstring_view SystemThemeValue{ L"system" };
static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" };
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
static constexpr std::string_view ForceFullRepaintRenderingKey{ "experimental.rendering.forceFullRepaint" };
static constexpr std::string_view SoftwareRenderingKey{ "experimental.rendering.software" };
static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" };
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
// Launch mode values
static constexpr std::wstring_view DefaultLaunchModeValue{ L"default" };
static constexpr std::wstring_view MaximizedLaunchModeValue{ L"maximized" };
static constexpr std::wstring_view FullscreenLaunchModeValue{ L"fullscreen" };
// Tab Width Mode values
static constexpr std::wstring_view EqualTabWidthModeValue{ L"equal" };
static constexpr std::wstring_view TitleLengthTabWidthModeValue{ L"titleLength" };
static constexpr std::wstring_view TitleLengthCompactModeValue{ L"compact" };
// Theme values
static constexpr std::wstring_view LightThemeValue{ L"light" };
static constexpr std::wstring_view DarkThemeValue{ L"dark" };
static constexpr std::wstring_view SystemThemeValue{ L"system" };
#ifdef _DEBUG
static constexpr bool debugFeaturesDefault{ true };
@ -322,6 +329,10 @@ TabViewWidthMode GlobalAppSettings::_ParseTabWidthMode(const std::wstring& tabWi
{
return TabViewWidthMode::SizeToContent;
}
else if (tabWidthModeString == TitleLengthCompactModeValue)
{
return TabViewWidthMode::Compact;
}
// default behavior for invalid data or EqualTabWidthValue
return TabViewWidthMode::Equal;
}

View File

@ -28,6 +28,7 @@ static constexpr std::string_view TabTitleKey{ "tabTitle" };
static constexpr std::string_view SuppressApplicationTitleKey{ "suppressApplicationTitle" };
static constexpr std::string_view HistorySizeKey{ "historySize" };
static constexpr std::string_view SnapOnInputKey{ "snapOnInput" };
static constexpr std::string_view AltGrAliasingKey{ "altGrAliasing" };
static constexpr std::string_view CursorColorKey{ "cursorColor" };
static constexpr std::string_view CursorShapeKey{ "cursorShape" };
static constexpr std::string_view CursorHeightKey{ "cursorHeight" };
@ -121,6 +122,7 @@ Profile::Profile(const std::optional<GUID>& guid) :
_suppressApplicationTitle{},
_historySize{ DEFAULT_HISTORY_SIZE },
_snapOnInput{ true },
_altGrAliasing{ true },
_cursorShape{ CursorStyle::Bar },
_cursorHeight{ DEFAULT_CURSOR_HEIGHT },
@ -188,6 +190,7 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
// Fill in the Terminal Setting's CoreSettings from the profile
terminalSettings.HistorySize(_historySize);
terminalSettings.SnapOnInput(_snapOnInput);
terminalSettings.AltGrAliasing(_altGrAliasing);
terminalSettings.CursorHeight(_cursorHeight);
terminalSettings.CursorShape(_cursorShape);
@ -475,6 +478,8 @@ void Profile::LayerJson(const Json::Value& json)
JsonUtils::GetBool(json, SnapOnInputKey, _snapOnInput);
JsonUtils::GetBool(json, AltGrAliasingKey, _altGrAliasing);
JsonUtils::GetUInt(json, CursorHeightKey, _cursorHeight);
if (json.isMember(JsonKey(CursorShapeKey)))

View File

@ -142,6 +142,7 @@ private:
bool _suppressApplicationTitle;
int32_t _historySize;
bool _snapOnInput;
bool _altGrAliasing;
uint32_t _cursorHeight;
winrt::Microsoft::Terminal::Settings::CursorStyle _cursorShape;

View File

@ -741,6 +741,7 @@ namespace winrt::TerminalApp::implementation
tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush);
tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush);
tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush);
tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush);
tab->_RefreshVisualState();
@ -774,7 +775,8 @@ namespace winrt::TerminalApp::implementation
L"TabViewItemHeaderForegroundSelected",
L"TabViewItemHeaderForegroundPointerOver",
L"TabViewItemHeaderBackgroundPressed",
L"TabViewItemHeaderForegroundPressed"
L"TabViewItemHeaderForegroundPressed",
L"TabViewButtonForegroundActiveTab"
};
// simply clear any of the colors in the tab's dict

View File

@ -79,13 +79,13 @@
</ProjectReference>
</ItemGroup>
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
</Target>

View File

@ -41,6 +41,7 @@
"icon": "ms-appx:///ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.png",
"padding": "8, 8, 8, 8",
"snapOnInput": true,
"altGrAliasing": true,
"useAcrylic": false,
"backgroundImage": "ms-appx:///internal-background.png",
"backgroundImageAlignment": "bottomRight",

View File

@ -42,6 +42,7 @@
"historySize": 9001,
"padding": "8, 8, 8, 8",
"snapOnInput": true,
"altGrAliasing": true,
"startingDirectory": "%USERPROFILE%",
"useAcrylic": false
},
@ -60,6 +61,7 @@
"historySize": 9001,
"padding": "8, 8, 8, 8",
"snapOnInput": true,
"altGrAliasing": true,
"startingDirectory": "%USERPROFILE%",
"useAcrylic": false
}

View File

@ -302,13 +302,13 @@
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
</Target>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.0.0" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.3.191217003-prerelease" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.4.2-prerelease.200604001" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
</packages>

View File

@ -140,11 +140,9 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
}
_snapOnInput = settings.SnapOnInput();
_altGrAliasing = settings.AltGrAliasing();
_wordDelimiters = settings.WordDelimiters();
_suppressApplicationTitle = settings.SuppressApplicationTitle();
_startingTitle = settings.StartingTitle();
// TODO:MSFT:21327402 - if HistorySize has changed, resize the buffer so we
@ -419,6 +417,7 @@ bool Terminal::SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlK
_StoreKeyEvent(vkey, scanCode);
const auto isAltOnlyPressed = states.IsAltPressed() && !states.IsCtrlPressed();
const auto isSuppressedAltGrAlias = !_altGrAliasing && states.IsAltPressed() && states.IsCtrlPressed();
// DON'T manually handle Alt+Space - the system will use this to bring up
// the system menu for restore, min/maximize, size, move, close.
@ -428,7 +427,17 @@ bool Terminal::SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlK
return false;
}
const auto ch = _CharacterFromKeyEvent(vkey, scanCode, states);
// By default Windows treats Ctrl+Alt as an alias for AltGr.
// When the altGrAliasing setting is set to false, this behaviour should be disabled.
//
// Whenever possible _CharacterFromKeyEvent() will return a valid character.
// For instance both Ctrl+Alt+Q as well as AltGr+Q return @ on a German keyboard.
//
// We can achieve the altGrAliasing functionality by skipping the call to _CharacterFromKeyEvent,
// as TerminalInput::HandleKey will then fall back to using the vkey which
// is the underlying ASCII character (e.g. A-Z) on the keyboard in our case.
// See GH#5525/GH#6211 for more details
const auto ch = isSuppressedAltGrAlias ? UNICODE_NULL : _CharacterFromKeyEvent(vkey, scanCode, states);
// Delegate it to the character event handler if this key event can be
// mapped to one (see method description above). For Alt+key combinations

View File

@ -212,6 +212,7 @@ private:
COLORREF _defaultBg;
bool _snapOnInput;
bool _altGrAliasing;
bool _suppressApplicationTitle;
#pragma region Text Selection

View File

@ -25,6 +25,7 @@ namespace Microsoft.Terminal.Settings
Int32 RowsToScroll;
Boolean SnapOnInput;
Boolean AltGrAliasing;
UInt32 CursorColor;
CursorStyle CursorShape;

View File

@ -52,6 +52,7 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
GETSET_PROPERTY(int32_t, InitialCols, 80);
GETSET_PROPERTY(bool, SnapOnInput, true);
GETSET_PROPERTY(bool, AltGrAliasing, true);
GETSET_PROPERTY(uint32_t, CursorColor, DEFAULT_CURSOR_COLOR);
GETSET_PROPERTY(CursorStyle, CursorShape, CursorStyle::Vintage);
GETSET_PROPERTY(uint32_t, CursorHeight, DEFAULT_CURSOR_HEIGHT);

View File

@ -29,6 +29,7 @@ namespace TerminalCoreUnitTests
uint32_t DefaultForeground() { return COLOR_WHITE; }
uint32_t DefaultBackground() { return COLOR_BLACK; }
bool SnapOnInput() { return false; }
bool AltGrAliasing() { return true; }
uint32_t CursorColor() { return COLOR_WHITE; }
CursorStyle CursorShape() const noexcept { return CursorStyle::Vintage; }
uint32_t CursorHeight() { return 42UL; }
@ -49,6 +50,7 @@ namespace TerminalCoreUnitTests
void DefaultForeground(uint32_t) {}
void DefaultBackground(uint32_t) {}
void SnapOnInput(bool) {}
void AltGrAliasing(bool) {}
void CursorColor(uint32_t) {}
void CursorShape(CursorStyle const&) noexcept {}
void CursorHeight(uint32_t) {}

View File

@ -101,14 +101,14 @@
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Import Project="..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets" Condition="Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets'))" />

View File

@ -2,7 +2,7 @@
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.0.0" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.3.191217003-prerelease" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.4.2-prerelease.200604001" targetFramework="native" />
<package id="Microsoft.VCRTForwarders.140" version="1.0.1-rc" targetFramework="native" />
<package id="Terminal.ThemeHelpers" version="0.2.200324001" targetFramework="native" />
</packages>

View File

@ -159,7 +159,7 @@
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
</ImportGroup>
@ -167,7 +167,7 @@
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props'))" />

View File

@ -89,7 +89,7 @@
<!-- From Microsoft.UI.Xaml.targets -->
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
<_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot>
<_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot>
</PropertyGroup>
<ItemDefinitionGroup>

View File

@ -922,6 +922,25 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
CATCH_RETURN();
}
[[nodiscard]] HRESULT DoSrvSetConsoleOutputCodePage(const unsigned int codepage)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// Return if it's not known as a valid codepage ID.
RETURN_HR_IF(E_INVALIDARG, !(IsValidCodePage(codepage)));
// Do nothing if no change.
if (gci.OutputCP != codepage)
{
// Set new code page
gci.OutputCP = codepage;
SetConsoleCPInfo(TRUE);
}
return S_OK;
}
// Routine Description:
// - Sets the codepage used for translating text when calling A versions of functions affecting the output buffer.
// Arguments:
@ -932,23 +951,9 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
{
try
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
// Return if it's not known as a valid codepage ID.
RETURN_HR_IF(E_INVALIDARG, !(IsValidCodePage(codepage)));
// Do nothing if no change.
if (gci.OutputCP != codepage)
{
// Set new code page
gci.OutputCP = codepage;
SetConsoleCPInfo(TRUE);
}
return S_OK;
return DoSrvSetConsoleOutputCodePage(codepage);
}
CATCH_RETURN();
}

View File

@ -50,6 +50,7 @@ void DoSrvSetCursorColor(SCREEN_INFORMATION& screenInfo,
void DoSrvPrivateRefreshWindow(const SCREEN_INFORMATION& screenInfo);
[[nodiscard]] HRESULT DoSrvSetConsoleOutputCodePage(const unsigned int codepage);
void DoSrvGetConsoleOutputCodePage(unsigned int& codepage);
[[nodiscard]] NTSTATUS DoSrvPrivateSuppressResizeRepaint();

View File

@ -553,6 +553,17 @@ bool ConhostInternalGetSet::PrivateWriteConsoleControlInput(const KeyEvent key)
key));
}
// Routine Description:
// - Connects the SetConsoleOutputCP API call directly into our Driver Message servicing call inside Conhost.exe
// Arguments:
// - codepage - the new output codepage of the console.
// Return Value:
// - true if successful (see DoSrvSetConsoleOutputCodePage). false otherwise.
bool ConhostInternalGetSet::SetConsoleOutputCP(const unsigned int codepage)
{
return SUCCEEDED(DoSrvSetConsoleOutputCodePage(codepage));
}
// Routine Description:
// - Connects the GetConsoleOutputCP API call directly into our Driver Message servicing call inside Conhost.exe
// Arguments:

View File

@ -115,6 +115,7 @@ public:
bool PrivateWriteConsoleControlInput(const KeyEvent key) override;
bool SetConsoleOutputCP(const unsigned int codepage) override;
bool GetConsoleOutputCP(unsigned int& codepage) override;
bool IsConsolePty() const override;

View File

@ -99,10 +99,16 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
ASB_AlternateScreenBuffer = 1049
};
enum VTCharacterSets : wchar_t
namespace CharacterSets
{
DEC_LineDrawing = L'0',
USASCII = L'B'
constexpr auto DecSpecialGraphics = std::make_pair(L'0', L'\0');
constexpr auto ASCII = std::make_pair(L'B', L'\0');
}
enum CodingSystem : wchar_t
{
ISO2022 = L'@',
UTF8 = L'G'
};
enum TabClearType : unsigned short

View File

@ -95,7 +95,12 @@ public:
virtual bool DeviceAttributes() = 0; // DA1
virtual bool Vt52DeviceAttributes() = 0; // VT52 Identify
virtual bool DesignateCharset(const wchar_t wchCharset) = 0; // SCS
virtual bool DesignateCodingSystem(const wchar_t codingSystem) = 0; // DOCS
virtual bool Designate94Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset) = 0; // SCS
virtual bool Designate96Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset) = 0; // SCS
virtual bool LockingShift(const size_t gsetNumber) = 0; // LS0, LS1, LS2, LS3
virtual bool LockingShiftRight(const size_t gsetNumber) = 0; // LS1R, LS2R, LS3R
virtual bool SingleShift(const size_t gsetNumber) = 0; // SS2, SS3
virtual bool SoftReset() = 0; // DECSTR
virtual bool HardReset() = 0; // RIS

View File

@ -8,6 +8,7 @@
#include "../../types/inc/Viewport.hpp"
#include "../../types/inc/utils.hpp"
#include "../../inc/unicode.hpp"
#include "../parser/ascii.hpp"
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::VirtualTerminal;
@ -46,7 +47,15 @@ AdaptDispatch::AdaptDispatch(std::unique_ptr<ConGetSet> pConApi,
// - <none>
void AdaptDispatch::Print(const wchar_t wchPrintable)
{
_pDefaults->Print(_termOutput.TranslateKey(wchPrintable));
const auto wchTranslated = _termOutput.TranslateKey(wchPrintable);
// By default the DEL character is meant to be ignored in the same way as a
// NUL character. However, it's possible that it could be translated to a
// printable character in a 96-character set. This condition makes sure that
// a character is only output if the DEL is translated to something else.
if (wchTranslated != AsciiChars::DEL)
{
_pDefaults->Print(wchTranslated);
}
}
// Routine Description
@ -335,6 +344,7 @@ bool AdaptDispatch::CursorSaveState()
savedCursorState.IsOriginModeRelative = _isOriginModeRelative;
savedCursorState.Attributes = attributes;
savedCursorState.TermOutput = _termOutput;
_pConApi->GetConsoleOutputCP(savedCursorState.CodePage);
}
return success;
@ -376,6 +386,12 @@ bool AdaptDispatch::CursorRestoreState()
// Restore designated character set.
_termOutput = savedCursorState.TermOutput;
// Restore the code page if it was previously saved.
if (savedCursorState.CodePage != 0)
{
success = _pConApi->SetConsoleOutputCP(savedCursorState.CodePage);
}
return success;
}
@ -1590,18 +1606,107 @@ void AdaptDispatch::_InitTabStopsForWidth(const size_t width)
}
//Routine Description:
// Designate Charset - Sets the active charset to be the one mapped to wch.
// See DispatchTypes::VTCharacterSets for a list of supported charsets.
// Also http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Controls-beginning-with-ESC
// For a list of all charsets and their codes.
// If the specified charset is unsupported, we do nothing (remain on the current one)
// DOCS - Selects the coding system through which character sets are activated.
// When ISO2022 is selected, the code page is set to ISO-8859-1, and both
// GL and GR areas of the code table can be remapped. When UTF8 is selected,
// the code page is set to UTF-8, and only the GL area can be remapped.
//Arguments:
// - wchCharset - The character indicating the charset we should switch to.
// - codingSystem - The coding system that will be selected.
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::DesignateCharset(const wchar_t wchCharset) noexcept
bool AdaptDispatch::DesignateCodingSystem(const wchar_t codingSystem)
{
return _termOutput.DesignateCharset(wchCharset);
// If we haven't previously saved the initial code page, do so now.
// This will be used to restore the code page in response to a reset.
if (!_initialCodePage.has_value())
{
unsigned int currentCodePage;
_pConApi->GetConsoleOutputCP(currentCodePage);
_initialCodePage = currentCodePage;
}
bool success = false;
switch (codingSystem)
{
case DispatchTypes::CodingSystem::ISO2022:
success = _pConApi->SetConsoleOutputCP(28591);
if (success)
{
_termOutput.EnableGrTranslation(true);
}
break;
case DispatchTypes::CodingSystem::UTF8:
success = _pConApi->SetConsoleOutputCP(CP_UTF8);
if (success)
{
_termOutput.EnableGrTranslation(false);
}
break;
}
return success;
}
//Routine Description:
// Designate Charset - Selects a specific 94-character set into one of the four G-sets.
// See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Controls-beginning-with-ESC
// for a list of all charsets and their codes.
// If the specified charset is unsupported, we do nothing (remain on the current one)
//Arguments:
// - gsetNumber - The G-set into which the charset will be selected.
// - charset - The characters indicating the charset that will be used.
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::Designate94Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset)
{
return _termOutput.Designate94Charset(gsetNumber, charset);
}
//Routine Description:
// Designate Charset - Selects a specific 96-character set into one of the four G-sets.
// See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Controls-beginning-with-ESC
// for a list of all charsets and their codes.
// If the specified charset is unsupported, we do nothing (remain on the current one)
//Arguments:
// - gsetNumber - The G-set into which the charset will be selected.
// - charset - The characters indicating the charset that will be used.
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::Designate96Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset)
{
return _termOutput.Designate96Charset(gsetNumber, charset);
}
//Routine Description:
// Locking Shift - Invoke one of the G-sets into the left half of the code table.
//Arguments:
// - gsetNumber - The G-set that will be invoked.
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::LockingShift(const size_t gsetNumber)
{
return _termOutput.LockingShift(gsetNumber);
}
//Routine Description:
// Locking Shift Right - Invoke one of the G-sets into the right half of the code table.
//Arguments:
// - gsetNumber - The G-set that will be invoked.
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::LockingShiftRight(const size_t gsetNumber)
{
return _termOutput.LockingShiftRight(gsetNumber);
}
//Routine Description:
// Single Shift - Temporarily invoke one of the G-sets into the code table.
//Arguments:
// - gsetNumber - The G-set that will be invoked.
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::SingleShift(const size_t gsetNumber)
{
return _termOutput.SingleShift(gsetNumber);
}
//Routine Description:
@ -1667,7 +1772,12 @@ bool AdaptDispatch::SoftReset()
}
if (success)
{
success = DesignateCharset(DispatchTypes::VTCharacterSets::USASCII); // Default Charset
_termOutput = {}; // Reset all character set designations.
if (_initialCodePage.has_value())
{
// Restore initial code page if previously changed by a DOCS sequence.
success = _pConApi->SetConsoleOutputCP(_initialCodePage.value());
}
}
if (success)
{

View File

@ -86,7 +86,12 @@ namespace Microsoft::Console::VirtualTerminal
bool ForwardTab(const size_t numTabs) override; // CHT, HT
bool BackwardsTab(const size_t numTabs) override; // CBT
bool TabClear(const size_t clearType) override; // TBC
bool DesignateCharset(const wchar_t wchCharset) noexcept override; // SCS
bool DesignateCodingSystem(const wchar_t codingSystem) override; // DOCS
bool Designate94Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset) override; // SCS
bool Designate96Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset) override; // SCS
bool LockingShift(const size_t gsetNumber) override; // LS0, LS1, LS2, LS3
bool LockingShiftRight(const size_t gsetNumber) override; // LS1R, LS2R, LS3R
bool SingleShift(const size_t gsetNumber) override; // SS2, SS3
bool SoftReset() override; // DECSTR
bool HardReset() override; // RIS
bool ScreenAlignmentPattern() override; // DECALN
@ -121,6 +126,7 @@ namespace Microsoft::Console::VirtualTerminal
bool IsOriginModeRelative = false;
TextAttribute Attributes = {};
TerminalOutput TermOutput = {};
unsigned int CodePage = 0;
};
struct Offset
{
@ -163,6 +169,7 @@ namespace Microsoft::Console::VirtualTerminal
std::unique_ptr<ConGetSet> _pConApi;
std::unique_ptr<AdaptDefaults> _pDefaults;
TerminalOutput _termOutput;
std::optional<unsigned int> _initialCodePage;
// We have two instances of the saved cursor state, because we need
// one for the main buffer (at index 0), and another for the alt buffer

File diff suppressed because it is too large Load Diff

View File

@ -76,6 +76,7 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool PrivateWriteConsoleControlInput(const KeyEvent key) = 0;
virtual bool PrivateRefreshWindow() = 0;
virtual bool SetConsoleOutputCP(const unsigned int codepage) = 0;
virtual bool GetConsoleOutputCP(unsigned int& codepage) = 0;
virtual bool PrivateSuppressResizeRepaint() = 0;

View File

@ -24,6 +24,7 @@
<ItemGroup>
<ClInclude Include="..\adaptDefaults.hpp" />
<ClInclude Include="..\adaptDispatch.hpp" />
<ClInclude Include="..\charsets.hpp" />
<ClInclude Include="..\DispatchTypes.hpp" />
<ClInclude Include="..\DispatchCommon.hpp" />
<ClInclude Include="..\InteractDispatch.hpp" />

View File

@ -80,6 +80,9 @@
<ClInclude Include="..\termDispatch.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\charsets.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />

View File

@ -89,7 +89,12 @@ public:
bool DeviceAttributes() noexcept override { return false; } // DA1
bool Vt52DeviceAttributes() noexcept override { return false; } // VT52 Identify
bool DesignateCharset(const wchar_t /*wchCharset*/) noexcept override { return false; } // SCS
bool DesignateCodingSystem(const wchar_t /*codingSystem*/) noexcept override { return false; } // DOCS
bool Designate94Charset(const size_t /*gsetNumber*/, const std::pair<wchar_t, wchar_t> /*charset*/) noexcept override { return false; } // SCS
bool Designate96Charset(const size_t /*gsetNumber*/, const std::pair<wchar_t, wchar_t> /*charset*/) noexcept override { return false; } // SCS
bool LockingShift(const size_t /*gsetNumber*/) noexcept override { return false; } // LS0, LS1, LS2, LS3
bool LockingShiftRight(const size_t /*gsetNumber*/) noexcept override { return false; } // LS1R, LS2R, LS3R
bool SingleShift(const size_t /*gsetNumber*/) noexcept override { return false; } // SS2, SS3
bool SoftReset() noexcept override { return false; } // DECSTR
bool HardReset() noexcept override { return false; } // RIS

View File

@ -3,161 +3,207 @@
#include <precomp.h>
#include <windows.h>
#include "charsets.hpp"
#include "terminalOutput.hpp"
#include "strsafe.h"
using namespace Microsoft::Console::VirtualTerminal;
// We include a full table so all we have to do is the lookup.
// The tables only ever change the values x20 - x7f, hence why the table starts at \x20
// From http://vt100.net/docs/vt220-rm/table2-4.html
static constexpr std::array<wchar_t, 96> s_decSpecialGraphicsTranslations{
L'\x20',
L'\x21',
L'\x22',
L'\x23',
L'\x24',
L'\x25',
L'\x26',
L'\x27',
L'\x28',
L'\x29',
L'\x2a',
L'\x2b',
L'\x2c',
L'\x2d',
L'\x2e',
L'\x2f',
L'\x30',
L'\x31',
L'\x32',
L'\x33',
L'\x34',
L'\x35',
L'\x36',
L'\x37',
L'\x38',
L'\x39',
L'\x3a',
L'\x3b',
L'\x3c',
L'\x3d',
L'\x3e',
L'\x3f',
L'\x40',
L'\x41',
L'\x42',
L'\x43',
L'\x44',
L'\x45',
L'\x46',
L'\x47',
L'\x48',
L'\x49',
L'\x4a',
L'\x4b',
L'\x4c',
L'\x4d',
L'\x4e',
L'\x4f',
L'\x50',
L'\x51',
L'\x52',
L'\x53',
L'\x54',
L'\x55',
L'\x56',
L'\x57',
L'\x58',
L'\x59',
L'\x5a',
L'\x5b',
L'\x5c',
L'\x5d',
L'\x5e',
L'\u0020', // L'\x5f', -> Blank
L'\u2666', // L'\x60', -> Diamond (more commonly U+25C6, but U+2666 renders better for us)
L'\u2592', // L'\x61', -> Checkerboard
L'\u2409', // L'\x62', -> HT, SYMBOL FOR HORIZONTAL TABULATION
L'\u240c', // L'\x63', -> FF, SYMBOL FOR FORM FEED
L'\u240d', // L'\x64', -> CR, SYMBOL FOR CARRIAGE RETURN
L'\u240a', // L'\x65', -> LF, SYMBOL FOR LINE FEED
L'\u00b0', // L'\x66', -> Degree symbol
L'\u00b1', // L'\x67', -> Plus/minus
L'\u2424', // L'\x68', -> NL, SYMBOL FOR NEWLINE
L'\u240b', // L'\x69', -> VT, SYMBOL FOR VERTICAL TABULATION
L'\u2518', // L'\x6a', -> Lower-right corner
L'\u2510', // L'\x6b', -> Upper-right corner
L'\u250c', // L'\x6c', -> Upper-left corner
L'\u2514', // L'\x6d', -> Lower-left corner
L'\u253c', // L'\x6e', -> Crossing lines
L'\u23ba', // L'\x6f', -> Horizontal line - Scan 1
L'\u23bb', // L'\x70', -> Horizontal line - Scan 3
L'\u2500', // L'\x71', -> Horizontal line - Scan 5
L'\u23bc', // L'\x72', -> Horizontal line - Scan 7
L'\u23bd', // L'\x73', -> Horizontal line - Scan 9
L'\u251c', // L'\x74', -> Left "T"
L'\u2524', // L'\x75', -> Right "T"
L'\u2534', // L'\x76', -> Bottom "T"
L'\u252c', // L'\x77', -> Top "T"
L'\u2502', // L'\x78', -> | Vertical bar
L'\u2264', // L'\x79', -> Less than or equal to
L'\u2265', // L'\x7a', -> Greater than or equal to
L'\u03c0', // L'\x7b', -> Pi
L'\u2260', // L'\x7c', -> Not equal to
L'\u00a3', // L'\x7d', -> UK pound sign
L'\u00b7', // L'\x7e', -> Centered dot
L'\x7f' // L'\x7f', -> DEL
};
bool TerminalOutput::DesignateCharset(const wchar_t newCharset) noexcept
TerminalOutput::TerminalOutput() noexcept
{
bool result = false;
if (newCharset == DispatchTypes::VTCharacterSets::DEC_LineDrawing ||
newCharset == DispatchTypes::VTCharacterSets::USASCII)
_gsetTranslationTables.at(0) = Ascii;
_gsetTranslationTables.at(1) = Ascii;
_gsetTranslationTables.at(2) = Latin1;
_gsetTranslationTables.at(3) = Latin1;
}
bool TerminalOutput::Designate94Charset(size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset)
{
switch (charset.first)
{
_currentCharset = newCharset;
result = true;
case L'B': // US ASCII
case L'1': // Alternate Character ROM
return _SetTranslationTable(gsetNumber, Ascii);
case L'0': // DEC Special Graphics
case L'2': // Alternate Character ROM Special Graphics
return _SetTranslationTable(gsetNumber, DecSpecialGraphics);
case L'<': // DEC Supplemental
return _SetTranslationTable(gsetNumber, DecSupplemental);
case L'A': // British NRCS
return _SetTranslationTable(gsetNumber, BritishNrcs);
case L'4': // Dutch NRCS
return _SetTranslationTable(gsetNumber, DutchNrcs);
case L'5': // Finnish NRCS
case L'C': // (fallback)
return _SetTranslationTable(gsetNumber, FinnishNrcs);
case L'R': // French NRCS
return _SetTranslationTable(gsetNumber, FrenchNrcs);
case L'f': // French NRCS (ISO update)
return _SetTranslationTable(gsetNumber, FrenchNrcsIso);
case L'9': // French Canadian NRCS
case L'Q': // (fallback)
return _SetTranslationTable(gsetNumber, FrenchCanadianNrcs);
case L'K': // German NRCS
return _SetTranslationTable(gsetNumber, GermanNrcs);
case L'Y': // Italian NRCS
return _SetTranslationTable(gsetNumber, ItalianNrcs);
case L'6': // Norwegian/Danish NRCS
case L'E': // (fallback)
return _SetTranslationTable(gsetNumber, NorwegianDanishNrcs);
case L'`': // Norwegian/Danish NRCS (ISO standard)
return _SetTranslationTable(gsetNumber, NorwegianDanishNrcsIso);
case L'Z': // Spanish NRCS
return _SetTranslationTable(gsetNumber, SpanishNrcs);
case L'7': // Swedish NRCS
case L'H': // (fallback)
return _SetTranslationTable(gsetNumber, SwedishNrcs);
case L'=': // Swiss NRCS
return _SetTranslationTable(gsetNumber, SwissNrcs);
case L'&':
switch (charset.second)
{
case L'4': // DEC Cyrillic
return _SetTranslationTable(gsetNumber, DecCyrillic);
case L'5': // Russian NRCS
return _SetTranslationTable(gsetNumber, RussianNrcs);
}
return false;
case L'"':
switch (charset.second)
{
case L'?': // DEC Greek
return _SetTranslationTable(gsetNumber, DecGreek);
case L'>': // Greek NRCS
return _SetTranslationTable(gsetNumber, GreekNrcs);
case L'4': // DEC Hebrew
return _SetTranslationTable(gsetNumber, DecHebrew);
}
return false;
case L'%':
switch (charset.second)
{
case L'=': // Hebrew NRCS
return _SetTranslationTable(gsetNumber, HebrewNrcs);
case L'0': // DEC Turkish
return _SetTranslationTable(gsetNumber, DecTurkish);
case L'2': // Turkish NRCS
return _SetTranslationTable(gsetNumber, TurkishNrcs);
case L'5': // DEC Supplemental
return _SetTranslationTable(gsetNumber, DecSupplemental);
case L'6': // Portuguese NRCS
return _SetTranslationTable(gsetNumber, PortugueseNrcs);
}
return false;
default:
return false;
}
return result;
}
bool TerminalOutput::Designate96Charset(size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset)
{
switch (charset.first)
{
case L'A': // ISO Latin-1 Supplemental
case L'<': // (UPSS when assigned to Latin-1)
return _SetTranslationTable(gsetNumber, Latin1);
case L'B': // ISO Latin-2 Supplemental
return _SetTranslationTable(gsetNumber, Latin2);
case L'L': // ISO Latin-Cyrillic Supplemental
return _SetTranslationTable(gsetNumber, LatinCyrillic);
case L'F': // ISO Latin-Greek Supplemental
return _SetTranslationTable(gsetNumber, LatinGreek);
case L'H': // ISO Latin-Hebrew Supplemental
return _SetTranslationTable(gsetNumber, LatinHebrew);
case L'M': // ISO Latin-5 Supplemental
return _SetTranslationTable(gsetNumber, Latin5);
default:
return false;
}
}
#pragma warning(suppress : 26440) // Suppress spurious "function can be declared noexcept" warning
bool TerminalOutput::LockingShift(const size_t gsetNumber)
{
_glSetNumber = gsetNumber;
_glTranslationTable = _gsetTranslationTables.at(_glSetNumber);
// If GL is mapped to ASCII then we don't need to translate anything.
if (_glTranslationTable == Ascii)
{
_glTranslationTable = {};
}
return true;
}
#pragma warning(suppress : 26440) // Suppress spurious "function can be declared noexcept" warning
bool TerminalOutput::LockingShiftRight(const size_t gsetNumber)
{
_grSetNumber = gsetNumber;
_grTranslationTable = _gsetTranslationTables.at(_grSetNumber);
// If GR is mapped to Latin1, or GR translation is not allowed, we don't need to translate anything.
if (_grTranslationTable == Latin1 || !_grTranslationEnabled)
{
_grTranslationTable = {};
}
return true;
}
#pragma warning(suppress : 26440) // Suppress spurious "function can be declared noexcept" warning
bool TerminalOutput::SingleShift(const size_t gsetNumber)
{
_ssTranslationTable = _gsetTranslationTables.at(gsetNumber);
return true;
}
// Routine Description:
// - Returns true if the current charset isn't USASCII, indicating that text has to come through here
// - Returns true if there is an active translation table, indicating that text has to come through here
// Arguments:
// - <none>
// Return Value:
// - True if the current charset is not USASCII
// - True if translation is required.
bool TerminalOutput::NeedToTranslate() const noexcept
{
return _currentCharset != DispatchTypes::VTCharacterSets::USASCII;
return !_glTranslationTable.empty() || !_grTranslationTable.empty() || !_ssTranslationTable.empty();
}
const std::wstring_view TerminalOutput::_GetTranslationTable() const noexcept
void TerminalOutput::EnableGrTranslation(boolean enabled)
{
switch (_currentCharset)
{
case DispatchTypes::VTCharacterSets::DEC_LineDrawing:
return { s_decSpecialGraphicsTranslations.data(), s_decSpecialGraphicsTranslations.size() };
}
return {};
_grTranslationEnabled = enabled;
// We need to reapply the right locking shift to (de)activate the translation table.
LockingShiftRight(_grSetNumber);
}
wchar_t TerminalOutput::TranslateKey(const wchar_t wch) const noexcept
{
wchar_t wchFound = wch;
if (_currentCharset == DispatchTypes::VTCharacterSets::USASCII ||
wch < '\x5f' || wch > '\x7f') // filter out the region we know is unchanged
if (!_ssTranslationTable.empty())
{
; // do nothing, these are the same as default.
if (wch - 0x20u < _ssTranslationTable.size())
{
wchFound = _ssTranslationTable.at(wch - 0x20u);
}
else if (wch - 0xA0u < _ssTranslationTable.size())
{
wchFound = _ssTranslationTable.at(wch - 0xA0u);
}
_ssTranslationTable = {};
}
else
{
const auto translationTable = _GetTranslationTable();
if (!translationTable.empty())
if (wch - 0x20u < _glTranslationTable.size())
{
wchFound = translationTable.at(wch - '\x20');
wchFound = _glTranslationTable.at(wch - 0x20u);
}
else if (wch - 0xA0u < _grTranslationTable.size())
{
wchFound = _grTranslationTable.at(wch - 0xA0u);
}
}
return wchFound;
}
bool TerminalOutput::_SetTranslationTable(const size_t gsetNumber, const std::wstring_view translationTable)
{
_gsetTranslationTables.at(gsetNumber) = translationTable;
// We need to reapply the locking shifts in case the underlying G-sets have changed.
return LockingShift(_glSetNumber) && LockingShiftRight(_grSetNumber);
}

View File

@ -23,15 +23,26 @@ namespace Microsoft::Console::VirtualTerminal
class TerminalOutput sealed
{
public:
TerminalOutput() = default;
TerminalOutput() noexcept;
wchar_t TranslateKey(const wchar_t wch) const noexcept;
bool DesignateCharset(const wchar_t wchNewCharset) noexcept;
bool Designate94Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset);
bool Designate96Charset(const size_t gsetNumber, const std::pair<wchar_t, wchar_t> charset);
bool LockingShift(const size_t gsetNumber);
bool LockingShiftRight(const size_t gsetNumber);
bool SingleShift(const size_t gsetNumber);
bool NeedToTranslate() const noexcept;
void EnableGrTranslation(boolean enabled);
private:
wchar_t _currentCharset = DispatchTypes::VTCharacterSets::USASCII;
bool _SetTranslationTable(const size_t gsetNumber, const std::wstring_view translationTable);
const std::wstring_view _GetTranslationTable() const noexcept;
std::array<std::wstring_view, 4> _gsetTranslationTables;
size_t _glSetNumber = 0;
size_t _grSetNumber = 2;
std::wstring_view _glTranslationTable;
std::wstring_view _grTranslationTable;
mutable std::wstring_view _ssTranslationTable;
boolean _grTranslationEnabled = false;
};
}

View File

@ -461,6 +461,12 @@ public:
return FALSE;
}
bool SetConsoleOutputCP(const unsigned int /*codepage*/) override
{
Log::Comment(L"SetConsoleOutputCP MOCK called...");
return TRUE;
}
bool GetConsoleOutputCP(unsigned int& codepage) override
{
Log::Comment(L"GetConsoleOutputCP MOCK called...");

View File

@ -50,6 +50,7 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool ActionSs3Dispatch(const wchar_t wch,
const std::basic_string_view<size_t> parameters) = 0;
virtual bool ParseControlSequenceAfterSs3() const = 0;
virtual bool FlushAtEndOfString() const = 0;
virtual bool DispatchControlCharsFromEscape() const = 0;
virtual bool DispatchIntermediatesFromEscape() const = 0;

View File

@ -1099,6 +1099,18 @@ bool InputStateMachineEngine::_GenerateKeyFromChar(const wchar_t wch,
return true;
}
// Method Description:
// - Returns true if the engine should attempt to parse a control sequence
// following an SS3 escape prefix.
// If this is false, an SS3 escape sequence should be dispatched as soon
// as it is encountered.
// Return Value:
// - True iff we should parse a control sequence following an SS3.
bool InputStateMachineEngine::ParseControlSequenceAfterSs3() const noexcept
{
return true;
}
// Method Description:
// - Returns true if the engine should dispatch on the last character of a string
// always, even if the sequence hasn't normally dispatched.

View File

@ -166,6 +166,7 @@ namespace Microsoft::Console::VirtualTerminal
bool ActionSs3Dispatch(const wchar_t wch,
const std::basic_string_view<size_t> parameters) override;
bool ParseControlSequenceAfterSs3() const noexcept override;
bool FlushAtEndOfString() const noexcept override;
bool DispatchControlCharsFromEscape() const noexcept override;
bool DispatchIntermediatesFromEscape() const noexcept override;

View File

@ -70,8 +70,14 @@ bool OutputStateMachineEngine::ActionExecute(const wchar_t wch)
// LF, FF, and VT are identical in function.
_dispatch->LineFeed(DispatchTypes::LineFeedType::DependsOnMode);
break;
case AsciiChars::SI:
_dispatch->LockingShift(0);
break;
case AsciiChars::SO:
_dispatch->LockingShift(1);
break;
default:
_dispatch->Execute(wch);
_dispatch->Print(wch);
break;
}
@ -221,6 +227,34 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
success = _dispatch->HardReset();
TermTelemetry::Instance().Log(TermTelemetry::Codes::RIS);
break;
case VTActionCodes::SS2_SingleShift:
success = _dispatch->SingleShift(2);
TermTelemetry::Instance().Log(TermTelemetry::Codes::SS2);
break;
case VTActionCodes::SS3_SingleShift:
success = _dispatch->SingleShift(3);
TermTelemetry::Instance().Log(TermTelemetry::Codes::SS3);
break;
case VTActionCodes::LS2_LockingShift:
success = _dispatch->LockingShift(2);
TermTelemetry::Instance().Log(TermTelemetry::Codes::LS2);
break;
case VTActionCodes::LS3_LockingShift:
success = _dispatch->LockingShift(3);
TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3);
break;
case VTActionCodes::LS1R_LockingShift:
success = _dispatch->LockingShiftRight(1);
TermTelemetry::Instance().Log(TermTelemetry::Codes::LS1R);
break;
case VTActionCodes::LS2R_LockingShift:
success = _dispatch->LockingShiftRight(2);
TermTelemetry::Instance().Log(TermTelemetry::Codes::LS2R);
break;
case VTActionCodes::LS3R_LockingShift:
success = _dispatch->LockingShiftRight(3);
TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3R);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
@ -229,37 +263,13 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
}
else if (intermediates.size() == 1)
{
const auto value = til::at(intermediates, 0);
DesignateCharsetTypes designateType = DefaultDesignateCharsetType;
success = _GetDesignateType(value, designateType);
if (success)
{
switch (designateType)
{
case DesignateCharsetTypes::G0:
success = _dispatch->DesignateCharset(wch);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG0);
break;
case DesignateCharsetTypes::G1:
success = false;
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1);
break;
case DesignateCharsetTypes::G2:
success = false;
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2);
break;
case DesignateCharsetTypes::G3:
success = false;
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3);
break;
default:
// If no functions to call, overall dispatch was a failure.
success = false;
break;
}
}
else if (value == L'#')
switch (til::at(intermediates, 0))
{
case L'%':
success = _dispatch->DesignateCodingSystem(wch);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DOCS);
break;
case L'#':
switch (wch)
{
case VTActionCodes::DECALN_ScreenAlignmentPattern:
@ -271,8 +281,16 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
success = false;
break;
}
break;
default:
success = _IntermediateScsDispatch(wch, intermediates);
break;
}
}
else if (intermediates.size() == 2)
{
success = _IntermediateScsDispatch(wch, intermediates);
}
// If we were unable to process the string, and there's a TTY attached to us,
// trigger the state machine to flush the string to the terminal.
@ -320,10 +338,10 @@ bool OutputStateMachineEngine::ActionVt52EscDispatch(const wchar_t wch,
success = _dispatch->CursorBackward(1);
break;
case Vt52ActionCodes::EnterGraphicsMode:
success = _dispatch->DesignateCharset(DispatchTypes::VTCharacterSets::DEC_LineDrawing);
success = _dispatch->Designate94Charset(0, DispatchTypes::CharacterSets::DecSpecialGraphics);
break;
case Vt52ActionCodes::ExitGraphicsMode:
success = _dispatch->DesignateCharset(DispatchTypes::VTCharacterSets::USASCII);
success = _dispatch->Designate94Charset(0, DispatchTypes::CharacterSets::ASCII);
break;
case Vt52ActionCodes::CursorToHome:
success = _dispatch->CursorPosition(1, 1);
@ -369,6 +387,57 @@ bool OutputStateMachineEngine::ActionVt52EscDispatch(const wchar_t wch,
return success;
}
// Routine Description:
// - Handles SCS charset designation actions that can have one or two possible intermediates.
// Arguments:
// - wch - Character to dispatch.
// - intermediates - Intermediate characters in the sequence
// Return Value:
// - True if handled successfully. False otherwise.
bool OutputStateMachineEngine::_IntermediateScsDispatch(const wchar_t wch,
const std::basic_string_view<wchar_t> intermediates)
{
bool success = false;
// If we have more than one intermediate, the second intermediate forms part of
// the charset identifier. Otherwise it's identified by just the final character.
const auto charset = intermediates.size() > 1 ? std::make_pair(intermediates.at(1), wch) : std::make_pair(wch, L'\0');
switch (intermediates.at(0))
{
case L'(':
success = _dispatch->Designate94Charset(0, charset);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG0);
break;
case L')':
success = _dispatch->Designate94Charset(1, charset);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1);
break;
case L'*':
success = _dispatch->Designate94Charset(2, charset);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2);
break;
case L'+':
success = _dispatch->Designate94Charset(3, charset);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3);
break;
case L'-':
success = _dispatch->Designate96Charset(1, charset);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG1);
break;
case L'.':
success = _dispatch->Designate96Charset(2, charset);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG2);
break;
case L'/':
success = _dispatch->Designate96Charset(3, charset);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DesignateG3);
break;
}
return success;
}
// Routine Description:
// - Triggers the CsiDispatch action to indicate that the listener should handle
// a control sequence. These sequences perform various API-type commands
@ -1345,43 +1414,16 @@ bool OutputStateMachineEngine::_GetTabClearType(const std::basic_string_view<siz
return success;
}
// Routine Description:
// - Retrieves a designate charset type from the intermediate we've stored. False otherwise.
// Arguments:
// - intermediate - Intermediate character in the sequence
// - designateType - Receives the designate type.
// Method Description:
// - Returns true if the engine should attempt to parse a control sequence
// following an SS3 escape prefix.
// If this is false, an SS3 escape sequence should be dispatched as soon
// as it is encountered.
// Return Value:
// - True if we successfully pulled the designate type from the intermediate we've stored. False otherwise.
bool OutputStateMachineEngine::_GetDesignateType(const wchar_t intermediate,
DesignateCharsetTypes& designateType) const noexcept
// - True iff we should parse a control sequence following an SS3.
bool OutputStateMachineEngine::ParseControlSequenceAfterSs3() const noexcept
{
bool success = false;
designateType = DefaultDesignateCharsetType;
switch (intermediate)
{
case '(':
designateType = DesignateCharsetTypes::G0;
success = true;
break;
case ')':
case '-':
designateType = DesignateCharsetTypes::G1;
success = true;
break;
case '*':
case '.':
designateType = DesignateCharsetTypes::G2;
success = true;
break;
case '+':
case '/':
designateType = DesignateCharsetTypes::G3;
success = true;
break;
}
return success;
return false;
}
// Routine Description:

View File

@ -55,6 +55,7 @@ namespace Microsoft::Console::VirtualTerminal
bool ActionSs3Dispatch(const wchar_t wch,
const std::basic_string_view<size_t> parameters) noexcept override;
bool ParseControlSequenceAfterSs3() const noexcept override;
bool FlushAtEndOfString() const noexcept override;
bool DispatchControlCharsFromEscape() const noexcept override;
bool DispatchIntermediatesFromEscape() const noexcept override;
@ -71,6 +72,8 @@ namespace Microsoft::Console::VirtualTerminal
std::function<bool()> _pfnFlushToTerminal;
wchar_t _lastPrintedChar;
bool _IntermediateScsDispatch(const wchar_t wch,
const std::basic_string_view<wchar_t> intermediates);
bool _IntermediateQuestionMarkDispatch(const wchar_t wchAction,
const std::basic_string_view<size_t> parameters);
bool _IntermediateExclamationDispatch(const wchar_t wch);
@ -127,6 +130,13 @@ namespace Microsoft::Console::VirtualTerminal
DECSCUSR_SetCursorStyle = L'q', // I believe we'll only ever implement DECSCUSR
DTTERM_WindowManipulation = L't',
REP_RepeatCharacter = L'b',
SS2_SingleShift = L'N',
SS3_SingleShift = L'O',
LS2_LockingShift = L'n',
LS3_LockingShift = L'o',
LS1R_LockingShift = L'~',
LS2R_LockingShift = L'}',
LS3R_LockingShift = L'|',
DECALN_ScreenAlignmentPattern = L'8'
};
@ -164,14 +174,6 @@ namespace Microsoft::Console::VirtualTerminal
ResetCursorColor = 112,
};
enum class DesignateCharsetTypes
{
G0,
G1,
G2,
G3
};
static constexpr DispatchTypes::GraphicsOptions DefaultGraphicsOption = DispatchTypes::GraphicsOptions::Off;
bool _GetGraphicsOptions(const std::basic_string_view<size_t> parameters,
std::vector<DispatchTypes::GraphicsOptions>& options) const;
@ -225,10 +227,6 @@ namespace Microsoft::Console::VirtualTerminal
bool _GetTabClearType(const std::basic_string_view<size_t> parameters,
size_t& clearType) const noexcept;
static constexpr DesignateCharsetTypes DefaultDesignateCharsetType = DesignateCharsetTypes::G0;
bool _GetDesignateType(const wchar_t intermediate,
DesignateCharsetTypes& designateType) const noexcept;
static constexpr DispatchTypes::WindowManipulationType DefaultWindowManipulationType = DispatchTypes::WindowManipulationType::Invalid;
bool _GetWindowManipulationType(const std::basic_string_view<size_t> parameters,
unsigned int& function) const noexcept;

View File

@ -190,10 +190,8 @@ static constexpr bool _isCsiInvalid(const wchar_t wch) noexcept
}
// Routine Description:
// - Determines if a character is "operating system control string" beginning
// indicator.
// This immediately follows an escape and signifies a signifies a varying
// length control sequence, quite similar to CSI.
// - Determines if a character is a "Single Shift Select" indicator.
// This immediately follows an escape and signifies a varying length control string.
// Arguments:
// - wch - Character to check.
// Return Value:
@ -217,8 +215,10 @@ static constexpr bool _isVt52CursorAddress(const wchar_t wch) noexcept
}
// Routine Description:
// - Determines if a character is a "Single Shift Select" indicator.
// This immediately follows an escape and signifies a varying length control string.
// - Determines if a character is "operating system control string" beginning
// indicator.
// This immediately follows an escape and signifies a signifies a varying
// length control sequence, quite similar to CSI.
// Arguments:
// - wch - Character to check.
// Return Value:
@ -838,7 +838,7 @@ void StateMachine::_EventEscape(const wchar_t wch)
{
_EnterOscParam();
}
else if (_isSs3Indicator(wch))
else if (_isSs3Indicator(wch) && _engine->ParseControlSequenceAfterSs3())
{
_EnterSs3Entry();
}

View File

@ -250,6 +250,14 @@ void TermTelemetry::WriteFinalTraceLog() const
TraceLoggingUInt32(_uiTimesUsed[DesignateG1], "DesignateG1"),
TraceLoggingUInt32(_uiTimesUsed[DesignateG2], "DesignateG2"),
TraceLoggingUInt32(_uiTimesUsed[DesignateG3], "DesignateG3"),
TraceLoggingUInt32(_uiTimesUsed[LS2], "LS2"),
TraceLoggingUInt32(_uiTimesUsed[LS3], "LS3"),
TraceLoggingUInt32(_uiTimesUsed[LS1R], "LS1R"),
TraceLoggingUInt32(_uiTimesUsed[LS2R], "LS2R"),
TraceLoggingUInt32(_uiTimesUsed[LS3R], "LS3R"),
TraceLoggingUInt32(_uiTimesUsed[SS2], "SS2"),
TraceLoggingUInt32(_uiTimesUsed[SS3], "SS3"),
TraceLoggingUInt32(_uiTimesUsed[DOCS], "DOCS"),
TraceLoggingUInt32(_uiTimesUsed[HVP], "HVP"),
TraceLoggingUInt32(_uiTimesUsed[DECSTR], "DECSTR"),
TraceLoggingUInt32(_uiTimesUsed[RIS], "RIS"),

View File

@ -77,6 +77,14 @@ namespace Microsoft::Console::VirtualTerminal
DesignateG1,
DesignateG2,
DesignateG3,
LS2,
LS3,
LS1R,
LS2R,
LS3R,
SS2,
SS3,
DOCS,
HVP,
DECSTR,
RIS,

View File

@ -7,6 +7,7 @@
#include "stateMachine.hpp"
#include "InputStateMachineEngine.hpp"
#include "ascii.hpp"
#include "../input/terminalInput.hpp"
#include "../../inc/unicode.hpp"
#include "../../types/inc/convert.hpp"
@ -263,6 +264,9 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest
TEST_METHOD(SGRMouseTest_Movement);
TEST_METHOD(SGRMouseTest_Scroll);
TEST_METHOD(CtrlAltZCtrlAltXTest);
TEST_METHOD(TestSs3Entry);
TEST_METHOD(TestSs3Immediate);
TEST_METHOD(TestSs3Param);
friend class TestInteractDispatch;
};
@ -1310,3 +1314,87 @@ void InputEngineTest::CtrlAltZCtrlAltXTest()
VerifyExpectedInputDrained();
}
void InputEngineTest::TestSs3Entry()
{
auto pfn = std::bind(&TestState::TestInputCallback, &testState, std::placeholders::_1);
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'm');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
void InputEngineTest::TestSs3Immediate()
{
// Intermediates aren't supported by Ss3 - they just get dispatched
auto pfn = std::bind(&TestState::TestInputCallback, &testState, std::placeholders::_1);
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'$');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'#');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'%');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'?');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
void InputEngineTest::TestSs3Param()
{
auto pfn = std::bind(&TestState::TestInputCallback, &testState, std::placeholders::_1);
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'3');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'2');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'4');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'8');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'J');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}

View File

@ -550,87 +550,6 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
mach.ProcessCharacter(AsciiChars::BEL);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestSs3Entry)
{
auto dispatch = std::make_unique<DummyDispatch>();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'm');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestSs3Immediate)
{
// Intermediates aren't supported by Ss3 - they just get dispatched
auto dispatch = std::make_unique<DummyDispatch>();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'$');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'#');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'%');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L'?');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestSs3Param)
{
auto dispatch = std::make_unique<DummyDispatch>();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'O');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Entry);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'3');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'2');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'4');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'8');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ss3Param);
mach.ProcessCharacter(L'J');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
};
class StatefulDispatch final : public TermDispatch

View File

@ -76,6 +76,7 @@ public:
bool ActionSs3Dispatch(const wchar_t /* wch */,
const std::basic_string_view<size_t> /* parameters */) override { return true; };
bool ParseControlSequenceAfterSs3() const override { return false; }
bool FlushAtEndOfString() const override { return false; };
bool DispatchControlCharsFromEscape() const override { return false; };
bool DispatchIntermediatesFromEscape() const override { return false; };