gioma 19 Posted May 15, 2023 Hi, I'm making a program that sends files from PC A to PC B. When I copy a file on one PC I would like it to be available on the other PC's cliboard. What I can do is manipulate the clibboard by copying the paths of the files I intend to transfer, but then I have to intercept the "copy" command in order to start the transfer. I tried with hooks but no way. Do any of you know how I could do this? Or maybe you have a different better idea? Thank you. Share this post Link to post
programmerdelphi2k 237 Posted May 15, 2023 (edited) I'm not an expert on Windows messages, but you would have to intercept the "WM_DRAWCLIPBOARD" message, to receive notification that MSExplorer intends to copy some object (file/folder/etc...). But first your app must be registered with a "Clipboard Viewer" ( SetClipboardViewer() ), and when you finish the app, do the "Un-Register" ( ChangeClipboardChain() ) try verify this message in your app: procedure WMDrawClipboard(var Msg: TMessage); message WM_DRAWCLIPBOARD; procedure WMChangeCBChain(var Msg: TMessage); message WM_CHANGECBCHAIN; NOTE: This does not guarantee that the file was actually copied, as you are only receiving the notification that something will be copied. The file may be large and the other application will need to check if the file "already exists on disk" Edited May 15, 2023 by programmerdelphi2k 1 Share this post Link to post
gioma 19 Posted May 15, 2023 27 minutes ago, programmerdelphi2k said: I'm not an expert on Windows messages, but you would have to intercept the "WM_DRAWCLIPBOARD" message, to receive notification that MSExplorer intends to copy some object (file/folder/etc...). But first your app must be registered with a "Clipboard Viewer" ( SetClipboardViewer() ), and when you finish the app, do the "Un-Register" ( ChangeClipboardChain() ) try verify this message in your app: procedure WMDrawClipboard(var Msg: TMessage); message WM_DRAWCLIPBOARD; procedure WMChangeCBChain(var Msg: TMessage); message WM_CHANGECBCHAIN; NOTE: This does not guarantee that the file was actually copied, as you are only receiving the notification that something will be copied. The file may be large and the other application will need to check if the file "already exists on disk" Thanks for the reply. I already do this, in fact if I copy paste a text it works perfectly. The problem instead I have with the files because they are not yet available on the file system because they come from the other PC. I send the list of files but I would like it to start the transfer only when "paste" is pressed and then the "paste window" waits for the files to be completely transferred and therefore available. Share this post Link to post
programmerdelphi2k 237 Posted May 15, 2023 (edited) @gioma maybe exists some other msg about "file created on disk...", I dont know exactly because my knowledge it's not enough here! try send the msg after the files was copyed from your app... Edited May 15, 2023 by programmerdelphi2k 1 Share this post Link to post
Remy Lebeau 1397 Posted May 15, 2023 (edited) 22 hours ago, programmerdelphi2k said: I'm not an expert on Windows messages, but you would have to intercept the "WM_DRAWCLIPBOARD" message, to receive notification that MSExplorer intends to copy some object (file/folder/etc...). But first your app must be registered with a "Clipboard Viewer" ( SetClipboardViewer() ), and when you finish the app, do the "Un-Register" ( ChangeClipboardChain() ) That is the OLD way to monitor the clipboard. Since Vista, the NEW way is using a Clipboard Format Listener instead, registered via AddClipboardFormatListener() and handling WM_CLIPBOARDUPDATE messages. 22 hours ago, gioma said: Thanks for the reply. I already do this, in fact if I copy paste a text it works perfectly. The problem instead I have with the files because they are not yet available on the file system because they come from the other PC. I send the list of files but I would like it to start the transfer only when "paste" is pressed and then the "paste window" waits for the files to be completely transferred and therefore available. Have you read Microsoft's documentation on Transferring Shell Objects with Drag-and-Drop and the Clipboard and Handling Shell Data Transfer Scenarios yet? Per Shell Clipboard Formats, you will likely want to implement the CFSTR_FILEDESCRIPTOR and CFSTR_FILECONTENTS formats. That way, you can put descriptions of the files onto the clipboard, and when they are pasted then you can stream in the actual file data as needed. Edited May 16, 2023 by Remy Lebeau 2 Share this post Link to post
gioma 19 Posted May 16, 2023 10 hours ago, Remy Lebeau said: That is the OLD way to monitor the clipboard. Since Vista, the NEW way is using a Clipboard Format Listener instead, registered via AddClipboardFormatListener() and handling WM_CLIPBOARDUPDATE messages. Hi, yes I'm using this way to monitor the clipboard, I didn't specify it because my problem arises later. 10 hours ago, Remy Lebeau said: Have you read Transferring Shell Objects with Drag-and-Drop and the Clipboard yet? Per Shell Clipboard Formats, you will likely want to implement the CFSTR_FILEDESCRIPTOR and CFSTR_FILECONTENTS formats. That way, you can put descriptions of the files onto the clipboard, and when they are pasted then you can stream in the actual file data as needed. I've read this article, but I still can't get into it. Time is never an ally, so I was looking for an example to start with, to avoid too many empty tests, and also because the testing phase is not easy, as we are dealing with two applications on two different PCs communicating via sockets. My specific case is as follows: I have two applications, A and B, connected by a socket connection. Application A intercepts the modification of its clipboard and sends the event to application B. Here we have two cases: 1 if it is a text, it copies it directly to the clipboard 2 if it is a file, it copies the list to the clipboard. Up to point 1 there are, while I have difficulty understanding how to pass the list of files to PC B (or rather I can do this phase) and make sure that when I press "paste" (here is the real problem which I'm racking my brain over) the file list transfer begins and the system waits for the files to be transferred before completing the paste command. I can't find half an example of this on the net, I can't believe no one has ever done this before, maybe even in C, C++. Share this post Link to post
programmerdelphi2k 237 Posted May 16, 2023 @gioma, I think you should avoid using Clipboard, even if it works in your project, for the simple fact that it is a neutral layer and used by any other application in your system. In this way, nothing guarantees that the information obtained will be the desired information. You know it! Now, as you are already communicating with the second (or any other) application on the network, then, I think the most sensible thing would be to approach this direction, that is, send the desired information through TCP/IP, UDP or similar communication ... etc... In this way, you can guarantee that your transmissions will be directed exclusively to your interested applications, that is, to everyone who makes use of this communication, it can be one, two, or as many as you wish. You could easily do this code without much impact on your current project, if you want, you could even create a small TCP/IP server using INDY or whatever to listen on a certain port, and thus receive all messages (information) desired. In this way, you can ensure that you receive the information sent by another application. Delphi can easily generate a DataSnap Server (ie the code) for you to add to your applications. Delphi has Tethering components, which you can easily add to a form, so almost no code is needed. Communication is carried out by TCP/IP and UDP protocols, in this way, you just need to place a Tethering Server component in the applications that will receive the information, and another Tethering Client in the application that will send the information, the rest is just using one or two events to receive the information. Very easy and without much complexity. here a great sample by Malcolm Groves : http://www.malcolmgroves.com/blog/?p=1891 1 Share this post Link to post
programmerdelphi2k 237 Posted May 16, 2023 (edited) @gioma based on @Remy Lebeau text, I have test this code and works... but not guaratee if it's good for all...ok? // VCL tests ... private FClipboardListenerHandle: HWND; procedure ClipboardUpdateHandler(var Msg: TMessage); message WM_CLIPBOARDUPDATE; ... implementation {$R *.dfm} uses Winapi.ShellAPI, Winapi.ShlObj, Vcl.Clipbrd; { TForm1 } procedure TForm1.FormCreate(Sender: TObject); begin if AddClipboardFormatListener(Handle) then; // ???? // Memo1.Lines.Add('AddClipboardFormatListener: GetLastError = ' + GetLastError.ToString); end; procedure TForm1.FormDestroy(Sender: TObject); begin if RemoveClipboardFormatListener(Handle) then; /// ??? // Memo1.Lines.Add('RemoveClipboardFormatListener: GetLastError = ' + GetLastError.ToString); end; procedure TForm1.ClipboardUpdateHandler(var Msg: TMessage); var LClipboardData : THandle; LGLobalLock : Pointer; LFilename : PWideChar; LFilenameBuffer: array [0 .. MAX_PATH] of WideChar; // cfShellIDList: UINT; begin if not IsClipboardFormatAvailable(CF_HDROP) then exit; // cfShellIDList := RegisterClipboardFormat(CFSTR_SHELLIDLIST); // if OpenClipboard(Handle) then // opening the Clipboard... begin try LClipboardData := GetClipboardData(CF_HDROP); // some data? // if (LClipboardData > 0) then try LGLobalLock := GlobalLock(LClipboardData); // lock it... try if (LGLobalLock <> nil) then begin DragQueryFileW(HDROP(LGLobalLock), 0, LFilenameBuffer, MAX_PATH); // catch the file/folder name // Memo1.Lines.Add('File name: ' + LFilenameBuffer); end else Memo1.Lines.Add('LGLobalLock = nil'); finally GlobalUnlock(LClipboardData); // unlock it... end; except on E: Exception do Memo1.Lines.Add('Exception: ' + E.Message); end else Memo1.Lines.Add('----- ClipboardData = 0 -----'); finally CloseClipboard; end; end else Memo1.Lines.Add('OpenClipboard = false'); end; Edited May 16, 2023 by programmerdelphi2k 1 Share this post Link to post
gioma 19 Posted May 16, 2023 (edited) 2 hours ago, programmerdelphi2k said: @gioma, I think you should avoid using Clipboard, even if it works in your project, for the simple fact that it is a neutral layer and used by any other application in your system. In this way, nothing guarantees that the information obtained will be the desired information. You know it! Now, as you are already communicating with the second (or any other) application on the network, then, I think the most sensible thing would be to approach this direction, that is, send the desired information through TCP/IP, UDP or similar communication ... etc... In this way, you can guarantee that your transmissions will be directed exclusively to your interested applications, that is, to everyone who makes use of this communication, it can be one, two, or as many as you wish. You could easily do this code without much impact on your current project, if you want, you could even create a small TCP/IP server using INDY or whatever to listen on a certain port, and thus receive all messages (information) desired. In this way, you can ensure that you receive the information sent by another application. Delphi can easily generate a DataSnap Server (ie the code) for you to add to your applications. Delphi has Tethering components, which you can easily add to a form, so almost no code is needed. Communication is carried out by TCP/IP and UDP protocols, in this way, you just need to place a Tethering Server component in the applications that will receive the information, and another Tethering Client in the application that will send the information, the rest is just using one or two events to receive the information. Very easy and without much complexity. here a great sample by Malcolm Groves : http://www.malcolmgroves.com/blog/?p=1891 Hi, thanks for the reply. I already do, mine is a remote control software. Currently I can transfer a file from one PC to another via a file browser (which opens on the parent site). But now I need to implement the "copy-paste" so I need to interact with the system clipboard. 2 hours ago, programmerdelphi2k said: @gioma based on @Remy Lebeau text, I have test this code and works... but not guaratee if it's good for all...ok? // VCL tests ... private FClipboardListenerHandle: HWND; procedure ClipboardUpdateHandler(var Msg: TMessage); message WM_CLIPBOARDUPDATE; ... implementation {$R *.dfm} uses Winapi.ShellAPI, Winapi.ShlObj, Vcl.Clipbrd; { TForm1 } procedure TForm1.FormCreate(Sender: TObject); begin if AddClipboardFormatListener(Handle) then; // ???? // Memo1.Lines.Add('AddClipboardFormatListener: GetLastError = ' + GetLastError.ToString); end; procedure TForm1.FormDestroy(Sender: TObject); begin if RemoveClipboardFormatListener(Handle) then; /// ??? // Memo1.Lines.Add('RemoveClipboardFormatListener: GetLastError = ' + GetLastError.ToString); end; procedure TForm1.ClipboardUpdateHandler(var Msg: TMessage); var LClipboardData : THandle; LGLobalLock : Pointer; LFilename : PWideChar; LFilenameBuffer: array [0 .. MAX_PATH] of WideChar; // cfShellIDList: UINT; begin if not IsClipboardFormatAvailable(CF_HDROP) then exit; // cfShellIDList := RegisterClipboardFormat(CFSTR_SHELLIDLIST); // if OpenClipboard(Handle) then // opening the Clipboard... begin try LClipboardData := GetClipboardData(CF_HDROP); // some data? // if (LClipboardData > 0) then try LGLobalLock := GlobalLock(LClipboardData); // lock it... try if (LGLobalLock <> nil) then begin DragQueryFileW(HDROP(LGLobalLock), 0, LFilenameBuffer, MAX_PATH); // catch the file/folder name // Memo1.Lines.Add('File name: ' + LFilenameBuffer); end else Memo1.Lines.Add('LGLobalLock = nil'); finally GlobalUnlock(LClipboardData); // unlock it... end; except on E: Exception do Memo1.Lines.Add('Exception: ' + E.Message); end else Memo1.Lines.Add('----- ClipboardData = 0 -----'); finally CloseClipboard; end; end else Memo1.Lines.Add('OpenClipboard = false'); end; Thank you, it's an interesting example. Now I'm trying to use the COleDataSource class, overriding the OnRenderFileData method. It's still theory at the moment, but I'm looking for a way to make it work. Anyway, any ideas are always welcome! PS for example it would be interesting to know on which unit Delphi has the definition of these classes.. Edited May 16, 2023 by gioma Share this post Link to post
Remy Lebeau 1397 Posted May 16, 2023 (edited) 8 hours ago, gioma said: My specific case is as follows: I have two applications, A and B, connected by a socket connection. Application A intercepts the modification of its clipboard and sends the event to application B. Here we have two cases: 1 if it is a text, it copies it directly to the clipboard 2 if it is a file, it copies the list to the clipboard. Up to point 1 there are, while I have difficulty understanding how to pass the list of files to PC B (or rather I can do this phase) and make sure that when I press "paste" (here is the real problem which I'm racking my brain over) the file list transfer begins and the system waits for the files to be transferred before completing the paste command. All the more reason to try the route I suggested. Application A could send its file list to Application B, then B can immediately put CFSTR_FILEDESCRIPTOR and CFSTR_FILECONTENTS items on its local clipboard for each file, having each CFSTR_FILECONTENTS hold an IStream to access its file data. Then, when the user pastes the clipboard somewhere (or an app reads the clipboard directly), the individual CFSTR_FILECONTENTS streams can/will be read from, which can then transfer the actual file data from machine A to B as needed. You could even go as far as having A transfer the file data to B in the background while waiting for a paste to happen, caching the data locally on B, and when CFSTR_FILECONTENTS is read from it can read from the local cache if available, otherwise initiate/wait-for the transfer to finish (even provide the data while it is begin transferred). When the clipboard gets cleared, the IStream's will be released, and their destructors can cancel any transfers in progress and delete any local caches as needed. Quote I can't find half an example of this on the net There are examples/tutorials online about working with CFSTR_FILEDESCRIPTOR/CFSTR_FILECONTENTS, for instance: https://www.codeproject.com/Articles/15576/How-to-drag-a-virtual-file-from-your-app-into-Wind https://www.codeproject.com/Articles/23139/Transferring-Virtual-Files-to-Windows-Explorer-in Quote I can't believe no one has ever done this before, maybe even in C, C++. I'm sure they have, but it is very specialized work, so you are not likely to see it talked about publicly very much. Edited May 16, 2023 by Remy Lebeau 1 Share this post Link to post
gioma 19 Posted May 16, 2023 37 minutes ago, Remy Lebeau said: There are examples/tutorials online about working with CFSTR_FILEDESCRIPTOR/CFSTR_FILECONTENTS, for instance: https://www.codeproject.com/Articles/15576/How-to-drag-a-virtual-file-from-your-app-into-Wind https://www.codeproject.com/Articles/23139/Transferring-Virtual-Files-to-Windows-Explorer-in Thanks for the reply. I had seen the first example and in fact I would like to try to derive the COleDataSource class, I just need to understand in which Delphi unit it is present! 39 minutes ago, Remy Lebeau said: I'm sure they have, but it is very specialized work, so you are not likely to see it talked about publicly very much. Of course I imagined it, but a little something could also be shared .. Share this post Link to post
programmerdelphi2k 237 Posted May 16, 2023 @gioma dont forget: you need verify if the file really exists and then some way block another app to change/delete... Quote var LFileToWork: TFileStream; begin // ... if FileExists('filename_received') then LFileToWork.Create('filename_received', fmOpenRead or fmShareExclusive { or another to avoid any other app changed } ) // ... what to do? 1 Share this post Link to post
gioma 19 Posted May 17, 2023 11 hours ago, programmerdelphi2k said: @gioma dont forget: you need verify if the file really exists and then some way block another app to change/delete... the file does not exist on the filesystem at the time of the paste action because it has to be transferred from another PC. Share this post Link to post
gioma 19 Posted May 17, 2023 (edited) 17 hours ago, Remy Lebeau said: All the more reason to try the route I suggested. Application A could send its file list to Application B, then B can immediately put CFSTR_FILEDESCRIPTOR and CFSTR_FILECONTENTS items on its local clipboard for each file, having each CFSTR_FILECONTENTS hold an IStream to access its file data. Then, when the user pastes the clipboard somewhere (or an app reads the clipboard directly), the individual CFSTR_FILECONTENTS streams can/will be read from, which can then transfer the actual file data from machine A to B as needed. You could even go as far as having A transfer the file data to B in the background while waiting for a paste to happen, caching the data locally on B, and when CFSTR_FILECONTENTS is read from it can read from the local cache if available, otherwise initiate/wait-for the transfer to finish (even provide the data while it is begin transferred). When the clipboard gets cleared, the IStream's will be released, and their destructors can cancel any transfers in progress and delete any local caches as needed. it's not a walk in the park, if you were to do it in c++ it would be easier. With Delphi I can't find the interface wrappers or the structures I should be using. Ex In C++ // Create an OLE data source on the heap COleDataSource* pData = new COleDataSource; // Get the currently selected data HGLOBAL hGlob = GlobalAlloc(GMEM_FIXED, 64); strcpy_s((char*)hGlob, 64, "Current selection\r\n"); // For the appropriate data formats... pData->CacheGlobalData( CF_TEXT, hGlob ); // The Clipboard now owns the allocated memory // and will delete this data object // when new data is put on the Clipboard pData->SetClipboard(); In Delphi not Exist class COleDataSource ( Or I haven't found it yet ) COleDataSource has the virtual function OnRenderData that I might need. Edited May 17, 2023 by gioma Share this post Link to post
programmerdelphi2k 237 Posted May 17, 2023 @gioma As I said just above, I would rule out using the Clipboard, and instead adopt a safer route like: PC1_aplicativoA sends the files to PC2 in a specific folder to receive the files (PC2_folderDownloadedFromPC1); PC2_aplicativoB monitors this folder from time to time, to check if a new file has been posted there (you could use a list to control the files already worked on, or even the date/time of the last jobs done, that is, if there are a new file, its creation date and time will be the latest in the folder); if there are new files in the folder (PC2_folderDownloadedFromPC1), then PC2_aplicativoB will be able to carry out the desired actions, and, clean the list or control date/time; as already mentioned, you can test whether the file has already completed its transfer from A to B, just by trying to open the file exclusively. This way, you will always have greater control over what has been processed or not, and you will no longer depend on the data in the Clipboard's memory (which can be easily changed by other applications on the PC2); 1 Share this post Link to post
Lajos Juhász 293 Posted May 17, 2023 4 hours ago, gioma said: In Delphi not Exist class COleDataSource ( Or I haven't found it yet ) I believe in Delphi you should use TDataObject. Share this post Link to post
programmerdelphi2k 237 Posted May 17, 2023 (edited) @gioma you could just check the data that "HDROP(LGlobalLock)" is pointed to, to get the objects this way ---> in message WM_CLIPBOARDUPDATE procedure LGLobalLock := GlobalLock(LClipboardData); try if (LGLobalLock <> nil) then begin LDQFindexFile := 0; // $FFFFFFFF = all; LDQFbufferSizeInChars := MAX_PATH; // LDQFresult := DragQueryFile(HDROP(LGLobalLock), LDQFindexFile, LFilenameBuffer, LDQFbufferSizeInChars); // if LDQFresult > 0 then begin // if is Directory, then use a "FOR" to process each object LItemIndex :=0; // "i" in "FOR" looping LDQFresultProcessEachItem := DragQueryFile(HDROP(LGLobalLock), LItemIndex, LFilenameBuffer, 255); // reviewing objets... // {$WARN SYMBOL_PLATFORM OFF} // remove this warning about platform specific! LIsDirectory := ((FileGetAttr(LFilenameBuffer) and faDirectory) = faDirectory); {$WARN SYMBOL_PLATFORM ON} // if LIsDirectory then Memo1.Lines.Add(string(LFilenameBuffer) + ' is a directory') else Memo1.Lines.Add(string(LFilenameBuffer) + ' is a file'); end else Memo1.Lines.Add('DragQueryFile = 0 = failed!'); end else Memo1.Lines.Add('LGLobalLock = nil'); finally GlobalUnlock(LClipboardData); end; Edited May 17, 2023 by programmerdelphi2k 1 Share this post Link to post
Remy Lebeau 1397 Posted May 17, 2023 (edited) 22 hours ago, gioma said: I had seen the first example and in fact I would like to try to derive the COleDataSource class, I just need to understand in which Delphi unit it is present! 6 hours ago, gioma said: In Delphi not Exist class COleDataSource ( Or I haven't found it yet ) COleDataSource has the virtual function OnRenderData that I might need. COleDataSource is just a Microsoft C++ helper class that implements the IDataObject interface, which can be put on the clipboard using the OleSetClipboard() function. In Delphi, you would have to implement the IDataObject interface manually (it is declared in the Winapi.Ole2 unit), or use a 3rd party implementation, such as from Melanders' Drag&Drop suite (probably via TDropEmptySource+TDataFormatAdapter). Edited May 17, 2023 by Remy Lebeau 1 Share this post Link to post
gioma 19 Posted May 18, 2023 15 hours ago, Remy Lebeau said: COleDataSource is just a Microsoft C++ helper class that implements the IDataObject interface, which can be put on the clipboard using the OleSetClipboard() function. In Delphi, you would have to implement the IDataObject interface manually (it is declared in the Winapi.Ole2 unit), or use a 3rd party implementation, such as from Melanders' Drag&Drop suite (probably via TDropEmptySource+TDataFormatAdapter). Thank you, you gave me a great case study 😊 Share this post Link to post