Hello all,
I have a multi-form application that is running fine, but now I would like to add code that would revert the application to its startup form if there is no user activity (mouse or keyboard).
I have 27 forms in my application, and instead of going into each form indiviudually and adding a timer, I was wondering if there were a method to simply have a separate application (or hidden form in the same application) that would detect mouse and keyboard activity running in the background much like the method that your Windows screen saver uses to detect whether there is activity or not before showing the screen saver.
I have looked into keyboard and mouse hooks but ran into a dead end; if I understand the documentation directly, .NET does not support global hooks which is what I am looking for here I guess.
Cheers all.
N. Farr

A background process to detect user activity / How to get VB.NET to use a C# class?
Chidu
Wayne Arant II
Hie guys, I'm quite new to .Net I'm programming in VB .Net 2003, for my Final year project at Uni. I'm facing a similar problem in trying to achieve the same and would like some help.
Thanks in advance.
mixxie
The "ApplicationIdle" codeproj is not the proper answer - it analyze counters once a sec. So if you moved mouse in between probes - your app wont notice that. Nice approach for the problem but I think is not sensible.
The Win32 hooks offered in first answer a bit scary - too many constants and author note is - I was too afraid to test it so I decided to help you out.
I have found similar to his solution - http://www.codeproject.com/csharp/globalhook.asp it is commented better and has much less constants and it almost guaranteed not to blow up anything.
Cheers
MeDave
Hi,
A while back I had a similar requirement on detecting operating system keyboard/mouse activity, at the time I did put together some code using SetWindowsHookEx API. However in the end I didn't incorporate this into my application because of the following reasons:
1. I'm not too familiar with native Win32 API and PInvoke
2. Low-level hooks are dangeous, they can easily lock your keyboard/mouse if not careful.
3. Didn't have much time to thoroughly test the code
That being said, this should give you something to start with, please use it at your own discretion(credit goes to pinvoke.net for most of the signatures):
This is the module containing all the pinvoke signatures:
Imports System.Runtime.InteropServices
Module HookLib
Delegate Function HookProc(ByVal code As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
Delegate Function LowLevelKeyboardProc(ByVal nCode As Integer, ByVal wParam As WindowMessages, ByVal lParam As KBDLLHOOKSTRUCT) As Integer
Delegate Function LowLevelMouseProc(ByVal nCode As Integer, ByVal wParam As WindowMessages, ByVal lParam As MSLLHOOKSTRUCT) As Integer
<DllImport("user32.dll")> _
Public Function SetWindowsHookEx(ByVal hook As HookType, ByVal callback As HookProc, ByVal hMod As IntPtr, ByVal dwThreadId As UInt32) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True)> _
Public Function SetWindowsHookEx(ByVal hook As HookType, ByVal callback As LowLevelKeyboardProc, ByVal hMod As IntPtr, ByVal dwThreadId As UInt32) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True)> _
Public Function SetWindowsHookEx(ByVal hook As HookType, ByVal callback As LowLevelMouseProc, ByVal hMod As IntPtr, ByVal dwThreadId As UInt32) As IntPtr
End Function
<DllImport("user32.dll")> _
Public Function CallNextHookEx(ByVal hhk As IntPtr, ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function
<DllImport("user32.dll")> _
Public Function UnhookWindowsHookEx(ByVal hhk As IntPtr) As Boolean
End Function
End Module
<StructLayout(LayoutKind.Sequential)> _
Public Class MSLLHOOKSTRUCT
Public pt As POINT
Public mouseData As Integer
Public flags As Integer
Public time As Integer
Public dwExtraInfo As IntPtr
End Class
<System.Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Sequential)> _
Public Structure POINT
Public X As Integer
Public Y As Integer
Public Sub New(ByVal X As Integer, ByVal Y As Integer)
Me.X = X
Me.Y = Y
End Sub
End Structure
<StructLayout(LayoutKind.Sequential)> _
Public Class KBDLLHOOKSTRUCT
Public vkCode As Integer
Public scanCode As Integer
Public flags As Integer
Public time As Integer
Public dwExtraInfo As IntPtr
End Class
Enum HookType
WH_JOURNALRECORD = 0
WH_JOURNALPLAYBACK = 1
WH_KEYBOARD = 2
WH_GETMESSAGE = 3
WH_CALLWNDPROC = 4
WH_CBT = 5
WH_SYSMSGFILTER = 6
WH_MOUSE = 7
WH_HARDWARE = 8
WH_DEBUG = 9
WH_SHELL = 10
WH_FOREGROUNDIDLE = 11
WH_CALLWNDPROCRET = 12
WH_KEYBOARD_LL = 13
WH_MOUSE_LL = 14
End Enum
Enum WindowMessages
WM_ACTIVATE = &H6
WM_ACTIVATEAPP = &H1C
WM_AFXFIRST = &H360
WM_AFXLAST = &H37F
WM_APP = &H8000
WM_ASKCBFORMATNAME = &H30C
WM_CANCELJOURNAL = &H4B
WM_CANCELMODE = &H1F
WM_CAPTURECHANGED = &H215
WM_CHANGECBCHAIN = &H30D
WM_CHAR = &H102
WM_CHARTOITEM = &H2F
WM_CHILDACTIVATE = &H22
WM_CLEAR = &H303
WM_CLOSE = &H10
WM_COMMAND = &H111
WM_COMPACTING = &H41
WM_COMPAREITEM = &H39
WM_CONTEXTMENU = &H7B
WM_COPY = &H301
WM_COPYDATA = &H4A
WM_CREATE = &H1
WM_CTLCOLORBTN = &H135
WM_CTLCOLORDLG = &H136
WM_CTLCOLOREDIT = &H133
WM_CTLCOLORLISTBOX = &H134
WM_CTLCOLORMSGBOX = &H132
WM_CTLCOLORSCROLLBAR = &H137
WM_CTLCOLORSTATIC = &H138
WM_CUT = &H300
WM_DEADCHAR = &H103
WM_DELETEITEM = &H2D
WM_DESTROY = &H2
WM_DESTROYCLIPBOARD = &H307
WM_DEVICECHANGE = &H219
WM_DEVMODECHANGE = &H1B
WM_DISPLAYCHANGE = &H7E
WM_DRAWCLIPBOARD = &H308
WM_DRAWITEM = &H2B
WM_DROPFILES = &H233
WM_ENABLE = &HA
WM_ENDSESSION = &H16
WM_ENTERIDLE = &H121
WM_ENTERMENULOOP = &H211
WM_ENTERSIZEMOVE = &H231
WM_ERASEBKGND = &H14
WM_EXITMENULOOP = &H212
WM_EXITSIZEMOVE = &H232
WM_FONTCHANGE = &H1D
WM_GETDLGCODE = &H87
WM_GETFONT = &H31
WM_GETHOTKEY = &H33
WM_GETICON = &H7F
WM_GETMINMAXINFO = &H24
WM_GETOBJECT = &H3D
WM_GETSYSMENU = &H313
WM_GETTEXT = &HD
WM_GETTEXTLENGTH = &HE
WM_HANDHELDFIRST = &H358
WM_HANDHELDLAST = &H35F
WM_HELP = &H53
WM_HOTKEY = &H312
WM_HSCROLL = &H114
WM_HSCROLLCLIPBOARD = &H30E
WM_ICONERASEBKGND = &H27
WM_IME_CHAR = &H286
WM_IME_COMPOSITION = &H10F
WM_IME_COMPOSITIONFULL = &H284
WM_IME_CONTROL = &H283
WM_IME_ENDCOMPOSITION = &H10E
WM_IME_KEYDOWN = &H290
WM_IME_KEYLAST = &H10F
WM_IME_KEYUP = &H291
WM_IME_NOTIFY = &H282
WM_IME_REQUEST = &H288
WM_IME_SELECT = &H285
WM_IME_SETCONTEXT = &H281
WM_IME_STARTCOMPOSITION = &H10D
WM_INITDIALOG = &H110
WM_INITMENU = &H116
WM_INITMENUPOPUP = &H117
WM_INPUTLANGCHANGE = &H51
WM_INPUTLANGCHANGEREQUEST = &H50
WM_KEYDOWN = &H100
WM_KEYFIRST = &H100
WM_KEYLAST = &H108
WM_KEYUP = &H101
WM_KILLFOCUS = &H8
WM_LBUTTONDBLCLK = &H203
WM_LBUTTONDOWN = &H201
WM_LBUTTONUP = &H202
WM_MBUTTONDBLCLK = &H209
WM_MBUTTONDOWN = &H207
WM_MBUTTONUP = &H208
WM_MDIACTIVATE = &H222
WM_MDICASCADE = &H227
WM_MDICREATE = &H220
WM_MDIDESTROY = &H221
WM_MDIGETACTIVE = &H229
WM_MDIICONARRANGE = &H228
WM_MDIMAXIMIZE = &H225
WM_MDINEXT = &H224
WM_MDIREFRESHMENU = &H234
WM_MDIRESTORE = &H223
WM_MDISETMENU = &H230
WM_MDITILE = &H226
WM_MEASUREITEM = &H2C
WM_MENUCHAR = &H120
WM_MENUCOMMAND = &H126
WM_MENUDRAG = &H123
WM_MENUGETOBJECT = &H124
WM_MENURBUTTONUP = &H122
WM_MENUSELECT = &H11F
WM_MOUSEACTIVATE = &H21
WM_MOUSEFIRST = &H200
WM_MOUSEHOVER = &H2A1
WM_MOUSELAST = &H20A
WM_MOUSELEAVE = &H2A3
WM_MOUSEMOVE = &H200
WM_MOUSEWHEEL = &H20A
WM_MOVE = &H3
WM_MOVING = &H216
WM_NCACTIVATE = &H86
WM_NCCALCSIZE = &H83
WM_NCCREATE = &H81
WM_NCDESTROY = &H82
WM_NCHITTEST = &H84
WM_NCLBUTTONDBLCLK = &HA3
WM_NCLBUTTONDOWN = &HA1
WM_NCLBUTTONUP = &HA2
WM_NCMBUTTONDBLCLK = &HA9
WM_NCMBUTTONDOWN = &HA7
WM_NCMBUTTONUP = &HA8
WM_NCMOUSEHOVER = &H2A0
WM_NCMOUSELEAVE = &H2A2
WM_NCMOUSEMOVE = &HA0
WM_NCPAINT = &H85
WM_NCRBUTTONDBLCLK = &HA6
WM_NCRBUTTONDOWN = &HA4
WM_NCRBUTTONUP = &HA5
WM_NEXTDLGCTL = &H28
WM_NEXTMENU = &H213
WM_NOTIFY = &H4E
WM_NOTIFYFORMAT = &H55
WM_NULL = &H0
WM_PAINT = &HF
WM_PAINTCLIPBOARD = &H309
WM_PAINTICON = &H26
WM_PALETTECHANGED = &H311
WM_PALETTEISCHANGING = &H310
WM_PARENTNOTIFY = &H210
WM_PASTE = &H302
WM_PENWINFIRST = &H380
WM_PENWINLAST = &H38F
WM_POWER = &H48
WM_PRINT = &H317
WM_PRINTCLIENT = &H318
WM_QUERYDRAGICON = &H37
WM_QUERYENDSESSION = &H11
WM_QUERYNEWPALETTE = &H30F
WM_QUERYOPEN = &H13
WM_QUEUESYNC = &H23
WM_QUIT = &H12
WM_RBUTTONDBLCLK = &H206
WM_RBUTTONDOWN = &H204
WM_RBUTTONUP = &H205
WM_RENDERALLFORMATS = &H306
WM_RENDERFORMAT = &H305
WM_SETCURSOR = &H20
WM_SETFOCUS = &H7
WM_SETFONT = &H30
WM_SETHOTKEY = &H32
WM_SETICON = &H80
WM_SETREDRAW = &HB
WM_SETTEXT = &HC
WM_SETTINGCHANGE = &H1A
WM_SHOWWINDOW = &H18
WM_SIZE = &H5
WM_SIZECLIPBOARD = &H30B
WM_SIZING = &H214
WM_SPOOLERSTATUS = &H2A
WM_STYLECHANGED = &H7D
WM_STYLECHANGING = &H7C
WM_SYNCPAINT = &H88
WM_SYSCHAR = &H106
WM_SYSCOLORCHANGE = &H15
WM_SYSCOMMAND = &H112
WM_SYSDEADCHAR = &H107
WM_SYSKEYDOWN = &H104
WM_SYSKEYUP = &H105
WM_SYSTIMER = &H118 ' undocumented, see http://support.microsoft.com/ id=108938
WM_TCARD = &H52
WM_TIMECHANGE = &H1E
WM_TIMER = &H113
WM_UNDO = &H304
WM_UNINITMENUPOPUP = &H125
WM_USER = &H400
WM_USERCHANGED = &H54
WM_VKEYTOITEM = &H2E
WM_VSCROLL = &H115
WM_VSCROLLCLIPBOARD = &H30A
WM_WINDOWPOSCHANGED = &H47
WM_WINDOWPOSCHANGING = &H46
WM_WININICHANGE = &H1A
WM_XBUTTONDBLCLK = &H20D
WM_XBUTTONDOWN = &H20B
WM_XBUTTONUP = &H20C
End Enum
This is the tester app, it's a simple form with 2 buttons:
Imports System.Runtime.InteropServices
Imports System.Reflection
Public Class Form1
Private delLLKB As New LowLevelKeyboardProc(AddressOf LowLevelKbCallback)
Private delLLMS As New LowLevelMouseProc(AddressOf LowLevelMsCallback)
Private kbHookPointer As IntPtr
Private msHookPointer As IntPtr
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Me.Hook()
End Sub
Private Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click
Me.UnHook()
End Sub
' Register global keyboard and mouse hooks
Private Sub Hook()
If kbHookPointer <> Nothing Or msHookPointer <> Nothing Then
Me.UnHook()
End If
Dim hMod As IntPtr = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()(0))
' For thread level hooks, use HookType.WH_KEYBOARD and WH_MOUSE,
' then set hMod to 0 and dwThreadId to AppDomain.GetCurrentThreadId()
kbHookPointer = SetWindowsHookEx(HookType.WH_KEYBOARD_LL, delLLKB, hMod, IntPtr.Zero)
msHookPointer = SetWindowsHookEx(HookType.WH_MOUSE_LL, delLLMS, hMod, IntPtr.Zero)
If kbHookPointer <> Nothing Then
MessageBox.Show("KB hook Success!")
Else
MessageBox.Show("KB hook Failed..")
End If
If msHookPointer <> Nothing Then
MessageBox.Show("Mouse hook Success!")
Else
MessageBox.Show("Mouse hook Failed..")
End If
End Sub
' Unregister global keyboard and mouse hooks
Private Sub UnHook()
If kbHookPointer <> Nothing Then
If UnhookWindowsHookEx(kbHookPointer) Then
MessageBox.Show("KB Unhook Success!")
Else
MessageBox.Show("KB Unhook failed")
End If
End If
If msHookPointer <> Nothing Then
If UnhookWindowsHookEx(msHookPointer) Then
MessageBox.Show("Mouse Unhook Success!")
Else
MessageBox.Show("Mouse Unhook failed")
End If
End If
End Sub
' Callback for Low-Level Mouse Events
Private Function LowLevelMsCallback(ByVal nCode As Integer, ByVal wParam As WindowMessages, ByVal lParam As MSLLHOOKSTRUCT) As Integer
Dim lParamPtr As IntPtr = GCHandle.ToIntPtr(GCHandle.Alloc(lParam))
If nCode < 0 Then
Return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParamPtr)
Else
'TODO: Put code here for handling global mouse events, wParam can be one of the following:
' WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN, or WM_RBUTTONUP
'lParam contains additional information
'Make sure to invoke CallNextHookEx
Return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParamPtr)
End If
End Function
' Callback for Low-Level Keyboard Events
Private Function LowLevelKbCallback(ByVal nCode As Integer, ByVal wParam As WindowMessages, ByVal lParam As KBDLLHOOKSTRUCT) As Integer
Dim lParamPtr As IntPtr = GCHandle.ToIntPtr(GCHandle.Alloc(lParam))
If nCode < 0 Then
Return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParamPtr)
Else
'TODO: Put code here for handling global low-level keyboard events, wParam can be one of the following:
'WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, or WM_SYSKEYUP
'lParam contains additional information
'Make sure to invoke CallNextHookEx
Return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParamPtr)
End If
End Function
End Class
Hope this helps.
BrettDerry