Trying to programmatically hold a key down - vb.net

I'm trying to write an app to perform some basic process automation by sending keyboard events (i.e. simulating single key presses as well as holding keys down) to a window in focus (any window, such as Notepad). I can get single key presses to work just fine, but I can't get it to hold a key down. Even if I do a key down event, followed by a lengthy delay, followed by a key up... all I get is a single keypress.
I've read so many tutorials, and many of them multiple times over to ensure I haven't missed something. Every single time however, all I get is a single key press, it fails to hold the key down.
The following is a code sample I found from:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/bad5b1f3-cf59-4a2b-889b-257ee590bf99/vb-advanced-key-bot?forum=vbgeneral
What I'm expecting to have happen is that it would send a keyboard event that tells the system to hold down a key (e.g. aaaaaaaaaaaaaaaaaaaa), but all I get is a single character. I've tried spamming the system with repeat keypresses, but the receiving app sees the different keyboard code for keydowns and keyups, as opposed to a key in a held status, and thus is not responding as though the key were actually held key down.
What am I doing wrong? Did they maybe change this dll?
A huge thanks to anyone who can help me get this working.
Public Class Form1
Private Declare Sub keybd_event Lib "user32.dll" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Integer, ByVal dwExtraInfo As Integer)
Private Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Integer, ByVal wMapType As Integer) As Integer
' flag to indicate completion
Dim finished As Boolean = True
' how long to 'press' the Space key
Dim delay As Integer = 3
' how many times to repeat Q and Space
Dim Repeats As Integer
' User closes application during processing
Dim UserInterupt As Boolean = False
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
KeyPreview = True
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
If Not finished Then
TextBox1.AppendText("USER closing" & vbCrLf)
UserInterupt = True
e.Cancel = True
End If
End Sub
Private Sub Form1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles Me.KeyPress
Select Case e.KeyChar
Case "z", "Z"
e.Handled = True
Repeats = 12
finished = False
Do While Not finished
TextBox1.AppendText("Pressing SPACE" & vbCrLf)
HoldKeyDown(Keys.Space, delay)
Loop
Case "x", "X"
e.Handled = True
TextBox1.AppendText("USER stopping" & vbCrLf)
finished = True
End Select
End Sub
Private Sub HoldKeyDown(ByVal k As Keys, ByVal Hold As Integer)
Dim HoldFor As DateTime = DateTime.Now().AddSeconds(Hold)
keybd_event(k, MapVirtualKey(k, 0), 0, 0)
While HoldFor.Subtract(DateTime.Now()).TotalSeconds > 0
Application.DoEvents()
End While
keybd_event(k, MapVirtualKey(k, 0), 2, 0)
TextBox1.AppendText("SPACE released" & vbCrLf)
Repeats -= 1
If Repeats = 0 Then
finished = True
TextBox1.AppendText("REPEATS completed" & vbCrLf)
End If
If UserInterupt Then End
End Sub
End Class

Answering my own question after going right down the rabbit hole on this one.
Basically put, the only way to do this is with SendKeys. The other methods are all deprecated and so will not work in this way anymore.
However this isn't a dead-end for you. If you want to use SendKeys to "hold down" a key, then spam the key at 10ms intervals and this should trigger the receiving app to think the key is held down.

Related

Global hotkey not registered in game using GetAsyncKeyState - vb.net

I've made a tool that will continuously click for the user if they decide to have it on with toggle keys etc, it all works fine in ordinary windows e.g google chrome, but when it comes to games it doesn't always work correctly.
(well it does in some games, then others it doesn't)
The code is designed to click fast while holding LButton, then stop when it's let go to act as an autoclicker (user has control of speed) which again works, but when in a game it clicks alot slower than it's suppose to / any other window / app.
I've figured out adding a delay using
Thread.Sleep(200)
fixes the speed of the autoclicker in game, but then it messes up the keybind which results in the autoclicker always clicking even when LButton isnt held / pressed.
Is there anything else that I could use as a delay, or anything else I can do to the code so it works correctly?
I've been trying many different variations and searching online the last few days trying to get it working, but none succeeded.
Here's all the code got to do with autoclicking in my project, i've added some notes to try and explain which part is doing what / speeds the timers are set to.
Imports System.Threading
Public Class Form1
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vkey As Integer) As Short
Private Declare Sub mouse_event Lib "user32" (ByVal dwflags As Integer, ByVal dx As Integer, ByVal cbuttons As Integer, ByVal dy As Integer, ByVal dwExtraInfo As Integer)
Private Const mouseclickup = 4
Private Const mouseclickdown = 2
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Timer1 isnt doing the clicking, Timer1 is just listening for LButton
'clicks which is why I have it always on aswell as a low interval.
Timer1.Start()
Timer1.Interval = 1
'LButton is the timer that will do the clicking.
LButton.Interval = 100
End Sub
Private Sub LButton_Tick(sender As Object, e As EventArgs) Handles LButton.Tick
If GetAsyncKeyState(Keys.LButton) Then
mouse_event(mouseclickup, 0, 0, 0, 0)
'Without Thread.Sleep(200) the code works as it's suppose to, clicks
'when LButton is held, stops clicking when LButton is let go,
'although without Thread.Sleep(200) it will not work in all games,
'but with it, it will continuously click even when LButton isn't held.
Thread.Sleep(200)
mouse_event(mouseclickdown, 0, 0, 0, 0)
Else
LButton.Stop()
End If
End Sub
Private Sub Timer1_Tick_1(sender As Object, e As EventArgs) Handles Timer1.Tick
'This is what will listen for the left clicks and also stop the left
'LButton timer if LButton is not held
If GetAsyncKeyState(Keys.LButton) Then
LButton.Start()
Else
LButton.Stop()
End If
End Sub
End Class
Quoting the MSDN documentation, GetAsyncKeyState() determines:
whether a key is up or down at the time the function is called, and whether the key was pressed after a previous call to GetAsyncKeyState.
So when you check the function via If GetAsyncKeyState(Keys.LButton) Then, it will return non-zero at least three times, thus execute the code more than you want (which is what you experience when you add Thread.Sleep(200)).
To check if the key is held down you have to check if the most significant bit is set, which for a Short is 0x8000 in hex and 32768 in decimal.
Checking a bit flag is done by checking (<number> And <bit>) = <bit> - where And is the bitwise And operator.
This would result in your code looking like this:
Const KeyDownBit As Integer = &H8000
Private Sub LButton_Tick(sender As Object, e As EventArgs) Handles LButton.Tick
If (GetAsyncKeyState(Keys.LButton) And KeyDownBit) = KeyDownBit Then
mouse_event(mouseclickup, 0, 0, 0, 0)
Thread.Sleep(200)
mouse_event(mouseclickdown, 0, 0, 0, 0)
Else
LButton.Stop()
End If
End Sub
Private Sub Timer1_Tick_1(sender As Object, e As EventArgs) Handles Timer1.Tick
If (GetAsyncKeyState(Keys.LButton) And KeyDownBit) = KeyDownBit Then
LButton.Start()
Else
LButton.Stop()
End If
End Sub
I'm not sure whether your second timer (Timer1) is actually needed in this case.

Listen to key press when the program is in the background

I am currently working with a program which is supposed to run in the background but also check if "mod + o" is pressed then do something. But I cannot figure out how a vb.net program can listen to key presses when the program is not Selected / Opened.
You can use P/Invocation to be able to use WinAPI's GetAsyncKeyState() function, then check that in a timer.
<DllImport("user32.dll")> _
Public Shared Function GetAsyncKeyState(ByVal vKey As System.Windows.Forms.Keys) As Short
End Function
Const KeyDownBit As Integer = &H8000
Private Sub Timer1_Tick(sender As Object, e As System.EventArgs) Handles Timer1.Tick
If (GetAsyncKeyState(Keys.LWin) And KeyDownBit) = KeyDownBit AndAlso (GetAsyncKeyState(Keys.O) And KeyDownBit) = KeyDownBit Then
'Do whatever you want when 'Mod + O' is held down.
End If
End Sub
EDIT:
To make the code only execute one time per key press, you can add a little While-loop to run until either of the buttons are released (add it inside your If-statement):
While GetAsyncKeyState(Keys.LWin) AndAlso GetAsyncKeyState(Keys.O)
End While
This will stop your code from executing more than once while you hold the keys down.
When using this in a Console Application just replace every System.Windows.Forms.Keys and Keys with ConsoleKey, and replace LWin with LeftWindows.

Basic Key Logger - Code Not Working

Public Class Form1
Dim KeyState
Public Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Int32) As Boolean
Private Sub LogTimer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles LogTimer.Tick
For I = 1 To 255
KeyState = 0
KeyState = GetAsyncKeyState(I)
If KeyState = True Then
Me.txtLog.Text = Me.txtLog.Text & Chr(I)
End If
Next I
End Sub
End Class
Just a run down:
I am attempting to get the up/down state of every key every tenth of a second(the timer), then add any keys pressed to a textbox.
I honestly cannot see why this code is not working.
Make sure that you actually have the timer being told to start somewhere. In my experience, I've always had to write actual code to tell it start, and the properties in design view always give me trouble.

Eventhandler "bug" using VB.NET with windows forms

i have the following code:
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Const WM_SYSCOMMAND As Integer = &H112
Const SC_SCREENSAVE As Integer = &HF140
MyBase.WndProc(m)
If bloqueado = 0 Then
If m.Msg = WM_SYSCOMMAND AndAlso m.WParam.ToInt32 = SC_SCREENSAVE Then
Timer2.Start()
inicio = Now
pausa = pausa + 1
AddHandler Application.Idle, AddressOf Application_Idle
End If
End If
End Sub
Private Sub Application_Idle(ByVal sender As Object, ByVal e As EventArgs)
Dim newitem As ListViewItem
Dim diferença As TimeSpan
'MsgBox(Now.ToString)'
Debug.Print(Now.ToString)
fim = Now
diferença = fim - inicio
Timer2.Stop()
newitem = New ListViewItem
newitem.Text = pausa
newitem.SubItems.Add(inicio.ToLongTimeString)
newitem.SubItems.Add(fim.ToLongTimeString)
newitem.SubItems.Add(diferença.ToString.Substring(0, 8))
ListView1.Items.Add(newitem)
parcial = parcial & pausa & vbTab & vbTab & inicio.ToLongTimeString & vbTab & vbTab & fim.ToLongTimeString _
& vbTab & vbTab & diferença.ToString.Substring(0, 8) & vbTab & vbTab & " screensaver" & System.Environment.NewLine
RemoveHandler Application.Idle, AddressOf Application_Idle
End Sub
Basically the first part detect when screensaver activates and creates a application.idle event handler and the second part, when activity is detected a bunch of code is run and the handler removed.
It's all works fine except for one point:
As you can see i have inicio = now when screensaver becomes active and fim = now when activity is detected (when screensaver becomes inactive), so i should have 2 differente times, but if i have it like i posted the 2 datetime will be the same. If you notice i have a msgbox displaying the now (when screensaver stops) in comment, if i take it out of comment the 2 datetimes will be differente and correct (i used a cronometer to make sure of the results)
Now my questions:
Why does it need the messagebox for the now to be updated and why doesn't it work it debug.print?
Is there a way to solve this problem/update the now var, without having to use a messagebox (i wouldn't like for the app to have pop-up messages)
If i really have to use msgbox for this purpose is there a way for it not to send the pop-up or to autoclick ok right after so it disappears instantly?
EDIT:
I have been searching and i found this code:
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public Function IsSNRunning() As Boolean
IsSNRunning = (FindWindow("WindowsScreenSaverClass", vbNullString) <> 0)
End Function
Private Sub Timer3_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer3.Tick
If IsSNRunning() Then
'Screen Saver Is Running
Else
Timer3.Stop()
code
End If
End Sub
i used Timer3.Start() when in the part that captures the start of the screensaver, my idea being if i start the timer when i know the screensaver if on, then when i get IsSNRunning as false is when the screensaver stops running, but it doesn't work, any ideas why?
Doing anything with Application.Idle is a lost cause. Not only does your app go idle immediately after the screen saver activates, you also never stop being idle while it is running. The screen saver switches the active desktop to a dedicated secure desktop, none of the running programs will ever get any input, not until it de-activates.
You can observe the desktop switch, the SystemEvents.SessionSwitch event fires.
Do note the considerable lack of practical usefulness of code like this. Curiosity is okay but there are always a lot of things to learn. The screen saver should be at the bottom of your list.
First i'll thank you guys for the help, like you said application.idle doesn't work, with you help i got this solution i VB:
Imports System
Imports Microsoft.Win32
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
<DllImport("user32.dll", CharSet:=CharSet.Auto)> Public Shared Function SystemParametersInfo(uAction As UInteger, _
uParam As UInteger, ByRef lpvParam As Boolean, fWinIni As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
' Check if the screensaver is busy running.'
Public Shared Function IsScreensaverRunning() As Boolean
Const SPI_GETSCREENSAVERRUNNING As Integer = 114
Dim isRunning As Boolean = False
If Not SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, isRunning, 0) Then
' Could not detect screen saver status...'
Return False
End If
If isRunning Then
' Screen saver is ON.'
Return True
End If
' Screen saver is OFF.'
Return False
End Function
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Const WM_SYSCOMMAND As Integer = &H112
Const SC_SCREENSAVE As Integer = &HF140
MyBase.WndProc(m)
If bloqueado = 0 Then
If m.Msg = WM_SYSCOMMAND AndAlso m.WParam.ToInt32 = SC_SCREENSAVE Then
Timer2.Start()
Timer3.Enabled = True
Timer3.Start()
'here we that that the screensaver started running so we start a timer'
End If
End If
End Sub
Private Sub Timer3_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer3.Tick
If IsScreensaverRunning() Then
'Screen Saver Is Running'
Else
Timer3.Stop()
Timer3.Enabled = False
'Screen Saver Is not Running'
End If
End Sub
Because the timer only starts running when the screensaver is running we know that when you get timer3.stop is when the screensaver stopped running
Important, don't put a msgbox before the timer stop because it wont work, the pop-up will show and it wont get to the stop so innumerous pop-up will appear (yeah... i made that mistake :S)
Again, thanks for helping me and hope it will help someone in the future

Key being held down in vb.net?

I am writing a game for class, and what the player does is uses "wasd" to swim away from a chasing shark. My code for movement is
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
Select Case e.KeyCode
Case Keys.W
If picSwimmer.Location.Y > -5 Then
Loc = New Point(picSwimmer.Location.X, picSwimmer.Location.Y - 15)
picSwimmer.Location = Loc
End If
Case Keys.S
End If
If picSwimmer.Location.Y < Me.Height Then
Loc = New Point(picSwimmer.Location.X, picSwimmer.Location.Y + 15)
picSwimmer.Location = Loc
End If
(I have the same setup for a&d as well). My problem is that when you hold down one of the "wasd" keys, the image will move slightly then stop, then start moving fluidly after about a second. I think this is due to the amount of time it takes for VB to recognize that a key is being held down, rather than being clicked once. How would I make it so that VB will register that the key is being held down from the beginning, or shortens the time it takes to recognize the fact that it's being held down? Or anything that would make the movement smooth as soon as the key is pressed?
What you need is GetAsyncKeyState and a constantly running timer or thread that queries all keys and triggers the functions you need (one "step", which you must align with the interval of your timer, so long as the key is held down). This is also the only way (I know of) that you can process more keys at the same time.
And it is independent of focus (so maybe you should check if the right element has the focus).
Basics:
Public Declare Function GetAsyncKeyState Lib "user32.dll" (ByVal vKey As Int32) As UShort
Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick
If GetAsyncKeyState(Convert.ToInt32(Keys.D)) Then Label1.Text = "RIGHT" Else Label1.Text = "-"
If GetAsyncKeyState(Convert.ToInt32(Keys.W)) Then Label2.Text = "UP" Else Label2.Text = "-"
End Sub
Using GetAsyncKeyState is much better in a situation where a second key may be pressed while the other is held.
For example:
Say that a player is:
moving his tank forward by pressing and holding down the Up key
and is shooting fire by pressing space key
In case you used the Keydown event, the tank will stop moving once the player press the space key.
To start moving again, he would have to release the arrow and re-press the up key after each fire.
Public Declare Function GetAsyncKeyState Lib "user32.dll" (ByVal vKey As Int32) As UShort
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
If GetAsyncKeyState(Convert.ToInt32(Keys.Up)) Then
''put here codes for moving the tank
End If
If GetAsyncKeyState(Convert.ToInt32(Keys.Space)) Then
''PUT HERE CODES FOR TANK FIRE
End If
End Sub
It is not a Visual Basic setting it is a Windows Setting under Keybaord Properties you will need to shorten the Repeat Delay Time it will affect all applications on your computer.