We help IT Professionals succeed at work.

Last time mouse moved or key pressed?

435 Views
Last Modified: 2013-11-07
I need a way to check when the last key was pressed, and when the mouse was last moved, regardless of whether or not my software has the focus.  I'm not afraid of system-wide hooks, having managed to get one working a few years ago, though I had to do some c++ programming because VB couldn't handle it (or was it managed software that couldn't handle it?) and I'd rather not have to re-learn all that.  Anyway I don't need to actually capture the keyboard press or the mouse movement, I just need to be able to periodically find out when was the last time the user hit a key or moved the mouse.  Thanks!
Comment
Watch Question

CERTIFIED EXPERT
Most Valuable Expert 2012
Top Expert 2008

Commented:
I came up with the class, but I haven't tested it in a while.  Give it a shot and let me know if it works for you.

Public Class ApplicationIdle

  Private WithEvents m_timerCheck As Timer

  Private m_idleMinutes As Integer = 10
  Private m_targetTime As Date = DateTime.MinValue

  Public Sub New()

    m_timerCheck = New Timer
    m_timerCheck.Enabled = False
    m_timerCheck.Interval = 1000

  End Sub

  Public Sub Start()
    m_timerCheck.Enabled = True
    m_timerCheck.Start()
  End Sub

  Public Sub [Stop]()
    m_timerCheck.Stop()
    m_timerCheck.Enabled = False
  End Sub

  Public Property Interval() As Integer
    Get
      Return m_timerCheck.Interval
    End Get
    Set(ByVal Value As Integer)
      m_timerCheck.Interval = Value
    End Set
  End Property

  Public Property IdleMinutes() As Integer
    Get
      Return m_idleMinutes
    End Get
    Set(ByVal Value As Integer)
      m_idleMinutes = Value

      If m_timerCheck.Enabled Then
        Me.CheckIdleState()
      End If
    End Set
  End Property

  Public ReadOnly Property TargetTime() As Date
    Get
      Return m_targetTime
    End Get
  End Property

  Public Event ApplicationIdle(ByVal sender As Object, ByVal e As EventArgs)

  Private Declare Function GetInputState Lib "user32" Alias "GetInputState" () As Integer

  Private Sub CheckIdleState()

    Dim result As Integer = GetInputState()

    If result Then
      m_targetTime = m_targetTime.AddMinutes(Me.IdleMinutes)
    Else

      If m_targetTime.Equals(DateTime.MinValue) Then
        m_targetTime = DateTime.Now.AddMinutes(Me.IdleMinutes)
      Else

        Dim timeDiff As Long = DateDiff(DateInterval.Second, DateTime.Now, Me.TargetTime)

        If timeDiff < 0 Then
          m_targetTime = m_targetTime.AddMinutes(Me.IdleMinutes)
          RaiseEvent ApplicationIdle(Me, EventArgs.Empty)
        End If

      End If

    End If
  End Sub

  Private Sub m_timerCheck_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_timerCheck.Tick
    Me.CheckIdleState()
  End Sub

End Class

Bob

Author

Commented:
Thanks Bob,
This looks real interesting.  I have one question.  Your class uses GetInputState, which, apparently "determines whether there are mouse-button or keyboard messages in the calling thread's message queue."  

Does this mean that the application will only detect keyboard or mouse events that happen when my application has the focus?  It's the "calling thread's message queue" language that I'm afraid of (and may well not understand).  Do all keyboard and mouse messages get sent to all applications, even if they don't have focus?
Thanks again, either way.
CERTIFIED EXPERT
Most Valuable Expert 2012
Top Expert 2008

Commented:
The name of the game is application idle, not Windows idle.  I haven't been able to find a good Window idle.

Bob
CERTIFIED EXPERT
Most Valuable Expert 2012
Top Expert 2008

Commented:
INFO: GetInputState Is Faster Than GetMessage() or PeekMessage()
http://support.microsoft.com/kb/35605

In Win32, message queues are not global as they are in 16-bit Windows. The message queues are local to the thread. When you call GetInputState, you are checking to see if there are mouse or keyboard messages for the calling thread only. If a window created by another thread in the application has the keyboard input waiting, GetInputState will not be able to check for those messages.

Bob
Mike TomlinsonHigh School Computer Science, Computer Applications, Digital Design, and Mathematics Teacher
CERTIFIED EXPERT
Top Expert 2009

Commented:
Here is an example that uses low level keyboard/mouse hooks.  Note that in VB.Net 2005 it will only work in the compiled EXE and NOT in the IDE.

' ------------------------------------------------------------
'  Form1
' ------------------------------------------------------------
Public Class Form1

    Private lah As New LastActivityHook

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Label1.Text = LastActivityHook.LastKeyboardActivity.ToString
        Label2.Text = LastActivityHook.LastMouseActivity.ToString
    End Sub

End Class

' ------------------------------------------------------------
'  Class LastActivityHook
' ------------------------------------------------------------
Imports System.Runtime.InteropServices
Public Class LastActivityHook

    Private Const HC_ACTION As Integer = 0

    Private Const WH_MOUSE_LL As Integer = 14
    Private Const WM_MOUSEMOVE As Integer = &H200
    Private Const WM_LBUTTONDOWN As Integer = &H201
    Private Const WM_LBUTTONUP As Integer = &H202
    Private Const WM_RBUTTONDOWN As Integer = &H204
    Private Const WM_RBUTTONUP As Integer = &H205
    Private Const WM_LBUTTONDBLCLK As Integer = &H203
    Private Const WM_RBUTTONDBLCLK As Integer = &H206

    Private Const WH_KEYBOARD_LL As Integer = 13
    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 Structure KBDLLHOOKSTRUCT
        Public vkCode As Integer
        Public scancode As Integer
        Public flags As Integer
        Public time As Integer
        Public dwExtraInfo As Integer
    End Structure

    Private Structure POINT
        Private x As Integer
        Private y As Integer
    End Structure

    Private Structure MSLLHOOKSTRUCT
        Private pt As POINT
        Private mouseData As Integer
        Private flags As Integer
        Private time As Integer
        Private dwExtraInfo As Integer
    End Structure

    Private Declare Function SetWindowsHookEx Lib "user32" _
        Alias "SetWindowsHookExA" ( _
        ByVal idHook As Integer, _
        ByVal lpfn As LowLevelMouseProcDelegate, _
        ByVal hmod As Integer, _
        ByVal dwThreadId As Integer) As Integer

    Private Declare Function CallNextHookEx Lib "user32" ( _
        ByVal hHook As Integer, _
        ByVal nCode As Integer, _
        ByVal wParam As Integer, _
        ByVal lParam As MSLLHOOKSTRUCT) As Integer

    Private Declare Function SetWindowsHookEx Lib "user32" _
        Alias "SetWindowsHookExA" ( _
        ByVal idHook As Integer, _
        ByVal lpfn As LowLevelKeyboardProcDelegate, _
        ByVal hmod As Integer, _
        ByVal dwThreadId As Integer) As Integer

    Private Declare Function CallNextHookEx Lib "user32" ( _
        ByVal hHook As Integer, _
        ByVal nCode As Integer, _
        ByVal wParam As Integer, _
        ByVal lParam As KBDLLHOOKSTRUCT) As Integer

    Private Declare Function UnhookWindowsHookEx Lib "user32" ( _
        ByVal hHook As Integer) As Integer

    Private Delegate Function LowLevelMouseProcDelegate( _
        ByVal nCode As Integer, _
        ByVal wParam As Integer, _
        ByVal lParam As MSLLHOOKSTRUCT) As Integer

    Private Delegate Function LowLevelKeyboardProcDelegate( _
        ByVal nCode As Integer, _
        ByVal wParam As Integer, _
        ByRef lParam As KBDLLHOOKSTRUCT) As Integer

    Private hhkLowLevelMouse As Integer
    Private mouseDelegate As LowLevelMouseProcDelegate
    Private hhkLowLevelKeyboard As Integer
    Private keyboardDelegate As LowLevelKeyboardProcDelegate
    Private Shared keyDT As DateTime
    Private Shared mouseDT As DateTime

    Public Shared ReadOnly Property LastKeyboardActivity() As DateTime
        Get
            Return LastActivityHook.keyDT
        End Get
    End Property

    Public Shared ReadOnly Property LastMouseActivity() As DateTime
        Get
            Return LastActivityHook.mouseDT
        End Get
    End Property

    Public Sub New()
        mouseDelegate = New LowLevelMouseProcDelegate(AddressOf Me.LowLevelMouseProc)
        hhkLowLevelMouse = SetWindowsHookEx(WH_MOUSE_LL, mouseDelegate, _
            Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly.GetModules()(0)).ToInt32, 0)

        keyboardDelegate = New LowLevelKeyboardProcDelegate(AddressOf Me.LowLevelKeyboardProc)
        hhkLowLevelKeyboard = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardDelegate, _
            Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly.GetModules()(0)).ToInt32, 0)
    End Sub

    Private Function LowLevelMouseProc( _
        ByVal nCode As Integer, _
        ByVal wParam As Integer, _
        ByVal lParam As MSLLHOOKSTRUCT) As Integer

        If (nCode = HC_ACTION) Then
            Select Case wParam
                Case WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_LBUTTONUP, _
                    WM_RBUTTONDOWN, WM_RBUTTONUP, WM_LBUTTONDBLCLK, _
                    WM_RBUTTONDBLCLK
                    LastActivityHook.mouseDT = DateTime.Now

            End Select
        End If

        Return CallNextHookEx(hhkLowLevelMouse, nCode, wParam, lParam)
    End Function

    Private Function LowLevelKeyboardProc( _
        ByVal nCode As Integer, _
        ByVal wParam As Integer, _
        ByRef lParam As KBDLLHOOKSTRUCT) As Integer

        If (nCode = HC_ACTION) Then
            Select Case wParam
                Case WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP
                    LastActivityHook.keyDT = DateTime.Now
            End Select
        End If

        Return CallNextHookEx(hhkLowLevelKeyboard, nCode, wParam, lParam)
    End Function

    Protected Overrides Sub Finalize()
        If hhkLowLevelMouse <> 0 Then
            UnhookWindowsHookEx(hhkLowLevelMouse)
        End If
        If hhkLowLevelKeyboard <> 0 Then
            UnhookWindowsHookEx(hhkLowLevelKeyboard)
        End If
        MyBase.Finalize()
    End Sub

End Class
Commented:
This one is on us!
(Get your first solution completely free - no credit card required)
UNLOCK SOLUTION
CERTIFIED EXPERT
Most Valuable Expert 2012
Top Expert 2008

Commented:
Emmit,

That looks like it has great potential.  Have you worked with it?  I believe it is what I was looking for.

Bob

Commented:
Yep, it works "as advertised"

Author

Commented:
Thanks everybody.  It looks like graye's solution should be the best, but i am having trouble implementing it in my software.  
I already have a timer in my software that calls a sub called Change when it's Timer1.Tick is fired.
I put the foloowing code at the top of the form:

Private Structure LASTINPUTINFO
        Dim cbSize As Integer
        Dim dwTime As Integer
    End Structure

    Private Declare Auto Function GetLastInputInfo Lib "User32.dll" (ByRef LastInputInfo As LASTINPUTINFO) As Boolean

And then in the Change sub has the following code:
            Dim lii As LASTINPUTINFO
            Dim ret As Integer
            lii.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(lii)
             ret = GetLastInputInfo(lii)
            Debug.WriteLine("current tick=" & Environment.TickCount & ", last input tick=" & lii.dwTime)

the problem is, it always displays the same values no matter what, regardless of if i hit  a key or anything.  That output is:
current tick=285332171, last input tick=0

Anyone know what the problem is?
Mike TomlinsonHigh School Computer Science, Computer Applications, Digital Design, and Mathematics Teacher
CERTIFIED EXPERT
Top Expert 2009

Commented:
It's working fine for me....

WinXP Pro SP2 using VB.Net 2005 Express:

Public Class Form1

    Private Structure LASTINPUTINFO
        Dim cbSize As Integer
        Dim dwTime As Integer
    End Structure

    Private Declare Auto Function GetLastInputInfo Lib "User32.dll" (ByRef LastInputInfo As LASTINPUTINFO) As Boolean

    Private WithEvents tmr As New System.Windows.Forms.Timer

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        tmr.Interval = 1000
        tmr.Start()
    End Sub

    Private Sub tmr_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmr.Tick
        Dim lii As LASTINPUTINFO
        Dim ret As Integer
        lii.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(lii)
        ret = GetLastInputInfo(lii)
        Debug.Print("current tick=" & Environment.TickCount & ", last input tick=" & lii.dwTime)
    End Sub

End Class

Gain unlimited access to on-demand training courses with an Experts Exchange subscription.

Get Access
Why Experts Exchange?

Experts Exchange always has the answer, or at the least points me in the correct direction! It is like having another employee that is extremely experienced.

Jim Murphy
Programmer at Smart IT Solutions

When asked, what has been your best career decision?

Deciding to stick with EE.

Mohamed Asif
Technical Department Head

Being involved with EE helped me to grow personally and professionally.

Carl Webster
CTP, Sr Infrastructure Consultant
Empower Your Career
Did You Know?

We've partnered with two important charities to provide clean water and computer science education to those who need it most. READ MORE

Ask ANY Question

Connect with Certified Experts to gain insight and support on specific technology challenges including:

  • Troubleshooting
  • Research
  • Professional Opinions
Unlock the solution to this question.
Join our community and discover your potential

Experts Exchange is the only place where you can interact directly with leading experts in the technology field. Become a member today and access the collective knowledge of thousands of technology experts.

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.