Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes key handling for Windows. #172

Conversation

tig
Copy link

@tig tig commented Jan 2, 2024

@BDisp this is still a work in progress.

I broke ENTER key handling in Textview but this is now on the right track, i think.

I need to take a break, so I'm submitting this before it's really ready.

I did not touch the VKeySimulator because A ton of the underlying code is simply wrong and needs to be redone.

Note that the whole VK_Packet stuff is only relevant on Windows (and perhaps only WindowsDriver). the Scenario should NOT even attempt to work on Linux IMO.

@BDisp
Copy link
Owner

BDisp commented Jan 2, 2024

Note that the whole VK_Packet stuff is only relevant on Windows (and perhaps only WindowsDriver). the Scenario should NOT even attempt to work on Linux IMO.

I disagree because it also work on Linux. Only some key bindings with shift aren´t recognized because of the SO itself but the all the rest work.
I'll take a look tomorrow, time to sleep. Take a break too. Thanks.

@tig
Copy link
Author

tig commented Jan 2, 2024

VK_PACKET is literally a virtual key defined in winuser.h.

There is no way for such a thing to be surfaced in curses.

However if you mean the simulator can work on Linux that's a different point... that I fail to see the point of. I also don't see what api you'd use to map KeyCodes to codes chars without a Linux equivalent of MapVirtualKeyCode.

Or, if you mean netdriver in Linux: how do we get the keyboard layout there?

@BDisp
Copy link
Owner

BDisp commented Jan 2, 2024

It's defined in dotnet as well in the ConsoleKey.Packet.

@tig
Copy link
Author

tig commented Jan 2, 2024

It's defined in dotnet as well in the ConsoleKey.Packet.

Sorry, I wasn't clear. Let me try to be as clear as possible:

Last year a developer submitted an Issue where key presses were not being handled correctly when using a Terminal.Gui app over RDP (gui-cs#2008). In investigating that issue you and @tznind learned about ConsoleKey.Packet.

We learned that ConsoleKey.Packet meant:

Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP"

The only OS that knows what VK_PACKET is, is Windows.

In the fix (gui-cs#2032) a whole bunch of infrastructure code was introduced to try to decode these packets to solve for this use-case (users using Terminal.Gui apps over RDP).

In addition to the infrastructure code (ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo etc...) a Scenario was developed to try to simulate things so this code could be better tested (VkeyPacketSimulator).

As far as I can tell, none of the core Terminal.Gui maintainers (myself, you, and @tznind) regularly test the VK_PACKET support in a real scenario (e.g. over RDP).

  1. The use-case we are discussing is completely tied to Windows. The only way a ConsoleDriver will ever get a key event with ConsoleKey.Packet is if the user is running their app on Windows and using that app over RDP (there may be other cases but nobody's articulated one precisely). The only ConsoleDrivers that will ever get a key event with ConsoleKey.Packet are WindowsDriver and NetDriver.

  2. The infrastructure code discussed above is inherently flawed. Specifically: HashSet<ScanCodeMapping> _scanCodes. It assumes it is possible to have a singular map of scancodes, virtual key codes (VK_), and shift modifiers to characters that works across all keyboard layouts. It, effectively, tries to duplicate what the Win32 method MapVirtualKeyCode does in a very simplistic way (that will never work).

  3. By extension to 2) above, the VkeyPacketSimulator is inherently flawed and doesn't do what it was intended to do (provide a way of testing VK_PACKET handling in WindowsDriver and NetDriver).

  4. Not directly related to VK_PACKET support, but relevant is the fact that WindowsDriver and NetDriver have always been flawed in how they handled keys like Oem3. The only correct way to handle those keys is to use MapVirtualKeyCode (this PR addresses this).

The correct solution to all of this is:

a) Create clear and easy to use instructions for setting up a system to routinely test Terminal.Gui over RDP (and any other use-case where VK_PACKET key events are generated).
b) Change WindowsDriver and NetDriver to use MapVirtualKeyCode when key events with VK_PACKET are received. Most of the code in ConsoleKeyMapping can be thrown away. I'm partially through making this change.
c) Built a better testing API into ConsoleDriver that lets us simulate VK_PACKET key events in unit tests such that both WindowsDriver and NetDriver are better tested.
d) Refactor the VkeyPacketSimulator to use c), so we can have a UI-based tester.

I have a bad memory, and I apologize this didn't come to me before. But in 2004-ish I was partially responsible for adding VK_PACKET support to Windows. I had created the concept of Windows Media Center Extenders (Bobsled) and we were in deep collaboration with the RDP team because I had the vision that Extenders would use RDP as their protocol. All UI could run on the Media Center PC an be remoted to the Bobsled device as GDI primitives via RDP. We needed two features RDP didn't have at the time:

  • The ability to have video streams be sent to the RDP client out-of-band of the GDI streaming.
  • The ability to have commands from the IR-based MCE remote control be sent out-of-band of the GDI streaming.

The RDP team had woken up to the fact that RDP didn't support Unicode and varying keyboard layouts well. Hence VK_PACKET was created (and the other Media Center specific VK_ keys such as VK_MEDIA_NEXT_TRACK.

I am pretty sure I'm still confused on some details of all of this (e.g. the whole AltGr thing), but I'm very confident I understand the architectural issues, and I'm 100% sure that trying to build Windows virtual key to char mapping without asking Windows (MapVirtualKeyCode) for help will never scale.

I hope that there's a similar API to MapVirtualKeyCode (or GetKeyboardLayout) on the Mac and Linux. There has to be. And it's possible the keyboard coding used maps well to a lot of Windows VK_ defines. There may even be a key event that is similar to VK_PACKET where all you get is the unicode char, which would be sweet. But that is a separate discussion.

@tig
Copy link
Author

tig commented Jan 2, 2024

Found this (which I haven't read yet, but wanted to document it):

https://wiki.archlinux.org/title/Keyboard_input

Note /usr/include/linux/input-event-codes.h is where Linux key codes are defined. There is no equivalent to VK_PACKET I can see.

@BDisp
Copy link
Owner

BDisp commented Jan 2, 2024

In Linux I really add code to test only the Simulator.

Terminal.Gui/ConsoleDrivers/WindowsDriver.cs Outdated Show resolved Hide resolved
Comment on lines +1436 to +1437
new (13, VK.OEM_6, 0, '+'), // Oem6
new (13, VK.OEM_6, ConsoleModifiers.Shift, '*'),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In POR layout Oem6 is « and with shift is ». I doubt this is the right way. So you made changes to get the right values for your keyboard layout but broken the POR layout.

image

image

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't send to the second TextView on the Simulator |!"#$%&/()=?»>;:_.

Copy link
Author

@tig tig Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what's going on here, but if I enable the "POR" keyboard and press the Shift-Oem6 key (labeled =/+ on the ENG keyboard) I see this:

event: [KeyEventRecord(down,1,OEM_6,13,»,ShiftPressed)] [ConsoleKeyInfoEx(Key: 221 (Oem6) | Shift, KeyChar: » (187))]
event: [KeyEventRecord(up,1,OEM_6,13,»,ShiftPressed)] [ConsoleKeyInfoEx(Key: 221 (Oem6) | Shift, KeyChar: » (187))]

This is the correct behavior.

I can't send to the second TextView on the Simulator |!"#$%&/()=?»>;:_.

At this point, I do not care about the simulator. It serves no purpose right now other than to be a distraction. It doesn't actually simulate anything important. I am focused on fixing keyboard handling in a keyboard layout-independent way. That has nothing to with VK_PACKET nor the simulator.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the values. You changed many of these and you must check it it's all right. It appears as OEM_6 because you configured with that value on the scan code equal to 13.

image

image

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what's going on here, but if I enable the "POR" keyboard and press the Shift-Oem6 key (labeled =/+ on the ENG keyboard) I see this:

Mine is labeled »/« on POR keyboard
image.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is going to be a lot of work, like trying to resurrect a dead person. If you think it's the correct behavior because on the POR layout of your keyboard is right, then we cannot have an agreement here because on mine it isn't correct.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I use the ENG layout I get the OemPlus which is the correct behavior. So something is wrong.

image

image

image

image

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pushed a quick change that will print the keyboard layout name on each keypress.

Note that GetKeybaordLayoutName uses the current threadID and thus will only report the correct name if you did NOT change the keyboard layout after the debugging session was started.

Please

  • Set your keyboard layout to POR
  • debug UICatalog
  • Press the »/« key

Send what you see in the debug output. I see:

event: KBD: 00000816 [KeyEventRecord(down,1,OEM_6,13,«,0)] [ConsoleKeyInfoEx(Key: 221 (Oem6), KeyChar: « (171))]
event: KBD: 00000816 [KeyEventRecord(up,1,OEM_6,13,«,0)] [ConsoleKeyInfoEx(Key: 221 (Oem6), KeyChar: « (171))]

If I re-run the above, but first set my keyboard layout to ENG I see:

event: KBD: 00000409 [KeyEventRecord(down,1,OEM_PLUS,13,=,0)] [ConsoleKeyInfoEx(Key: 187 (OemPlus), KeyChar: = (61))]
event: KBD: 00000409 [KeyEventRecord(up,1,OEM_PLUS,13,=,0)] [ConsoleKeyInfoEx(Key: 187 (OemPlus), KeyChar: = (61))]

Both of these are correct.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the same as your.

event: KBD: 00000816 [KeyEventRecord(down,1,OEM_6,13,«,0)] [ConsoleKeyInfoEx(Key: 221 (Oem6), KeyChar: « (171))]
event: KBD: 00000816 [KeyEventRecord(up,1,OEM_6,13,«,0)] [ConsoleKeyInfoEx(Key: 221 (Oem6), KeyChar: « (171))]
event: KBD: 00000409 [KeyEventRecord(down,1,OEM_PLUS,13,=,0)] [ConsoleKeyInfoEx(Key: 187 (OemPlus), KeyChar: = (61))]
event: KBD: 00000409 [KeyEventRecord(up,1,OEM_PLUS,13,=,0)] [ConsoleKeyInfoEx(Key: 187 (OemPlus), KeyChar: = (61))]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good. So it's all working correctly now. Yay!

@BDisp
Copy link
Owner

BDisp commented Jan 2, 2024

@tig do you want I merge this now?

@tig
Copy link
Author

tig commented Jan 3, 2024

Feel free to merge my pr into your pr. But this is not ready to merge to v2_develop.

If you merge into your pr, I'll keep doing more PRs to yours.

@tig
Copy link
Author

tig commented Jan 3, 2024

I thought I was going crazy.

Everything was working so well.

Then I tried testing Ctrl+\ and Ctrl+Shift+\ (Oem6).

No matter what I did, I couldn't get a KeyDown for Ctrl+\.

After pulling my hair out, I discovered that ShareX is somehow intercepting Ctrl+\.

Sheesh.

@tig
Copy link
Author

tig commented Jan 3, 2024

I have been unable to get any key events with inputEvent.KeyEvent.wVirtualKeyCode == (VK)ConsoleKey.Packet via RDP.

Until we can get a setup that actually causes the code in FromVKPacketToKeyEventRecord to be called for real, we should just ignore all the related code and the VkeySimulator scenario.

@tig
Copy link
Author

tig commented Jan 3, 2024

@BDisp Windows driver seems to be working well now.

Next we need to tweak NetDriver to do the same. Right now it's pretty broken.

@BDisp
Copy link
Owner

BDisp commented Jan 3, 2024

Feel free to merge my pr into your pr. But this is not ready to merge to v2_develop.

If you merge into your pr, I'll keep doing more PRs to yours.

So I'll not merge it yet until you think it are ready to merge.

@tig
Copy link
Author

tig commented Jan 3, 2024

Are you wiling to help with NetDriver? If so, merge now?

If not, I'll try to get to NetDriver asap.

@BDisp
Copy link
Owner

BDisp commented Jan 3, 2024

I think you are in the right way and I would prefer to wait to see what is yours idea for this, if you don't mind. Thanks.

@BDisp
Copy link
Owner

BDisp commented Jan 3, 2024

@tig
Copy link
Author

tig commented Jan 3, 2024

@tig I don't know if this can help to play with VK_PACKET. https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-keybdinput

This is in Python: https://gist.github.com/wgm89/1c2c0d3ff50f63df975a

This is in C#: https://gist.github.com/pedoc/4126e8716667bdb46d4989cddffef2e6

None of these are "real world" setups. They are all simulations.

If we want more simulated/forced scenarios, we could also use my own Media Center Controller which uses SendInput for the CharsCommand and SendInputCommands.

My latest research indicates that the VK_PACKET code gets lit up when using non-Windows RDP clients (like the one on an iPad) or AnyDesk.

@tig
Copy link
Author

tig commented Jan 4, 2024

@BDisp have you ever seen NetDriver behave completely differently under WSL vs. Windows in regards to what key events it gets?

@BDisp
Copy link
Owner

BDisp commented Jan 4, 2024

I think so. What differences do you are getting?

@tig
Copy link
Author

tig commented Jan 4, 2024

I think so. What differences do you are getting?

As an example: Ctrl+7 works fine with -usc under WSL.
image

On Windows it's broken.
image

"Ctrl+8" gets "Backspace":
image

I just tested develop and v2_develop and the same thing happens there.

@BDisp
Copy link
Owner

BDisp commented Jan 4, 2024

Ok I will see that tomorrow.

@tig
Copy link
Author

tig commented Jan 4, 2024

Ok I will see that tomorrow.

Related, I think: https://devblogs.microsoft.com/dotnet/console-readkey-improvements-in-net-7/

@BDisp
Copy link
Owner

BDisp commented Jan 4, 2024

It seems Net80 complicates it even more.

@BDisp
Copy link
Owner

BDisp commented Jan 4, 2024

Sorry @tig but Console.ReadKey is very buggy on both but worse on Windows. It worked much better before. The dotnet guys are screwing this up using virtual terminal. I'm not even try to fix this because isn't Terminal.Gui issue. Not returning nothing because a key binding is consumed by the OS is normal, but returning wrong values is bug and without sense because the values can't even be mapped. I prefer to receive the values as ANSI escape and decode them, than getting very buggy values.

@tig
Copy link
Author

tig commented Jan 4, 2024

Sorry @tig but Console.ReadKey is very buggy on both but worse on Windows. It worked much better before. The dotnet guys are screwing this up using virtual terminal. I'm not even try to fix this because isn't Terminal.Gui issue. Not returning nothing because a key binding is consumed by the OS is normal, but returning wrong values is bug and without sense because the values can't even be mapped. I prefer to receive the values as ANSI escape and decode them, than getting very buggy values.

dotnet/runtime#96490

@tig
Copy link
Author

tig commented Jan 4, 2024

This makes me happy: VS2022's new Attach to Process capability actually works well for WSL finally!

image

My plan is to get NetDriver working as well as I can under WSL and ignore Windows for now. It's close.

@BDisp
Copy link
Owner

BDisp commented Jan 4, 2024

This makes me happy: VS2022's new Attach to Process capability actually works well for WSL finally!

@tig but this always worked well with the Attach to Process. What profile you are using? The only issue with VS2022 isn't providing debug WSL directly on a console by pressing F5. What I'm missing?

@BDisp
Copy link
Owner

BDisp commented Jan 4, 2024

Sorry for I misunderstood badly and merge this, but I already saw the #173.

@tig
Copy link
Author

tig commented Jan 4, 2024

Sorry for I misunderstood badly and merge this, but I already saw the #173.

All good. Back on track.

@BDisp
Copy link
Owner

BDisp commented Jan 4, 2024

This makes me happy: VS2022's new Attach to Process capability actually works well for WSL finally!

@tig but this always worked well with the Attach to Process. What profile you are using? The only issue with VS2022 isn't providing debug WSL directly on a console by pressing F5. What I'm missing?

Unless you are using a preview version with a new feature.

@tig
Copy link
Author

tig commented Jan 4, 2024

This makes me happy: VS2022's new Attach to Process capability actually works well for WSL finally!

@tig but this always worked well with the Attach to Process. What profile you are using? The only issue with VS2022 isn't providing debug WSL directly on a console by pressing F5. What I'm missing?

Unless you are using a preview version with a new feature.

Older versions would never connect for me. I'm running Version 17.9.0 Preview 2.1

@tig
Copy link
Author

tig commented Jan 9, 2024

@BDisp - see this: dotnet/runtime#96490 (comment)

I just did a test:

I commented out

		if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
			//IsWinPlatform = true;
			//try {
			//	NetWinConsole = new NetWinVTConsole ();
			//} catch (ApplicationException) {
			//	// Likely running as a unit test, or in a non-interactive session.
			//}
		}

Theoretically, this should have made Windows work just like WSL. And for KEYBOARD it does!

image

To debug further, based on Adam's question, I tried this:

		if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT) {
			//mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
			if (!SetConsoleMode (_inputHandle, mode)) {
				throw new ApplicationException ($"Failed to set input console mode, error code: {GetLastError ()}.");
			}
		}

By not enabling ENABLE_VIRTUAL_TERMINAL_INPUT the keyboard problem on Windows is fixed too.

But, mouse no longer works.

Do you know why Mouse does not work on Windows with NetDriver but does on WSL with native System.Console without all the ENABLE_VIRTUAL_TERMINAL_INPUT stuff?

Before I reply to Adam, I'd like to understand the history better. Not sure if you know or not. Thanks.

@BDisp
Copy link
Owner

BDisp commented Jan 9, 2024

@tig - Without the ENABLE_VIRTUAL_TERMINAL_INPUT on Windows the mouse doesn't work because Console.ReadKey will only return the keyboard keys and not mouse clicks/move. With ENABLE_VIRTUAL_TERMINAL_INPUT then the Console.ReadKey will return all the ANSI escape sequences when the keyboard and mouse are used.

@tig tig deleted the BDisp-v2_vkeypacketsimulator-fix_3054 branch April 3, 2024 01:47
BDisp pushed a commit that referenced this pull request Dec 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants