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
LonerD
Senior Member
Senior Member
Posts: 381
Joined: 2010-06-19, 20:18 UTC
Location: Makeyevka, Russia
Contact:

Post by *LonerD »

2artt
Thank you for explanation.
FIXED... it will not wait for 5 seconds for the empty value
Still wait.
And you lost CD-Command section in script comment ))
"I used to feel guilty in Cambridge that I spent all day playing games, while I was supposed to be doing mathematics. Then, when I discovered surreal numbers, I realized that playing games IS math." John Horton Conway
User avatar
artt
Junior Member
Junior Member
Posts: 50
Joined: 2009-05-03, 07:03 UTC

Post by *artt »

2LonerD
Thank you - the comments restored.

In my system, after last fix there is no wait. Can you please check if you have an old version of AHK?

If the wait is still remaining, you can make a test to see if TC is not sending the answer immediately.
Using this test, a new MessageBox will inform you about the data received by TC immediately when they received.

replace

Code: Select all

   IF ((msg=0x4A) AND (hwnd=A_ScriptHwnd)) ; EnSure is trigered by this Script.
      EXIT (TC_ReceiveDataValue:=StrGet(NumGet(CmdType + A_PtrSize * 2)), TC_DataReceived:="1")
with

Code: Select all

   IF ((msg=0x4A) AND (hwnd=A_ScriptHwnd)) ; EnSure is trigered by this Script.
      {
      TC_ReceiveDataValue:=StrGet(NumGet(CmdType + A_PtrSize * 2))
      TC_DataReceived:="1"
      MsgBox,4096,Those Data Received by TC:,[%TC_ReceiveDataValue%]
      EXIT
      }
User avatar
LonerD
Senior Member
Senior Member
Posts: 381
Joined: 2010-06-19, 20:18 UTC
Location: Makeyevka, Russia
Contact:

Post by *LonerD »

2artt
Finally - now with string
IfEqual, TC_DataReceived, 1, Break
script work without delay. :-)
(this string was absent yesterday).
Thank you for work
"I used to feel guilty in Cambridge that I spent all day playing games, while I was supposed to be doing mathematics. Then, when I discovered surreal numbers, I realized that playing games IS math." John Horton Conway
User avatar
artt
Junior Member
Junior Member
Posts: 50
Joined: 2009-05-03, 07:03 UTC

Post by *artt »

2LonerD
The IF-statement you mention was not absent yesterday,
but i am happy the function is working as expected now.
Thank you for your help!
stalker0
Junior Member
Junior Member
Posts: 8
Joined: 2015-05-24, 08:11 UTC
Location: Slovakia

Post by *stalker0 »

Here is an example in VB.NET. Maybe it'll help somebody.
(it is just my proof of concept demo, so code is not very nice)

Code: Select all

Imports System.Runtime.InteropServices
Imports System.Windows.Interop

Class MainWindow

  <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
  Private Shared Function SendMessage(hWnd As IntPtr, Msg As UInteger, wParam As IntPtr, lParam As IntPtr) As IntPtr
  End Function

  Private Const TCMD_MSG = 1075 ' WM_USER + 51

  Private Function GetProcess() As Process
    Return Process.GetProcessesByName("TOTALCMD64").First()
  End Function

  Private Sub btnCmCommand_Click(sender As Object, e As RoutedEventArgs) Handles btnCmCommand.Click
    Using p = GetProcess()
      ' Example: 2027 = cm_PrintDir - print current directory (with preview)
      SendMessage(p.MainWindowHandle, TCMD_MSG, New IntPtr(2027), IntPtr.Zero)
    End Using
  End Sub

  Private Const WM_COPYDATA = &H4A

  <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
  Private Shared Function SendMessage(hWnd As IntPtr, Msg As UInteger, wParam As IntPtr, ByRef lParam As COPYDATASTRUCT) As Boolean
  End Function

  <StructLayout(LayoutKind.Sequential)>
  Structure COPYDATASTRUCT
    Dim dwData As IntPtr
    Dim cbData As Int32
    Dim lpData As IntPtr
  End Structure

  Private Function IntPtrAlloc(Of T)(param As T) As IntPtr
    Dim ptr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(param))
    Marshal.StructureToPtr(param, ptr, False)
    Return ptr
  End Function

  Private Sub IntPtrFree(preAllocated As IntPtr)
    If IntPtr.Zero = preAllocated Then Throw New Exception()
    Marshal.FreeHGlobal(preAllocated)
    preAllocated = IntPtr.Zero
  End Sub

  Private Sub SendCommand(commandType As String, command As String)
    SendCommand(commandType, command, IntPtr.Zero)
  End Sub

  Private Sub SendCommand(commandType As String, command As String, hWnd As IntPtr)
    Using p = GetProcess()
      Dim dataStruct = New COPYDATASTRUCT
      dataStruct.dwData = New IntPtr(Convert.ToInt32(commandType(0)) + 256 * Convert.ToInt32(commandType(1)))
      dataStruct.cbData = command.Length + 1
      dataStruct.lpData = Marshal.StringToHGlobalAnsi(command)

      Dim dataStructPointer = IntPtrAlloc(dataStruct)
      SendMessage(p.MainWindowHandle, WM_COPYDATA, hWnd, dataStructPointer)
      IntPtrFree(dataStruct.lpData)
      IntPtrFree(dataStructPointer)
    End Using
  End Sub

  Private Sub btnEmCommand_Click(sender As Object, e As RoutedEventArgs) Handles btnEmCommand.Click
    Dim commandType = "EM"
    Dim command = "em_PutCommandNameHere"

    SendCommand(commandType, command)
  End Sub

  Private Sub btnChangeDir_Click(sender As Object, e As RoutedEventArgs) Handles btnChangeDir.Click
    Dim commandType = "CD"
    Dim left = "c:\!Temp\"
    Dim right = "d:\!Temp\"
    ' S - use source/target instead of left/right (optional)
    ' T - open in new tab (optional)
    Dim command = left & Convert.ToChar(13) & right & Convert.ToChar(0) & "ST"

    SendCommand(commandType, command)
  End Sub

  Private Sub btnGetInfo_Click(sender As Object, e As RoutedEventArgs) Handles btnGetInfo.Click
    ' W returns data in UTF16 (or use A instead)
    Dim commandType = "GW"
    ' 1 byte: A: Active side (returns L or R)
    ' or
    ' 2 bytes:
    ' first byte: L=left, R=right, S=source, T=target
    ' second byte: P=current path, C=list count, I=caret index, N=name of file under caret
    Dim command = "SN"
    Dim hWnd = DirectCast(PresentationSource.FromVisual(Me), HwndSource).Handle ' because of WPF

    SendCommand(commandType, command, hWnd)
  End Sub

  Private Function WndProc(hWnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr
    If msg = WM_COPYDATA Then
      ' W returns data in UTF16 (or use A instead)
      Dim expected = "RW"
      Dim dataStruct = DirectCast(Marshal.PtrToStructure(lParam, GetType(COPYDATASTRUCT)), COPYDATASTRUCT)

      If dataStruct.dwData.ToInt32() = Convert.ToInt32(expected(0)) + 256 * Convert.ToInt32(expected(1)) Then
        Dim data = New Byte(dataStruct.cbData) {}
        Marshal.Copy(dataStruct.lpData, data, 0, dataStruct.cbData)
        Dim text = System.Text.Encoding.Unicode.GetString(data)
        MessageBox.Show(text)

        handled = True
      End If
    End If

    Return IntPtr.Zero
  End Function

  ' used because there is no WndProc to override in WPF
  Private Sub MainWindow_SourceInitialized(sender As Object, e As EventArgs) Handles Me.SourceInitialized
    DirectCast(PresentationSource.FromVisual(Me), HwndSource).AddHook(New HwndSourceHook(AddressOf WndProc))
  End Sub

  Private Sub MainWindow_Closing(sender As Object, e As ComponentModel.CancelEventArgs) Handles Me.Closing
    DirectCast(PresentationSource.FromVisual(Me), HwndSource).RemoveHook(New HwndSourceHook(AddressOf WndProc))
  End Sub
End Class
User avatar
Dalai
Power Member
Power Member
Posts: 9364
Joined: 2005-01-28, 22:17 UTC
Location: Meiningen (Südthüringen)

Post by *Dalai »

I wanted to post my Delphi example in another thread but since I don't find it, I post it here.

Code: Select all

function _TotalCmdChangeDir(const TC: THandle; const ADir: string = '';
                            const ADir2: string = ''; const AFlags: string = '';
                            const ASender: THandle = 0): Boolean;
var
    Ls: string;
    Lcds: TCopyDataStruct;
begin
    Result:= False;
    if (ADir = '') AND (ADir2 = '') then Exit;
    if (ADir <> '') then
        Ls:= ADir;
    Ls:= Ls + #13;
    if (ADir2 <> '') then
        Ls:= Ls + ADir2;
    if AFlags <> '' then
        Ls:= Ls + #0 + AFlags;

    ZeroMemory(@Lcds, SizeOf(Lcds));
    Lcds.dwData := Ord('C') + 256 * Ord('D');
    Lcds.cbData := (Length(Ls) + 1);
    Lcds.lpData := PChar(Ls);
    SendMessage(TC, WM_COPYDATA, ASender, LPARAM(@Lcds));
    Result:= True;
end;

//------------------------------------------------------------------------------

{$IFDEF UNICODE}
function _TotalCmdChangeDirW(const TC: THandle; const ADir: string = '';
                             const ADir2: string = ''; const AFlags: string = '';
                             const ASender: THandle = 0): Boolean;
const UTF8BOM : RawByteString = #$EF + #$BB + #$BF;
var Ls: RawByteString;
    Lcds: TCopyDataStruct;
begin
    Result:= False;
    if (ADir = '') AND (ADir2 = '') then Exit;
    if (ADir <> '') then
        Ls:= UTF8BOM + UTF8Encode(ADir);
    Ls:= Ls + #13;
    if (ADir2 <> '') then
        Ls:= Ls + UTF8BOM + UTF8Encode(ADir2);
    if AFlags <> '' then
        Ls:= Ls + #0 + RawByteString(AFlags);

    ZeroMemory(@Lcds, SizeOf(Lcds));
    Lcds.dwData := Ord('C') + 256 * Ord('D');
    Lcds.cbData := (Length(Ls) + 1);
    Lcds.lpData := Pointer(Ls);
    SendMessage(TC, WM_COPYDATA, ASender, LPARAM(@Lcds));
    Result:= True;
end;
{$ENDIF}

//------------------------------------------------------------------------------

function TotalCmdChangeDir(const TCHandle: THandle; const ADir: string; ANewTab: Boolean = True): Boolean;
var Lflags: string;
begin
    Lflags:= 'S';
    if ANewTab then
        Lflags:= Lflags + 'T';
    {$IFDEF UNICODE}
    Result:= _TotalCmdChangeDirW(TCHandle, ADir, '', Lflags);
    {$ELSE}
    Result:= _TotalCmdChangeDir(TCHandle, ADir, '', Lflags);
    {$ENDIF}
end;
This code is a full translation of MVV's C++ example - full at least regarding changing directories (I omitted the user command stuff). It works with left and/or right panel, can open a new tab, uses Unicode if available and so on.

The third function just calls one of the first two which do the real work (sending the message to TC).

If you have improvements, please don't hesitate to post them :).

Regards
Dalai
#101164 Personal licence
Ryzen 5 2600, 16 GiB RAM, ASUS Prime X370-A, Win7 x64

Plugins: Services2, Startups, CertificateInfo, SignatureInfo, LineBreakInfo - Download-Mirror
User avatar
MVV
Power Member
Power Member
Posts: 8702
Joined: 2008-08-03, 12:51 UTC
Location: Russian Federation

Post by *MVV »

This is not a my example BTW, I've only posted a link to it on ghisler.ch/wiki.
Giovanni
Member
Member
Posts: 154
Joined: 2005-03-23, 18:28 UTC

Post by *Giovanni »

Do I need to set specific modes or flags for Free Pascal? I struggled with FPC for a while before giving up last year. Couldn't find working wfx and wlx complete examples.
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Just use Lazarus and create a DLL project, works fine for me. My makebat plugin on ghisler.com is compiled with Lazarus / Free Pascal, and comes with source code.
Author of Total Commander
https://www.ghisler.com
User avatar
LonerD
Senior Member
Senior Member
Posts: 381
Joined: 2010-06-19, 20:18 UTC
Location: Makeyevka, Russia
Contact:

Re: New WM_COPYData Examples

Post by *LonerD »

2artt
This script has problems with Unicode file names :(
I tried

Code: Select all

TC_SendData("D:\日本" "`r", "ST")
but TC open D: instead directory.
Last edited by LonerD on 2018-10-25, 11:13 UTC, edited 1 time in total.
"I used to feel guilty in Cambridge that I spent all day playing games, while I was supposed to be doing mathematics. Then, when I discovered surreal numbers, I realized that playing games IS math." John Horton Conway
User avatar
MVV
Power Member
Power Member
Posts: 8702
Joined: 2008-08-03, 12:51 UTC
Location: Russian Federation

Re: New WM_COPYData Examples

Post by *MVV »

It must convert path to UTF-8 and prepend BOM to it in order to support Unicode.
User avatar
dindog
Senior Member
Senior Member
Posts: 315
Joined: 2010-10-18, 07:41 UTC

Re: Just another working AHK script

Post by *dindog »

artt wrote: 2015-04-23, 06:05 UTC Just another working AHK script
TESTED AND WORKING ON: AHK_L v1.1.21.03 UNICODE X32 + Win7 X32 + TC 8.51a
I took this script and edit it a bit based on the result of the other post
https://www.ghisler.ch/board/viewtopic.php?f=3&t=52056&p=363387#p363387

And here is a code snippet for both ANSI & Unicode AHK basically cover all the wm_copydata function TC provides now.


Notice I add some non-ASCII character in my locale codepage and some unicode character for testing, ANSI AHK version will faill to parse the unicode part of the path.

标签, 测试,命令 are non-ASCII and non-unicode character in my codepage(Chinese)
ビデオゲームミュージック are unicode character in my codepaage

Code: Select all

; F1 for input cm_ & em_ command test
; F2 for asking TC some simple panel status
; F12 for a batch test, but you will need to set up some user command and Dirs first.

EM	  = em_foo|em_cd *.ini *.exe|em_命令 *.*|em_APPENDTABS D:\TC\Tab\标签.tab  ;
CM    = cm_select 5|cm_wait 2000|cm_about
ASK	 = A|LP|LC|LI|LN|RP|RC|RI|RN|SP|SC|SI|SN|TP|TC|TI|TN
bom :=( A_IsUnicode )?Chr(0xFEFF):""
Dir1	:= "e:\测试\ビデオゲームミュージック"
Dir2	:= "%windir%"
Dir3	:= "E:\测试\totalcmd950b6x64.cab" ; it is an arhive for testing "A" parameters
Dir4	:= "d:\tc"
return

F1::
InputBox, StringToSend, Send command via WM_COPYDATA,interanl command cm_select 5 `nuser command em_foo (you need to define it first):
TC_SendData(bom StringToSend, "EM")
return

F2::
InputBox, StringToSend, Send command via WM_COPYDATA,A|LP|LC|LI|LN|RP|RC|RI|RN|SP|SC|SI|SN|TP|TC|TI|TN `n for example ask for A for Active panel:
answer:=TC_SendData(StringToSend)
MsgBox, %answer%
return

F12::
i:=""
LOOP, PARSE, ASK, |	; Ask TC
	i .= A_LoopField ":`t" TC_SendData(A_LoopField) (A_Index=1 OR A_Index=5 OR A_Index=9 OR A_Index=13 ? "`n`n" : "`n")
MsgBox,4096, Ask TC, % i

LOOP, PARSE, CM, |	; internal command, but call as em_cmd
	{
	MsgBox,4096, internal command, %A_LoopField%
	TC_SendData(bom A_LoopField,"EM")
	}

LOOP, PARSE, EM, |	; User Command
	{
	MsgBox,4096, User Command, %A_LoopField%
	TC_SendData(bom A_LoopField,"EM")
	}

MsgBox,4096, CD Command, LeftDir=%Dir1% - RightDir=%Dir2% `n Active->Right
TC_SendData(bom Dir1 "`r" bom Dir2, "R")
	MsgBox,4096, CD Command, SourceDir=%Dir3% `n open in NEW tab `n if path is an archive, do not open but select it `n Active->Left
	TC_SendData(bom Dir3 "`r" bom "c:" , "ASTL")
	MsgBox,4096, CD Command, TargetDir=%Dir4% `n open in NEW tab
	TC_SendData( "`r" bom Dir4, "ST")
MsgBox,4096, CD Command, SourceDir=%Dir2% `n open in NEW tab `n Active->Right
TC_SendData(bom Dir2 "`r", "SRT")
MsgBox,4096, CD Command, RightDir=%Dir3% `n when you have no CD param "STLRA" just leave "CD" for distinguishing from "ASK"
TC_SendData( "`r" bom Dir3, "CD") ; 
	MsgBox,4096, CD Command, SourceDir=%Dir3% TargetDir=%Dir4% `n open in NEW Background tab `n Active->Left
	TC_SendData(bom Dir3 "`r" bom Dir4, "STBL")
Return

/*
TESTED AND WORKING ON: AHK_L v 1.1.31.01 unicode & ansi version, Win 10 64bit and TC9.22
modified by dindog
-------------------------------------------------------------------------
TC_SendData("em_FOO"         	     , "EM") ; User Command
TC_SendData("em_APPENDTABS C:\my.tab", "EM") ; User Command with parameters (usercmd.ini as following)
															;				[em_APPENDTABS]
															;				cmd=APPENDTABS
															;				param=%A
TC_SendData("em_CD C:", "EM") ; User Command with parameters (usercmd.ini as following)
															;				[em_cd]
															;				cmd=cd
															;				param=%A
TC_SendData("em_命令 *.exe", "EM") ; User Command with parameters (usercmd.ini as following) test for command name non-ASCII
															;				[em_命令]
															;				cmd=cd
															;				param=%A

TC_SendData("cmd") 								  ; Ask TC :     (cmd one of the following varues:)
															; A  = Active Side
						
															; LP = Left Path            RP = Right Path
															; LC = Left List Count      RC = Right List Count
															; LI = Left Caret Index     RI = Right Caret Index
															; LN = Left Name Caret      RN = Right Name Caret

															; SP = Source Path          TP = Target Path
															; SC = Source List Count    TC = Target List Count
															; SI = Source Caret Index   TI = Target Caret Index
															; SN = Source Name Caret    TN = Target Name Caret

TC_SendData("C:\tc" "`r" "D:\data", "CD")	 ; CD   Command: (LeftDir - RightDir)
TC_SendData("C:\tc" "`r"          , "R")	 ; CD   Command: (LeftDir) and activate Right panel
TC_SendData(        "`r" "D:\data", "LT")	 ; CD   Command: (          RightDir) in new tab and activate left panel

TC_SendData("C:\tc" "`r" "D:\data", "S")	 ; CD   Command: (SourceDir - TargetDir)
TC_SendData("C:\tc" "`r"          , "SBT")	 ; CD   Command: (SourceDir) in new background tab
TC_SendData(        "`r" "D:\data", "ST")	 ; CD   Command: (            TargetDir) in new background tab
S: Interpret the paths as source/target instead of left/right
T: Open path(s) in new tabs
B: Open tabs in background (do not activate them)
L: Activate the left panel
R: Activate the right panel
A: Do not open archives as directories. Instead, open parent directory and place cursor on it.
TC accepts more then 2 parameters here, so sending e.g. STBL is legitimate.
*/
TC_SendData(Cmd, CmdType="", msg="", hwnd="") {
   Critical   ; Define "OnMessage" as STATIC it is registered at Script startup.
   STATIC om:=OnMessage(0x4a, "TC_SendData"), TC_ReceiveDataValue:="", TC_DataReceived:=""	; 0x4a is WM_COPYDATA

   IF ((msg=0x4A) AND (hwnd=A_ScriptHwnd)) ; EnSure is trigered by this Script.
      EXIT (TC_ReceiveDataValue:=StrGet(NumGet(CmdType + A_PtrSize * 2)), TC_DataReceived:="1")

   VarSetCapacity(CopyDataStruct, A_PtrSize * 3), TC_ReceiveDataValue:=1, TC_DataReceived:=""
   if (CmdType="") ; Ask TC
      CmdType:=(A_IsUnicode ? "GW" : "GA"), TC_ReceiveDataValue:=""
   else if (CmdType="EM") or (CmdType="em") ; em command
      CmdType:="EM"
   else  ; CD command STBALR
      DirType:=(CmdType="CD")?"":CmdType, CmdType:="CD"

      ;;;;;;VarSetCapacity need to request at least 5 more byte to allow 4 CD params
      VarSetCapacity(cmdA, StrPut(cmd, (A_IsUnicode ?"UTF-8":"CP0")) + (CmdType="CD" ? 5 : 0) * (A_IsUnicode ? 2 : 1), 0)	,	Len:=StrPut(cmd, &cmdA, (A_IsUnicode ?"UTF-8":"CP0"))

   		

   NumPut( Asc(SubStr(CmdType,1,1)) + 256 * Asc(SubStr(CmdType,2,1)), CopyDataStruct,0 )
   NumPut( Len + (CmdType="CD" ? 5 : 0) * (A_IsUnicode ? 2 : 1) , CopyDataStruct, A_PtrSize )
   NumPut( &cmdA , CopyDataStruct, A_PtrSize * 2)
   Loop, % strlen(DirType) ;(CmdType=="CD" ? 2 : 0)
   {  
      NumPut(Asc(SubStr(DirType,A_Index,1)), cmdA ,Len+A_Index-1, "Char")
      }
   SendMessage, 0x4A,%A_ScriptHwnd%, &CopyDataStruct,, ahk_class TTOTAL_CMD
   
   While (TC_ReceiveDataValue="") {
      IfEqual, TC_DataReceived,    1, Break
      IfGreaterOrEqual, A_Index, 500, Break
      Sleep,10
   }
   Return TC_ReceiveDataValue
}

jathri
Junior Member
Junior Member
Posts: 8
Joined: 2016-08-29, 09:11 UTC

Re: New WM_COPYData Examples

Post by *jathri »

Just another working AHK script allows to get source/target caret (cursor) index. I was wondering if there is a way to set source/target caret index? And possibly to set these indexes by name?
Post Reply