Solved

How to determine 3rd party command button state

Posted on 2011-09-09
11
257 Views
Last Modified: 2012-05-12
Good afternoon experts! I have written a low level keyboard hook that is specifically looking to trap the key down event of the enter key. What I'm looking to do is to find out if a particular OK button has focus when the enter key is pressed. I have the windows handle of the OK button that I'm monitoring.

It seems at some point I read something about an API call out there that could find out the state of a command button but can not find what I'm looking for.

Any suggestions are greatly appreciated!  I've attached the code that I have written so far.

Thanks!!!

R
Imports System.Runtime.InteropServices
Imports System.Drawing
Imports System.Reflection
Imports System.Threading
Public Class clsKeyboardHook

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

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

    Private Declare Function GetAsyncKeyState Lib "user32" _
      (ByVal vKey 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

    Public Structure KBDLLHOOKSTRUCT
        Public vkCode As Integer
        Public scanCode As Integer
        Public flags As Integer
        Public time As Integer
        Public dwExtraInfo As Integer
    End Structure

    ' Low-Level Keyboard Constants
    Private Const HC_ACTION As Integer = 0
    Private Const LLKHF_EXTENDED As Integer = &H1
    Private Const LLKHF_INJECTED As Integer = &H10
    Private Const LLKHF_DOWN As Integer = &H81

    ' Virtual Keys
    Public Const VK_ENTER = &HD
    Private Const WH_KEYBOARD_LL As Integer = 13&
    Public KeyboardHandle As Integer
    Private k_btnClicked As Boolean = False
    Public Property BtnClicked() As Boolean
        Get
            Return k_btnClicked
        End Get
        Set(ByVal value As Boolean)
            k_btnClicked = value
        End Set
    End Property


    Public Function IsHooked( _
      ByRef Hookstruct As KBDLLHOOKSTRUCT) As Boolean
        If (Hookstruct.vkCode = VK_ENTER) Then
            Return True
        End If
        'Note: you can add any key to this routine if you need to hook something other than enter
        Return False
    End Function

    
    Public Function KeyboardCallback(ByVal Code As Integer, _
      ByVal wParam As Integer, _
      ByRef lParam As KBDLLHOOKSTRUCT) As Integer

        If (Code = HC_ACTION) Then
            If (IsHooked(lParam)) Then 'is hooked will only be true on enter key

                'Here I determine if the enter key down event took place on the OK button I'm intercepting

                BtnClicked = True
                Return 1
            End If
        End If
        Return CallNextHookEx(KeyboardHandle, _
          Code, wParam, lParam)

    End Function


    Public Delegate Function KeyboardHookDelegate( _
      ByVal Code As Integer, _
      ByVal wParam As Integer, ByRef lParam As KBDLLHOOKSTRUCT) _
                   As Integer

    <MarshalAs(UnmanagedType.FunctionPtr)> _
    Private callback As KeyboardHookDelegate

    Public Function HookKeyboard(ByVal btnHndl As Integer) As Boolean
        callback = New KeyboardHookDelegate(AddressOf KeyboardCallback)

        KeyboardHandle = SetWindowsHookEx( _
          WH_KEYBOARD_LL, callback, _
          Marshal.GetHINSTANCE( _
          [Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, 0)

        If CheckHooked() Then
            Return True
        Else
            Return False
        End If
    End Function

    Public Function CheckHooked() As Boolean
        If Not (Hooked()) Then
            Throw New Exception("Could not set keyboard hook")
            Return False
        Else
            Return True
        End If
    End Function

    Private Function Hooked()
        Hooked = KeyboardHandle <> 0
    End Function

    Public Sub UnhookKeyboard()
        If (Hooked()) Then
            Call UnhookWindowsHookEx(KeyboardHandle)
        End If
    End Sub



End Class

Open in new window

0
Comment
Question by:romieb69
  • 6
  • 5
11 Comments
 
LVL 85

Accepted Solution

by:
Mike Tomlinson earned 500 total points
ID: 36513533
Hmmm...

First I'd use the GetForegroundWindow() API to see if your external applications main window has focus:
http://msdn.microsoft.com/en-us/library/ms633505(VS.85).aspx

If yes, then use GetWindowThreadProcessId() and AttachThreadInput() followed by GetFocus() to determine if your target Button is currently focused:
http://msdn.microsoft.com/en-us/library/ms633522(VS.85).aspx
http://msdn.microsoft.com/en-us/library/ms681956(VS.85).aspx
http://msdn.microsoft.com/en-us/library/ms646294(VS.85).aspx
0
 

Author Comment

by:romieb69
ID: 36514473
Yeah... I see what you mean. I'm already using GetForegroundWindow so I'm good there and have the target window in focus.

Isn't there something like GetButtonState or something similar that I can provide the winId for that button to and find out what it's various properties are? Or is that kind of functionality limited to my own app and not necessairly a button running in an external application?
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 36514682
Alrighty then...try using SendMessage() with BM_GETSTATE and check for a return value of BST_FOCUS:
http://msdn.microsoft.com/en-us/library/bb775988(VS.85).aspx


=)
0
 

Author Comment

by:romieb69
ID: 36514781
Aaahhh yeah good idea. I'll give it a shot and see if I can make that do it on Monday. Thanks!!
0
 

Author Comment

by:romieb69
ID: 36538529
Hey IM ...

I'm just now getting a chance to play with this again. I've given up on SendKeys... you can find out what a button's state is (depressed etc) through this avenue but not whether or not it has focus. So I've gone back to the first suggestion you have made.

Not quite making it work and I suspect I'm calling AttachThreadInput incorrectly.

On attempt number 1 I tried GetWindowThreadProcessId(winHndl, intPID)  where winHndl is the actual window that contains the button.  On attempt number 2 I tried GetWindowThreadProcessId(btnHndl, intPID) where btnHndl is the actual button being monitored.

I'm missing something ...the boolean blnAttach is always false.
Class...
    <DllImport("user32", CharSet:=CharSet.Ansi, SetLastError:=True, ExactSpelling:=True)> _
    Public Shared Function AttachThreadInput(ByVal idAttach As Integer, ByVal idAttachTo As Integer, ByVal fAttach As Integer) As Integer
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
    Public Shared Function GetWindowThreadProcessId(ByVal handle As IntPtr, ByRef processId As Integer) As Integer
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)> _
    Public Shared Function GetFocus() As IntPtr
    End Function


Public Function KeyboardCallback(ByVal Code As Integer, _
      ByVal wParam As Integer, _
      ByRef lParam As KBDLLHOOKSTRUCT) As Integer
        Dim ptrOK As IntPtr
        Dim intPID As Integer
        Dim blnAttach As Boolean = False
        Dim thCurThread As Thread

        If (Code = HC_ACTION) Then
            If (IsHooked(lParam)) Then 'is hooked will only be true on enter key
                thCurThread = Thread.CurrentThread()

                'Here I determine if the enter key down event took place on the OK button I'm intercepting
                'GetWindowThreadProcessId(winHndl, intPID)
                GetWindowThreadProcessId(btnHndl, intPID)
                AttachThreadInput(thCurThread.ManagedThreadId, intPID, blnAttach)
                If blnAttach Then
                    ptrOK = GetFocus()
                    If ptrOK.ToInt32 = btnHndl Then
                        BtnClicked = True
                        btnHndl = 0
                        Return 1
                    End If
                End If
            End If
        End If
        Return CallNextHookEx(KeyboardHandle, _
          Code, wParam, lParam)
If (Code = HC_ACTION) Then
            If (IsHooked(lParam)) Then 'is hooked will only be true on enter key
                thCurThread = Thread.CurrentThread()

                'Here I determine if the enter key down event took place on the OK button I'm intercepting
                'GetWindowThreadProcessId(winHndl, intPID)
                GetWindowThreadProcessId(btnHndl, intPID)
                AttachThreadInput(thCurThread.ManagedThreadId, intPID, blnAttach)
                If blnAttach Then
                    ptrOK = GetFocus()
                    If ptrOK.ToInt32 = btnHndl Then
                        BtnClicked = True
                        btnHndl = 0
                        Return 1
                    End If
                End If
            End If
        End If
        Return CallNextHookEx(KeyboardHandle, _
          Code, wParam, lParam)

    End Function

Open in new window

0
Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 36538794
Hmmm...haven't used it in awhile.  You need the PID for your thread and the PID for the external thread.

Next you use AttachThreadInput() to attach your thread to the external thread.

Something like:

                Dim ThreadID1 As Integer = GetWindowThreadProcessId(Me.Handle, 0) ' <-- Our PID
                Dim ThreadID2 As Integer = GetWindowThreadProcessId(btnHndl, 0) ' <-- External PID
                Call AttachThreadInput(ThreadID1, ThreadID2, True) ' <-- Attach

                ' ... use GetFocus() in here ...

                Call AttachThreadInput(ThreadID1, ThreadID2, False) ' <-- Unattach

What kind of app are you using though?  I've used "Me.Handle" which assumes a WinForms app and that the code is running in the context of the Form.  You may need to pass in a form/control into your class or we can figure out a different route to get your PID (possibly through Process class).
0
 

Author Comment

by:romieb69
ID: 36539038
This code is a class in a DLL that is called by a win form app when I need to activate my keyboard hook delegate...

So I was thinking that something like this:

Dim thCurThread As Thread

thCurThread = Thread.CurrentThread()   'set thCurThread to the current thread
intMyPID = thCurThread.ManagedThreadId 'obtain the current threads ID

AttachThreadInput(intMyPID, intBtnPID, blnAttach)

Interestingly enough intMyPID is always 10 ...

Also I read somewhere that the first parameter of AttachThreadInput can't be a system thread. I wonder if that is what I'm actually providing it...

I've attached all of the code in the class thus far.
Imports System.Runtime.InteropServices
Imports System.Drawing
Imports System.Reflection
Imports System.Threading
Public Class clsKeyboardHook
    <DllImport("user32", CharSet:=CharSet.Ansi, SetLastError:=True, ExactSpelling:=True)> _
    Public Shared Function AttachThreadInput(ByVal idAttach As Integer, ByVal idAttachTo As Integer, ByVal fAttach As Integer) As Integer
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
    Public Shared Function GetWindowThreadProcessId(ByVal handle As IntPtr, ByRef processId As Integer) As Integer
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)> _
    Public Shared Function GetFocus() As IntPtr
    End Function

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

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

    Private Declare Function GetAsyncKeyState Lib "user32" _
      (ByVal vKey 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

    Public Structure KBDLLHOOKSTRUCT
        Public vkCode As Integer
        Public scanCode As Integer
        Public flags As Integer
        Public time As Integer
        Public dwExtraInfo As Integer
    End Structure

    ' Low-Level Keyboard Constants
    Private Const HC_ACTION As Integer = 0
    Private Const LLKHF_EXTENDED As Integer = &H1
    Private Const LLKHF_INJECTED As Integer = &H10
    Private Const LLKHF_DOWN As Integer = &H81

    ' Virtual Keys
    Public Const VK_ENTER = &HD
    Private Const WH_KEYBOARD_LL As Integer = 13&
    Public KeyboardHandle As Integer
    Public btnHndl As Integer
    Public winHndl As Integer
    Private k_btnClicked As Boolean = False
    Public Property BtnClicked() As Boolean
        Get
            Return k_btnClicked
        End Get
        Set(ByVal value As Boolean)
            k_btnClicked = value
        End Set
    End Property


    Public Function IsHooked( _
      ByRef Hookstruct As KBDLLHOOKSTRUCT) As Boolean
        If (Hookstruct.vkCode = VK_ENTER) Then
            Return True
        End If
        'Note: you can add any key to this routine if you need to hook something other than enter
        Return False
    End Function


    Public Function KeyboardCallback(ByVal Code As Integer, _
      ByVal wParam As Integer, _
      ByRef lParam As KBDLLHOOKSTRUCT) As Integer
        Dim ptrOK As IntPtr
        Dim intMyPID As Integer = 0
        Dim intBtnPID As Integer = GetWindowThreadProcessId(btnHndl, 0)
        Dim blnAttach As Boolean = False
        Dim thCurThread As Thread

        If (Code = HC_ACTION) Then
            If (IsHooked(lParam)) Then 'is hooked will only be true on enter key
                thCurThread = Thread.CurrentThread()
                intMyPID = thCurThread.ManagedThreadId

                'Here I determine if the enter key down event took place on the OK button I'm intercepting
                AttachThreadInput(intMyPID, intBtnPID, blnAttach)
                If blnAttach Then
                    ptrOK = GetFocus()
                    If ptrOK.ToInt32 = btnHndl Then
                        BtnClicked = True
                        btnHndl = 0
                        Return 1
                    End If
                End If
            End If
        End If
        Return CallNextHookEx(KeyboardHandle, _
          Code, wParam, lParam)

    End Function


    Public Delegate Function KeyboardHookDelegate( _
      ByVal Code As Integer, _
      ByVal wParam As Integer, ByRef lParam As KBDLLHOOKSTRUCT) _
                   As Integer

    <MarshalAs(UnmanagedType.FunctionPtr)> _
    Private callback As KeyboardHookDelegate

    Public Function HookKeyboard(ByVal btnHndlPassIn As Integer, ByVal winHndlInsuranceScr As Integer) As Boolean
        callback = New KeyboardHookDelegate(AddressOf KeyboardCallback)
        btnHndl = btnHndlPassIn
        winHndl = winHndlInsuranceScr
        KeyboardHandle = SetWindowsHookEx( _
          WH_KEYBOARD_LL, callback, _
          Marshal.GetHINSTANCE( _
          [Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, 0)

        If CheckHooked() Then
            Return True
        Else
            Return False
        End If
    End Function

    Public Function CheckHooked() As Boolean
        If Not (Hooked()) Then
            Throw New Exception("Could not set keyboard hook")
            Return False
        Else
            Return True
        End If
    End Function

    Private Function Hooked()
        Hooked = KeyboardHandle <> 0
    End Function

    Public Sub UnhookKeyboard()
        If (Hooked()) Then
            Call UnhookWindowsHookEx(KeyboardHandle)
        End If
    End Sub



End Class

Open in new window

0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 36539133
I'm not that good at the lower level stuff...I think you need ID() instead of ManagedThreadID():

    intMyPID = Thread.CurrentThread.ID
0
 

Author Closing Comment

by:romieb69
ID: 36552877
This solution got it done!

Idle Mind thanks for an excellent suggestion!!
0
 

Author Comment

by:romieb69
ID: 36552949
Here's the mistakes I made in the last version of this

1. I tried to use this to set intMyPID
'thCurThread = Thread.CurrentThread()
'intMyPID = thCurThread.ManagedThreadId

What I needed to use was this      

intMyPID = AppDomain.GetCurrentThreadId


But it generates the following warning

Public Shared Function GetCurrentThreadId() As Integer' is obsolete: 'AppDomain.GetCurrentThreadId has been deprecated because it does not provide a stable Id when managed threads are running on fibers (aka lightweight threads). To get a stable identifier for a managed thread, use the ManagedThreadId property on Thread.  http://go.microsoft.com/fwlink/?linkid=14202'.

2. I was attaching to the btn pid incorrectly... this is the correct way:
AttachThreadInput(intMyPID, intBtnPID, True) with the third parameter being a boolean toggle

3. This one killed me and took forever to think to pop in there before calling GetFocus()
SetForegroundWindow(winHndl)

Thanks again IM for the great suggestion and all the guidance.
Imports System.Runtime.InteropServices
Imports System.Drawing
Imports System.Reflection
Imports System.Threading
Public Class clsKeyboardHook
    <DllImport("user32", CharSet:=CharSet.Ansi, SetLastError:=True, ExactSpelling:=True)> _
    Public Shared Function AttachThreadInput(ByVal idAttach As Integer, ByVal idAttachTo As Integer, ByVal fAttach As Integer) As Integer
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
    Public Shared Function GetWindowThreadProcessId(ByVal handle As IntPtr, ByRef processId As Integer) As Integer
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)> _
    Public Shared Function GetFocus() As IntPtr
    End Function

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

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

    Private Declare Function GetAsyncKeyState Lib "user32" _
      (ByVal vKey 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

    Public Structure KBDLLHOOKSTRUCT
        Public vkCode As Integer
        Public scanCode As Integer
        Public flags As Integer
        Public time As Integer
        Public dwExtraInfo As Integer
    End Structure

    ' Low-Level Keyboard Constants
    Private Const HC_ACTION As Integer = 0
    Private Const LLKHF_EXTENDED As Integer = &H1
    Private Const LLKHF_INJECTED As Integer = &H10
    Private Const LLKHF_DOWN As Integer = &H81

    ' Virtual Keys
    Public Const VK_ENTER = &HD
    Private Const WH_KEYBOARD_LL As Integer = 13&
    Public KeyboardHandle As Integer
    Public btnHndl As Integer
    Public winHndl As Integer
    Public intMyPID As Integer = 0
    Public intBtnPID As Integer
    Private k_btnClicked As Boolean = False
    Public Property BtnClicked() As Boolean
        Get
            Return k_btnClicked
        End Get
        Set(ByVal value As Boolean)
            k_btnClicked = value
        End Set
    End Property


    Public Function IsHooked( _
      ByRef Hookstruct As KBDLLHOOKSTRUCT) As Boolean
        If (Hookstruct.vkCode = VK_ENTER) Then
            Return True
        End If
        'Note: you can add any key to this routine if you need to hook something other than enter
        Return False
    End Function


    Public Function KeyboardCallback(ByVal Code As Integer, _
      ByVal wParam As Integer, _
      ByRef lParam As KBDLLHOOKSTRUCT) As Integer
        Dim ptrOK As IntPtr
        If (Code = HC_ACTION) Then
            If (IsHooked(lParam)) Then 'is hooked will only be true on enter key
                'thCurThread = Thread.CurrentThread()
                'intMyPID = thCurThread.ManagedThreadId
                'intMyPID = AppDomain.GetCurrentThreadId
                'Here I determine if the enter key down event took place on the OK button I'm intercepting
                AttachThreadInput(intMyPID, intBtnPID, True)
                SetForegroundWindow(winHndl)
                ptrOK = GetFocus()
                If ptrOK.ToInt32 = btnHndl Then
                    BtnClicked = True
                    btnHndl = 0
                    blnAttach = False
                    AttachThreadInput(intMyPID, intBtnPID, False)
                    Return 1

                End If
            End If
        End If
        AttachThreadInput(intMyPID, intBtnPID, False)
        Return CallNextHookEx(KeyboardHandle, _
          Code, wParam, lParam)

    End Function


    Public Delegate Function KeyboardHookDelegate( _
      ByVal Code As Integer, _
      ByVal wParam As Integer, ByRef lParam As KBDLLHOOKSTRUCT) _
                   As Integer

    <MarshalAs(UnmanagedType.FunctionPtr)> _
    Private callback As KeyboardHookDelegate

    Public Function HookKeyboard(ByVal btnHndlPassIn As Integer, ByVal winHndlInsuranceScr As Integer) As Boolean
        Dim thCurThread As Thread

        callback = New KeyboardHookDelegate(AddressOf KeyboardCallback)
        btnHndl = btnHndlPassIn
        intBtnPID = GetWindowThreadProcessId(btnHndl, 0)
        winHndl = winHndlInsuranceScr
        KeyboardHandle = SetWindowsHookEx( _
          WH_KEYBOARD_LL, callback, _
          Marshal.GetHINSTANCE( _
          [Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, 0)

        If CheckHooked() Then
            intMyPID = AppDomain.GetCurrentThreadId
            thCurThread = Thread.CurrentThread()
            'AttachThreadInput(intMyPID, intBtnPID, True)
            Return True
        Else
            Return False
        End If
    End Function

    Public Function CheckHooked() As Boolean
        If Not (Hooked()) Then
            Throw New Exception("Could not set keyboard hook")
            Return False
        Else
            Return True
        End If
    End Function

    Private Function Hooked()
        Hooked = KeyboardHandle <> 0
    End Function

    Public Sub UnhookKeyboard()
        If (Hooked()) Then
            Call UnhookWindowsHookEx(KeyboardHandle)
        End If
    End Sub



End Class

Open in new window

0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 36552985
Yay!...glad you were able to hammer something together.  =)
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

Suggested Solutions

This article is meant to give a basic understanding of how to use R Sweave as a way to merge LaTeX and R code seamlessly into one presentable document.
Since upgrading to Office 2013 or higher installing the Smart Indenter addin will fail. This article will explain how to install it so it will work regardless of the Office version installed.
Viewers will learn how to properly install Eclipse with the necessary JDK, and will take a look at an introductory Java program. Download Eclipse installation zip file: Extract files from zip file: Download and install JDK 8: Open Eclipse and …
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

9 Experts available now in Live!

Get 1:1 Help Now