Double characters shown when typing special characters while logging keystrokes in c#

霸气de小男生 提交于 2019-12-19 04:15:07

问题


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 and ToUnicode

    In the question, you mentioned about that you've tried both ToAscii and ToUnicode 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 method KeyboardHookProc.

    Then You can save the code above in a new file such like HookManager.Modified.cs and add it in project Gma.UserActivityMonitor, or just paste it at the rear of HookManager.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 or ToLower to get a opposite case with Ǹ and ǹ, I even tried ToUpperInvariant and ToLowerInvariant. Thus I choose to use a hard-coded sequence in AccentFormatter, 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!