Reset timer when mouse is moved anywhere in windows

3.6k views Asked by At

I have a Visual Basic application where I would like to have it perform certain tasks after 60 seconds have gone by without the user clicking on anything or interacting with the screen.

So basically I would like to have a timer reset any time the user moves, clicks, or presses a key on the keyboard.

I know how to do this with particular controls on my forms, but I would like it to register even if the focus is outside my application. Any ideas?

Any help with this would be greatly appreciated.

3

There are 3 answers

2
Idle_Mind On

Here's a quick example using IMessageFilter as I mentioned previously in the comments. IMessageFilter is particularly desirable here as it will work across all forms of the application, not just the main one:

Public Class Form1

    Private WithEvents IdleDetector As IdleTimeout

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        IdleDetector = New IdleTimeout(New TimeSpan(0, 1, 0)) ' one minute timeout duration
        Application.AddMessageFilter(IdleDetector)
        IdleDetector.Reset()
    End Sub

    Private Sub IdleDetector_UserIsIdle() Handles IdleDetector.UserIsIdle

        ' ... do something in here ...
        MessageBox.Show("Inactivity detected...locking some feature!")
        Button1.Enabled = False

        ' restart the timeout period?
        ' IdleDetector.Reset()
    End Sub

End Class

Public Class IdleTimeout
    Implements IMessageFilter

    Public Event UserIsIdle()

    Private Const WM_MOUSEMOVE As Integer = &H200
    Private Const WM_LBUTTONDOWN As Integer = &H201
    Private Const WM_LBUTTONUP As Integer = &H202
    Private Const WM_LBUTTONDBLCLK As Integer = &H203
    Private Const WM_RBUTTONDOWN As Integer = &H204
    Private Const WM_RBUTTONUP As Integer = &H205
    Private Const WM_RBUTTONDBLCLK As Integer = &H206
    Private Const WM_MBUTTONDOWN As Integer = &H207
    Private Const WM_MBUTTONUP As Integer = &H208
    Private Const WM_MBUTTONDBLCLK As Integer = &H209
    Private Const WM_MOUSEWHEEL As Integer = &H20A
    Private Const WM_KEYDOWN As Integer = &H100
    Private Const WM_KEYUP As Integer = &H101
    Private Const WM_SYSKEYDOWN As Integer = &H104
    Private Const WM_SYSKEYUP As Integer = &H105

    Private IdleTimeoutDuration As TimeSpan
    Private WithEvents tmr As System.Timers.Timer
    Private TargetDateTime As DateTime
    Private SC As WindowsFormsSynchronizationContext

    Public Sub New(ByVal TimeoutDuration As TimeSpan)
        SC = System.Windows.Forms.WindowsFormsSynchronizationContext.Current
        Me.IdleTimeoutDuration = TimeoutDuration
        tmr = New System.Timers.Timer
        tmr.Interval = 1000
        Me.Reset()
    End Sub

    Public Sub Reset()
        TargetDateTime = DateTime.Now.Add(IdleTimeoutDuration)
        tmr.Start()
    End Sub

    Public Function PreFilterMessage(ByRef m As System.Windows.Forms.Message) As Boolean Implements System.Windows.Forms.IMessageFilter.PreFilterMessage
        Select Case m.Msg
            Case WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP, _
                    WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, _
                    WM_RBUTTONDOWN, WM_RBUTTONUP, WM_RBUTTONDBLCLK, _
                    WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MBUTTONDBLCLK

                TargetDateTime = DateTime.Now.Add(IdleTimeoutDuration)
        End Select

        Return False
    End Function

    Private Sub tmr_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles tmr.Elapsed
        If TargetDateTime.Subtract(DateTime.Now).TotalMilliseconds < 0 Then
            tmr.Stop()

            If Not IsNothing(SC) Then
                SC.Post(New System.Threading.SendOrPostCallback(AddressOf RaiseIdleEvent), Nothing)
            End If
        Else
            ' This is a one second timer so you could raise a "time out duration left" type event if you wanted to ...
        End If
    End Sub

    Private Sub RaiseIdleEvent(ByVal x As Object)
        ' ... do not call directly ...
        ' Called via SC.Post() which puts this in the main UI thread!
        RaiseEvent UserIsIdle()
    End Sub

End Class
0
SomeNickName On

You have to work with this to get the idle time of the system (which is the lastinputinfo time)

<DllImport("user32.dll")> _
Shared Function GetLastInputInfo(ByRef plii As LASTINPUTINFO) As Boolean
End Function

Refer here

It's C# but i hope it helps you.

0
GameAlchemist On

I get the idle time from LastInputInfo.
I use a timer to retrieve it and test it every 500ms, the code looks like this (it has been in production for a few years now) :

Private ATimer As DispatcherTimer

Public Sub New()
        ....

    ATimer = New DispatcherTimer
    AddHandler ATimer.Tick, AddressOf Me.ATimer_Tick
    ATimer.Interval = TimeSpan.FromMilliseconds(500)  'Checks for idle every 500ms
    ATimer.Start()
End Sub


Public Structure LASTINPUTINFO
    Public cbSize As Integer
    Public dwTime As Integer
End Structure

Public Declare Function GetLastInputInfo Lib "User32.dll" _
                              (ByRef lii As LASTINPUTINFO) As Boolean

Private Sub ATimer_Tick(ByVal sender As Object, ByVal e As EventArgs)

    MyLastInputInfo = New LASTINPUTINFO
    MyLastInputInfo.cbSize = Runtime.InteropServices.Marshal.SizeOf(MyLastInputInfo)

   ' get last input info from Windows
    If GetLastInputInfo(MyLastInputInfo) Then     ' if we have an input info     
       ' compute idle time
       Dim sysIdleTime_ms As Integer = (GetTickCount() - MyLastInputInfo.dwTime)

       ' ... Now you have the idle time in ms, do whatever you want with it :=)
 End Sub