问题
I have an application that logs whatever the user press, but when I press special characters like ´
with a
, to get á
, I get ´´a
; same thing when I want to get à
, then i get ``a
, so all special characters get typed twice and then the regular character get typed after.
I have searched for ever and can't find anything really. But I have noticed that the problem is in the ToAscii
method , without that the characters are typed correctly.
public string GetString(IntPtr lParam, int vCode)
{
try
{
bool shift = Keys.Shift == Control.ModifierKeys || Console.CapsLock;
string value = "";
KeyboardHookStruct MyKeyboardHookStruct =
(KeyboardHookStruct)Marshal.PtrToStructure(
lParam, typeof(KeyboardHookStruct));
byte[] keyState = new byte[256];
byte[] inBuffer = new byte[2];
DllClass.GetKeyboardState(keyState);
var ascii=
DllClass.ToAscii(
MyKeyboardHookStruct.vkCode,
MyKeyboardHookStruct.scanCode,
keyState, inBuffer, MyKeyboardHookStruct.flags
);
if (ascii == 1)
{
char key = (char)inBuffer[0];
if ((shift) && Char.IsLetter(key))
key = Char.ToUpper(key);
value = key.ToString();
}
return value;
}
catch (Exception)
{
return "";
}
}
Am I missing something or doing something wrong? All other characters are working perfectly but it's the special characters that is coming as double chars.
EDIT:
Trying with ToUnicode
instead.
[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern int ToUnicode(
uint virtualKey, uint scanCode, byte[] keyStates,
[MarshalAs(UnmanagedType.LPArray)] [Out] char[] chars,
int charMaxCount, uint flags);
public string GetString(IntPtr lParam, int vCode)
{
try
{
bool shift = Keys.Shift == Control.ModifierKeys || Console.CapsLock;
string value = "";
KeyboardHookStruct MyKeyboardHookStruct =
(KeyboardHookStruct)Marshal.PtrToStructure(
lParam, typeof(KeyboardHookStruct));
byte[] keyState = new byte[256];
byte[] inBuffer = new byte[2];
char[] chars = new char[2];
DllClass.GetKeyboardState(keyState);
int val = 0;
val = ToUnicode(
(uint)MyKeyboardHookStruct.vkCode,
(uint)MyKeyboardHookStruct.scanCode,
keyState, chars, chars.Length, 0
);
val = ToUnicode(
(uint)MyKeyboardHookStruct.vkCode,
(uint)MyKeyboardHookStruct.scanCode,
keyState, chars, chars.Length, 0
);
if (val == 1)
{
char key = (char)chars[0];
if ((shift) && Char.IsLetter(key))
key = Char.ToUpper(key);
value = key.ToString();
}
return value;
}
catch (Exception)
{
return "";
}
}
Someone PLEASE help me, I really need to figure this out =/
.
EDIT:
int val = -1;
if (IsDeadKey((uint)vCode))
{
while (val == -1)
{
val = ToUnicode(
(uint)MyKeyboardHookStruct.vkCode,
(uint)MyKeyboardHookStruct.scanCode,
keyState, chars, chars.Length, 0
);
}
}
else
val = ToUnicode(
(uint)MyKeyboardHookStruct.vkCode,
(uint)MyKeyboardHookStruct.scanCode,
keyState, chars, chars.Length, 0
);
So now I have tried calling the ToAscii
or ToUnicode
a couple of times to flush the real character but without success. Am I doing it wrong?
Like for ASCII, first call for ´
I get -1
, so I call it again, then I get 1
; and then I press like a
, to get á
, but then I only get a
. Same thing if I use ToUnicode
twice after each other, I get just a
instead of á
, and so on ...
回答1:
The myth about
ToAscii
andToUnicode
In the question, you mentioned about that you've tried both
ToAscii
andToUnicode
with no success. And I also searched a question which is relative:ToAscii/ToUnicode in a keyboard hook destroys dead keys
I'm not saying any answer is right or wrong. However, we can think about:
A (not so) tricky game
In this game, I give the player a random flipped 50 cent coin at a time. One can challenge to get one dollar bill from me, if who collected a pair of 50 cent coins with one is a head and another is a tail.
If one gave up to challenge, then who can reserve that 50 cent, and the game restarts. If who tried but did not collect two meet the rule, then I ask to return me those I've given.
How can one get one dollar bill from me without losing any coin?
Maybe a time-travelling ..
Base on [Processing Global Mouse and Keyboard Hooks in C#] on CodeProject
and my very old answer: Konami Code in C#
I did some modification for answering your question.
Code
namespace Gma.UserActivityMonitor { using System.Diagnostics; using System.Windows.Forms; using System.Collections.Generic; using System.Linq; partial class HookManager { private static int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) { // indicates if any of underlaing events set e.Handled flag bool handled=false; if(nCode>=0) { // read structure KeyboardHookStruct at lParam var MyKeyboardHookStruct= (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct)); // raise KeyDown if(s_KeyDown!=null&&(wParam==WM_KEYDOWN||wParam==WM_SYSKEYDOWN)) { Keys keyData=(Keys)MyKeyboardHookStruct.VirtualKeyCode; KeyEventArgs e=new KeyEventArgs(keyData); s_KeyDown.Invoke(null, e); handled=e.Handled; } // raise KeyPress if(s_KeyPress!=null&&wParam==WM_KEYDOWN) { var keyText=GetString(lParam, nCode, ref handled); if(""!=keyText) { var keyChar=keyText.First(); Debug.Print("keyText => {0}", keyText); #if false if(AccentFormatter.Combination.Values.Contains(keyChar)) { SendKeys.Send("\b"+keyText); return -1; } #endif } } // raise KeyUp if(s_KeyUp!=null&&(wParam==WM_KEYUP||wParam==WM_SYSKEYUP)) { Keys keyData=(Keys)MyKeyboardHookStruct.VirtualKeyCode; KeyEventArgs e=new KeyEventArgs(keyData); s_KeyUp.Invoke(null, e); handled=handled||e.Handled; } } // if event handled in application do not handoff to other listeners if(handled) return -1; // forward to other application return CallNextHookEx(s_KeyboardHookHandle, nCode, wParam, lParam); } public static String GetString(IntPtr lParam, int vCode, ref bool handled) { var MyKeyboardHookStruct= (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct)); bool isDownShift=((GetKeyState(VK_SHIFT)&0x80)==0x80?true:false); bool isDownCapslock=(GetKeyState(VK_CAPITAL)!=0?true:false); byte[] keyState=new byte[256]; GetKeyboardState(keyState); byte[] inBuffer=new byte[2]; var keyText=""; var ascii= ToAscii( MyKeyboardHookStruct.VirtualKeyCode, MyKeyboardHookStruct.ScanCode, keyState, inBuffer, MyKeyboardHookStruct.Flags ); if(ascii==1) { char key=(char)inBuffer[0]; if((isDownCapslock^isDownShift)&&Char.IsLetter(key)) key=Char.ToUpper(key); KeyPressEventArgs e=new KeyPressEventArgs(key); s_KeyPress.Invoke(null, e); handled=handled||e.Handled; keyText=new String(new[] { e.KeyChar }); var sequence=KeySequence.Captured(e.KeyChar); if(null!=sequence) keyText=sequence.ToString(AccentFormatter.Default); } return keyText; } } public class KeySequence { public String ToString(IFormatProvider provider) { return null==provider ?new String(Sequence.Select(x => (char)x).ToArray()) :String.Format(provider, "{0}", Sequence); } public override String ToString() { return this.ToString(default(IFormatProvider)); } public bool Captures(int keyValue) { for(var i=Sequence.Length; i-->0; ) { if(Sequence[i]!=keyValue) { if(0==i) Count=0; continue; } if(Count!=i) continue; ++Count; break; } var x=Sequence.Length==Count; Count=x?0:Count; return x; } public KeySequence(int[] newSequence) { Sequence=newSequence; } public static KeySequence Captured(int keyValue) { return m_List.FirstOrDefault(x => x.Captures(keyValue)); } public int Count { private set; get; } public int[] Sequence { set; get; } static KeySequence() { m_List.AddRange( from x in AccentFormatter.Combination.Keys let intArray=x.Select(c => (int)c).ToArray() select new KeySequence(intArray) ); } static readonly List<KeySequence> m_List=new List<KeySequence>(); } public class AccentFormatter: IFormatProvider, ICustomFormatter { String ICustomFormatter.Format(String format, object arg, IFormatProvider formatProvider) { return GetAccent(new String((arg as int[]).Select(x => (char)x).ToArray())); } object IFormatProvider.GetFormat(Type formatType) { return typeof(ICustomFormatter)!=formatType?null:this; } public static String GetAccent(String input) { return Combination.Keys.Contains(input, StringComparer.OrdinalIgnoreCase) ?Combination[input].ToString() :""; } static AccentFormatter() { AcuteSymbol=((char)0xb4).ToString(); GraveSymbol=('`').ToString(); var ae=(char)0xe6; var oe=(char)0xf8; AcuteCandidates="acegiklmnoprsuwyz".ToArray().Concat(new[] { ae, oe }).ToArray(); GraveCandidates="aeinouwy".ToArray(); var lowerAcuteAccents=( new[] { 0xe1, 0x107, 0xe9, 0x1f5, 0xed, 0x1e31, 0x13a, 0x1e3f, 0x144, 0xf3, 0x1e55, 0x155, 0x15b, 0xfa, 0x1e83, 0xfd, 0x17a, 0x1fd, 0x1ff } ).Select( (x, i) => new { Key=AcuteSymbol+AcuteCandidates[i], Value=(char)x } ); var upperAcuteAccents=( new[] { 0xc1, 0x106, 0xc9, 0x1f4, 0xcd, 0x1e30, 0x139, 0x1e3e, 0x143, 0xd3, 0x1e54, 0x154, 0x15a, 0xda, 0x1e82, 0xdd, 0x179, 0x1fc, 0x1fe } ).Select( (x, i) => new { Key=AcuteSymbol+char.ToUpper(AcuteCandidates[i]), Value=(char)x } ); var lowerGraveAccents=( new[] { 0xe0, 0xe8, 0xec, 0x1f9, 0xf2, 0xf9, 0x1e81, 0x1ef3 } ).Select( (x, i) => new { Key=GraveSymbol+GraveCandidates[i], Value=(char)x } ); var upperGraveAccents=( new[] { 0xc0, 0xc8, 0xcc, 0x1f8, 0xd2, 0xd9, 0x1e80, 0x1ef2 } ).Select( (x, i) => new { Key=GraveSymbol+char.ToUpper(GraveCandidates[i]), Value=(char)x } ); Combination= lowerAcuteAccents .Concat(upperAcuteAccents) .Concat(lowerGraveAccents) .Concat(upperGraveAccents) .ToDictionary(x => x.Key, x => x.Value); } public static readonly Dictionary<String, char> Combination; public static readonly String AcuteSymbol, GraveSymbol; public static readonly char[] AcuteCandidates, GraveCandidates; public static readonly AccentFormatter Default=new AccentFormatter(); } }
First off, with the code downloaded from CodeProject, find
HookManager.Callbacks.cs
and delete the whole methodKeyboardHookProc
.Then You can save the code above in a new file such like
HookManager.Modified.cs
and add it in projectGma.UserActivityMonitor
, or just paste it at the rear ofHookManager.Callbacks.cs
.Remember to set
Gma.UserActivityMonitorDemo
as startup project. It was set target framework to 2.0, and you might want to set to a higher.Input → Output
About the accent
As I searched there are two kind of general accent, they are grave accent and acute accent.
The diacritic of acute accent is´
, and the possible letters areáǽćéǵíḱĺḿńóǿṕŕśúẃýź
.
The diacritic of grave accent is ``, and the possible letters are
àèìǹòùẁỳ`.Both of them are possible in upper case or lower case, but I did not find an easy enough way works with them in classes those are built-in of the framework. For example, I cannot use
ToUpper
orToLower
to get a opposite case withǸ
andǹ
, I even triedToUpperInvariant
andToLowerInvariant
. Thus I choose to use a hard-coded sequence inAccentFormatter
, and you may change it to reading from files, or implement another custom formatter yourself.Array arrangement of hexadecimal representation
In the code, I arranged the acute accent sequence as:
0xc1, 0x106, 0xc9, 0x1f4, 0xcd, 0x1e30, 0x139, 0x1e3e, 0x143, 0xd3, 0x1e54, 0x154, 0x15a, 0xda, 0x1e82, 0xdd, 0x179, 0x1fc, 0x1fe
which is:
ÁĆ ÉǴ ÍḰĹḾŃ ÓṔŔŚ ÚẂÝŹ ǼǾ
The
Ǽ
andǾ
are put at the rear becauseÆ
andØ
do not have a corresponding character in ASCII(non-extended).I preferred to save the code in ANSI with a default code page, since I have a different
CultureInfo
from the languages which is in alphabet. And you might want to change them to exact characters for readability.Send keys to the active application
If you want to send the key to the active application, then do the following change in the code:
change
#if false
to
#if !false
Inside the conditional compilation block, the code fragment is
if(AccentFormatter.Combination.Values.Contains(keyChar)) { SendKeys.Send("\b"+keyText); return -1; }
It use a backspace character to erase the previous typed ``
or
´` once it recognized as a specific combination. Backspace here is the time machine ..
After this demo, I guess you'll know how to modify them to merge in your code.
回答2:
But i have noticed that the problem is in the ToAsciii method , without that the characters are typed correctly.
That's exactly what I was about to guess. I appreciate you having done the legwork for me! :-)
The problem is that these "special" characters are not ASCII characters. That is, they're actually some type of fancy-pants Unicode characters that are not part of the ASCII character set.
When you try to convert them to ASCII characters, the function presumably does the best it can, decomposing the code points that make up á
into the separate characters ´
and a
.
Obviously that's not what you want. You want to treat á
as a single character, so you need to use Unicode. That's not really a problem: Windows has been all Unicode internally for at least a decade. Ditch the antiquated ToAscii function; instead you'll want to use either MapVirtualKey or MapVirtualKeyEx to convert the virtual key code you're getting through the low-level keyboard hook into a character value.
来源:https://stackoverflow.com/questions/15488682/double-characters-shown-when-typing-special-characters-while-logging-keystrokes