Search code examples
delphiwinapiwindow-functionswindow-handles

GetFocus() returns null for MS Word


I am writing a Keyboard application that hooks the keyboard and remaps the keys. For this, I have created two projects, one .exe and .dll. In the .dll project, I detect the Handle of the Window in which the user is typing by GetFocus(). However, it works fine in notepad, but not in MS Word since I am not able to get the Window's Handle for the MS Word, using GetFocus()

I understand, that is because it might be running under different thread and hence, I need to get the Parent Window Handle by GetForegroundWindow() and iterate through its child windows and somehow get the right Handle. While searching on internet I found following code (http://www.codeproject.com/Articles/34752/Control-in-Focus-in-Other-Processes)

activeWindowHandle:= GetForegroundWindow();
activeWindowThread:= GetWindowThreadProcessId(activeWindowHandle, 0);
thisWindowThread:= GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
AttachThreadInput(activeWindowThread, thisWindowThread, true);
lpHookRec^.TheAppWinHandle:= GetFocus();
AttachThreadInput(activeWindowThread, thisWindowThread, false);

However, it is not working for me :(

In my code I have written

lpHookRec^.TheAppWinHandle := GetFocus();

and that gives me the Handle of the NotePad window in lpHookRec^.TheAppWinHandle. However, if I use MS Word instead of NotePad, the above code gives me null(zero). So need to write function that returns the correct Handle, irrespective of thread it is running under, something like

function GetAppliWinHandle: Hwnd;
  var
    activeWindowHandle,activeWindowThread,thisWindowThread,focusedControlHandle: Hwnd;
  begin { GetAppliWinHandle }
    focusedControlHandle := GetFocus();
    if focusedControlHandle = 0 then
      begin
        activeWindowHandle := GetForegroundWindow();
        activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
        thisWindowThread := GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
        AttachThreadInput(activeWindowThread, thisWindowThread, true);
        focusedControlHandle := GetFocus();
        AttachThreadInput(activeWindowThread, thisWindowThread, false);
      end;
    Result:=focusedControlHandle
  end; { GetAppliWinHandle }

and here is the complete code for the dll

library TheHook;

uses
  Windows, Messages, SysUtils;

{Define a record for recording and passing information process wide}
type
  PHookRec = ^THookRec;
  THookRec = packed record
    TheHookHandle: HHOOK;
    TheAppWinHandle: HWND;
    TheCtrlWinHandle: HWND;
    TheKeyCount: DWORD;
  end;

var
  hObjHandle: THandle; {Variable for the file mapping object}
  lpHookRec: PHookRec; {Pointer to our hook record}

procedure MapFileMemory(dwAllocSize: DWORD);
begin
  {Create a process wide memory mapped variable}
  hObjHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, dwAllocSize,
    'HookRecMemBlock');
  if (hObjHandle = 0) then
  begin
    MessageBox(0, 'Hook DLL', 'Could not create file map object', MB_OK);
    exit;
  end;
  {Get a pointer to our process wide memory mapped variable}
  lpHookRec := MapViewOfFile(hObjHandle, file_MAP_write, 0, 0, dwAllocSize);
  if (lpHookRec = nil) then
  begin
    CloseHandle(hObjHandle);
    MessageBox(0, 'Hook DLL', 'Could not map file', MB_OK);
    exit;
  end;
end;

procedure UnMapFileMemory;
begin
  {Delete our process wide memory mapped variable}
  if (lpHookRec <> nil) then
  begin
    UnMapViewOfFile(lpHookRec);
    lpHookRec := nil;
  end;
  if (hObjHandle > 0) then
  begin
    CloseHandle(hObjHandle);
    hObjHandle := 0;
  end;
end;

function GetHookRecPointer: pointer stdcall;
begin
  {Return a pointer to our process wide memory mapped variable}
  result := lpHookRec;
end;

{The function that actually processes the keystrokes for our hook}

function KeyBoardProc(Code: integer; wParam: integer; lParam: integer): integer;
  stdcall;
  function GetAppliWinHandle: Hwnd;
  var
    activeWindowHandle,activeWindowThread,thisWindowThread,focusedControlHandle: Hwnd;
  begin { GetAppliWinHandle }
    focusedControlHandle := GetFocus();
    if focusedControlHandle = 0 then
      begin
        activeWindowHandle := GetForegroundWindow();
        activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
        thisWindowThread := GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
        AttachThreadInput(activeWindowThread, thisWindowThread, true);
        focusedControlHandle := GetFocus();
        AttachThreadInput(activeWindowThread, thisWindowThread, false);
      end;
    Result:=focusedControlHandle
  end; { GetAppliWinHandle }

var
  KeyUp: bool;
  {Remove comments for additional functionability ... :

  IsAltPressed: bool;
  IsCtrlPressed: bool;
  IsShiftPressed: bool;
  }
begin
  result := 0;
  case Code of
    HC_ACTION:
      begin
        {We trap the keystrokes here}
        {is this a key up message?}
        KeyUp := ((lParam and (1 shl 31)) <> 0);

        {Remove comments for additional functionability ... :

        {is the Alt key pressed}
        if ((lParam and (1 shl 29)) <> 0) then
        begin
          IsAltPressed := TRUE;
        end
        else
        begin
          IsAltPressed := FALSE;
        end;
        {is the Control key pressed}
        if ((GetKeyState(VK_CONTROL) and (1 shl 15)) <> 0) then
        begin
          IsCtrlPressed := TRUE;
        end
        else
        begin
          IsCtrlPressed := FALSE;
        end;
        {if the Shift key pressed}
        if ((GetKeyState(VK_SHIFT) and (1 shl 15)) <> 0) then
        begin
          IsShiftPressed := TRUE;
        end
        else
        begin
          IsShiftPressed := FALSE;
        end;
        }

        {if KeyUp then increment the key count}
        if (KeyUp <> FALSE) then
        begin
          Inc(lpHookRec^.TheKeyCount);
        end;
        case wParam of
          {Was the enter key pressed?}
          VK_RETURN:
            begin
              {if KeyUp}
              if (KeyUp <> FALSE) then
              begin
                {Post a bogus message to the window control in our app}
                PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYDOWN, 0, 0);
                PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYUP, 0, 0);
              end;
              {if you wanted to swallow the keystroke then return -1, else if you 
want 
                            to allow the keystroke then return 0}
              result := 0;
              exit;
            end; {VK_RETURN}
          {if the left arrow key is pressed then lets play a joke!}
          VK_LEFT:
            begin
              {Get the Handle of the Application Window in lpHookRec^.TheAppWinHandle}
             lpHookRec^.TheAppWinHandle:=GetAppliWinHandle;

              {if KeyUp}
              if (KeyUp <> FALSE) then
              begin
                {Create a UpArrow keyboard event}
                keybd_event(VK_RIGHT, 0, 0, 0);
                keybd_event(VK_RIGHT, 0, KEYEVENTF_KEYUP, 0);
              end;
              {Swallow the keystroke}
              result := -1;
              exit;
            end; {VK_LEFT}
        end; {case wParam}
        {Allow the keystroke}
        result := 0;
      end; {HC_ACTION}
    HC_NOREMOVE:
      begin
        {This is a keystroke message, but the keystroke message has not been removed
             from the message queue, since an application has called PeekMessage() 
            specifying PM_NOREMOVE}
        result := 0;
        exit;
      end;
  end; {case code}
  if (Code < 0) then
    {Call the next hook in the hook chain}
    result := CallNextHookEx(lpHookRec^.TheHookHandle, Code, wParam, lParam);
end;

procedure StartKeyBoardHook stdcall;
begin
  {if we have a process wide memory variable and the hook has not already been 
set...}
  if ((lpHookRec <> nil) and (lpHookRec^.TheHookHandle = 0)) then
  begin
    {set the hook and remember our hook handle}
    lpHookRec^.TheHookHandle := SetWindowsHookEx(WH_KEYBOARD, @KeyBoardProc,
      hInstance, 0);
  end;
end;

procedure StopKeyBoardHook stdcall;
begin
  {if we have a process wide memory variable and the hook has already been set...}
  if ((lpHookRec <> nil) and (lpHookRec^.TheHookHandle <> 0)) then
  begin
    {Remove our hook and clear our hook handle}
    if (UnHookWindowsHookEx(lpHookRec^.TheHookHandle) <> FALSE) then
    begin
      lpHookRec^.TheHookHandle := 0;
    end;
  end;
end;

procedure DllEntryPoint(dwReason: DWORD);
begin
  case dwReason of
    Dll_Process_Attach:
      begin
        {if we are getting mapped into a process, then get a pointer to our 
                process wide memory mapped variable}
        hObjHandle := 0;
        lpHookRec := nil;
        MapFileMemory(sizeof(lpHookRec^));
      end;
    Dll_Process_Detach:
      begin
        {if we are getting unmapped from a process then, remove the pointer to 
                our process wide memory mapped variable}
        UnMapFileMemory;
      end;
  end;
end;

exports
  KeyBoardProc name 'KEYBOARDPROC',
  GetHookRecPointer name 'GETHOOKRECPOINTER',
  StartKeyBoardHook name 'STARTKEYBOARDHOOK',
  StopKeyBoardHook name 'STOPKEYBOARDHOOK';

begin
  {set our Dll's main entry point}
  DLLProc := @DllEntryPoint;
  {Call our Dll's main entry point}
  DllEntryPoint(Dll_Process_Attach);

end.

Solution

  • Per the GetFocus() documentation:

    Retrieves the handle to the window that has the keyboard focus, if the window is attached to the calling thread's message queue.

    ...

    GetFocus returns the window with the keyboard focus for the current thread's message queue. If GetFocus returns NULL, another thread's queue may be attached to a window that has the keyboard focus.

    Use the GetForegroundWindow function to retrieve the handle to the window with which the user is currently working. You can associate your thread's message queue with the windows owned by another thread by using the AttachThreadInput function.

    You are trying to do this part, but you are not doing it correctly, and you are not checking for errors along the way. You are also mistakenly using the HWND data type for thread IDs, but they are not HWNDs, they are DWORDs instead.

    Try something more like this:

    function GetAppliWinHandle: HWND;
    var
      activeWindowHandle: HWND;
      activeWindowThread, thisThread: DWORD;
    begin
      Result := GetFocus();
      if Result = 0 then
      begin
        activeWindowHandle := GetForegroundWindow();
        if activeWindowHandle <> 0 then
        begin
          activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
          thisThread := GetCurrentThreadId();
          if AttachThreadInput(activeWindowThread, thisThread, TRUE) then
          begin
            Result := GetFocus();
            AttachThreadInput(activeWindowThread, thisThread, FALSE);
          end;
        end;
      end;
    end;
    

    However, the same documentation also says:

    To get the window with the keyboard focus on the foreground queue or the queue of another thread, use the GetGUIThreadInfo function.

    For example:

    function GetAppliWinHandle: HWND;
    var
      activeWindowHandle: HWND;
      activeWindowThread: DWORD;
      gui: TGUIThreadinfo;
    begin
      Result := GetFocus();
      if Result = 0 then
      begin
        activeWindowHandle := GetForegroundWindow();
        if activeWindowHandle <> 0 then
        begin
          activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
          gui.cbSize := sizeof(gui);
          if GetGUIThreadInfo(activeWindowThread, gui) then
            Result := gui.hwndFocus;
        end;
      end;
    end;
    

    Or simpler:

    function GetAppliWinHandle: HWND;
    var
      gui: TGUIThreadinfo;
    begin
      gui.cbSize := sizeof(gui);
      if GetGUIThreadInfo(0, gui) then
        Result := gui.hwndFocus
      else
        Result := 0;
    end;