1

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 ...

Ken Kin
  • 4,503
  • 3
  • 38
  • 76
syncis
  • 1,395
  • 4
  • 25
  • 43
  • 11
    Keyloggers... They shouldn't even exist. – Robert Harvey Mar 18 '13 at 23:11
  • 1
    I totally agree my friend but this aint a keylogger of the kind that you think of. This is actually a school project for people that having problems of remembering usernames/passwords and with this tool they will have everything saved in a safe place. – syncis Mar 18 '13 at 23:17
  • 9
    Yeah, there's nothing unsafe about keyloggers and passwords. – Cody Gray - on strike Mar 18 '13 at 23:23
  • @syncis: So now you get it to work. – Ken Kin Apr 02 '13 at 23:35
  • @KenKin Actually no, i havent =/ – syncis Apr 03 '13 at 09:55
  • @syncis: You may ask for the question you further encountered. – Ken Kin Apr 04 '13 at 15:09
  • Sry m8 but ur code its just not working as I want it, try this : If you go to notepad right now without logging and type first ´ and then a, you will get á, that's how I want it to be, logged correctly and text in notepad while logging should be correct also. When I removed the = 1 check in your code, it would log á and all those characters but in notepad it just screwed up with double accents every time I typed ´ or ` . – syncis Apr 23 '13 at 08:46

2 Answers2

5

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.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • Thank you very much for this information, but i have 1 question. There is also a method that is similiar to ToAsciii, named ToUnicode, couldnt that be something or should i try with mapvirtualkey? – syncis Mar 18 '13 at 23:38
  • @syncis Yes, you might have some luck with `ToUnicode`/`ToUnicodeEx`, but both of these methods still have problems with dead keys. One possible workaround is to call it a second time if you get a dead key (a return value of -1) the first time through. Related reading: http://blogs.msdn.com/b/michkap/archive/2005/01/19/355870.aspx – Cody Gray - on strike Mar 18 '13 at 23:41
  • Ok lets try the first method by calling them twice, i first call it with ToAscii, and if it returns -1, i call it again right? Trying that right now and i didnt get double chars BUT i didnt get the ´ over the a, i just got the a, instead of á. Maybe mapvirtualkeyex solves this? – syncis Mar 18 '13 at 23:49
  • `ToUnicode` will solve that. Your issue with `ToAscii` is still that `á` is not an ASCII character. – Cody Gray - on strike Mar 18 '13 at 23:52
  • Hey Cody, please check my post, i have edited now and included the tounicode code, but its still the same result. What am i missing? – syncis Mar 19 '13 at 00:15
  • I just wanted to add that calling Tounicode or Toascoo twice will result in only getting an a instead of á, so the double accent is removed but now I aint getting the accent over the a. – syncis Mar 19 '13 at 09:17
  • 2
    Your code still doesn't check return values, it just blindly calls the function twice. I strongly suggest reading the linked documentation. – Cody Gray - on strike Mar 19 '13 at 16:57
  • Ok Check my edit now, you mean something like that? I have read the documentation and it says that i should just call tounicode until i get the right value.... – syncis Mar 19 '13 at 22:27
3
  • 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

    Vmh1A.jpg

  • 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.

Community
  • 1
  • 1
Ken Kin
  • 4,503
  • 3
  • 38
  • 76
  • +1 thanks for sharing. I did a bunch of research and found many people getting stuck on this. – Jeremy Thompson Mar 31 '13 at 04:04
  • Í cant get it to work, still getting the a without the á. What am I doing wrong, I am using the exact project that you mentioned and upgraded both projects to 4.0 and they still wont work. – syncis Mar 31 '13 at 18:10
  • Yea i think i got ur idea right now, very good my friend, very good! I think actually I can somehow make this work with my application because I am receiving the á now. – syncis Mar 31 '13 at 18:22
  • @JeremyThompson: Thank you. But I'm not sure *stuck on this* means *global hook* or *capture specific key sequence* .. – Ken Kin Mar 31 '13 at 18:30
  • I noticed the same problem now, it works within the application but when I am logging and writing in notepad, I will get ´´a ... back to scratch 1 :( Maybe changing ToAscii to ToUnicode will work? – syncis Mar 31 '13 at 18:44
  • Yea please do, but I know that the problem with the ´´a lies in the ToAscii call and not in the return value but we will see what we can find out. – syncis Mar 31 '13 at 18:54
  • Getting error at : AcuteCandidates = "acegiklmnoprsuwyz".Concat(new[] { ae, oe }).ToArray(); Member 'string.Concat(System.Collections.Generic.IEnumerable)' cannot be accessed with an instance reference; qualify it with a type name instead – syncis Apr 01 '13 at 08:31
  • Sorry m8 :/ First of, with ascii == 1 , I will never get the á because once the ´ its pressed, the ascii return value ´ will never even be processed, and in notepad it still showed ´´a =/, try typing in notepad and see what you get there when you type á. – syncis Apr 01 '13 at 08:48
  • When I am logging, and opening notepad to test it, pressing ´ will result in ´´ , that means double accents. And if i press a after it will be just an a without the ´. – syncis Apr 01 '13 at 09:22
  • I am doing exactly as you are doing in your code but I am testing the logging in notepad also instead of only in the application. Problems lies in the ToAscii call, without that it wont do double accents. – syncis Apr 01 '13 at 10:06
  • @syncis: `´` cannot be ***eaten***, and cannot be blocked to wait for next character, it's a character itself. The combination can be detect when it occurs, but cannot swallow a legal character. – Ken Kin Apr 01 '13 at 10:10
  • But how can we solve the double accents ´´ when calling ToAscii and trying in notepad? – syncis Apr 01 '13 at 10:12
  • Ok got the email. But the problems is with ur code is that the ToAscii call is made before the KeySequence.Captured code for detecting the combination, so the double accent will happen before the code that watch for combinations. – syncis Apr 01 '13 at 10:37
  • @syncis: I guess `ToAscii` would not be the critical problem, since either `ToUnicode` will fall into the same situation. I'm going to make your solution work once I recieve it. – Ken Kin Apr 01 '13 at 10:49
  • As you can see from the first comment, the ToAscii will not return a valid value when pressing ´ but when calling ToUnicode will, and ToUnicode will also tell you that you have a "combination" as a return value, but thats the tricky part. With your return value from ToAscii and checking for only = 1 , you will only get the a without the ´ and that means without the á. – syncis Apr 01 '13 at 10:53
  • @syncis: It in fact would not be a API issue, it's a question of logic. – Ken Kin Apr 01 '13 at 11:10
  • About the solution, you can use the solution that you provided with your code and if you can make it to log both á and not type double accents when pressing ´ , then email me the solution. I will send you an email right now. – syncis Apr 01 '13 at 12:08
  • If you go to notepad right now without logging and type first ´ and then a, you will get á, that's how I want it to be, logged correctly and text in notepad while logging should be correct also. When I removed the = 1 check in your code, it would log á and all those characters but in notepad it just screwed up with double accents every time I typed ´ or ` . – syncis Apr 01 '13 at 12:18