1
votes

I have a Spanish keyboard so it is using ISO-LATIN, my codepage is 1252.

I found a keyboard low-level hook class and I made apart a code to manage all the keyboard keys I need, the problem is for example in my keyboard the "Keys.Oemtilde" will be the "ñ" character, so I need to hardcode much of the keys.

How I can print the correct character for me without hard modifications?:

MsgBox(ChrW(Keys.Oemtilde)) ' Result: À
' Correct result woould be: ñ

This is the hook class:

#Region " KeyboardHook Class "

Imports System.Runtime.InteropServices

Public Class KeyboardHook

    <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
    Private Overloads Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal HookProc As KBDLLHookProc, ByVal hInstance As IntPtr, ByVal wParam As Integer) As Integer
    End Function

    <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
    Private Overloads Shared Function CallNextHookEx(ByVal idHook As Integer, ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
    End Function

    <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
    Private Overloads Shared Function UnhookWindowsHookEx(ByVal idHook As Integer) As Boolean
    End Function

    <StructLayout(LayoutKind.Sequential)> _
    Private Structure KBDLLHOOKSTRUCT
        Public vkCode As UInt32
        Public scanCode As UInt32
        Public flags As KBDLLHOOKSTRUCTFlags
        Public time As UInt32
        Public dwExtraInfo As UIntPtr
    End Structure

    <Flags()> _
    Private Enum KBDLLHOOKSTRUCTFlags As UInt32
        LLKHF_EXTENDED = &H1
        LLKHF_INJECTED = &H10
        LLKHF_ALTDOWN = &H20
        LLKHF_UP = &H80
    End Enum

    Public Shared Event KeyDown(ByVal Key As Keys)
    Public Shared Event KeyUp(ByVal Key As Keys)

    Private Const WH_KEYBOARD_LL As Integer = 13
    Private Const HC_ACTION As Integer = 0
    Private Const WM_KEYDOWN = &H100
    Private Const WM_KEYUP = &H101
    Private Const WM_SYSKEYDOWN = &H104
    Private Const WM_SYSKEYUP = &H105

    Private Delegate Function KBDLLHookProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer

    Private KBDLLHookProcDelegate As KBDLLHookProc = New KBDLLHookProc(AddressOf KeyboardProc)
    Private HHookID As IntPtr = IntPtr.Zero

    Private Function KeyboardProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
        If (nCode = HC_ACTION) Then
            Dim struct As KBDLLHOOKSTRUCT
            Select Case wParam
                Case WM_KEYDOWN, WM_SYSKEYDOWN
                    RaiseEvent KeyDown(CType(CType(Marshal.PtrToStructure(lParam, struct.GetType()), KBDLLHOOKSTRUCT).vkCode, Keys))
                Case WM_KEYUP, WM_SYSKEYUP
                    RaiseEvent KeyUp(CType(CType(Marshal.PtrToStructure(lParam, struct.GetType()), KBDLLHOOKSTRUCT).vkCode, Keys))
            End Select
        End If
        Return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam)
    End Function

    Public Sub New()
        HHookID = SetWindowsHookEx(WH_KEYBOARD_LL, KBDLLHookProcDelegate, System.Runtime.InteropServices.Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly.GetModules()(0)).ToInt32, 0)
        If HHookID = IntPtr.Zero Then
            Throw New Exception("Could not set keyboard hook")
        End If
    End Sub

    Protected Overrides Sub Finalize()
        If Not HHookID = IntPtr.Zero Then
            UnhookWindowsHookEx(HHookID)
        End If
        MyBase.Finalize()
    End Sub

End Class

#End Region

...And this is my own code:

#Region " KeyLogger "

Public WithEvents KeysHook As New KeyboardHook

Dim Auto_Backspace_Key As Boolean = True
Dim Auto_Enter_Key As Boolean = True
Dim Auto_Tab_Key As Boolean = True
Dim No_F_Keys As Boolean = False

Private Sub KeysHook_KeyDown(ByVal Key As Keys) Handles KeysHook.KeyDown

    Select Case Control.ModifierKeys

        Case 393216 ' Alt-GR + Key

            Select Case Key
                Case Keys.D1 : Key_Listener("|")
                Case Keys.D2 : Key_Listener("@")
                Case Keys.D3 : Key_Listener("#")
                Case Keys.D4 : Key_Listener("~")
                Case Keys.D5 : Key_Listener("€")
                Case Keys.D6 : Key_Listener("¬")
                Case Keys.E : Key_Listener("€")
                Case Keys.Oem1 : Key_Listener("[")
                Case Keys.Oem5 : Key_Listener("\")
                Case Keys.Oem7 : Key_Listener("{")
                Case Keys.Oemplus : Key_Listener("]")
                Case Keys.OemQuestion : Key_Listener("}")
                Case Else : Key_Listener("")
            End Select

        Case 65536 ' LShift/RShift + Key

            Select Case Key
                Case Keys.D0 : Key_Listener("=")
                Case Keys.D1 : Key_Listener("!")
                Case Keys.D2 : Key_Listener("""")
                Case Keys.D3 : Key_Listener("·")
                Case Keys.D4 : Key_Listener("$")
                Case Keys.D5 : Key_Listener("%")
                Case Keys.D6 : Key_Listener("&")
                Case Keys.D7 : Key_Listener("/")
                Case Keys.D8 : Key_Listener("(")
                Case Keys.D9 : Key_Listener(")")
                Case Keys.Oem1 : Key_Listener("^")
                Case Keys.Oem5 : Key_Listener("ª")
                Case Keys.Oem6 : Key_Listener("¿")
                Case Keys.Oem7 : Key_Listener("¨")
                Case Keys.OemBackslash : Key_Listener(">")
                Case Keys.Oemcomma : Key_Listener(";")
                Case Keys.OemMinus : Key_Listener("_")
                Case Keys.OemOpenBrackets : Key_Listener("?")
                Case Keys.OemPeriod : Key_Listener(":")
                Case Keys.Oemplus : Key_Listener("*")
                Case Keys.OemQuestion : Key_Listener("Ç")
                Case Keys.Oemtilde : Key_Listener("Ñ")
                Case Else : Key_Listener("")
            End Select

        Case Else

            If Key.ToString.Length = 1 Then ' Single alpha key

                If Control.IsKeyLocked(Keys.CapsLock) Or Control.ModifierKeys = Keys.Shift Then
                    Key_Listener(Key.ToString.ToUpper)
                Else
                    Key_Listener(Key.ToString.ToLower)
                End If

            Else

                Select Case Key ' Single special key 
                    Case Keys.Add : Key_Listener("+")
                    Case Keys.Back : Key_Listener("{BackSpace}")
                    Case Keys.D0 : Key_Listener("0")
                    Case Keys.D1 : Key_Listener("1")
                    Case Keys.D2 : Key_Listener("2")
                    Case Keys.D3 : Key_Listener("3")
                    Case Keys.D4 : Key_Listener("4")
                    Case Keys.D5 : Key_Listener("5")
                    Case Keys.D6 : Key_Listener("6")
                    Case Keys.D7 : Key_Listener("7")
                    Case Keys.D8 : Key_Listener("8")
                    Case Keys.D9 : Key_Listener("9")
                    Case Keys.Decimal : Key_Listener(".")
                    Case Keys.Delete : Key_Listener("{Supr}")
                    Case Keys.Divide : Key_Listener("/")
                    Case Keys.End : Key_Listener("{End}")
                    Case Keys.Enter : Key_Listener("{Enter}")
                    Case Keys.F1 : Key_Listener("{F1}")
                    Case Keys.F10 : Key_Listener("{F10}")
                    Case Keys.F11 : Key_Listener("{F11}")
                    Case Keys.F12 : Key_Listener("{F12}")
                    Case Keys.F2 : Key_Listener("{F2}")
                    Case Keys.F3 : Key_Listener("{F3}")
                    Case Keys.F4 : Key_Listener("{F4}")
                    Case Keys.F5 : Key_Listener("{F5}")
                    Case Keys.F6 : Key_Listener("{F6}")
                    Case Keys.F7 : Key_Listener("{F7}")
                    Case Keys.F8 : Key_Listener("{F8}")
                    Case Keys.F9 : Key_Listener("{F9}")
                    Case Keys.Home : Key_Listener("{Home}")
                    Case Keys.Insert : Key_Listener("{Insert}")
                    Case Keys.Multiply : Key_Listener("*")
                    Case Keys.NumPad0 : Key_Listener("0")
                    Case Keys.NumPad1 : Key_Listener("1")
                    Case Keys.NumPad2 : Key_Listener("2")
                    Case Keys.NumPad3 : Key_Listener("3")
                    Case Keys.NumPad4 : Key_Listener("4")
                    Case Keys.NumPad5 : Key_Listener("5")
                    Case Keys.NumPad6 : Key_Listener("6")
                    Case Keys.NumPad7 : Key_Listener("7")
                    Case Keys.NumPad8 : Key_Listener("8")
                    Case Keys.NumPad9 : Key_Listener("9")
                    Case Keys.Oem1 : Key_Listener("`")
                    Case Keys.Oem5 : Key_Listener("º")
                    Case Keys.Oem6 : Key_Listener("¡")
                    Case Keys.Oem7 : Key_Listener("´")
                    Case Keys.OemBackslash : Key_Listener("<")
                    Case Keys.Oemcomma : Key_Listener(",")
                    Case Keys.OemMinus : Key_Listener(".")
                    Case Keys.OemOpenBrackets : Key_Listener("'")
                    Case Keys.OemPeriod : Key_Listener("-")
                    Case Keys.Oemplus : Key_Listener("+")
                    Case Keys.OemQuestion : Key_Listener("ç")
                    Case Keys.Oemtilde : Key_Listener("ñ")
                    Case Keys.PageDown : Key_Listener("{AvPag}")
                    Case Keys.PageUp : Key_Listener("{RePag}")
                    Case Keys.Space : Key_Listener(" ")
                    Case Keys.Subtract : Key_Listener("-")
                    Case Keys.Tab : Key_Listener("{Tabulation}")
                    Case Else : Key_Listener("")
                End Select

            End If

    End Select

End Sub

Public Sub Key_Listener(ByVal key As String)

    If Auto_Backspace_Key AndAlso key = "{BackSpace}" Then ' Delete character
        RichTextBox1.Text = RichTextBox1.Text.Substring(0, RichTextBox1.Text.Length - 1)
    ElseIf Auto_Enter_Key AndAlso key = "{Enter}" Then ' Insert new line
        RichTextBox1.Text += ControlChars.NewLine
    ElseIf Auto_Tab_Key AndAlso key = "{Tabulation}" Then ' Insert Tabulation
        RichTextBox1.Text += ControlChars.Tab
    ElseIf No_F_Keys AndAlso key.StartsWith("{F") Then ' Ommit F Keys
    Else ' Print the character
        RichTextBox1.Text += key
    End If

End Sub

#End Region
1

1 Answers

2
votes

You can never write a correct low-level keyboard hook that translates virtual keys to typing keys. The keyboard state and the active keyboard layout are properties of the process that owns the foreground window. Never of the process that implements the hook.

In particular the keyboard state will be wrong, you don't know if the logical state of the keyboard for the process has the shift, alt, control and Windows key active. That state is recorded when the program receives a keyboard event. Particular to a keyboard layout for languages that use diacritics are the state of the dead keys, the ones you type to get the next typed letter to have an accent. This keyboard state is a per-process state and cannot be retrieved from another process. It is only discoverable within the process itself, GetKeyboardState() function. Much the same for the active keyboard layout, GetKeyboardLayout() function. The language bar allows processes to use different layouts.

It can only ever work 100% correctly when you use a WH_KEYBOARD hook. It requires a DLL that can be injected into other processes. The 3rd argument of SetWindowsHookEx(). Which ensures that GetKeyboardState and GetKeyboardLayout return accurate information. You cannot write such a DLL in VB.NET, the process you inject won't have the CLR loaded to execute managed code. A language like C, C++ or Delphi is required, languages that have very modest runtime support requirements. This is usually where the project peters out. Not just because of the runtime injection problem, debugging such code and dealing with the bitness of a process on a 64-bit operating system as well as UAC are major headaches.

You can limp along somewhat by using GetAsyncKeyState() to get the state of the modifier keys. There is no solution for dead keys other than an injected DLL. This is not a helpful answer, it merely explains why you can never make it work completely reliably in vb.net.