WIN32 keyboard Hook and send keys to other app.

Windows specific questions.
Post Reply
geminis4941
Posts: 64
Joined: Jul 15, 2009 12:41

WIN32 keyboard Hook and send keys to other app.

Post by geminis4941 »

Hello,
I want to make a keyboard hook that send keys to Audacity , althought you are working on other app. I have two the separate parts, one from a forum sample and the other is the hook. I need to find the process that runs audacity to pass the keys. If someone has experience with this, it would be great.
Thanks everybody in advanced.

first part from forum viewtopic.php?f=2&t=21639&hilit=hook modified:

Code: Select all

'run audacity.exe, send key
' Windows console app
'
#include once "windows.bi"

dim as integer pid,res
dim as string tfn,txt
dim as HANDLE kproc,edit = 0,hWnd = 0
dim as STARTUPINFO si
    si.cb=len(si)
dim as PROCESS_INFORMATION pi
declare function Get_hWnd (pid as integer) as hWnd
'
tfn="C:\Program Files (x86)\Audacity\audacity.exe" 'may need complete path
res=CreateProcess(NULL,tfn,NULL,NULL,NULL,_
                  NULL,NULL,NULL,@si,@pi)
'
if res=0 then
   print "Failed to start Audacity"
   print "Press a key to Exit"
   sleep
   end
end if
'
'wait for process initialization
res=WaitForInputIdle(pi.hProcess,2000)   'error if res<>0
'
'get notepad's hWnd from process id
pid=pi.dwProcessId
hWnd=Get_hWnd(pid)                       'error if hWnd=0
'
'get notepad's "Edit" window hWnd
'edit=FindWindowEx(hWnd,Null,"Edit",Null) 'error if edit=0
'
'send it some text
txt="Hello World!"
'res=SendMessage(hWnd,WM_SETTEXT,Null,cast(LPARAM,strptr(txt)))
				PostMessage(hWnd,WM_KEYDOWN, VK_F1,0)
print "Sleeping to Exit.."
sleep
'
'This kills the notepad instance, brutally
kproc=OpenProcess(SYNCHRONIZE or PROCESS_TERMINATE,FALSE,pid)
if kproc>0 then
    res=TerminateProcess(kproc,0)        'fail if res=FALSE
    CloseHandle(kproc)
end if
'end
'
function Get_hWnd(pid as integer) as hWnd
'
    dim as integer ProcID
    dim as HWND hWnd=0
    '   
    hWnd=FindWindow(NULL,NULL)
    do while hWnd>0
        if GetParent(hwnd)=0 then
            GetWindowThreadProcessId(hWnd,@ProcID)
            if ProcID=pid then
                return hWnd
            end if
        end if
        hWnd=GetWindow(hWnd,GW_HWNDNEXT)
    loop
    '   
    return 0
'
end function

The second part is the hook:

Code: Select all


#include once "windows.bi"
#include once "crt.bi"
#include once "fbgfx.bi"
using fb
'
declare function On_Key(idHook as integer,_
                 lpfn as HOOKPROC) as integer
'
declare function KBProc1(ByVal Code As integer, _
                 ByVal wParam As integer,_
                 ByVal lParam As integer) As integer
'
dim shared KBHandle as HHOOK
dim shared hwnd as HWND
dim as integer res
dim shared as integer idKey,LastKey
'
type _hookstruct_           'see MDSN "KeyboardProc"
    as ubyte state    : 1
    as ubyte prevkey  : 1
    as ubyte alt      : 1
    as ubyte reserved : 4
    as ubyte extended : 1
    scancode as ushort
    repeat as uinteger
end type
'
Function WndProc( byval hWnd as HWND,_
                  byval uMsg as uint,_
                  byval wParam as uint,_
                  byval lParam as uint) as uint

    dim hDC as HDC
    dim ps as PAINTSTRUCT

    select case uMsg
        case WM_PAINT

            hDC = BeginPaint( hWnd, @ps )

            '' Update the display.
            

            EndPaint( hWnd, @ps )

          case WM_DESTROY

            PostQuitMessage( null )
            Return 0

    end select

    return DefWindowProc( hWnd, uMsg, wParam, lParam )

end function

function WinMain ( byval hInstance as HINSTANCE,_
                   byval hPrevInstance as HINSTANCE,_
                   lpCmdLine as string,_
                   byval nCmdShow as uint ) as uint

    dim msg as MSG
    dim wc as WNDCLASSEX

    with wc
        .cbSize = sizeof( WNDCLASSEX )
        .style = CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW
        .lpfnWndProc = cast( WNDPROC, @WndProc )
        .cbClsExtra = null
        .cbWndExtra = null
        .hInstance = hInstance
        .hbrBackground = cast( HBRUSH,COLOR_WINDOW + 1 )
        .lpszMenuName = null
        .lpszClassName = @"mywindow"
        .hIcon = null
        .hCursor = LoadCursor ( null, IDC_ARROW )
        .hIconSm = 0
    end with

    RegisterClassEx( @wc )
    hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW,_
                           "mywindow",_
                           "Test",_
                           WS_OVERLAPPEDWINDOW,_
                           0,0,500,370,_
                           null, null,_
                           hInstance, null )
    ShowWindow( hWnd, SW_SHOWNORMAL )


	 On_Key(37,@KBProc1) 'see MultiKey scancodes, winuser.bi
 
    while (GetMessage(@msg, NULL, 0, 0))
        TranslateMessage(@msg)
        DispatchMessage(@msg)
    Wend
    


	UnhookWindowsHookEx(KBHandle)
	Print "Sleeping to Exit.. press a key"
	
return msg.wParam

End Function 

end WinMain( GetModuleHandle( null ), null, command$, SW_NORMAL )
'
function On_Key(idHook as integer,_
         lpfn as HOOKPROC) as integer
         
    idKey=idHook
    'KBHandle=SetWindowsHookEx(WH_KEYBOARD,_
     KBHandle=SetWindowsHookEx(WH_KEYBOARD_LL,_ 
             lpfn,_
             NULL,_
             0)
End function
'
Dim Shared As KBDLLHOOKSTRUCT kbdStruct
function KBProc1(ByVal Code As integer, _
         ByVal wParam As integer,_
         ByVal lParam As integer) As integer
 
    Print "HOOK procedure..."
    If (Code >= 0) Then
		' the action is valid: HC_ACTION.
		if (wParam = WM_KEYDOWN)Then
			' lParam is the pointer to the struct containing the data needed, so cast and assign it to kdbStruct.
			kbdStruct = *(Cast(KBDLLHOOKSTRUCT Ptr,lParam))
			' a key (non-system) is pressed.
			if (kbdStruct.vkCode = VK_F1) Then
				' F1 is pressed!
				MessageBox(NULL, "F1 is pressed!", "key pressed", MB_ICONINFORMATION)
				'Dim As HWND WindowToFind = FindWindow(null, "???????")
				'PostMessage(WindowToFind,WM_KEYDOWN, VK_F1,0)

			End If
		End if
    End If
'
    'pass-on keys we don't want
    KBProc1=CallNextHookEx(KBHandle,_
            Code, wParam, lParam)
End Function

jj2007
Posts: 2326
Joined: Oct 23, 2016 15:28
Location: Roma, Italia
Contact:

Re: WIN32 keyboard Hook and send keys to other app.

Post by jj2007 »

I am not sure if your code will work, but if not, there is was another road:

Code: Select all

GetWindowThreadProcessId
GetCurrentThreadId
AttachThreadInput
SetKeyboardState
SendMessage, .., WM_KEYDOWN
SendMessage, .., WM_KEYUP
SetKeyboardState
AttachThreadInput
SendMessage, .., WM_ACTIVATE, WA_CLICKACTIVE, 0
I wrote "was" because a quick check tells me that it doesn't work any more. Six years ago it worked just fine on Win7. For Microsoft, this has always been a potential security breach, so it is possible that they closed that hole. Be prepared for surprises...
grindstone
Posts: 862
Joined: May 05, 2015 5:35
Location: Germany

Re: WIN32 keyboard Hook and send keys to other app.

Post by grindstone »

Get the PID and window handle of a running Audacity:

Code: Select all

#Include Once "windows.bi"
#Include Once "win/tlhelp32.bi"

Dim As String g
Dim As HANDLE snapshothandle
Dim As PROCESSENTRY32 processentry
Dim As Integer a_pid
Dim As HWND hWndAudacity
Dim As LPDWORD process

'get audacity process ID
g = ""
snapshothandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0)
processentry.dwSize = SizeOf(processentry) 
Process32First(snapshothandle,@processentry) 'first process handle (is SYSTEM IDLE PROCESS)
Do While Process32Next(snapshothandle, @processentry) 'query all processes
	If LCase(processentry.szExeFile) = "audacity.exe" Then 'process belongs to audacity
		g = processentry.szExeFile 'file name
		a_pid = processentry.th32ProcessID 'process ID
		Exit Do 'terminate scan
	EndIf
Loop
CloseHandle(snapshothandle)

Print "PID =";a_pid
Print "*";g;"*"

'get window handle from PID
hWndAudacity = GetWindow(FindWindow(0, 0), GW_HWNDFIRST) 'first window handle
Do 
	hWndAudacity = GetWindow(hWndAudacity, GW_HWNDNEXT) 'next window handle
	GetWindowThreadProcessId(hWndAudacity, Cast(Any Ptr, @process)) 'get PID of the window
	If a_pid = process Then 'window belongs to the wanted process
		If IsWindowVisible(hWndAudacity) Then 'main window
			Exit Do 'terminate
		EndIf
	EndIf
Loop While hWndAudacity

Print "Window handle = ";hWndAudacity
But AFAIK you can't send a keystroke to a process that hasn't the focus.
jj2007 wrote:I wrote "was" because a quick check tells me that it doesn't work any more. Six years ago it worked just fine on Win7. For Microsoft, this has always been a potential security breach, so it is possible that they closed that hole.
That would break downward compatibility.
Post Reply