Page 1 of 2

Window hooking?

Posted: Sun Jan 30, 2005 8:39 am
by PB
Anyone know how to perform window hooking so I know when a third-party
window is opening? I'd like to make the Windows Calculator always open in
the center of the Desktop, but not move it after it's opened.

Posted: Sun Jan 30, 2005 10:00 am
by Heis Spiter

Re: Window hooking?

Posted: Sun Jan 30, 2005 12:27 pm
by PB
Thanks Heis, but I'm still confused over this. :(

If you look at http://tinyurl.com/5dzq7 there's an example of how to position
a MessageBox before it's displayed (click the "Listing 1" link in the article);
however I've not been successful in converting it to PureBasic. Anyone able
to offer any advice? Thanks.

Posted: Sun Jan 30, 2005 2:08 pm
by Heis Spiter
I'll try to convert Listing 1. But, I'm not sure I'll managed to... :?

Posted: Sun Jan 30, 2005 3:52 pm
by freak
This code should get you started:

Hook dll:

Code: Select all

; This is the hook procedure. It must be compiled to a dll.
; Every process on which this hook is installed will load
; this dll and execute the procedure.
;
; Even though you also load this dll from the program that installes
; the hook, you have to keep in mind that this code is mostly excecuted
; inside a foreign process, not the one of your program.
;
; For this example, all the code does is catch a #WM_CREATE message
; and broadcast our own message, so the original program can read
; it. Usually, whatever task needs to be done can be done right here
; inside the procedure, so that extra message is not needed.
;
Global CreateMessage.l

ProcedureDLL AttachProcess(Instance)
  CreateMessage = RegisterWindowMessage_("MyHook_Window_Created")
EndProcedure

; This is the hook procedure
; The parameters depend on what type of hook you install.
; This is a hook which is called before the window procedure of the
; process is called.
;
ProcedureDLL MyHookProcedure(nCode.l, wParam.l, *lParam.CWPSTRUCT)

  ; Just catch this one message.
  If *lParam\message = #WM_CREATE
    PostMessage_(#HWND_BROADCAST, CreateMessage, *lParam\hwnd, 0)
  EndIf

  ; This is very important to allow multiple hooks to work side by side.
  ProcedureReturn CallNextHookEx_(@MyHookProcedure(), nCode, wParam, *lParam)
EndProcedure
Program:

Code: Select all

; This is the program that installes the hook.
; Note: it also needs to load the hook dll, even if it itself is not hooked.

; little gui stuff
Enumeration
  #GADGET_List
  #GADGET_Start
  #GADGET_Stop
EndEnumeration

#Hook_Path = "b:\test.dll" ; the hook dll
#LIBRARY_Hook = 0          ; PB id for our dll.

If OpenWindow(0, 0, 0, 400, 400, #PB_Window_SystemMenu|#PB_Window_Screencentered, "Hook example")
  If CreateGadgetList(WindowID())
    ListViewGadget(#GADGET_List, 5, 5, 390, 365)
    ButtonGadget(#GADGET_Start, 5, 375, 120, 20, "Install Hook")
    ButtonGadget(#GADGET_Stop, 130, 375, 120, 20, "Remove Hook")    
  EndIf
  
  DisableGadget(#GADGET_Stop, 1)
  CreateMessage = RegisterWindowMessage_("MyHook_Window_Created")
  
  Repeat
    Event = WaitWindowEvent()
    
    ; here we received our create message..
    ;
    If Event = CreateMessage
      hwnd = EventwParam()
      Name$ = Space(500)
      GetWindowText_(hwnd, @Name$, 500)
      AddGadgetItem(#GADGET_List, -1, "WM_CREATE: "+RSet(Hex(hwnd), 8, "0")+" -> "+Name$)
      SetGadgetState(#GADGET_List, CountGadgetItems(#GADGET_List)-1)
    
    ElseIf Event = #PB_EventCloseWindow
      Quit = 1
    
    ElseIf Event = #PB_EventGadget
      Select EventGadgetID()
      
        Case #GADGET_Start
        
          ; This code installes the hook.        
          If OpenLibrary(#LIBRARY_Hook, #Hook_Path)
            dllInstance = LibraryID(#LIBRARY_Hook)
            dllFunction = IsFunction(#LIBRARY_Hook, "MyHookProcedure")
            
            ; If you set the last parameter to a threadid, only this thread will get
            ; hooked. Like this, every thread is hooked.
            HookID = SetWindowsHookEx_(#WH_CALLWNDPROC, dllFunction, dllInstance, #NULL)
            If HookID
              DisableGadget(#GADGET_Start, 1)
              DisableGadget(#GADGET_Stop, 0)
              AddGadgetItem(#GADGET_List, -1, "=====> Hook installed")
              SetGadgetState(#GADGET_List, CountGadgetItems(#GADGET_List)-1)
            EndIf
          EndIf
        
        Case #GADGET_Stop
        
          ; This removes the hook.. quite simple.
          If HookID
            UnhookWindowsHookEx_(HookID)
            CloseLibrary(#LIBRARY_Hook)
            DisableGadget(#GADGET_Start, 0)
            DisableGadget(#GADGET_Stop, 1)
            AddGadgetItem(#GADGET_List, -1, "=====> Hook removed")
            SetGadgetState(#GADGET_List, CountGadgetItems(#GADGET_List)-1)
            HookID = 0
          EndIf
      
      EndSelect
    
    EndIf
  Until Quit
  
EndIf

; unhook if not allready done.
If HookID
  UnhookWindowsHookEx_(HookID)
  CloseLibrary(#LIBRARY_Hook)
EndIf
End
For your calculator problem, this should work well, because #WM_CREATE
is send after the window is created, but before it is displayed.
So if you move it from inside the hook procedure, it should work, and also
look good :)

Posted: Sun Jan 30, 2005 8:29 pm
by PB
Thanks Freak, I'll have a play later on... but I thought it would be a lot simpler
than using a DLL? I'd prefer a standalone exe solution, like that Visual Basic
example uses... if VB can do it, surely PureBasic can? Here's what I've been
testing, but it fails and even crashes my PC sometimes:

Code: Select all

; WARNING: Running this *might* crash your PC!

Procedure WindowHook(uMsg,wParam,lParam)
  If uMsg=#HCBT_ACTIVATE
    AddGadgetItem(0,-1,Str(wParam)) ; Show window handle that just opened?
    UnhookWindowsHookEx_(hHook)
  EndIf
  ProcedureReturn #FALSE
EndProcedure

If OpenWindow(1,300,250,400,200,#PB_Window_SystemMenu,"test")
  CreateGadgetList(WindowID())
  ListViewGadget(0,0,0,400,200)
  hHook=SetWindowsHookEx_(#WH_CBT,@WindowHook(),GetModuleHandle_(0),GetCurrentThreadId_())
  Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow
EndIf

Posted: Sun Jan 30, 2005 8:44 pm
by freak
Well, according to the documentation, you need to have the code located
in a dll if you want the hook to be system wide or on a thread which is not
your own.

And since you want it to work on the calculator, it won't help to hook your
own process.

btw, in your example, you should set the 3rd parameter to 0 instead of
GetModuleHandle_(0). This should only be a instance handle if the code IS
inside a dll, otheweise it should be 0.

Posted: Sun Jan 30, 2005 8:46 pm
by PB
> according to the documentation, you need to have the code located
> in a dll if you want the hook to be system wide

Ah, I see. No wonder you used a DLL then! :) I'll play with your code then.
Many thanks for your help on this. Hopefully I can get it doing what I want.

Posted: Mon Jan 31, 2005 8:36 pm
by PB
Hi Freak... Unfortunately I can't get it doing what I want. I've been watching
for the opening of calc.exe but by the time the DLL sees it, it's already drawn.
Do you have any suggestions?

Posted: Mon Jan 31, 2005 11:57 pm
by freak
Hum, it seems to work fine here.
Did you put the code to move the window in the dll or the main app?

If you put it in the app, the window is of course allready drawn by the time
that the communication from dll to app is done.
But you don't need to do it from the app, this was only for my example,
you should do it from the dll.

Here's the code:

Code: Select all

ProcedureDLL MyHookProcedure(nCode.l, wParam.l, *lParam.CWPSTRUCT)

  ; Just catch this one message.
  If *lParam\message = #WM_CREATE
  
    ; only check toplevel windows
    If GetWindowLong_(*lParam\hwnd, #GWL_STYLE) & #WS_CHILD = 0
    
      Name$ = Space(1000)
      
      ; since this dll is executed by the hooked programm, this call will
      ; return the calculator executable name
      ;
      GetModuleFileName_(0, @Name$, 1000)
      If LCase(GetFilePart(Name$)) = "calc.exe"
       
        ; get the window size and move it. 
        GetWindowRect_(*lParam\hwnd, @Size.RECT)
        w = Size\right - Size\left
        h = Size\bottom - Size\top
        x = (GetSystemMetrics_(#SM_CXSCREEN)-w)/2
        y = (GetSystemMetrics_(#SM_CYSCREEN)-h)/2
        MoveWindow_(*lParam\hwnd, x, y, w, h, #False)
        
      EndIf
    EndIf    
  EndIf

  ; This is very important to allow multiple hooks to work side by side.
  ProcedureReturn CallNextHookEx_(@MyHookProcedure(), nCode, wParam, *lParam)
EndProcedure
Compile it to a dll and use the above app code to set/remove the hook.
Works fine here.

Posted: Tue Feb 01, 2005 11:54 am
by PB
> Did you put the code to move the window in the dll or the main app?

The main app. :oops: Thanks for your new code, I'll study it well. :)

Enumeration??

Posted: Fri Feb 04, 2005 4:49 am
by Lane
Enumeration does not appear to be a valid command.

It's not listed in the online command index either.

I'm running 3.10 - Ebay special

Re: Enumeration??

Posted: Fri Feb 04, 2005 5:15 am
by PB
> Enumeration does not appear to be a valid command

It was introduced in v3.80: http://www.purebasic.com/news36.php3

> It's not listed in the online command index either

See: http://www.purebasic.com/documentation/ ... tions.html

I'm running 3.10 - Ebay special

There's your problem. :) E-mail support@purebasic.com and talk to them.

correct sir!

Posted: Fri Feb 04, 2005 5:30 am
by Lane
Ok thanks.

Posted: Sat May 21, 2005 7:41 pm
by Julien
How to change this code for Work with KEYBOARD ?

Thank