SendKeys alternative that works on Citrix

11k views Asked by At

I recently developed a virtual keyboard application for a customer. The program is working fine with almost all programs, but certain commands like {ENTER} or {DEL} are not working with Citrix. Is there are workaround or an alternative to SendKeys?

Edit 1: I tried the SendInput method (Windows Input Simulator uses SendInput) and the DEL key as well as the arrow keys are still not working. The ENTER key works however.

Edit 2: Solved it. Tested with two different versions of Citrix. This question helped me a lot.:

Citrix thin clients uses the scancode param of keybd_event even when MS says it is unused and should be 0. You need to supply the physical scancode aswell for the citrix client to get it. Citrix client also has major problem with keyboard input generated with the SendInput API.

I patched the code in Windows Input Simulator:

// Function used to get the scan code
[DllImport("user32.dll")]
static extern uint MapVirtualKey(uint uCode, uint uMapType);


/// <summary>
/// Calls the Win32 SendInput method ...
/// </summary>
/// <param name="keyCode">The VirtualKeyCode to press</param>
public static void SimulateKeyPress(VirtualKeyCode keyCode)
{
    var down = new INPUT();
    down.Type = (UInt32)InputType.KEYBOARD;
    down.Data.Keyboard = new KEYBDINPUT();
    down.Data.Keyboard.Vk = (UInt16)keyCode;
    // Scan Code here, was 0
    down.Data.Keyboard.Scan = (ushort) MapVirtualKey((UInt16)keyCode, 0);
    down.Data.Keyboard.Flags = 0;
    down.Data.Keyboard.Time = 0;
    down.Data.Keyboard.ExtraInfo = IntPtr.Zero;

    var up = new INPUT();
    up.Type = (UInt32)InputType.KEYBOARD;
    up.Data.Keyboard = new KEYBDINPUT();
    up.Data.Keyboard.Vk = (UInt16)keyCode;
    // Scan Code here, was 0
    up.Data.Keyboard.Scan = (ushort)MapVirtualKey((UInt16)keyCode, 0);
    up.Data.Keyboard.Flags = (UInt32)KeyboardFlag.KEYUP;
    up.Data.Keyboard.Time = 0;
    up.Data.Keyboard.ExtraInfo = IntPtr.Zero;

    INPUT[] inputList = new INPUT[2];
    inputList[0] = down;
    inputList[1] = up;

    var numberOfSuccessfulSimulatedInputs = SendInput(2, 
         inputList, Marshal.SizeOf(typeof(INPUT)));
    if (numberOfSuccessfulSimulatedInputs == 0) 
       throw new Exception(
       string.Format("The key press simulation for {0} was not successful.", 
       keyCode));
}
4

There are 4 answers

1
Giorgi On BEST ANSWER

Try using Windows Input Simulator. Not sure if it supports Citrix but it is much more powerfull compared to SendKeys.

0
globalheap On

Try to utilize API call wia P-Invoke signature (Content edited: this is now working example - I'm sending character 'a' to the textBox, on the click of a button) :

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime;
using System.Runtime.InteropServices;

namespace Test2
{
    public partial class Form1 : Form
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct KEYBOARD_INPUT
        {
            public const uint Type = 1;
            public ushort wVk;
            public ushort wScan;
            public uint dwFlags;
            public uint time;
            public IntPtr dwExtraInfo;
        }  

        [StructLayout(LayoutKind.Sequential)]
        struct MOUSEINPUT
        {
             public int dx;
             public int dy;
             public uint mouseData;
             public uint dwFlags;
             public uint time;
             public IntPtr dwExtraInfo;
        };

        [StructLayout(LayoutKind.Explicit)]
        struct KEYBDINPUT 
        {
            [FieldOffset(0)]
            public ushort wVk;
            [FieldOffset(2)]
            public ushort wScan;
            [FieldOffset(4)]
            public uint dwFlags;
            [FieldOffset(8)]
            public uint time;
            [FieldOffset(12)]
            public IntPtr dwExtraInfo;
        };

        [StructLayout(LayoutKind.Sequential)]
        struct HARDWAREINPUT
        {
             public uint uMsg;
             public ushort wParamL;
             public ushort wParamH;
        };

        [StructLayout(LayoutKind.Explicit)]
        struct INPUT 
        {
             [FieldOffset(0)]
             public int type;
             [FieldOffset(4)]
             public MOUSEINPUT mi;
             [FieldOffset(4)]
             public KEYBDINPUT ki;
             [FieldOffset(4)]
             public HARDWAREINPUT hi;
        };
        [DllImport("user32.dll", SetLastError = true)]
        static extern uint SendInput(uint nInputs, IntPtr pInput, int cbSize);

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            textBox1.Focus();
            INPUT Input = new INPUT();

            Input.type = 1;
            Input.ki.wVk = 0x41;  //ASCII for letter 'A'
            Input.ki.dwFlags = 0;  //Key is pressed down
            Input.ki.dwExtraInfo = IntPtr.Zero;
            IntPtr pInput;
            pInput = Marshal.AllocHGlobal(Marshal.SizeOf(Input));

            Marshal.StructureToPtr(Input, pInput, false);
            SendInput(1, pInput, Marshal.SizeOf(Input));
            Input.ki.dwFlags = 2;  //Key is released on the keyboard

            Marshal.StructureToPtr(Input, pInput, false);
            SendInput(1, pInput, Marshal.SizeOf(Input));
        }
    }
}
1
CustodianOfCode On

I'm also attempting to control a citrix application using the windows InputSimulator library. Your code above looked promising, so I updated it to work with the latest version of InputSimulator (where you use sim.Keyboard.Keypress rather than InputSimulator.SimulateKeyPress). Here is the code that I added to InputSimulator, and I am delighted to report that it works as expected, and solves a problem that I previously thought was not possible. Thanks so much.

In IKeyboardSimulator.cs:

    /// <summary>
    /// Simulates the key press gesture for the specified key.
    /// </summary>
    /// <param name="keyCode">The <see cref="VirtualKeyCode"/> for the key.</param>
    IKeyboardSimulator CITRIXKeyPress(VirtualKeyCode keyCode);

In KeyboardSimulator.cs:

    using System.Runtime.InteropServices;

    .
    .
    .

    // CITRIX HACK
    // Function used to get the scan code
    [DllImport("user32.dll")]
    static extern uint MapVirtualKey(uint uCode, uint uMapType);

    [DllImport("User32.dll")]
    private static extern uint SendInput(uint numberOfInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] INPUT[] input, int structSize);


    /// <summary>
    /// Calls the Win32 SendInput method ...
    /// </summary>
    /// <param name="keyCode">The VirtualKeyCode to press</param>
    public IKeyboardSimulator CITRIXKeyPress(VirtualKeyCode keyCode) //prev public static void
    {
        var down = new INPUT();
        down.Type = (UInt32)InputType.Keyboard;
        down.Data.Keyboard = new KEYBDINPUT();
        down.Data.Keyboard.KeyCode = (UInt16)keyCode; //prev .Keyboard.Vk
        // Scan Code here, was 0
        down.Data.Keyboard.Scan = (ushort)MapVirtualKey((UInt16)keyCode, 0);
        down.Data.Keyboard.Flags = 0;
        down.Data.Keyboard.Time = 0;
        down.Data.Keyboard.ExtraInfo = IntPtr.Zero;

        var up = new INPUT();
        up.Type = (UInt32)InputType.Keyboard;
        up.Data.Keyboard = new KEYBDINPUT();
        up.Data.Keyboard.KeyCode = (UInt16)keyCode;
        // Scan Code here, was 0
        up.Data.Keyboard.Scan = (ushort)MapVirtualKey((UInt16)keyCode, 0);
        up.Data.Keyboard.Flags = (UInt32)KeyboardFlag.KeyUp;
        up.Data.Keyboard.Time = 0;
        up.Data.Keyboard.ExtraInfo = IntPtr.Zero;

        INPUT[] inputList = new INPUT[2];
        inputList[0] = down;
        inputList[1] = up;

        var numberOfSuccessfulSimulatedInputs = SendInput(2,
             inputList, Marshal.SizeOf(typeof(INPUT)));
        if (numberOfSuccessfulSimulatedInputs == 0)
            throw new Exception(
            string.Format("The key press simulation for {0} was not successful.",
            keyCode));
        return this;
    }
0
Jaws On

For the windows input simulator solution, you can modify the source code directly so that the built in functions will send scan codes with the virtual keys.

InputBuilder.cs:

using System.Runtime.InteropServices;
.
.
.
[DllImport("user32.dll")]
static extern uint MapVirtualKeyEx(uint uCode, uint uMapType, IntPtr dwhkl);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern short VkKeyScanEx(char ch, IntPtr dwhkl);
.
.
.
public InputBuilder AddKeyDown(VirtualKeyCode keyCode)
{
    var down =
        new INPUT
        {
            Type = (UInt32)InputType.Keyboard,
            Data =
                    {
                        Keyboard =
                            new KEYBDINPUT
                                {
                                    KeyCode = (UInt16) keyCode,
                                    Scan = (UInt16)MapVirtualKeyEx((UInt16)keyCode, 0, IntPtr.Zero),
                                    Flags = IsExtendedKey(keyCode) ? (UInt32) KeyboardFlag.ExtendedKey : (UInt32) KeyboardFlag.ScanCode,
                                    Time = 0,
                                    ExtraInfo = IntPtr.Zero
                                }
                    }
            };

    _inputList.Add(down);
    return this;
}
.
.
.
public InputBuilder AddKeyUp(VirtualKeyCode keyCode)
{
    var up =
        new INPUT
            {
                Type = (UInt32) InputType.Keyboard,
                Data =
                    {
                        Keyboard =
                            new KEYBDINPUT
                                {
                                    KeyCode = (UInt16) keyCode,
                                    Scan = (UInt16)MapVirtualKeyEx((UInt16)keyCode, 0,IntPtr.Zero),
                                    Flags = (UInt32) (IsExtendedKey(keyCode)
                                                          ? KeyboardFlag.KeyUp | KeyboardFlag.ExtendedKey
                                                          : KeyboardFlag.KeyUp | KeyboardFlag.ScanCode),
                                    Time = 0,
                                    ExtraInfo = IntPtr.Zero
                                }
                    }
            };

    _inputList.Add(up);
    return this;
}
.
.
.
public InputBuilder AddCharacter(char character)
{
    bool shiftChr = ((UInt16)VkKeyScanEx(character, IntPtr.Zero) >> 8).Equals(1);
    if (shiftChr)
    {
        AddKeyDown(VirtualKeyCode.VK_SHIFT);
    }

    UInt16 scanCode = shiftChr ? (UInt16)MapVirtualKeyEx((UInt16)(VkKeyScanEx(character, IntPtr.Zero) & 0xff),0,IntPtr.Zero) : (UInt16)MapVirtualKeyEx((UInt16)VkKeyScanEx(character, IntPtr.Zero), 0, IntPtr.Zero);

    var down = new INPUT
                   {
                       Type = (UInt32)InputType.Keyboard,
                       Data =
                           {
                               Keyboard =
                                   new KEYBDINPUT
                                       {
                                           KeyCode = 0,
                                           Scan = scanCode,
                                           Flags = (UInt32)KeyboardFlag.ScanCode,
                                           Time = 0,
                                           ExtraInfo = IntPtr.Zero
                                       }
                           }
                   };

    var up = new INPUT
                 {
                     Type = (UInt32)InputType.Keyboard,
                     Data =
                         {
                             Keyboard =
                                 new KEYBDINPUT
                                     {
                                         KeyCode = 0,
                                         Scan = scanCode,
                                         Flags =
                                             (UInt32)(KeyboardFlag.KeyUp | KeyboardFlag.ScanCode),
                                         Time = 0,
                                         ExtraInfo = IntPtr.Zero
                                     }
                         }
                 };

    _inputList.Add(down);
    _inputList.Add(up);

    if (shiftChr)
    {
        AddKeyUp(VirtualKeyCode.VK_SHIFT);
    }

    return this;
}

With these changes TextEntry, KeyPress, and ModifiedKeyStroke will send the scan codes associated with the virtual keys passed in.