none
Problem with programically repeating left/right mouse clicks

    Question

  • I have some code that was sent to me from this site for repeating left clicks while left mouse button is held and also repeating right clicks while right mouse button is held, but there's one problem with it.

    When I hold left or right button by itself for it to start repeating automatically, it works fine, no problems, stops when I release that button, but when I hold both Left and Right mouse buttons at the same time, then release them and start holding again, that's when I run into problems, not always but sometimes they will both continue to click even while they're not held.

    Is there any way to fix this? (When I press left or right mouse buttons again while its stuck clicking, it will reset and then stop clicking, but I would like to prevent this problem from happening all together if possible)

    Here is the code I have so far: (I have other options to specify when it will auto repeat but I didn't find it necessary to include that code)

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private LeftDown As Boolean = False
        Private RightDown As Boolean = False
        Private MouseDownDeviceHandle As IntPtr = IntPtr.Zero
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            TimerLeftClick.Interval = 40
            TimerRightClick.Interval = 40
    
            Dim rid(0) As tagRAWINPUTDEVICE
            rid(0).usUsagePage = HID_USAGE_PAGE_GENERIC
            rid(0).usUsage = RID_MOUSE
            rid(0).dwFlags = RIDF_INPUTSINK
            rid(0).hwndTarget = Me.Handle
            If Not RegisterRawInputDevices(rid, CUInt(rid.Length), CUInt(Marshal.SizeOf(GetType(tagRAWINPUTDEVICE)))) Then
                MessageBox.Show("Failed to register for raw mouse device input.")
            End If
    
        End Sub
    
        Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
            Dim rid(0) As tagRAWINPUTDEVICE
            rid(0).usUsagePage = HID_USAGE_PAGE_GENERIC
            rid(0).usUsage = RID_MOUSE
            rid(0).dwFlags = RIDF_REMOVE
            rid(0).hwndTarget = IntPtr.Zero
            If Not RegisterRawInputDevices(rid, CUInt(rid.Length), CUInt(Marshal.SizeOf(GetType(tagRAWINPUTDEVICE)))) Then
                MessageBox.Show("Failed to unregister for raw mouse device input.")
            End If
        End Sub
    
        Protected Overrides Sub WndProc(ByRef m As Message)
            If m.Msg = WM_INPUT AndAlso (m.WParam.ToInt32 = RIM_INPUT OrElse m.WParam.ToInt32 = RIM_INPUTSINK) Then
                Dim rihPtr As IntPtr = GetRawInputPtr(RID_HEADER, m.LParam)
                Dim rih As tagRAWINPUTHEADER = CType(Marshal.PtrToStructure(rihPtr, GetType(tagRAWINPUTHEADER)), tagRAWINPUTHEADER)
                Marshal.FreeHGlobal(rihPtr)
    
    
                If rih.dwType = RIM_TYPEMOUSE Then
                    Dim riPtr As IntPtr = GetRawInputPtr(RID_INPUT, m.LParam)
                    Dim ri As tagRAWINPUT = CType(Marshal.PtrToStructure(riPtr, GetType(tagRAWINPUT)), tagRAWINPUT)
                    Marshal.FreeHGlobal(riPtr)
    
                    'Left Clicks
                    If ri.Data.Union1.Struct1.usButtonFlags = RI_MOUSE_LEFT_BUTTON_DOWN Then
                        If rih.hDevice <> IntPtr.Zero Then
                            MouseDownDeviceHandle = rih.hDevice
                            LeftDown = True
                            TimerLeftClick.Start()
                        End If
                    ElseIf ri.Data.Union1.Struct1.usButtonFlags = RI_MOUSE_LEFT_BUTTON_UP Then
                        If MouseDownDeviceHandle = rih.hDevice Then
                            TimerLeftClick.Stop()
                            LeftDown = False
                        End If
                    End If
    
    
                    'Right Clicks
                    If ri.Data.Union1.Struct1.usButtonFlags = RI_MOUSE_RIGHT_BUTTON_DOWN Then
                        If rih.hDevice <> IntPtr.Zero Then
                            MouseDownDeviceHandle = rih.hDevice
                            RightDown = True
                            TimerRightClick.Start()
                        End If
                    ElseIf ri.Data.Union1.Struct1.usButtonFlags = RI_MOUSE_RIGHT_BUTTON_UP Then
                        If MouseDownDeviceHandle = rih.hDevice Then
                            TimerRightClick.Stop()
                            RightDown = False
                        End If
                    End If
                End If
    
            End If
            MyBase.WndProc(m)
        End Sub
    
        Private Sub TimerLeftClick_Tick(sender As Object, e As EventArgs) Handles TimerLeftClick.Tick
    
            If LeftDown Then
                LeftDown = False
                mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
            Else
                LeftDown = True
                mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
            End If
    
        End Sub
    
        Private Sub TimerRightClick_Tick(sender As Object, e As EventArgs) Handles TimerRightClick.Tick
    
            If RightDown Then
                RightDown = False
                mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0)
            Else
                RightDown = True
                mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0)
            End If
    
        End Sub
    
        Private Function GetRawInputPtr(ridType As UInteger, lParam As IntPtr) As IntPtr
            Dim bufSize As UInteger = 0
            If GetRawInputData(lParam, ridType, IntPtr.Zero, bufSize, CUInt(Marshal.SizeOf(GetType(tagRAWINPUTHEADER)))) <> 0 Then
                Throw New Exception("GetRawInputData failed to get the size of the raw mouse input data.")
            End If
            Dim bufferPntr As IntPtr = Marshal.AllocHGlobal(CInt(bufSize))
            If GetRawInputData(lParam, ridType, bufferPntr, bufSize, CUInt(Marshal.SizeOf(GetType(tagRAWINPUTHEADER)))) <> bufSize Then
                Throw New Exception("GetRawInputData failed to get a pointer to the raw mouse input data.")
            End If
            Return bufferPntr
        End Function
    
    
    #Region "NativeMethods"
        Private Const HID_USAGE_PAGE_GENERIC As UShort = &H1
        Private Const RID_MOUSE As UShort = &H2
        Private Const MOUSEEVENTF_LEFTDOWN As UInteger = &H2
        Private Const MOUSEEVENTF_LEFTUP As UInteger = &H4
        Private Const RIDF_REMOVE As UInteger = &H1
        Private Const RIDF_NOLEGACY As UInteger = &H30
        Private Const RIDF_INPUTSINK As UInteger = &H100
        Private Const WM_INPUT As Integer = &HFF
        Private Const RIM_INPUT As Integer = &H0
        Private Const RIM_INPUTSINK As Integer = &H1
        Private Const RIM_TYPEMOUSE As UInteger = &H0
        Private Const RID_INPUT As UInteger = &H10000003
        Private Const RID_HEADER As UInteger = &H10000005
        Private Const RI_MOUSE_LEFT_BUTTON_DOWN As UInteger = &H1
        Private Const RI_MOUSE_LEFT_BUTTON_UP As UInteger = &H2
    
        Private Const RI_MOUSE_RIGHT_BUTTON_DOWN As UInteger = &H4
        Private Const RI_MOUSE_RIGHT_BUTTON_UP As UInteger = &H8
        Private Const RI_MOUSE_MIDDLE_BUTTON_DOWN As UInteger = &H10
        Private Const RI_MOUSE_MIDDLE_BUTTON_UP As UInteger = &H20
    
        Private Const MOUSEEVENTF_RIGHTDOWN As UInteger = &H8
        Private Const MOUSEEVENTF_RIGHTUP As UInteger = &H10
        Private Const MOUSEEVENTF_MIDDLEDOWN As UInteger = &H20
        Private Const MOUSEEVENTF_MIDDLEUP As UInteger = &H40
    
        <StructLayout(LayoutKind.Sequential)>
        Private Structure tagRAWINPUTDEVICE
            Public usUsagePage As UShort
            Public usUsage As UShort
            Public dwFlags As UInteger
            Public hwndTarget As IntPtr
        End Structure
    
        <StructLayout(LayoutKind.Sequential)>
        Private Structure tagRAWINPUTHEADER
            Public dwType As UInteger
            Public dwSize As UInteger
            Public hDevice As IntPtr
            Public wParam As UInteger
        End Structure
    
        <StructLayout(LayoutKind.Sequential)>
        Private Structure tagRAWINPUT
            Public Header As tagRAWINPUTHEADER
            Public Data As tagRAWMOUSE 'shortened union to only have a tagRawMouse structure since we are only registering for raw mouse device input
        End Structure
    
        <StructLayout(LayoutKind.Sequential)>
        Private Structure tagRAWMOUSE
            Public usFlags As UShort
            Public Union1 As RAWMOUSE_UNION1
            Public ulRawButtons As UInteger
            Public lLastX As Integer
            Public lLastY As Integer
            Public ulExtraInformation As UInteger
        End Structure
        <StructLayout(LayoutKind.Explicit)>
        Private Structure RAWMOUSE_UNION1
            <FieldOffset(0)> Public ulButtons As UInteger
            <FieldOffset(0)> Public Struct1 As RAWMOUSE_UNION2
        End Structure
        <StructLayout(LayoutKind.Sequential)>
        Private Structure RAWMOUSE_UNION2
            Public usButtonFlags As UInteger
            Public usButtonData As Short
        End Structure
    
        <DllImport("user32.dll")>
        Private Shared Function RegisterRawInputDevices(<MarshalAs(UnmanagedType.LPArray, ArraySubType:=UnmanagedType.Struct, SizeParamIndex:=1)> ByVal pRawInputDevices() As tagRAWINPUTDEVICE, ByVal uiNumDevices As UInteger, ByVal cbSize As UInteger) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function
    
        <DllImport("user32.dll")>
        Private Shared Function GetRawInputData(ByVal hRawInput As IntPtr, ByVal uiCommand As UInteger, ByVal pData As IntPtr, ByRef pcbSize As UInteger, ByVal cbSizeHeader As UInteger) As UInteger
        End Function
    
        <DllImport("user32.dll")>
        Private Shared Sub mouse_event(ByVal dwFlags As UInteger, ByVal dx As Integer, ByVal dy As Integer, ByVal dwData As Integer, ByVal dwExtraInfo As UInteger)
        End Sub
    
    
    #End Region
    End Class


    • Edited by bbbbbb32 Friday, April 21, 2017 6:02 PM
    Friday, April 21, 2017 5:47 PM

All replies

  • but when I hold both Left and Right mouse buttons at the same time, then release them and start holding again, that's when I run into problems

    Just by looking at the code I would guess that when the left button is clicked you need to check if the right button click is active and if it is then cancel it (timer and flag) and vice versa for the right button click. I can't see that you would want both to run at the same time, so when you press both at the same time you get either right or left. 

    Friday, April 21, 2017 10:13 PM
  • but when I hold both Left and Right mouse buttons at the same time, then release them and start holding again, that's when I run into problems

    Just by looking at the code I would guess that when the left button is clicked you need to check if the right button click is active and if it is then cancel it (timer and flag) and vice versa for the right button click. I can't see that you would want both to run at the same time, so when you press both at the same time you get either right or left. 

    I worded that a little wrong, if I hold left and right buttons for lets say 2 seconds, then release them, wait maybe 1-2 seconds, then hold them both again I have no problems, it's only when they're being held, then released, then held straight away again that it sometimes gets locked into a continuous loop.  My guess is that the messages are being sent perhaps too quickly and therefore it's not registering the newest input, I'm just not sure how to fix that as this tool is for a game, therefore alot of the times both left and right buttons need to be held at the same time, but at certain points you may need to release the buttons and then quickly press them again. or sometimes only one of them needs to be held then released.

    I did a small test and completely removed all the right clicks code, if I have it repeating Left Mouse Button and manually keep clicking / releasing right mouse button, the auto repeat for the left clicks sometimes gets stuck in the loop, but if I don't touch right mouse button at all then there's no problems with repeating left button (even if I keep clicking then releasing the key).  It runs into a problem when there's more than one button pressed, possibly only detecting one of the buttons since they're pressed at the same time? I honestly don't know.. :/

    Saturday, April 22, 2017 12:16 PM
  • Hi

    I have no idea if the following is of any use to you, but here it is anyway - just ignore if irrelevant.

    This code should show mouse button left/right hold on a Button, and separate out which mouse button is being held.

    This example needs Timer1, Timer2, Button1,Label1 and Label2 on the default Form1. (or, run time created). The Button1 is targeted as the control to monitor, but it could easily be any other.

    Option Strict On
    Option Explicit On
    Public Class Form1
        Dim sw1, sw2 As New Stopwatch
        Dim both As Integer = 0
        Private Sub Button1_MouseDown(sender As Object, e As MouseEventArgs) Handles Button1.MouseDown
            Select Case e.Button
                Case MouseButtons.Left
                    sw1.Start()
                    Timer1.Enabled = True
                    both += 1
                Case MouseButtons.Right
                    sw2.Start()
                    Timer2.Enabled = True
                    both += 1
            End Select
            If both = 2 Then
                Label3.Text = "both"
            Else
                Label3.Text = Nothing
            End If
        End Sub
        Private Sub Button1_MouseUp(sender As Object, e As MouseEventArgs) Handles Button1.MouseUp
            Select Case e.Button
                Case MouseButtons.Left
                    Timer1.Enabled = False
                    sw1.Reset()
                    both -= 1
                Case MouseButtons.Right
                    Timer2.Enabled = False
                    sw2.Reset()
                    both -= 1
            End Select
            If both < 0 Then both = 0
        End Sub
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
            Label1.Text = "Left: " & sw1.ElapsedMilliseconds.ToString
        End Sub
        Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick
            Label2.Text = "Right: " & sw2.ElapsedMilliseconds.ToString
        End Sub
    End Class




    Regards Les, Livingston, Scotland




    • Edited by leshay Friday, May 5, 2017 11:36 PM
    Saturday, April 22, 2017 1:49 PM