New WM_COPYData Examples

Discuss and announce Total Commander plugins, addons and other useful tools here, both their usage and their development.

Moderators: white, Hacker, petermad, Stefan2

User avatar
franck8244
Power Member
Power Member
Posts: 703
Joined: 2003-03-06, 17:37 UTC
Location: Geneva...

Post by *franck8244 »

Is that actually necessary?
Indeed, here it's not needed , I just took a send_copydata function where it was needed ...
TC#88260 -
User avatar
Samuel
Power Member
Power Member
Posts: 1929
Joined: 2003-08-29, 15:44 UTC
Location: Germany, Brandenburg an der Havel
Contact:

Post by *Samuel »

Thanks to all of you. I have a nice working script now.
Do you:
a) have ideas how we could use the new infos?
b) have ideas what else could be needed?
User avatar
Balderstrom
Power Member
Power Member
Posts: 2148
Joined: 2005-10-11, 10:10 UTC

Post by *Balderstrom »

There's a request I believe for retrieving the selected file list via the new WM_COPYData mechanism.

And now that it's implemented, a few things I've asked in the past could be done:
ActiveTab#, ActiveTabName

And with the 64bit compile using lazarus, most of the Window Panels/Controls are all classed Window#
---- It would be great to be able to query the HWND of a given control by name.
E.g. command char might be "G" and implemented similiar to how TC can accept an EM_UserCommand or CD-path by name --- but instead return the HWND of the given control.

There are a number of things that are not possible or very difficult with the lazarus compile since all controls except the FileLists are classed Window#.
User avatar
ZoSTeR
Power Member
Power Member
Posts: 1008
Joined: 2004-07-29, 11:00 UTC

Post by *ZoSTeR »

Example for AutoIt:

Code: Select all

#Include <GUIConstantsEx.au3>
#include <SendMessage.au3>

Opt('GUIOnEventMode', 1)

Global $hGUI

_Main()

Func _Main()
    Local $iBtn
    $hGUI = GUICreate('Test', 150, 50)
    GUISetOnEvent($GUI_EVENT_CLOSE, 'Event_GUIClose')
    $iBtn = GUICtrlCreateButton('Click Here', 10, 10, 100, 30)
    GUICtrlSetOnEvent(-1, 'handle_button_press')
    ; This registers the function to call when we receive a WM_COPYDATA (0x004A) back from TC:
	 GUIRegisterMsg(0x004A, 'Our_Func_for_WM_COPYDATA')
    GUISetState()
    While 1
        Sleep(500)
    WEnd
EndFunc   ;==>_Main

Func _Msg2TC($prmCommand = "A")
	$hTC = WinGetHandle("[CLASS:TTOTAL_CMD]")
	$dwData = Asc("G") + 256 * Asc("A")
	$lpDataLen = StringLen($prmCommand)
	$lpData = DllStructCreate("char[" & $lpDataLen + 1 & "]")
	DllStructSetData($lpData, 1, $prmCommand)
	$lpDataPtr = DllStructGetPtr($lpData)
	$structCOPYDATA = DllStructCreate("dword;dword;ptr")
	DllStructSetData($structCOPYDATA, 1, $dwData)
	DllStructSetData($structCOPYDATA, 2, $lpDataLen)
	DllStructSetData($structCOPYDATA, 3, $lpDataPtr)
	$ptrCOPYDATA = DllStructGetPtr($structCOPYDATA)
	$arrReturn = _SendMessage($hTC, 0x004A, $hGUI, $ptrCOPYDATA)
EndFunc

Func Our_Func_for_WM_COPYDATA($hWnd, $iMsg, $wParam, $lParam)
	$returnCOPYDATA = DllStructCreate("dword;dword;ptr", $lParam)
	$returnDWData = DllStructGetData($returnCOPYDATA, 1)
	$returnLPDataLen = DllStructGetData($returnCOPYDATA, 2)
	$returnLPDataPtr = DllStructGetData($returnCOPYDATA, 3)
	$returnLPData = DllStructCreate("char[" & $returnLPDataLen + 1 & "]", $returnLPDataPtr)
	$retValue = DllStructGetData($returnLPData, 1)
	; Avoid MsgBox in real life at this point (see AutoIt Help 'GUIRegisterMsg')
	MsgBox(0,"TC Reply", $retValue)
EndFunc

Func handle_button_press()
	_Msg2TC()
EndFunc

Func Event_GUIClose()
    Exit
EndFunc
rmmaniac
Junior Member
Junior Member
Posts: 20
Joined: 2009-04-14, 05:56 UTC

Post by *rmmaniac »

can we use this new feature to opentabs file or open bartton file(not via user command) in a Ahk script ?. is there an example. Thanks
fallmq
Junior Member
Junior Member
Posts: 19
Joined: 2012-06-28, 16:47 UTC

Post by *fallmq »

I attemp to write a autohotkey script in order to send a em_xx cmd to TC, I see the sample code here and did a test, unfortunately it didn't work, anybody can tell me what is the problem in my code?

PS: I am using autohotkey_L 1.1.08.01 x64, and Total commander 8.01 rc5 X64

Code: Select all

#NoTrayIcon
#SingleInstance force
SetBatchLines -1
SendMode, Input 

TC_SendWMCopyData("EM", "em_test", "", "ahk_class TTOTAL_CMD")

return

TC_SendWMCopyData( cmdType, byRef cmd, byRef addParams="", aWin="A" )
{
Critical
   VarSetCapacity( CopyDataStruct, A_PtrSize * 3 )
   if( A_IsUnicode )
   {
      VarSetCapacity( cmdA, StrPut(cmd, "cp0"))
      Loop, % StrLen(cmd)
         NumPut( Asc(SubStr(cmd, A_Index, 1)), cmdA, A_Index - 1, "Char")
   }
   NumPut( Asc(SubStr(cmdType,1,1)) + 256 * Asc(SubStr(cmdType,2,1)), CopyDataStruct )
   NumPut( StrLen(cmd) + (cmdType="CD" ? 5 : 1), CopyDataStruct, A_PtrSize )
   NumPut((A_IsUnicode ? &cmdA : &cmd), CopyDataStruct, A_PtrSize * 2)
   
   Loop, % (cmdType=="CD" ? 2 : 0)
      NumPut( Asc(SubStr(addParams, A_Index, 1)), (A_IsUnicode ? cmdA : cmd), (StrLen(cmd) + A_Index), "Char" )
   SendMessage, 0x4A,, &CopyDataStruct,, ahk_id %aWin%
return
}
and here is my em_cmd

Code: Select all

[em_test]
button=
cmd=notepad
User avatar
Balderstrom
Power Member
Power Member
Posts: 2148
Joined: 2005-10-11, 10:10 UTC

Post by *Balderstrom »

That looks like the current version of my code, I think yer not calling it correctly.

Code: Select all

TC_SendWMCopyData("EM", "em_test", "", "ahk_class TTOTAL_CMD") 

return 

TC_SendWMCopyData( cmdType, byRef cmd, byRef addParams="", aWin="A" )
Note parameter #2: cmd, is a byRef - it has to be a variable.

So you call it like this:

Code: Select all

TC_SendWMCopyData("EM", gCmd:="em_test", gNul:="", "ahk_class TTOTAL_CMD") 
But I use a higher-level function to call into TC_SendWMCopyData
e.g.

Code: Select all

TC_EMC( cmd, wID="ahk_class TTOTAL_CMD")
{

	TC_SendWMCopyData( "EM", cmd, params:="", wID )
return
}
So you would do:

Code: Select all

TC_EMC("em_test")
Which keeps global variables out of the namespace as well.


EDIT: That wont work, in my actual TC_EMC, I also call:
TC_Activate()

Code: Select all

TC_EMC( cmd, wID="ahk_class TTOTAL_CMD", activateWin=FALSE, showMsg=FALSE )
{
	TC_Activate( wID, activateWin, showMsg, cmd )
	TC_SendWMCopyData( "EM", cmd, params:="", wID )
return
}
Wherein, wID is assigned to it's ID# (ahk_id).

Code: Select all

TC_Activate( byRef wID, activateWin=TRUE, showMsg=TRUE, cmd="" )
{
	wID:=QueryWinID(wID, TRUE)
;	MsgBox, %A_ThisFunc% :: wID: %wID%
	if(!activateWin ) ;&& WinExist("ahk_id " wID))
		return FALSE
	if( showMsg )
		MsgBox,,%A_ThisFunc%, % "Activating TC" ( cmd ? ", for command: " cmd "`n" : "`n"), 1
	MsgBox, %A_ThisFunc%, Activating TC
	WinActivate, ahk_id %wID%
return TRUE
}

QueryWinID( aWin="A", canExist=FALSE, winText="", notTitle="", notText="" )
{
;	MsgBox,, %A_ThisFunc%, aWin: %aWin%, canExist: %canExist%
	if( !(retVal:=WinActiveA( aWin, winText, notTitle, notText )) )
		retVal:=( !canExist ? 0 : WinExistA( aWin, winText, notTitle, notText ))
return retVal
}

WinActiveA( aWin="", winText="", notTitle="", notText="" )
{
	return WinActive( (aWin+0 ? "ahk_id " aWin : aWin), winText, notTitle, notText )
}

WinExistA( aWin="", winText="", notTitle="", notText="" )
{
	return WinExist( (aWin+0 ? "ahk_id " aWin : aWin), winText, notTitle, notText )
}
Now you don't necessarily have to do all that. But the WMCopyData function is expecting a ID# so minimally you need to do
wID:=WinExist("ahk_class TTOTAL_CMD")
or
wID:=WinActive("ahk_class TTOTAL_CMD")

So long as it's the only instance of TC running, it would be fine.

It's probably a slight bug that the default variable for wID is ="A" in TC_SendWMCopyData() as that wont work with the SendMessage --- I just never use it like that and didn't really notice --- I always call it with caller functions, like TC_EMC(), TC_CMD() and TC_CD()

Code: Select all

TC_CMD( cmd, wID="ahk_class TTOTAL_CMD", activateWin=FALSE, post=TRUE, showMsg=FALSE )
{
	TC_Activate( wID, activateWin, showMsg, cmd )
	if( !post )
		SendMessage, 0x433, TC_@( cmd ), 0x0,, ahk_id %wID%
	else
		PostMessage, 0x433, TC_@( cmd ), 0x0,, ahk_id %wID%
return TRUE
}

TC_@( cm_cmd="" )
{
	static
	static init := 0
	global TC__TotalCmd@INC
	local iCmd, iCmd1, iCmd2, iCmd3, iTmp, retVal, tmpVar

	if( !init && init := 1 && !ErrorLevel:="")
	{
		local aDrive, aFile, aPath
		if(!(aPath:=TC__TotalCMD@INC))
		{
			EnvGet, aPath, COMMANDER_PATH
;			MsgBox, TC__TotalCmd@INC: %TC__TotalCmd@INC%
			if( !aPath )
			{
				EnvGet, aDrive, SystemDrive
				if(!FileExist(aPath:=aDrive "\TotalCmd"))
					if(!FileExist(aPath:=ProgramFiles "\TotalCMD"))
						aPath:=ProgramFiles
				FileSelectFile, TC__TotalCmd@INC,, %aPath%,Location of TotalCMD.inc file?,*.inc
			}
			else
				TC__TotalCmd@INC:=aPath "\TotalCMD.inc"
		}
;		MsgBox, TC__TotalCmd@INC: %TC__TotalCmd@INC%
;		MsgBox, aPath: %aPath%
		SplitPath( TC__TotalCmd@INC, aFile )
;		MsgBox, aFile: %aFile%`nErrorLevel: %ErrorLevel%
		onErrorExit((ErrorLevel || !RegExMatch( aFile, "i)^TotalCMD.inc$")) && !(init:=0), "ERROR: Invalid TotalCMD.inc File.")
		FileRead, TcmdINC, %TC__TotalCmd@INC%
		Loop, Parse, TcmdINC, `n, `r
		{
			if( !regExMatch( A_LoopField, "^cm_([a-zA-Z]+)([0-9]+)?=([0-9]+);", iCmd ) )
				continue
			if( iCmd3 > 6000 && iCmd3 <= 20000)
				continue
			if( iCmd3 > 5000 && iCmd3 <= 5500)
				continue
			else
			if( iCmd3 > 2060 && iCmd3 <= 2120 )	; cm_GotoDriveA ...
				continue
			else
			if( iCmd3 >= 701 && iCmd3 <= 900 )	; cm_UserMenu1 ... cm_UserMenu300
				continue
			else
			if( iCmd3 >= 271 && iCmd3 <= 299 )	; cm_SrcCustomView1 - 29
				continue
			else
			if( iCmd3 >= 171 && iCmd3 <= 199 )	; cm_RightCustomView1 - 29		
				continue
			else
			if( iCmd3 >=  71 && iCmd3 <= 99 )	; cm_LeftCustomView1 - 29
				continue

			tmpVar = cm_%iCmd1%%iCmd2%
			StringUpper, tmpVar, tmpVar
			%tmpVar% := iCmd3
		}
		if( !cm_cmd )
			return
	}

	Sleep, 50
	if( !cm_cmd )
		return
;	if(  )
	onErrorExit( !inStr( cm_cmd, "cm_"), "UnSupported: Input must be a `cm_` string." cm_cmd ": " %cm_cmd%, A_ThisFunc )

	StringUpper, cm_cmd, cm_cmd
	if( retVal := %cm_cmd% )
		return retVal

	if( RegExMatch( cm_cmd, "^CM_(LEFT|RIGHT|SRC)(CUSTOMVIEW)(\d+)$", iCmd ) )
	{
		onErrorExit( ( iCmd3 < 1 || iCmd3 > 29 ), "Illegal value for: " cm_cmd, A_ThisFunc )
		
		local LEFT := 0, RIGHT := 100, SRC := 200
	return % ( %iCmd1% + iCmd3 + 70 )
	}	  

	if( RegExMatch( cm_cmd, "^(CM_)(USERMENU)(\d+)$", iCmd ) )
	{
		onErrorExit( ( iCmd3 < 1 || iCmd3 > 200 ), "Illegal value for: " cm_cmd, A_ThisFunc )
	return ( iCmd3 + 700 )
	}
	
	if( RegExMatch( cm_cmd, "CM_(LEFT|RIGHT|SRC|TRG)(ACTIVATETAB|SORTBYCOL)(\d+)", iCmd ) )
	{
		onErrorExit( ( iCmd3 < 1 || iCmd3 > 99 ), "Illegal value for: " cm_cmd , A_ThisFunc )
		local SRC := 0, TRG := 100, LEFT := 200, RIGHT := 300
		local ACTIVATETAB := 5000, SORTBYCOL := 6000
	return % ( %iCmd1% + %iCmd2% + iCmd3 )
	}
	
	if( RegExMatch( cm_cmd, "(CM_)(GOTODRIVE)([A-Z])", iCmd ) )
	{
	return ( ASC(iCmd3) + 2000	-4 )
	}
	;onErrorExit( TRUE, "Variable: " cm_cmd " doesn't exist!", A_ThisFunc )
}

onErrorExit( checkEval, errMsg="", fn="", delay=3 )
{
	if( !checkEval )
		return 0
	if((!ErrorLevel || ErrorLevel:=1) && !errMsg)
		Exit
	RegExMatch(fn, "^([^:]+)(::([^ ].*))?$", rTmp )
	msg := ( rTmp1 ? rTmp1 "(" rTmp3 "): " : "" ) errMsg
	MsgBox,, % A_ScriptName " :: " rTmp1, % msg, % delay
Exit
}

SplitPath( fullPath, byRef aFile="", byRef aDir="", byRef aExt="", byRef aDrive="", byRef aNameOnly="" )
{
	fullPath.=((inStr(FileExist(fullPath), "D") && SubStr(fullPath, 0) <> "\") ? "\" : "")
	SplitPath, fullPath, aFile, aDir, aExt, aNameOnly, aDrive
	aDir.=(aDir && (aDir==aDrive)) ? "\" : ""
return 
}
TC_CD() is a neat piece of code, but it pretty much requires a LIB.ahk as it uses a fair number of defined functions, not really pasteable here.
fallmq
Junior Member
Junior Member
Posts: 19
Joined: 2012-06-28, 16:47 UTC

Post by *fallmq »

thanks Balderstrom, my code can work now,
just as you said, the problem is on the last param of TC_SendWMCopyData, a winExist("ahk_class TTOTAL_CMD") or WinActive("ahk_class TTOTAL_CMD") should be necessary.

I have another two questions:
1. the input parameter of "CD" is so complex, src" `r"target" ", do we MUST write the parameter like this?
2. it seems the "CD" and "EM" are both internal message format defined by TC, but I didn't find the detail information for these message types, can you tell you where can I get the detail description?
User avatar
Stefan2
Power Member
Power Member
Posts: 4132
Joined: 2007-09-13, 22:20 UTC
Location: Europa

Post by *Stefan2 »

Interesting topic! Please don't hesitate anybody to explain in most possible details :D ... i am learning too.
User avatar
Balderstrom
Power Member
Power Member
Posts: 2148
Joined: 2005-10-11, 10:10 UTC

Post by *Balderstrom »

Mr.Ghisler used half of a carriage return "`r" of an "`r`n" to separate two possible variables, as a SendMessage can only pass a single parameter. Likewise, a file path can't contain a partial carriage return.

Thus, SRC_Path "`r" TRG_Path
Since it is possible to only change a single panel's path, e.g.
examplePath:="`r" TRG_Path ; Only change Target Panel
examplePath:=SRC_Path "`r" ; Only change Source Panel
examplePath:=SRC_Path "`r" TRG_Path ; Change both Panels.
*BLINK* TC9 Added WM_COPYDATA and WM_USER queries for scripting.
User avatar
Balderstrom
Power Member
Power Member
Posts: 2148
Joined: 2005-10-11, 10:10 UTC

Post by *Balderstrom »

Code: Select all

WinActiveA( aWin="", winText="", notTitle="", notText="" ) 
 { 
    return WinActive( (aWin+0 ? "ahk_id " aWin : aWin), winText, notTitle, notText ) 
 }
That was one of my favorite compact functions.

It allows you to pass around either a "string" for aWin (e.g. ahk_class TTOTAL_CMD), or a winID.

aWin+0 will be an empty-string if it IS a string and you add 0 to it.
aWin+0 will remain a number if it was a number and you add 0 to it.

Thus we have,

Code: Select all

if( aWin+0 ) ; if a number
    return WinActive("ahk_id " aWin, winText, notTitle, notText)
else
    return WinActive(aWin, winText, notTitle, notText)
I usually pass around winID's but not always, by utilizing a custom WinActive or WinExist, you don't have to worry about what you passed to a given function - be it a title string, an ahk_class, or a plain ID# without prefacing it with "ahk_id".
Thus it becomes cleaner to write functions that rely on each other when you can do:
SomeFunction(winID, TRUE)

And within SomeFunction() we validate the winID by using WinActiveA() or WinExistA() instead of the vanilla AHK functions WinActive/WinExist. If you use the default AHK functions, then you need to always know what it is you are passing around --- and in a scripting language that mutates strings to numbers and vica versa - it is beneficial to have functions that can handle both as well.
User avatar
nsp
Power Member
Power Member
Posts: 1803
Joined: 2005-12-04, 08:39 UTC
Location: Lyon (FRANCE)
Contact:

Post by *nsp »

fallmq wrote:thanks Balderstrom, my code can work now,
just as you said, the problem is on the last param of TC_SendWMCopyData, a winExist("ahk_class TTOTAL_CMD") or WinActive("ahk_class TTOTAL_CMD") should be necessary.

I have another two questions:
1. the input parameter of "CD" is so complex, src" `r"target" ", do we MUST write the parameter like this?
2. it seems the "CD" and "EM" are both internal message format defined by TC, but I didn't find the detail information for these message types, can you tell you where can I get the detail description?
The complete syntax is in fact :
<Left>\r<Right>\0
<Source>\r<Target>\0S
<Left>\r<Right>\0T open in new Tab

If you only need to open in current source tab you can define a user command em_cd with command cd AND %A As parameter;

After, you can use your em_cd like sending em_cd D:\MyFolder it will also allows you to add filter like em_cd *.ini *.reg *.key

-- edited --
We already add such discussion on em_cmd and specific CD command in 2006 ! http://ghisler.ch/board/viewtopic.php?p=104086#104086 and http://www.ghisler.ch/board/viewtopic.php?p=105246
User avatar
nsp
Power Member
Power Member
Posts: 1803
Joined: 2005-12-04, 08:39 UTC
Location: Lyon (FRANCE)
Contact:

Post by *nsp »

Details are on the forum ;) or in the history.txt !!!
Hurdet
Power Member
Power Member
Posts: 620
Joined: 2003-05-10, 18:02 UTC

Post by *Hurdet »

ZoSTeR wrote:Example for AutoIt:

Code: Select all

#Include <GUIConstantsEx.au3>
#include <SendMessage.au3>

Opt('GUIOnEventMode', 1)

Global $hGUI

_Main()

Func _Main()
    Local $iBtn
    $hGUI = GUICreate('Test', 150, 50)
    GUISetOnEvent($GUI_EVENT_CLOSE, 'Event_GUIClose')
    $iBtn = GUICtrlCreateButton('Click Here', 10, 10, 100, 30)
    GUICtrlSetOnEvent(-1, 'handle_button_press')
    ; This registers the function to call when we receive a WM_COPYDATA (0x004A) back from TC:
	 GUIRegisterMsg(0x004A, 'Our_Func_for_WM_COPYDATA')
    GUISetState()
    While 1
        Sleep(500)
    WEnd
EndFunc   ;==>_Main

Func _Msg2TC($prmCommand = "A")
	$hTC = WinGetHandle("[CLASS:TTOTAL_CMD]")
	$dwData = Asc("G") + 256 * Asc("A")
	$lpDataLen = StringLen($prmCommand)
	$lpData = DllStructCreate("char[" & $lpDataLen + 1 & "]")
	DllStructSetData($lpData, 1, $prmCommand)
	$lpDataPtr = DllStructGetPtr($lpData)
	$structCOPYDATA = DllStructCreate("dword;dword;ptr")
	DllStructSetData($structCOPYDATA, 1, $dwData)
	DllStructSetData($structCOPYDATA, 2, $lpDataLen)
	DllStructSetData($structCOPYDATA, 3, $lpDataPtr)
	$ptrCOPYDATA = DllStructGetPtr($structCOPYDATA)
	$arrReturn = _SendMessage($hTC, 0x004A, $hGUI, $ptrCOPYDATA)
EndFunc

Func Our_Func_for_WM_COPYDATA($hWnd, $iMsg, $wParam, $lParam)
	$returnCOPYDATA = DllStructCreate("dword;dword;ptr", $lParam)
	$returnDWData = DllStructGetData($returnCOPYDATA, 1)
	$returnLPDataLen = DllStructGetData($returnCOPYDATA, 2)
	$returnLPDataPtr = DllStructGetData($returnCOPYDATA, 3)
	$returnLPData = DllStructCreate("char[" & $returnLPDataLen + 1 & "]", $returnLPDataPtr)
	$retValue = DllStructGetData($returnLPData, 1)
	; Avoid MsgBox in real life at this point (see AutoIt Help 'GUIRegisterMsg')
	MsgBox(0,"TC Reply", $retValue)
EndFunc

Func handle_button_press()
	_Msg2TC()
EndFunc

Func Event_GUIClose()
    Exit
EndFunc
When i use:

Code: Select all

$dwData = Asc("G") + 256 * Asc("A")
It work fine.
Instead, when i use:

Code: Select all

$dwData = Asc("G") + 256 * Asc("W")
for unicode it return only 1 character.
Why?
User avatar
ZoSTeR
Power Member
Power Member
Posts: 1008
Joined: 2004-07-29, 11:00 UTC

Post by *ZoSTeR »

A year too late but better than never ;)

You have to change the "char" in

Code: Select all

$returnLPData = DllStructCreate("char[" & $returnLPDataLen + 1 & "]", $returnLPDataPtr)
to "wchar"

Code: Select all

$returnLPData = DllStructCreate("wchar[" & $returnLPDataLen + 1 & "]", $returnLPDataPtr)
Post Reply