Page 1 of 1

Hooking the #WM_COMMAND message

Posted: Tue Sep 25, 2007 1:00 am
by r_hyde
I'm trying to create a utility to put apps in the system tray when minimized by hooking the #WM_SYSCOMMAND message with an #SC_MINIMIZE wParam. Every bit of literature I've found so far indicates that this message should be sent when the user either clicks Minimize in the system menu OR click the minimize button on the titlebar. For some reason, though, I'm not catching the minimize message unless it's selected from the system menu - it doesn't seem to fire when the titlebar button is pressed. Here's a minimal version of the code I'm using; maybe someone can put me on the right path, or at least prove to me that it's just my system that's screwing this up.

Code: Select all

;Hook procedure
;compile this To a Shared library called "minimizehook.dll"

#WM_MYEVENT = #WM_USER + 1

ProcedureDLL MinimizeProc(code.l, wParam.l, *lParam.MSG)
  If code < 0
    ProcedureReturn CallNextHookEx_(@MinimizeProc(), code, wParam, lParam)
  EndIf
  If *lParam\message & $FFFF = #WM_SYSCOMMAND
    If (*lParam\wParam & $FFFF) = #SC_MINIMIZE
      hwnd = FindWindow_(0, "Minimize Hook Test")
      If hwnd
        SendMessage_(hwnd, #WM_MYEVENT, 0, 0)
      EndIf
    EndIf
  EndIf 
  ProcedureReturn CallNextHookEx_(@MinimizeProc(), code, wParam, lParam) 
EndProcedure

Code: Select all

;Test app
;Either save this in the folder where "minimizehook.dll" was built,
;or change the path to the dll in the OpenLibrary() call, then execute.
;The hook is global, so any window that's minimized while this is
;running should case a message to be displayed in the hook test window.

#WM_MYEVENT = #WM_USER + 1

Enumeration 
  #MainWindow
  #MessageText
  #MessageTimer
EndEnumeration

Global timer_start.l, hook_dll.l, hook.l

Procedure WndProc(hwnd, msg, wParam, lParam) 
  result = #PB_ProcessPureBasicEvents
  Select msg 
    Case #WM_MYEVENT
      SetGadgetText(#MessageText, "A window has been minimized")
      timer_start = ElapsedMilliseconds()
  EndSelect 
  ProcedureReturn result 
EndProcedure 

hook_dll = OpenLibrary(0, "minimizehook.dll")
If Not hook_dll
  MessageRequester("Error", "Could not load minimizehook.dll")
  End
EndIf

If OpenWindow(#MainWindow, 0, 0, 320, 240, "Minimize Hook Test", #PB_Window_ScreenCentered|#PB_Window_SizeGadget|#PB_Window_MaximizeGadget|#PB_Window_MinimizeGadget)
  If CreateGadgetList(WindowID(#MainWindow)) 
    SetWindowCallback(@WndProc()) 
    SetWindowPos_(WindowID(#MainWindow), #HWND_TOPMOST, 0, 0, 0, 0, #SWP_NOMOVE|#SWP_NOSIZE)
    TextGadget(#MessageText, 10, 10, 300, 15, "")

    hook = SetWindowsHookEx_(#WH_GETMESSAGE, GetProcAddress_(hook_dll, "MinimizeProc"), hook_dll, 0)
    If Not hook
      MessageRequester("Error", "Unable to install message hook")
      CloseLibrary(0)
      End
    EndIf
      
    Repeat 
      event = WindowEvent()
      If timer_start And (ElapsedMilliseconds() - timer_start) > 2000
        SetGadgetText(#MessageText, "")
        timer_start = 0
      EndIf
      Delay(1)
    Until event = #PB_Event_CloseWindow
  EndIf
EndIf

UnhookWindowsHookEx_(hook)
CloseLibrary(0) 
End

Posted: Tue Sep 25, 2007 3:03 am
by Sparkie
Instead of a dll/hook can you use something like this :?: If not I'll be happy to take a closer look at your code tomorrow when I have more time. ;)

Code: Select all

#EVENT_SYSTEM_MINIMIZESTART = $16
#EVENT_SYSTEM_MINIMIZEEND = $17
#WINEVENT_SKIPOWNPROCESS = 2
#WINEVENT_OUTOFCONTEXT = 0

Procedure EventProc(hWinEventHook.l, event.l, hwnd.l, idObject.l, idChild.l, idEventThread.l, dwmsEventTime.l) 
  Debug event
  Select event
    Case #EVENT_SYSTEM_MINIMIZESTART
      Debug "Minimize: " + Str(hwnd)
    Case #EVENT_SYSTEM_MINIMIZEEND
      Debug "Maximize: " + Str(hwnd)
  EndSelect
EndProcedure

If OpenWindow(0, 0, 0, 700, 500, "", #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget | #PB_Window_SystemMenu) And CreateGadgetList(WindowID(0)) 
  hWinHook = SetWinEventHook_(#EVENT_SYSTEM_MINIMIZESTART, #EVENT_SYSTEM_MINIMIZEEND, 0, @EventProc(), 0, 0, #WINEVENT_SKIPOWNPROCESS | #WINEVENT_OUTOFCONTEXT)
  Debug hWinHook
  Repeat 
    wEvent = WaitWindowEvent() 
  Until wEvent = #PB_Event_CloseWindow 
  If hWinHook
    UnhookWinEvent_(hWinHook)
  EndIf
EndIf 
End

Posted: Tue Sep 25, 2007 7:56 am
by r_hyde
Wow, that's nice! I've been doing winapi a long time, and it never ceases to amaze me that there's always something more to learn about it. It also surprises me that I didn't land on that command in all my numerous google searches on the topic!

Posted: Tue Sep 25, 2007 1:03 pm
by Sparkie
I stumbled upon that API function a while back and it has come in handy a few times for me. :)

I was able to get your DLL hook working by changing from a #WH_GETMESSAGE hook to a #WH_CBT hook.
msdn wrote:WH_CBT Hook
The system calls a WH_CBT hook procedure before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the input focus; or before synchronizing with the system message queue. The value the hook procedure returns determines whether the system allows or prevents one of these operations. The WH_CBT hook is intended primarily for computer-based training (CBT) applications.
Here's the new DLL code...

Code: Select all

;Hook procedure 
;compile this To a Shared library called "minimizehook.dll" 

#WM_MYEVENT = #WM_USER + 1 

ProcedureDLL MinimizeProc(code.l, wParam.l, lParam) 
  result = 0
  If code < 0 
    ProcedureReturn CallNextHookEx_(@MinimizeProc(), code, wParam, lParam) 
  EndIf 
  If code = #HCBT_MINMAX
    If (lParam & $FFFF) = #SW_MINIMIZE 
      hwnd = FindWindow_(0, "Minimize Hook Test") 
      If hwnd 
        SendMessage_(hwnd, #WM_MYEVENT, 0, 0) 
        ;... Return 0 to allow operation to continue
        ;... Return 1 to prevent it from continuing
        result = 0
      EndIf 
    EndIf 
  EndIf 
  ProcedureReturn result
EndProcedure
and then change the SetWindowsHookEx line in your test app to

Code: Select all

hook = SetWindowsHookEx_(#WH_CBT , GetProcAddress_(hook_dll, "MinimizeProc"), hook_dll, 0) ]

Posted: Tue Sep 25, 2007 7:28 pm
by r_hyde
I appreciate your continued research on this, though your first solution has already done the trick. This is only my second attempt at api hooking, and my success the first time clearly must have been a lucky strike. This is a part of the winapi I would definitely like to get a little more comfortable with.