runtime: Console.ReadKey returns incomplete information for some key sequences on Linux
AB#1115513 Using PowerShell 6.0.0-alpha.10 on Ubuntu 14.04, I run
PS> [Console]::ReadKey()
and type Ctrl-@. I get back
KeyChar Key Modifiers
------- --- ---------
0 0
I see somewhat similar results with other key presses like Ctrl-! or Ctrl-%. In some cases, KeyChar
has the correct key, but Modifiers
is still 0
, resulting in incorrectly assuming Ctrl was not down and e.g. inserting a !
instead of doing whatever action Ctrl+!
was bound to.
This issue affects some of the key bindings in PSReadline.
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 5
- Comments: 33 (26 by maintainers)
Commits related to this issue
- Update dependencies from https://github.com/dotnet/arcade build 20220106.6 (#802) [main] Update dependencies from dotnet/arcade — committed to radical/runtime by dotnet-maestro[bot] 2 years ago
Below is a summary of the existing shortcomings and how they relate to how things work in the Unix world.
Please note:
Context:
The fundamental restrictions regarding <kbd>Control</kbd>-based chords and <kbd>Alt</kbd>-based chords - which come from most Unix-like OSs running
xterm
-emulation terminal programs are:<kbd>Control</kbd>-based chords are in effect limited to the 33 well-defined chords of caret notation - plus a few effective (but presumable accidental) aliases (see bottom); the
^
symbol is known as the caret and represents the <kbd>Control</kbd> key; for instance,^A
represents the chord <kbd>Control+A</kbd> (strictly speaking, it is the uppercaseA
, but at least for letters it doesn’t matter whether <kbd>Shift</kbd> is held down or not):(0x0) NUL
/^A
through(0x1f) US
) /^_
, plus(0x7F) DEL
/^?
) and programs running inside that terminal see only the resulting control character.There are no <kbd>Alt</kbd>-based chords at all, but most modern terminal programs translate <kbd>Alt+{char}</kbd>-based chords (with some terminals on an opt-in basis, as on macOS) into <kbd>Esc</kbd>, <kbd>{char}</kbd> sequences, and programs running inside that terminal see only the resulting sequence.
That’s why, in the
bash
world, for instance,readline
definitions are expressed in terms of the latter.(Additionally, a given terminal program itself may have key bindings for <kbd>Control</kbd>-based and/or <kbd>Alt</kbd>-based chords, which may preempt any attempts to use these chords by programs running inside these terminals.)
Shortcomings of the existing
[Console]::ReadKey()
implementation on Unix-like platforms:Note: I’m using lowercase letters in the chord representations below to indicate chords not involving the <kbd>Shift</kbd> key.
On Unix-like platforms, instead of letting programs see the translation of key chords the same way as described above - which may be a single ASCII control character or an <kbd>Esc</kbd>, <kbd>{char}</kbd> sequence - CoreFX selectively retranslates that into a chord, as you would see it on Windows_:
<kbd>Control</kbd>-based chords
Sensibly, <kbd>Control</kbd>-based chords that translate into control characters that have dedicated keys are reflected as such in the
.Key
property:<kbd>Control+i</kbd> a.k.a
^I
, which is control char.HT
, corresponds to the <kbd>Tab</kbd> key<kbd>Control+h</kbd> a.k.a
^H
, which is control char.BS
, corresponds to the <kbd>Backspace</kbd> key<kbd>Control+[</kbd> a.k.a
^[
, which is control char.ESC
, corresponds to the <kbd>Escape</kbd> key.Unfortunately, <kbd>Control+j</kbd> a.k.a
^J
, which is control char.LF
(0xA
) is mistakenly conflated with <kbd>Control+m</kbd> a.k.a^M
, which is control char.CR
(0xD
): both result in the.KeyChar
property containing`n
(i.e.,LF
), and.Key
containingEnter
, which is incorrect:^M
corresponds to key <kbd>Enter</kbd>, and the.KeyChar
property should contain`r
(i.e.,CR
), not`n
.^J
has the correct.KeyChar
value (`n
), but its.Key
property should beJ
, and its.Modifier
property should beControl
.Most letter-based <kbd>Control</kbd>-based chords (e.g., <kbd>Control+a</kbd>) are passed through the same way they are on Windows (rather than being translated into ASCII control characters).
However, letter-based chords with uppercase letters, i.e. with <kbd>Shift</kbd> held down (e.g., <kbd>Control+Shift+a</kbd>) are currently quietly ignored.
Additionally, curiously, <kbd>Control+z</kbd>, even in its “shift-less” (lowercase) variant is quietly ignored as well; note that <kbd>Control+z</kbd> has special meaning in traditional shells (not the terminal emulators themselves): in
cmd.exe
, it signals EOF during interactive input (copy con ...
); inbash
, it suspends the currently executing program and sends it to the background.For <kbd>Control+c</kbd> to be recognized as a keypress (as opposed to being treated as the signal to terminate a command),
[console]::TreatControlCAsInput = $true
must be executed first; without that, the terminating effect is curiously delayed: nothing appears to happen at first, but the very next next keypress then terminates the call, without returning anything (only the character typed is printed to the terminal).Another quietly ignored chord is <kbd>Control+\ </kbd>
The remaining punctuation-based chords (other than <kbd>Control+[</kbd> a.k.a. <kbd>Esc</kbd>) pass the control character correctly in the
.KeyChar
property, but fail to populate the.Key
and.Modifier
properties:<kbd>Alt</kbd>-based chords
CoreFX tries to translate <kbd>Esc</kbd>, <kbd>{char}</kbd> sequences , as reported by the terminal emulator, back into <kbd>Alt+{char}</kbd>-based chords, but not consistently:
Letter-based <kbd>Alt</kbd>-based chords, including uppercase variants, cords are correctly translated, and behave as on Windows.
By contrast, symbol-/punctuation-based <kbd>Alt</kbd> chords (e.g., <kbd>Alt+'</kbd>) do appear to arrive as their raw <kbd>Esc</kbd>, <kbd>{char}</kbd> sequences, which
[Console]::ReadKey()
and therefore PSReadLine cannot handle:[Console]::ReadKey()
only reports the <kbd>Esc</kbd> keypressTo predict if a given chord will currently work on Unix-like platforms, run the following from PowerShell:
To see what key chords
[Console]::ReadKey()
and therefore PSReadline should be able to handle, as @lzybkr has stated, runshowkey -a
in a terminal on a Linux platform (you may have to install thekbd
package first).Column 1 is the resulting character(s).
^[
, i.e.,ESC
(27
/0x1b
) in caret notation.Columns 2-4 are the resulting character’s code point in decimal, octal, and hexadecimal notation
The full list of caret-notation (<kbd>Control</kbd>-based) key chords and their - inconsistently supported - aliases:
Caveats re aliases:
Alias chords ultimately map to the same, single control character, so they are effectively indistinguishable from the canonical caret-notation chord.
To avoid confusion - and given that the aliases function inconsistently across terminal emulators - PSReadLine keybindings should only be defined in terms of the caret-notation chords.
Column
ShiftAliases
refers to, loosely speaking, aliases that are the “+ Shift” / uppercase equivalents, calculated by adding0x20
to a caret-notation character’s code point.With letter-based chords that means that whether you hold down <kbd>Shift</kbd> or not doesn’t matter - this should apply to all Latin-alphabet-based keyboard layouts.
With punctuation-based chords, whether you hold down <kbd>Shift</kbd> or not also doesn’t matter with an US-English keyboard layout, except for
_
and?
(the “shifted”^_
and^?
actually produce each other, i.e. produce a different control char.)^`
for^@
works neither in the default terminal (Terminal.app
) nor in popular replacementiTerm.app
(the former ignores^`
, the latter treats it as plain`
); if you run an X11 xterm emulator, however, it does work.Column
DeFactoAliases
lists additional aliases:It is unclear to me why these aliases work and whether they do so accidentally. Their functioning may depend on the language-specific keyboard layout.
^space
is a de-facto alias for^@
that works in all terminals I’ve looked at.By contrast, all the other ones - discovered experimentally with an US-English keyboard layout - seem to ONLY work in Linux xterm-256color terminals (and also in an X11 xterm emulator on macOS).
We are going to move this out to .NET 7 with the intention of grouping this with other Console-related efforts as being discussed in #52374.
I don’t see this specifically mentioned, but <kbd>shift</kbd>+<kbd>enter</kbd> is also incomplete on Linux (I’m testing on WSL2 Ubuntu). Using the following program returns different results on Windows vs Linux:
On Windows, when we press <kbd>shift</kbd>+<kbd>enter</kbd> we get:
On Linux, we get:
On
Linux
theSystem.Console.ReadKey
is sendingShift+PageDown
on aShift+CursorDown
andShift+PageUp
on aShift+CursorUp
. Also it doesn’t respond on pressingShift+End
,Shift+Home
,Shift+PageDown
andShift+PageUp
.The
ConsoleKeyInfo
modifiers inLinux (NET.Core)
does not caught without decode theAnsi Escape Sequence
. OnlyWindows OS
caught the modifiers keys. Unfortunately,System.Console
don’t uses theConsole Virtual Terminal Sequences
onWindows
, but only onLinux
. Also, some keys aren’t caught byWindows
orLinux
because these keys are used by theOS
.