Getting Dock Up and Running

Get help with using AutoHotkey and its commands and hotkeys
Voidraizer
Posts: 11
Joined: 07 Jun 2016, 07:57

Getting Dock Up and Running

23 Aug 2016, 10:24

Hello, I came across Dock https://autohotkey.com/board/topic/1785 ... -b3/page-1 while hunting for a script to do exactly what it does. After I grabbed the latest version from post #357, which was updated for AHK_L, I still had an issue getting it to work right. After I resolved what mostly came down to me failing to call functions properly, I noticed that I couldn't get the *T* parameter to function correctly. It seems to be behaving in the exact opposite fashion that it should i.e. it's making the docked client gui appear under all windows instead of ontop of the host.

The ask of this post is a two parter. First I was hoping someone could explain this freaky occurrence I found below and second, I was hoping someone could help me fix the *T* functionality.

For the first part, I was digging through the code and trying to decipher it and I found something that I can't explain. On lines 261 and 262 I placed two message boxes to output some parameters to figure out how this function worked. What's been blowing my mind is that there's 0 code between the two message boxes but the variable changes between them. On top of that, it doesn't have to do with the way I output the variable because you can flip the two message boxes and the outcome will be the same. How does this variable switch from being a hwnd to "1"?

Then secondly, could someone help me figure out what's wrong with the *T* functionality and restore it so that the client gui is always on top of the host but not always on top of everything - only shown when the host gui is shown?

The code:

Code: Select all

; Title:     Dock 
;            *Dock desired top level windows (dock clients) to any top level window (dock host)* 
;
; 
;
;			 Using dock module you can glue your or third-party windows to any top level window.
;			 Docked windows in module terminology are called Clients and the window that keeps their 
;			 position relative to itself is called Host. Once Clients are connected to the Host, this
;			 group of windows will behave like single window - moving, sizing, focusing, hiding and other
;			 OS events will be handled by the module so that the "composite window" behaves like the single window.
;
;			 Module uses system hook to monitor windows changes, so it's idle when it is not arranging windows.
; 
;---------------------------------------------------------------------------------------------------------------------------------- 
; Function:  Dock 
;            Instantiate dock of given client upon host. Multiple clients per one host are supported. 
; 
; 
; Parameters: 
; 
;            pClientId   - HWND of the Client GUI. Dock is created or updated (if already exists) for that hwnd.    
;            pDockDef   - Dock definition, see bellow. To remove dock client pass "-". 
;                       If you pass empty string, client will be docked to the host according to its current position relative to the host. 
;            reset      - internal parameter, do not use. 
; 
; Globals: 
;            Dock_HostID   - Sets docking host 
;         Dock_OnHostDeath - Sets label that will be called when host dies. Afterwards, module will disable itself using Dock_Toggle(false).
;
; Dock definition:  
;			Dock definition is string containing unordered white space separated parameters which describe Client's position relative to the Host. The big number of parameters allow
;			for fine tuning of Client's position and basically every setup is possible. Parameters are grouped into 4 classes - x, y, w & h parameters.
;			Classes and their parameters are optional.
;			
;> 		Syntax:		x(hw,cw,dx)  y(hh,ch,dy)  w(hw,dw)  h(hh,dh)  t
;
; 
;            o The *X* coordinate of the top, left corner of the client window is computed as 
;            *HostX + hw*HostWidth + cw*ClientWidth + dx*, with the parameters hw, cw & dx (shorten from host width and client width multipliers, delta x).
; 
;            o The *Y* coordinate of the top, left corner of the client window is computed as 
;            *HostY + hh*HostHeight + ch*ClientHeight + dy*, with the parameters hh, ch and dy.
; 
;            o The width *W* of the client window is computed as *hw*HostWidth + dw*, with the parameters hw & wd.
; 
;            o The height *H* of the client window is computed as *hh*HostHeight + dh*, with the parameters hh & hd.
;
;			 o The topmost state of the client is *T*. Specify this option to to set the Client always on top the Host. This allows client to be positioned inside the Host.
;
;			If you omit any of the class parameters it will default to 0. So, the following expressions all have the same effect :
;> 		    x(0,0,0) = x(0,0) = x(0,0,) = x(0) = x(0,)= x(0,,) = x() = x(0,,0) = x(,0,0) = x(,,0) = ...
;>			y(0,1,0) = y(0,1) = y(,1) = y(,1,) = y(,1,0) = ...
;
;			Keep in mind that x() is not the same as omitting x entirely. First case is equal to x(0,0,0) so it will set Client's X coordinate to be equal as Host's. 
;			In second case, x coordinate of the client will not be touched by the Dock module but Client will keep whatever x it had before.
;			
; 
; Returns: 
;            "OK" or "Err" with text describing last successful or failed action.
;
; Remarks:
;			You must set DetectHiddenWindows if Host is practicing hiding. Otherwise, Dock will treat Host hiding as death.
;			All clients will be hidden once host is terminated or it becomes hidden itself.
;
;			Use SetBatchLines, -1 with dock module for fluid client movement. You will experience delay in clients moving otherwise.
;			However, if CPU usage is very high, you might experience a delay in client movement anyway.
;
;			If you are using *Gui, Show* command immediately before registering client, make sure you specify *NoActivate* flag.
;
;			"Topmost" feature can be used to create additional caption buttons. Caption buttons are topmost clients containing only 1 button
;			and docked to the Host so that they appear in its caption. To setup caption button, only X class is needed. For instance
;			"x(1,0,-100)" can be used to set caption button 100 pixels from the right edge of the Host's caption.
; 
; Example: 
;>      Dock(Client1ID, "x(0,-1,-10)  y(0,0,0)  w(0,63)  h(1,0)")   ;top left, host height 
;>      Dock(Client2ID, "x() y(,-1,-5) w(1)  h(,30)")				;top above, host width, short definition
;> 		Dock(hTitleBtn, "x(1,0,-80), y(0,0,5) w(0,20) h(0,15) t")   ;add title button, topmost client docked inside Host, on title, with 20x15 size
;
Dock(pClientID, pDockDef="", reset=0) {                    ;Reset is internal parameter, used by Dock_Shutdown 
   local cnt, new, cx, cy, hx, hY, t, hP
   static init=0, idDel, classes="x|y|w|h"

    if (reset)                                             ;Used by Dock Shutdown to reset the function 
		return init := 0 

	if !init
		Dock_aClient_0_ := 0

   cnt := Dock_aClient_0_ 

   ;remove dock client ? 
   if (pDockDef="-") and (Dock_%pClientID%){ 

      idDel := Dock_%pClientID% 

      loop, parse, classes, |
		loop, 3
         Dock_%pClientID%_%A_LoopField%%A_Index% := ""
	  Dock_%pClientID% := ""			; don't remove t Dock_%pClientID%_t := 
       
      ;move last one to the place of the deleted one 
      Dock_aClient_%idDel%_ := Dock_aClient_%cnt%_, Dock_aClient_%cnt%_ := "", Dock_aClient_0_-- 
      return "OK - Remove" 
   } 

   if pDockDef = 
   { 
      WinGetPos hX, hY,,, ahk_id %Dock_HostID%
      WinGetPos cX, cY,,, ahk_id %pClientID%
      pDockDef := "x(0,0," cX - hX ")  y(0,0," cY - hY ")"
   } 
    
   ;add new dock client if it not exists, or update its dock settings if it exists 
	loop, parse, pDockDef, %A_Space%%A_Tab%
	   if (A_LoopField != "") {
			t := A_LoopField, c := SubStr(t,1,1)
            
			if c not in x,y,w,h,t
				return "ERR: Bad dock definition"

			if c = t
			{
				 Dock_%pClientID%_t := 1
			}
			else {
				t := SubStr(t,3,-1)
				StringReplace, t, t,`,,|,UseErrorLevel
				t .= !ErrorLevel ? "||" : (ErrorLevel=1 ? "|" : "")
				loop, parse, t,|,%A_Space%%A_Tab% 
					Dock_%pClientID%_%c%%A_Index% := A_LoopField ? A_LoopField : 0
			}
	   }
    ; msgbox % Dock_%pClientID%
	if !Dock_%pClientID% 
    {
		Dock_%pClientID%   := ++cnt, 
		Dock_aClient_%cnt%_ := pClientID 
		Dock_aClient_0_++ 
        ; msgbox % "Dock_%pClienID%  " Dock_%pClientID% "`npClientId  " pClientID "`nDock_aClient_%cnt%  " Dock_aClient_%cnt%_
	}

   ;start the dock if its not already started
   If !init { 
      init++, Dock_hookProcAdr := RegisterCallback("Dock_HookProc")
      Dock_Toggle(true)	 
   } 
   
   Dock_Update()
   return "OK" 
}       


;----------------------------------------------------------------------------------------------------- 
;Function:  Dock_Shutdown 
;			Uninitialize dock module. This will clear all clients and internal data and unregister hooks. 
;			Dock_OnHostDeath, Dock_HostId are kept on user values. 
; 
Dock_Shutdown() { 
   local cID 

   Dock_Toggle(false) 
   DllCall("GlobalFree", "UInt", Dock_hookProcAdr), Dock_hookProcAdr := "" 
   Dock(0,0,1)         ;reset dock function 

   ;erase clients 
   loop, % Dock_aClient_0_ 
   { 
      cId := Dock_aClient_%A_Index%_, Dock_aClient_%A_Index%_ := "" 
      Dock_%cID% := "" 
      loop, 10 
         Dock_%cID%_%A_Index% := "" 
   }
}

;----------------------------------------------------------------------------------------------------- 
;Function: Dock_Toggle 
;          Toggles the dock module ON or OFF.
; 
;Parameters: 
;         enable - Set to true to set the dock ON, set to FALSE to turn it OFF. Skip to toggle. 
; 
;Remarks: 
;         Use Dock_Toggle(false) to suspend the dock module (to unregister hook), leaving its internal data in place. 
;         This is different from Dock_Shutdown as latest removes module completely from memory and 
;         unregisters its clients. 
;          
;         You can also use this function to temporary disable module when you don't want dock update routine to interrupt your time critical sections. 
;
Dock_Toggle( enable="" ) { 
   global 

   if Dock_hookProcAdr = 
      return "ERR - Dock must be loaded." 

   if enable = 
      enable := !Dock_hHook1 
   else if (enable && Dock_hHook1)
		return	"ERR - Dock already enabled"

   if !enable 
      API_UnhookWinEvent(Dock_hHook1), API_UnhookWinEvent(Dock_hHook2), API_UnhookWinEvent(Dock_hHook3), Dock_hHook3 := Dock_hHook1 := Dock_hHook2 := "" 
   else  { 
      Dock_hHook1 := API_SetWinEventHook(3,3,0,Dock_hookProcAdr,0,0,0)				; EVENT_SYSTEM_FOREGROUND 
      Dock_hHook2 := API_SetWinEventHook(0x800B,0x800B,0,Dock_hookProcAdr,0,0,0)	; EVENT_OBJECT_LOCATIONCHANGE 
	  Dock_hHook3 := API_SetWinEventHook(0x8002,0x8003,0,Dock_hookProcAdr,0,0,0)	; EVENT_OBJECT_SHOW, EVENT_OBJECT_HIDE

      if !(Dock_hHook1 && Dock_hHook2 && Dock_hHook3) {	   ;some of them failed, unregister everything
         API_UnhookWinEvent(Dock_hHook1), API_UnhookWinEvent(Dock_hHook2), API_UnhookWinEvent(Dock_hHook3) 
         return "ERR - Hook failed" 
      } 

	 Dock_Update() 
   } 
   return enable
} 
;==================================== INTERNAL ====================================================== 
Dock_Update() { 
   local hX, hY, hW, hh, W, H, X, Y, cx, cy, cw, ch, fid, wd, cid 
   static gid=0   ;fid & gid are function id and global id. I use them to see if the function interupted itself. 
   
   wd := A_WinDelay 
   SetWinDelay, -1 
   fid := gid += 1 
   WinGetPos hX, hY, hW, hH, ahk_id %Dock_HostID% 
;   OutputDebug %hX% %hY% %hW% %hH%	 %event%

   ;xhw,xw,xd,  yhh,yh,yd,  whw,wd,  hhh,hd 
   
	loop, % Dock_aClient_0_ 
	{ 
		cId := Dock_aClient_%A_Index%_
        ; msgbox %A_Index% . %cId%
		WinGetPos cX, cY, cW, cH, ahk_id %cID% 
		W := Dock_%cId%_w1*hW + Dock_%cId%_w2,  H := Dock_%cId%_h1*hH + Dock_%cId%_h2 
		X := hX + Dock_%cId%_x1*hW + Dock_%cId%_x2* (W ? W : cW) + Dock_%cId%_x3
		Y := hY + Dock_%cId%_y1*hH + Dock_%cId%_y2* (H ? H : cH) + Dock_%cId%_y3

		if (fid != gid) 				;some newer instance of the function was running, so just return (function was interupted by itself). Without this, older instance will continue with old host window position and clients will jump to older location. This is not so visible with WinMove as it is very fast, but SetWindowPos shows this in full light. 
			break
		
		if dock_resetT
		{
			dock_resetT := false
			DllCall("SetWindowLong", "uint", cId, "int", -8, "uint", 0)
		}
        
		DllCall("SetWindowPos", "uint", cId, "uint", 0, "uint", X ? X : cX, "uint", Y ? Y : cY, "uint", W ? W : cW, "uint", H ? H :cH, "uint", 1044) ;4 | 0x10 | 0x400 
		; WinMove ahk_id %cId%,,X ? X:"" ,Y ? Y:"", W ? W : "" ,H ? H : "" 
	}      
	SetTimer, Dock_SetZOrder, -80		;set z-order in another thread (protects also from spaming z-order changes when host is rapidly moved).
	SetWinDelay, %wd% 
}

Dock_SetZOrder: 
;    OutputDebug setzorder
	exists := WinExist("ahk_id " Dock_HostID), active := WInExist("A") = Dock_HostId
    
	loop, % Dock_aClient_0_  
	{
	  _ := Dock_aClient_%A_Index%_
      ; msgbox % _
      ; msgbox "_" %_%
      _ := Dock_%_%_t
      ; msgbox % _
	    if !_
	      DllCall("SetWindowPos", "uint", Dock_aClient_%A_Index%_, "uint", Dock_HostID, "uint", 0, "uint", 0, "uint", 0, "uint", 0, "uint", 19 | 0x4000 | 0x40)
	    else
        {
            ;Set owned clients if they are not already set. Host may not exist here.
            _ := DllCall("GetWindowLong", "uint", Dock_aClient_%A_Index%_, "int", -8)
            
            if !_ and exists
            {
                dock_resetT := true
                DllCall("SetWindowLong", "uint", Dock_aClient_%A_Index%_, "int", -8, "uint", Dock_HostId)
            }
		
            DllCall("SetWindowPos", "uint", Dock_aClient_%A_Index%_, "uint"
				, active ? 0 : DllCall("GetWindow", "uint", Dock_HostId, "uint", 3) ;hwndprev
				, "uint", 0, "uint", 0, "uint", 0, "uint", 0, "uint", 0x40 | 19 | 0x4000) ;SWP_SHOWWINDOW ..., no activate

        }
	}
return 

Dock_SetZOrder_OnClientFocus:
    ; OutputDebug setzorder on focus

	;Set host just bellow focused client.
	res := DllCall("SetWindowPos", "uint", Dock_HostID, "uint", Dock_AClient, "uint", 0, "uint", 0, "uint", 0, "uint", 0, "uint", 19) ;SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE ...

	;Set the non-T children , T children are handled normaly by OS as owned.
	loop, % Dock_aClient_0_
	{
		_ := Dock_aClient_%A_Index%_, _ := Dock_%_%_t
		if !_
			 DllCall("SetWindowPos", "uint", Dock_aClient_%A_Index%_, "uint", Dock_HostID, "uint", 0, "uint", 0, "uint", 0, "uint", 0, "uint", 19) ;SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE
	}
return 

;-----------------------------------------------------------------------------------------
; Events :
;			3	  - Host is set to foreground
;			32779 - Location change
;			32770 - Show
;			32771 - Hide (also called on exit)
;
Dock_HookProc(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime ) { 
	local e, cls, style

	if idObject or idChild
		return
	WinGet, style, Style, ahk_id %hwnd%
	if (style & 0x40000000)					;RETURN if hwnd is child window, for some reason idChild may be 0 for some children ?!?! ( I hate ms )
		return

;	WINGETCLASS, cls, ahk_id %hwnd%
;	if cls in #32768,#32771,#32772,#32769,SysShadow,tooltips_class32		;skip some windows classes, just to speed it up
;		return

	;outputdebug % cls " " hwnd " " event
	if (event = 3) 
	{
		;check if client is taking focus
		loop, % Dock_aClient_0_
 		  if (hwnd = Dock_aClient_%A_Index%_){
			Dock_AClient := hwnd
			gosub Dock_SetZOrder_OnClientFocus
			return
		}		
	}

	If (hwnd != Dock_HostID){
      if !WinExist("ahk_id " Dock_HostID) && IsLabel(Dock_OnHostDeath)
	  {
 		 Dock_Toggle(false)
		 gosub %Dock_OnHostDeath% 
 		 loop, % Dock_aClient_0_
			DllCall("ShowWindow", "uint", Dock_aClient_%A_Index%_, "uint",  0)
	  }
	  return 
	} 

	
	if event in 32770,32771
	{
	   e := (event - 32771) * -5
	   loop, % Dock_aClient_0_
			DllCall("ShowWindow", "uint", Dock_aClient_%A_Index%_, "uint",  e)
	}

	Dock_Update() 
;	SetTimer, Dock_Update, -10
} 

Dock_Update:
	Dock_Update()
return




API_SetWinEventHook(eventMin, eventMax, hmodWinEventProc, lpfnWinEventProc, idProcess, idThread, dwFlags) { 
   DllCall("CoInitialize", "uint", 0) 
   return DllCall("SetWinEventHook", "uint", eventMin, "uint", eventMax, "uint", hmodWinEventProc, "uint", lpfnWinEventProc, "uint", idProcess, "uint", idThread, "uint", dwFlags) 
} 

API_UnhookWinEvent( hWinEventHook ) { 
   return DllCall("UnhookWinEvent", "uint", hWinEventHook) 
} 

;--------------------------------------------------------------------------------------------------------------------- 
; Group: Presets
;		 This section contains some common docking setups. You can just copy/paste dock definition strings in your script.
;
;		x(,-1) y()						- top left, own size
;		x(,-1,10) y()					- top left, own size, 10px padding 
;		x(,-1)  y() h(1)				- top left, use host's height, keep own width
;		x(,-1,20) y() w(,50) h(1)		- top left, use host's height, set width to 50 and padding to 20px
;		x(,-1)  y(.5,-.5)				- middle left, keep own size
;			
;		x(,-1)  y(1,-1) w(,20) h(,20)	- bottom left, fixed width & height to 20px
;		x(,-1)  y(1,-1) h(.5)			- bottom left, keep height half of the Host's height, keep own width
;		x(1,-1) y(1)  w(.25) h(.25)		- bottom right, width and height 1/5 of the Host
;		
;		x()	y(1) w(1) h(,100)			- below the host, use host's width, height = 100
;		x()	y(,-1,-5) w(1)   			- above the host, use host's width, keep own height, 5px padding
;		x(.5,-.5) y(,-1) w(,200) h(,30)	- center above the host, width=200, height=30
;		x(.5,-.5) y(1) w(0.3) h(,30)	- center bellow the host, use 1/3 Host's width, height=30
;		
;		x(1) y()						- top right, own size
;		x(1) y() w(,40) h(1)			- top right, use host's height, width = 40
;       x(1) y(.5,-.5)					- middle right, keep own size

;--------------------------------------------------------------------------------------------------------------------- 
; Group: About 
;      o Ver 2.0 b3 by majkinetor. See http://www.autohotkey.com/forum/topic19400.html 
;	   o Thank You's: Laszlo, JGR, Joy2World, bmcclure 
;      o Licenced under Creative Commons Attribution-Noncommercial <http://creativecommons.org/licenses/by-nc/3.0/>.
Thanks for the assistance.


Edit: I forgot to include the code I'm using to test it

Code: Select all

Run notepad,,,nPID
WinWait ahk_pid %nPID%
Dock_HostID := WinExist("ahk_pid " . nPID)
; msgbox % Dock_HostID

Gui +LastFound -Caption +ToolWindow +Border
Gui Add, Text,,Docked Window
Gui +hwndclienthwnd
Gui Show
; msgbox % clienthwnd
result := Dock(clienthwnd, "x(,-.5) y(.5,-.5)")
; result := Dock(clienthwnd, "x(,-.5) y(.5,-.5) t")
If( result != "OK" )
    msgbox % result
return

;================================
;     END AUTOEXECUTE REGION
;================================

esc::exitapp

#Include Dock.ahk
hunter99
Posts: 128
Joined: 20 Jan 2014, 17:57

Re: Getting Dock Up and Running

23 Aug 2016, 14:41

Hi Voidraizer:
First I'm running AHK Version v1.1.24.00 Uni32 on Win 7 Pro.
Your test script work fine. I keep all my libs in "C:\Program Files\AutoHotkey\Lib" folder so I use "#Include <Dock>" instead of "#Include Dock.ahk".
Renamed my Dock.ahk and used yours, the script still ran fine, the clients on top of host and only host. Covered when host was, and uncovered when host was not.
The thing that gives me concern is where you were putting some of those msgboxs, right in the "Dock_SetZOrder:" subroutine. Think maybe a typo or something got in there.
That lib been out since about 2006 or 7 and no problems. Anyway that's all I come up with for now. Here is a little quick mod of your script I was testing with, may give you some ideas.

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

Run notepad,,,nPID
WinWait ahk_pid %nPID%
Dock_HostID := WinExist("ahk_pid " . nPID)


Gui Win1: +LastFound -Caption +ToolWindow +Border
Gui Win1: add, Text, gwin1,Docked Window 1
Gui Win1: Show
hwnd1 := WinExist()

Gui Win2: +LastFound -Caption +ToolWindow +Border
Gui Win2: add, Text, gwin2,Docked Window 2`nPut more test here`nor make it a button.
Gui Win2: Show
hwnd2 := WinExist()

result := dock(hwnd1, "x(,-.5) y(.5,-.5) t")			;the x(,-1) puts the right edge of client to left edge of host(remember the numbers are scaling not pixcels)
result := dock(hwnd2, "x(1,-1.25) y(,0,55) t")          ;Remember the numbers are scaling numbers, NOT pixels.
If( result != "OK" )
    msgbox % result
return

win1:
msgbox,,, you clicked window 1
return

win2:
msgbox,,, you clicked window 2
return


; Subroutine called when the Host window is closed.
esc::
OnHostDeath:
    Gui Win1: Destroy					;Destroys the client
    Gui Win2: Destroy					;Destroys the client
    gosub, OnExit

OnExit:
	Dock_shutdown()
    WinClose, ahk_pid %nPID%		;close notepad
 	ExitApp

#Include <Dock>   ;I keep all my libs in "C:\Program Files\AutoHotkey\Lib" so use this.
;#Include Dock.ahk
Good luck, hunter
Voidraizer
Posts: 11
Joined: 07 Jun 2016, 07:57

Re: Getting Dock Up and Running

23 Aug 2016, 15:13

Hi hunter, thanks for the reply. I did what you said and relocated dock to my lib and copied your enhanced test script and ran it. Unfortunately, it's not working as intended. However, now that you mention you're on win7, I believe we may have found culprit. I'm on win 8.1 (work laptop) and this is the environment I'd like it to work on. Any idea as to why the variable changes from a hwnd to 1?
Voidraizer
Posts: 11
Joined: 07 Jun 2016, 07:57

Re: Getting Dock Up and Running

23 Aug 2016, 15:17

Also, here is a gif of what I'm seeing, pardon the URL as it's randomly generated. https://gfycat.com/ShortMasculineAss

Return to “Ask For Help”

Who is online

Users browsing this forum: Google [Bot], masato, moowee, songdg and 47 guests