You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Console.ReadKey returns incomplete information for some key sequences on Linux #802
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.
In general we don't have a good way to do better than this. They're is no API similar to what Windows has for getting detailed information on the keys that were typed; we simply do our best to infer it from the escape sequences sent to the terminal.
There are key bindings that work fine in bash but not with PSReadline, e.g. C-] is character search (a little like F3 on Windows). C-@ is also used in bash to set a mark, then C-x,C-x to exchange the point and mark.
GNU readline understands these escape sequences, so presumably .Net core can as well.
GNU readline understands these escape sequences, so presumably .Net core can as well.
If you can make it work, we'd welcome a PR. This is a known limitation we shipped with (mono has a similar limitation), and this is not something we plan to revisit soon.
ps I should clarify. I haven't checked the exact combinations you're talking about. If those combinations result in a unique sequence of escape codes we can reliably recognize, then fixing it should be relatively easy. My concern is they don't, and an entirely different mechanism would be needed.
Assuming I can attempt to see the sequences easily via Console.In.Read, the keys I'm concerned about don't actually generate anything, so some investigation is necessary. I'm not sure if it's something stty controls.
At any rate, I'll try and investigate further at some point, I expect some PowerShell users will want undo to work properly on the command line on Linux.
A quick update - showkeys -a shows me that there are distinct sequences for some keys that we care about - below I typed a character followed by Alt and that character.
After reviewing the code, I think I believe the bug is related to this method - it doesn't handle most of these characters, it returns default(ConsoleKey), which means the return from here is false, and then here we fall through to the !alt case and return just the Escape character.
It's not that useful to expect a valid ConsoleKey for these characters because it depends on the keyboard layout. On Windows, I'm forced to use the ConsoleKey because the KeyChar is sometimes '\0', but it doesn't have to be this way, so I'm open to returning a ConsoleKeyInfo with the ConsoleKey set to default(ConsoleKey).
Edit by @carlossanlop: since we moved to dotnet/runtime, the StdInReader.cs method mentioned above, returning default(ConsoleKey), is located here.
Below is a summary of the existing shortcomings and how they relate to how things work in the Unix world.
Please note:
I haven't looked at the source code, so some of this is speculative. Do tell me where I'm wrong.
Context:
The fundamental restrictions regarding Control-based chords and Alt-based chords - which come from most Unix-like OSs running xterm-emulation terminal programs are:
Control-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 Control key; for instance, ^A represents the chord Control+A (strictly speaking, it is the uppercaseA, but at least for letters it doesn't matter whether Shift is held down or not):
There are noAlt-based chords at all, but most modern terminal programs translateAlt+{char}-based chords (with some terminals on an opt-in basis, as on macOS) into Esc, {char}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 Control-based and/or Alt-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 Shift 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 Esc, {char}sequence - CoreFX selectively retranslates that into a chord, as you would see it on Windows_:
Control-based chords
Sensibly, Control-based chords that translate into control characters that have dedicated keys are reflected as such in the .Key property:
Control+i a.k.a ^I, which is control char. HT, corresponds to the Tab key
Control+h a.k.a ^H, which is control char. BS, corresponds to the Backspace key
Control+[ a.k.a ^[, which is control char. ESC, corresponds to the Escape key.
Unfortunately, Control+j a.k.a ^J, which is control char. LF (0xA) is mistakenly conflated with Control+m a.k.a ^M, which is control char. CR (0xD): both result in the .KeyChar property containing `n (i.e., LF), and .Key containing Enter, which is incorrect:
Only ^M corresponds to key Enter, and the .KeyChar property should contain `r (i.e., CR), not `n.
^J has the correct .KeyChar value (`n), but its .Key property should be J, and its .Modifier property should be Control.
* Somewhat ironically, this distinction is correctly observed on Windows.
Mostletter-basedControl-based chords (e.g., Control+a) 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 Shift held down (e.g., Control+Shift+a) are currently quietly ignored.
Additionally, curiously, Control+z, even in its "shift-less" (lowercase) variant is quietly ignored as well; note that Control+z has special meaning in traditional shells (not the terminal emulators themselves): in cmd.exe, it signals EOF during interactive input (copy con ...); in bash, it suspends the currently executing program and sends it to the background.
For Control+c 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 Control+\
The remaining punctuation-based chords (other than Control+[ a.k.a. Esc) pass the control character correctly in the .KeyChar property, but fail to populate the .Key and .Modifier properties:
Letter-basedAlt-based chords, including uppercase variants, cords are correctly translated, and behave as on Windows.
By contrast, symbol-/punctuation-based Alt chords (e.g., Alt+') do appear to arrive as their raw Esc, {char}sequences, which [Console]::ReadKey() and therefore PSReadLine cannot handle: [Console]::ReadKey() only reports the Esc keypress
To predict if a given chord will currently work on Unix-like platforms, run the following from PowerShell:
# Press one key chord at a time to see its interpretation as a [ConsoleKeyInfo] instance,# followed by the hex. representation of the resulting character (of the `.KeyChar` property).# To exit, press Ctrl+c, then Enter.while ($true) { $ck= [console]::readkey(); $ck|out-string; 'KeyChar code point: {0} (0x{0:x})'-f [int] [char] $ck.KeyChar }
To see what key chords [Console]::ReadKey() and therefore PSReadline should be able to handle, as @lzybkr has stated, run showkey -a in a terminal on a Linux platform (you may have to install the kbd package first).
Column 1 is the resulting character(s).
For most Control- andAlt-based chords the character is represented in caret notation; and, as stated, since Alt+{char}-based chords are translated into Esc, then {char}sequences, you'll see the two resulting characters, the first of which is ^[, 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 (Control-based) key chords and their - inconsistently supported - aliases:
Alias chords ultimately map to the same, single control character, so they are effectively indistinguishable from the canonical caret-notation chord.
This fact needs to be documented, so that users understand why different chords can trigger the same PSReadLine key binding.
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 adding 0x20 to a caret-notation character's code point.
With letter-based chords that means that whether you hold down Shift or not doesn't matter - this should apply to all Latin-alphabet-based keyboard layouts.
With punctuation-based chords, whether you hold down Shift 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.)
On macOS, curiously, ^` for ^@ works neither in the default terminal (Terminal.app) nor in popular replacement iTerm.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).
This issue is likely to move to .NET 7.0, where we're anticipating a collection of Console-related work will be done. Leaving it in 6.0 for the time-being though.
@waf that's normal: In Windows the System.Console modifiers keys are detected but on Linux it only detects the Enter key and the Shift key is wrapped with Escape Sequences that needs to being decoded.
Please, correct me if I'm wrong: Linux (NET.Core) doesn't have convenient ConsoleKeyInfo modifier describer because there are problems with particular combinations detection (20-30% of all) in OS.
The ConsoleKeyInfo modifiers in Linux (NET.Core) does not caught without decode the Ansi Escape Sequence. Only Windows OS caught the modifiers keys. Unfortunately, System.Console don't uses the Console Virtual Terminal Sequences on Windows, but only on Linux. Also, some keys aren't caught by Windows or Linux because these keys are used by the OS.
On Linux the System.Console.ReadKey is sending Shift+PageDown on a Shift+CursorDown and Shift+PageUp on a Shift+CursorUp. Also it doesn't respond on pressing Shift+End, Shift+Home, Shift+PageDown and Shift+PageUp.
Activity
stephentoub commentedon Oct 4, 2016
In general we don't have a good way to do better than this. They're is no API similar to what Windows has for getting detailed information on the keys that were typed; we simply do our best to infer it from the escape sequences sent to the terminal.
lzybkr commentedon Oct 4, 2016
There are key bindings that work fine in bash but not with PSReadline, e.g. C-] is character search (a little like F3 on Windows). C-@ is also used in bash to set a mark, then C-x,C-x to exchange the point and mark.
GNU readline understands these escape sequences, so presumably .Net core can as well.
stephentoub commentedon Oct 4, 2016
If you can make it work, we'd welcome a PR. This is a known limitation we shipped with (mono has a similar limitation), and this is not something we plan to revisit soon.
stephentoub commentedon Oct 4, 2016
ps I should clarify. I haven't checked the exact combinations you're talking about. If those combinations result in a unique sequence of escape codes we can reliably recognize, then fixing it should be relatively easy. My concern is they don't, and an entirely different mechanism would be needed.
lzybkr commentedon Oct 4, 2016
Assuming I can attempt to see the sequences easily via Console.In.Read, the keys I'm concerned about don't actually generate anything, so some investigation is necessary. I'm not sure if it's something
sttycontrols.At any rate, I'll try and investigate further at some point, I expect some PowerShell users will want
undoto work properly on the command line on Linux.karelz commentedon Nov 18, 2016
@lzybkr were you able to make any progress?
cc: @ianhays
lzybkr commentedon Nov 18, 2016
The affected key bindings aren't too critical, so I haven't found time to take a look yet.
lzybkr commentedon Oct 17, 2017
A quick update -
showkeys -ashows me that there are distinct sequences for some keys that we care about - below I typed a character followed by Alt and that character.After reviewing the code, I think I believe the bug is related to this method - it doesn't handle most of these characters, it returns
default(ConsoleKey), which means the return from here isfalse, and then here we fall through to the !alt case and return just the Escape character.It's not that useful to expect a valid
ConsoleKeyfor these characters because it depends on the keyboard layout. On Windows, I'm forced to use theConsoleKeybecause theKeyCharis sometimes'\0', but it doesn't have to be this way, so I'm open to returning aConsoleKeyInfowith theConsoleKeyset todefault(ConsoleKey).Edit by @carlossanlop: since we moved to dotnet/runtime, the
StdInReader.csmethod mentioned above, returningdefault(ConsoleKey), is located here.mklement0 commentedon Apr 15, 2018
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 Control-based chords and Alt-based chords - which come from most Unix-like OSs running
xterm-emulation terminal programs are:Control-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 Control key; for instance,^Arepresents the chord Control+A (strictly speaking, it is the uppercaseA, but at least for letters it doesn't matter whether Shift is held down or not):(0x0) NUL/^Athrough(0x1f) US) /^_, plus(0x7F) DEL/^?) and programs running inside that terminal see only the resulting control character.There are no Alt-based chords at all, but most modern terminal programs translate Alt+{char}-based chords (with some terminals on an opt-in basis, as on macOS) into Esc, {char} sequences, and programs running inside that terminal see only the resulting sequence.
That's why, in the
bashworld, for instance,readlinedefinitions are expressed in terms of the latter.(Additionally, a given terminal program itself may have key bindings for Control-based and/or Alt-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 Shift 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 Esc, {char} sequence - CoreFX selectively retranslates that into a chord, as you would see it on Windows_:
Control-based chords
Sensibly, Control-based chords that translate into control characters that have dedicated keys are reflected as such in the
.Keyproperty:Control+i a.k.a
^I, which is control char.HT, corresponds to the Tab keyControl+h a.k.a
^H, which is control char.BS, corresponds to the Backspace keyControl+[ a.k.a
^[, which is control char.ESC, corresponds to the Escape key.Unfortunately, Control+j a.k.a
^J, which is control char.LF(0xA) is mistakenly conflated with Control+m a.k.a^M, which is control char.CR(0xD): both result in the.KeyCharproperty containing`n(i.e.,LF), and.KeycontainingEnter, which is incorrect:^Mcorresponds to key Enter, and the.KeyCharproperty should contain`r(i.e.,CR), not`n.^Jhas the correct.KeyCharvalue (`n), but its.Keyproperty should beJ, and its.Modifierproperty should beControl.* Somewhat ironically, this distinction is correctly observed on Windows.
Most letter-based Control-based chords (e.g., Control+a) 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 Shift held down (e.g., Control+Shift+a) are currently quietly ignored.
Additionally, curiously, Control+z, even in its "shift-less" (lowercase) variant is quietly ignored as well; note that Control+z 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 Control+c to be recognized as a keypress (as opposed to being treated as the signal to terminate a command),
[console]::TreatControlCAsInput = $truemust 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 Control+\
The remaining punctuation-based chords (other than Control+[ a.k.a. Esc) pass the control character correctly in the
.KeyCharproperty, but fail to populate the.Keyand.Modifierproperties:Alt-based chords
CoreFX tries to translate Esc, {char} sequences , as reported by the terminal emulator, back into Alt+{char}-based chords, but not consistently:
Letter-based Alt-based chords, including uppercase variants, cords are correctly translated, and behave as on Windows.
By contrast, symbol-/punctuation-based Alt chords (e.g., Alt+') do appear to arrive as their raw Esc, {char} sequences, which
[Console]::ReadKey()and therefore PSReadLine cannot handle:[Console]::ReadKey()only reports the Esc 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 -ain a terminal on a Linux platform (you may have to install thekbdpackage 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 (Control-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
ShiftAliasesrefers to, loosely speaking, aliases that are the "+ Shift" / uppercase equivalents, calculated by adding0x20to a caret-notation character's code point.With letter-based chords that means that whether you hold down Shift or not doesn't matter - this should apply to all Latin-alphabet-based keyboard layouts.
With punctuation-based chords, whether you hold down Shift 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
DeFactoAliaseslists 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.
^spaceis 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).
carlossanlop commentedon Mar 4, 2020
Triage:
@lzybkr would you be interested in working on a PR for this?
28 remaining items
jeffhandley commentedon Jun 24, 2021
This issue is likely to move to .NET 7.0, where we're anticipating a collection of Console-related work will be done. Leaving it in 6.0 for the time-being though.
BDisp commentedon Jun 25, 2021
@waf that's normal: In
WindowstheSystem.Consolemodifiers keys are detected but onLinuxit only detects theEnterkey and theShiftkey is wrapped withEscape Sequencesthat needs to being decoded.Alex-K-O-R commentedon Jun 30, 2021
Please, correct me if I'm wrong: Linux (NET.Core) doesn't have convenient ConsoleKeyInfo modifier describer because there are problems with particular combinations detection (20-30% of all) in OS.
Is that correct?
BDisp commentedon Jun 30, 2021
The
ConsoleKeyInfomodifiers inLinux (NET.Core)does not caught without decode theAnsi Escape Sequence. OnlyWindows OScaught the modifiers keys. Unfortunately,System.Consoledon't uses theConsole Virtual Terminal SequencesonWindows, but only onLinux. Also, some keys aren't caught byWindowsorLinuxbecause these keys are used by theOS.jeffhandley commentedon Jul 7, 2021
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.
danmoseley commentedon Mar 11, 2022
https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1486859
BDisp commentedon Jun 27, 2022
On
LinuxtheSystem.Console.ReadKeyis sendingShift+PageDownon aShift+CursorDownandShift+PageUpon aShift+CursorUp. Also it doesn't respond on pressingShift+End,Shift+Home,Shift+PageDownandShift+PageUp.Update dependencies from https://github.com/dotnet/arcade build 20220…