Extended scancodes in sendinput

A while ago I wrote a virtual KVM program that would allow you to drive the keyboard and mouse of other computers (even cross-platform), using a single host machine. Communication was done over the network. I kept track a the "virtual" position of the mouse, and if you veered into the screen position of another computer I would eat all keystrokes / mouse movement via a hook and replay it on the other machine. https://github.com/michaelwda/OutsideTheBox/blob/master/OTB.Core/Hook/Platform/Windows/WindowsGlobalHook.cs

Pretty fun code to write, for a toy project. I ran into some interesting things regarding keyboard input. Really the best method here is to go as low-level as possible. On Windows I decided to use SendInput, but it would probably be even better to write a virtual keyboard/mouse driver.

When using SendInput, you can specify that it's a keyboard input and send the scancodes directly. This gives you lots of power to replay keystrokes exactly, but it was a little tricky when trying to replay extended scancodes.

From some old comments of mine:

/*
* The extended-key flag indicates whether the keystroke message originated from one of the additional keys on the enhanced keyboard.
* The extended keys consist of the ALT and CTRL keys on the right-hand side of the keyboard; the INS, DEL, HOME, END, PAGE UP, PAGE DOWN, and arrow keys in the clusters
* to the left of the numeric keypad; the NUM LOCK key; the BREAK (CTRL+PAUSE) key; the PRINT SCRN key; and the divide (/) and ENTER keys in the numeric keypad. The extended-key flag is set if the key is an extended key.
*/

If you're trying to replay an extended scancode via sendinput, you have to pass an array of scancodes with the first one being 0Xe0.

 public override void SendKeyDown(Key key)
        {
            int tscancode;
            VirtualKeys tvk;
            int tflags;
            var keyup = false;
            var altDown = KeyboardState.IsKeyDown(Key.AltLeft) || KeyboardState.IsKeyDown(Key.AltRight);
            bool extended;
            WinKeyMap.ReverseTranslateKey(key, keyup, altDown, out tscancode, out tvk, out tflags, out extended);

            bool sysKey = (!altDown && key == Key.AltLeft) || (!altDown && key == Key.AltRight) || ((key != Key.AltLeft && key != Key.AltRight && altDown));

            var dwFlags = 0x0008;
            if (extended)
                dwFlags = dwFlags | 0x0001;

            var altdown = ((tflags) & ((int)KeyFlags.KF_ALTDOWN >> 8)) > 0;
            var dlgmode = ((tflags) & ((int)KeyFlags.KF_DLGMODE >> 8)) > 0;
            var menumode = ((tflags) & ((int)KeyFlags.KF_MENUMODE >> 8)) > 0;
            var repeat = ((tflags) & ((int)KeyFlags.KF_REPEAT >> 8)) > 0;
            var up = ((tflags) & ((int)KeyFlags.KF_UP >> 8)) > 0;

            KeyboardState.SetKeyState(key, true);

            NativeMethods.INPUT[] inputs;
            if (extended)
            {
                inputs = new[]
                {
                    new NativeMethods.INPUT
                    {
                        type = NativeMethods.INPUT_KEYBOARD,

                        u = new NativeMethods.InputUnion
                        {
                            ki = new NativeMethods.KEYBDINPUT()
                            {
                                wScan = (ushort) 0xe0,
                                wVk = (ushort) 0,
                                dwFlags = (ushort) 0,
                                dwExtraInfo = NativeMethods.GetMessageExtraInfo()
                            }
                        }
                    },
                    new NativeMethods.INPUT
                    {
                        type = NativeMethods.INPUT_KEYBOARD,

                        u = new NativeMethods.InputUnion
                        {
                            ki = new NativeMethods.KEYBDINPUT()
                            {
                                wScan = (ushort) tscancode,
                                wVk = (ushort) tvk,
                                dwFlags = (ushort) dwFlags,
                                dwExtraInfo = NativeMethods.GetMessageExtraInfo()
                            }
                        }
                    }
                };
            }
            else
            {
                inputs = new[]
                {
                    new NativeMethods.INPUT
                    {
                        type = NativeMethods.INPUT_KEYBOARD,

                        u = new NativeMethods.InputUnion
                        {
                            ki = new NativeMethods.KEYBDINPUT()
                            {
                                wScan = (ushort) tscancode,
                                wVk = (ushort) tvk,
                                dwFlags = (ushort) dwFlags,
                                dwExtraInfo = NativeMethods.GetMessageExtraInfo()
                            }
                        }
                    }
                };
            }

            NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
        }

I couldn't find this documented ANYWHERE except in a footnote of an image in some scanned documentation. Maybe it's in some manuals somewhere, but I had the hardest time figuring it out.

At some point I'm going to reboot and completely rewrite this project, especially now that C# has traits :)