I am trying to develop a keystroke macro program using vb.net which will record keystrokes like keydown and keyup events and then play it anywhere while the main program is running in the background. As of now I have successfully captured the keystrokes and stored those strokes. But the problem which I m facing, is at the time of playing those stored keystrokes. I can't fire the KeyDown and KeyUp events from any external program. I have tried SendKeys method as well but it cannot differentiate between KeyDown and KeyUp separately. Help in this scenario will highly be appreciated.
The KeyDown event which is accessible in parent program only.
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
'MessageBox.Show(e.KeyCode)
'bla bla bla
End Sub
'Use of SendKeys but it cannot distinguish between KeyDown and KeyUp
Private Function AutoSendKey(ByVal keystroke As String, ByVal delay As Integer)
System.Threading.Thread.Sleep(delay)
My.Computer.Keyboard.SendKeys(keystroke, True)
End Function
I need an approach to trigger KeyDown and KeyUp events from external applications. Thanks in advance
CodePudding user response:
I just was looking for the same thing and found few resources and then managed to make it into one application:
Basically this will work unless you use some sort of intercept on a driver level, in which case the keyboard that is intercepted by the intercept application wont be visible to windows and thus this script is useless.
Other than that, here is my code. This code is optimized for .NET 5.0
First create KeyboardHook.vb
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 Key)
Public Shared Event KeyUp(ByVal Key As Key)
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
Dim aKey = KeyInterop.KeyFromVirtualKey(CType(Marshal.PtrToStructure(lParam, struct.GetType()), KBDLLHOOKSTRUCT).vkCode)
RaiseEvent KeyDown(aKey)
Case WM_KEYUP, WM_SYSKEYUP
Dim aKey = KeyInterop.KeyFromVirtualKey(CType(Marshal.PtrToStructure(lParam, struct.GetType()), KBDLLHOOKSTRUCT).vkCode)
RaiseEvent KeyUp(aKey)
End Select
End If
Return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam)
End Function
Public Sub New()
HHookID = SetWindowsHookEx(WH_KEYBOARD_LL, KBDLLHookProcDelegate, System.Diagnostics.Process.GetCurrentProcess().MainModule.BaseAddress, 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
Now create your own class/usercontrol with KeyboardHook event handlers.
For me this was simple OnScreenKBView.xaml
with textblocks that had text property bound to a viewmodel class properties, which shows what keys are pressed down and released up and how many keys are pressed at the same time:
<Grid>
<Border Background="Red" >
<StackPanel Orientation="Vertical">
<TextBlock Text="KeyDown:"/>
<TextBlock Text="{Binding keyDown, Converter={StaticResource KeyToStringVC}}"/>
<TextBlock Text="KeyUp:"/>
<TextBlock Text="{Binding keyUp, Converter={StaticResource KeyToStringVC}}"/>
<TextBlock Text="TotalKeysPressed at the same time:"/>
<TextBlock Text="{Binding keysPressed, Converter={StaticResource ListToCountVC}}"/>
<TextBlock Text="KeysPressed:"/>
<TextBlock Text="{Binding keysPressed, Converter={StaticResource ListToStringVC}}" TextWrapping="Wrap"/>
</StackPanel>
</Border>
</Grid>
Notice I have few value converters KeyToStringVC
, ListToStringVC
and ListToCountVC
.
KeyToStringVC:
Return value.ToString
ListToCountVC:
If Not value Is Nothing Then
Return value.count
Else
Return Nothing
End If
ListToStringVC :
Dim kl As ObservableCollection(Of Key) = value
Dim str As String = Nothing
If Not kl Is Nothing Then
For Each item In kl
str = item.ToString & "; "
Next
End If
Return str
You will have to look up how to use/create value converters if you dont know how.
VB code behind the OnScreenKBView.xaml:
Imports System.Collections.ObjectModel
Public Class OnScreenKBView
Private WithEvents kbHook As New KeyboardHook
Private viewModel As OnScreenKBViewModel
Private Sub kbHook_KeyDown(ByVal Key As Key) Handles kbHook.KeyDown
viewModel.keyDown = Key
'check if list already has the key in it
Dim hasKey As Boolean = False
If Me.viewModel.keysPressed.Contains(Key) Then
hasKey = True
End If
If Not hasKey Then
Me.viewModel.keysPressed.Add(Key)
Dim localCol = Me.viewModel.keysPressed
Dim newCol = New ObservableCollection(Of Key)(From i In localCol Select i)
Me.viewModel.keysPressed = newCol
End If
End Sub
Private Sub kbHook_KeyUp(ByVal Key As Key) Handles kbHook.KeyUp
viewModel.keyUp = Key
Me.viewModel.keysPressed.Remove(Key)
Dim localCol = Me.viewModel.keysPressed
Dim newCol = New ObservableCollection(Of Key)(From i In localCol Select i)
Me.viewModel.keysPressed = newCol
End Sub
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.viewModel = Application.Current.MainWindow.DataContext
End Sub
End Class
This should give you some ideas i hope. If ya need whole project i have it on github but its private, i could zip it for ya.
Lil demo: GIF here
CodePudding user response:
Imports System.Runtime.InteropServices
Imports System.Windows.Forms 'for the keys. enumeration
Public Module SendWinKey
Const KEYEVENTF_KEYDOWN As Integer = &H0
Const KEYEVENTF_KEYUP As Integer = &H2
Declare Sub keybd_event Lib "User32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As UInteger, ByVal dwExtraInfo As UInteger)
Public Sub Main()
keybd_event(CByte(Keys.LWin), 0, KEYEVENTF_KEYDOWN, 0) 'press the left Win key down
keybd_event(CByte(Keys.R), 0, KEYEVENTF_KEYDOWN, 0) 'press the R key down
keybd_event(CByte(Keys.R), 0, KEYEVENTF_KEYUP, 0) 'release the R key
keybd_event(CByte(Keys.LWin), 0, KEYEVENTF_KEYUP, 0) 'release the left Win key
End Sub
End Module
As you can see it is really simple.