mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-12 00:07:24 -06:00
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:
commit
935702cde9
@ -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
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
ACLs
|
||||
DACL
|
||||
DACLs
|
||||
LKG
|
||||
mfcribbon
|
||||
microsoft
|
||||
@ -5,6 +8,7 @@ microsoftonline
|
||||
osgvsowi
|
||||
powerrename
|
||||
powershell
|
||||
SACLs
|
||||
tdbuildteamid
|
||||
vcruntime
|
||||
visualstudio
|
||||
|
||||
14
.github/actions/spell-check/expect/expect.txt
vendored
14
.github/actions/spell-check/expect/expect.txt
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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> /\()"'-:,.;<>~!@#$%^&*|+=[]{}~?│</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. |
|
||||
|
||||
@ -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"]
|
||||
|
||||
319
doc/specs/drafts/#1256 - Tab tearoff.md
Normal file
319
doc/specs/drafts/#1256 - Tab tearoff.md
Normal 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)
|
||||
115
doc/specs/drafts/#997 Non-Terminal-Panes.md
Normal file
115
doc/specs/drafts/#997 Non-Terminal-Panes.md
Normal 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
|
||||
@ -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>
|
||||
|
||||
@ -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>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\runtimes\win10-$(Native-Platform)\native\"</_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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)))
|
||||
|
||||
@ -142,6 +142,7 @@ private:
|
||||
bool _suppressApplicationTitle;
|
||||
int32_t _historySize;
|
||||
bool _snapOnInput;
|
||||
bool _altGrAliasing;
|
||||
uint32_t _cursorHeight;
|
||||
winrt::Microsoft::Terminal::Settings::CursorStyle _cursorShape;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -212,6 +212,7 @@ private:
|
||||
COLORREF _defaultBg;
|
||||
|
||||
bool _snapOnInput;
|
||||
bool _altGrAliasing;
|
||||
bool _suppressApplicationTitle;
|
||||
|
||||
#pragma region Text Selection
|
||||
|
||||
@ -25,6 +25,7 @@ namespace Microsoft.Terminal.Settings
|
||||
Int32 RowsToScroll;
|
||||
|
||||
Boolean SnapOnInput;
|
||||
Boolean AltGrAliasing;
|
||||
|
||||
UInt32 CursorColor;
|
||||
CursorStyle CursorShape;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {}
|
||||
|
||||
@ -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'))" />
|
||||
|
||||
@ -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>
|
||||
@ -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'))" />
|
||||
|
||||
@ -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>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
1055
src/terminal/adapter/charsets.hpp
Normal file
1055
src/terminal/adapter/charsets.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@ -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...");
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -77,6 +77,14 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
DesignateG1,
|
||||
DesignateG2,
|
||||
DesignateG3,
|
||||
LS2,
|
||||
LS3,
|
||||
LS1R,
|
||||
LS2R,
|
||||
LS3R,
|
||||
SS2,
|
||||
SS3,
|
||||
DOCS,
|
||||
HVP,
|
||||
DECSTR,
|
||||
RIS,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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; };
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user