diff options
| author | George Hazan <george.hazan@gmail.com> | 2023-07-13 12:06:19 +0300 |
|---|---|---|
| committer | George Hazan <george.hazan@gmail.com> | 2023-07-13 12:06:19 +0300 |
| commit | 641cb4d06283b4f76d539092c92cabeb7192ca6e (patch) | |
| tree | d70abee3c06f9d9a7a0859b51babb22be777a3df /src/mir_app | |
| parent | 5ea12e6c6ca67668c8806f9b1ccc091028dc5de5 (diff) | |
StdFile considered useless and returned back to mir_app
Diffstat (limited to 'src/mir_app')
| -rw-r--r-- | src/mir_app/mir_app.vcxproj | 8 | ||||
| -rw-r--r-- | src/mir_app/mir_app.vcxproj.filters | 27 | ||||
| -rw-r--r-- | src/mir_app/res/resource.rc | 166 | ||||
| -rw-r--r-- | src/mir_app/src/clisttray.cpp | 2 | ||||
| -rw-r--r-- | src/mir_app/src/file.cpp | 436 | ||||
| -rw-r--r-- | src/mir_app/src/file.h | 136 | ||||
| -rw-r--r-- | src/mir_app/src/fileexistsdlg.cpp | 340 | ||||
| -rw-r--r-- | src/mir_app/src/fileopts.cpp | 241 | ||||
| -rw-r--r-- | src/mir_app/src/filerecvdlg.cpp | 411 | ||||
| -rw-r--r-- | src/mir_app/src/filesenddlg.cpp | 334 | ||||
| -rw-r--r-- | src/mir_app/src/filexferdlg.cpp | 752 | ||||
| -rw-r--r-- | src/mir_app/src/ftmanager.cpp | 516 | ||||
| -rw-r--r-- | src/mir_app/src/modules.cpp | 2 | ||||
| -rw-r--r-- | src/mir_app/src/newplugins.cpp | 12 |
14 files changed, 3376 insertions, 7 deletions
diff --git a/src/mir_app/mir_app.vcxproj b/src/mir_app/mir_app.vcxproj index 5cb64ca6a3..2610d0ef02 100644 --- a/src/mir_app/mir_app.vcxproj +++ b/src/mir_app/mir_app.vcxproj @@ -77,10 +77,17 @@ <ClCompile Include="src\encrypt.cpp" />
<ClCompile Include="src\enterstring.cpp" />
<ClCompile Include="src\extracticon.cpp" />
+ <ClCompile Include="src\file.cpp" />
+ <ClCompile Include="src\fileexistsdlg.cpp" />
+ <ClCompile Include="src\fileopts.cpp" />
+ <ClCompile Include="src\filerecvdlg.cpp" />
+ <ClCompile Include="src\filesenddlg.cpp" />
+ <ClCompile Include="src\filexferdlg.cpp" />
<ClCompile Include="src\filter.cpp" />
<ClCompile Include="src\findadd.cpp" />
<ClCompile Include="src\FontOptions.cpp" />
<ClCompile Include="src\FontService.cpp" />
+ <ClCompile Include="src\ftmanager.cpp" />
<ClCompile Include="src\headerbar.cpp" />
<ClCompile Include="src\help.cpp" />
<ClCompile Include="src\hotkeys.cpp" />
@@ -167,6 +174,7 @@ <ClInclude Include="src\database.h" />
<ClInclude Include="src\encrypt.h" />
<ClInclude Include="src\extraicons.h" />
+ <ClInclude Include="src\file.h" />
<ClInclude Include="src\filter.h" />
<ClInclude Include="src\findadd.h" />
<ClInclude Include="src\FontService.h" />
diff --git a/src/mir_app/mir_app.vcxproj.filters b/src/mir_app/mir_app.vcxproj.filters index 3a925e5b5a..a73e0dfbd4 100644 --- a/src/mir_app/mir_app.vcxproj.filters +++ b/src/mir_app/mir_app.vcxproj.filters @@ -404,6 +404,27 @@ <ClCompile Include="src\chat_loginfo.cpp">
<Filter>Source Files\Chats</Filter>
</ClCompile>
+ <ClCompile Include="src\file.cpp">
+ <Filter>Source Files\File</Filter>
+ </ClCompile>
+ <ClCompile Include="src\fileexistsdlg.cpp">
+ <Filter>Source Files\File</Filter>
+ </ClCompile>
+ <ClCompile Include="src\fileopts.cpp">
+ <Filter>Source Files\File</Filter>
+ </ClCompile>
+ <ClCompile Include="src\filerecvdlg.cpp">
+ <Filter>Source Files\File</Filter>
+ </ClCompile>
+ <ClCompile Include="src\filesenddlg.cpp">
+ <Filter>Source Files\File</Filter>
+ </ClCompile>
+ <ClCompile Include="src\filexferdlg.cpp">
+ <Filter>Source Files\File</Filter>
+ </ClCompile>
+ <ClCompile Include="src\ftmanager.cpp">
+ <Filter>Source Files\File</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\filter.h">
@@ -466,6 +487,9 @@ <ClInclude Include="src\profilemanager.h">
<Filter>Source Files\Database</Filter>
</ClInclude>
+ <ClInclude Include="src\file.h">
+ <Filter>Source Files\File</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="res\resource.rc">
@@ -510,5 +534,8 @@ <Filter Include="Source Files\Plugins">
<UniqueIdentifier>{2f5b2fe9-25c8-4029-8a52-f34a11f984a7}</UniqueIdentifier>
</Filter>
+ <Filter Include="Source Files\File">
+ <UniqueIdentifier>{bf5fc3bb-4d3d-4237-a43b-94cb18041b25}</UniqueIdentifier>
+ </Filter>
</ItemGroup>
</Project>
\ No newline at end of file diff --git a/src/mir_app/res/resource.rc b/src/mir_app/res/resource.rc index d3f3bccf3c..301cd47f9f 100644 --- a/src/mir_app/res/resource.rc +++ b/src/mir_app/res/resource.rc @@ -999,6 +999,172 @@ BEGIN LTEXT "(Setting timeout to 0 means default setting and -1 means indefinite time)",IDC_STATIC,33,151,207,17
END
+IDD_FILESEND DIALOGEX 0, 0, 256, 177
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Send file(s)"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ EDITTEXT IDC_MSG,6,102,245,46,ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL
+ DEFPUSHBUTTON "&Send",IDOK,67,157,50,15
+ PUSHBUTTON "Cancel",IDCANCEL,140,157,50,15
+ LTEXT "To:",IDC_STATIC,6,23,24,9,SS_CENTERIMAGE
+ CONTROL "",IDC_TO,"Static",SS_SIMPLE | SS_NOPREFIX | WS_GROUP,43,24,159,9
+ LTEXT "File(s):",IDC_STATIC,7,39,30,8
+ EDITTEXT IDC_FILE,38,38,213,31,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY
+ PUSHBUTTON "&Choose again...",IDC_CHOOSE,39,74,77,14
+ RTEXT "Total size:",IDC_STATIC,119,76,68,8
+ CONTROL "",IDC_TOTALSIZE,"Static",SS_SIMPLE | SS_NOPREFIX | WS_GROUP,191,76,58,8
+ LTEXT "Description:",IDC_STATIC,6,93,96,8
+ CONTROL "&User menu",IDC_USERMENU,"MButtonClass",WS_TABSTOP,195,5,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "User &details",IDC_DETAILS,"MButtonClass",WS_TABSTOP,213,5,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&History",IDC_HISTORY,"MButtonClass",WS_TABSTOP,231,5,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ ICON "",IDC_PROTOCOL,5,7,20,20
+ LTEXT "",IDC_NAME,19,7,151,9,SS_NOPREFIX | SS_CENTERIMAGE
+END
+
+IDD_FILERECV DIALOGEX 0, 0, 256, 174
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Incoming file transfer"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "A&ccept",IDOK,68,155,50,14
+ PUSHBUTTON "&Decline",IDCANCEL,138,155,50,14
+ LTEXT "From:",IDC_STATIC,6,20,24,9,SS_CENTERIMAGE
+ CONTROL "",IDC_FROM,"Static",SS_SIMPLE | SS_NOPREFIX | WS_GROUP,39,21,159,9
+ LTEXT "Date:",IDC_STATIC,6,35,28,9,SS_CENTERIMAGE
+ CONTROL "",IDC_DATE,"Static",SS_SIMPLE | SS_NOPREFIX | WS_GROUP,39,34,159,9
+ LTEXT "Files:",IDC_STATIC,6,50,28,8
+ EDITTEXT IDC_FILENAMES,39,50,210,16,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | NOT WS_BORDER
+ LTEXT "Description:",IDC_STATIC,6,69,64,8
+ EDITTEXT IDC_MSG,6,79,243,45,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY
+ LTEXT "Save to:",IDC_STATIC,6,131,34,8
+ PUSHBUTTON "...",IDC_FILEDIRBROWSE,235,130,14,10
+ COMBOBOX IDC_FILEDIR,45,129,187,108,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP
+ CONTROL "&Add",IDC_ADD,"MButtonClass",WS_TABSTOP,177,5,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&User menu",IDC_USERMENU,"MButtonClass",WS_TABSTOP,195,5,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "User &details",IDC_DETAILS,"MButtonClass",WS_TABSTOP,213,5,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&History",IDC_HISTORY,"MButtonClass",WS_TABSTOP,231,5,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ ICON "",IDC_PROTOCOL,5,7,20,20
+ LTEXT "",IDC_NAME,19,7,151,9,SS_NOPREFIX | SS_CENTERIMAGE
+END
+
+IDD_FILETRANSFERINFO DIALOGEX 0, 0, 256, 44
+STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "&User menu",IDC_CONTACT,"MButtonClass",WS_TABSTOP,5,1,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "",IDC_CONTACTNAME,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_WORDELLIPSIS | WS_GROUP,25,1,174,14
+ CONTROL "&Open...",IDC_OPENFILE,"MButtonClass",WS_DISABLED | WS_TABSTOP,203,1,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "Open &folder",IDC_OPENFOLDER,"MButtonClass",WS_TABSTOP,219,1,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&Cancel",IDCANCEL,"MButtonClass",WS_TABSTOP,235,1,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "",IDC_ALLFILESPROGRESS,"msctls_progress32",PBS_SMOOTH | NOT WS_VISIBLE | WS_DISABLED,25,16,190,12
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | WS_GROUP,25,17,190,10
+ ICON "",IDC_FILEICON,25,15,16,14,SS_CENTERIMAGE | SS_REALSIZEIMAGE
+ CONTROL "Transfer completed, open file(s).",IDC_TRANSFERCOMPLETED,
+ "Hyperlink",NOT WS_VISIBLE | WS_TABSTOP,42,17,173,10
+ LTEXT "No data transferred",IDC_ALLTRANSFERRED,25,29,230,14,SS_NOPREFIX | SS_CENTERIMAGE
+ RTEXT "",IDC_ALLSPEED,252,29,3,14,SS_NOPREFIX | SS_CENTERIMAGE
+ LTEXT "",IDC_ALLPRECENTS,218,14,33,14,SS_CENTERIMAGE
+ CONTROL "",IDC_FRAME,"Static",SS_ETCHEDHORZ,1,43,254,1
+END
+
+IDD_FILEEXISTS DIALOGEX 0, 0, 288, 181
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "File already exists"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ PUSHBUTTON "Resume",IDC_RESUME,5,144,65,14
+ PUSHBUTTON "Resume all",IDC_RESUMEALL,5,162,65,14
+ PUSHBUTTON "Overwrite",IDC_OVERWRITE,76,144,65,14
+ PUSHBUTTON "Overwrite all",IDC_OVERWRITEALL,76,162,65,14
+ PUSHBUTTON "Save as...",IDC_SAVEAS,147,144,65,14
+ PUSHBUTTON "Auto rename",IDC_AUTORENAME,147,162,65,14
+ PUSHBUTTON "Skip",IDC_SKIP,218,144,65,14
+ PUSHBUTTON "Cancel transfer",IDCANCEL,218,162,65,14
+ LTEXT "You are about to receive the file",IDC_STATIC,5,5,278,8
+ EDITTEXT IDC_FILENAME,15,16,268,8,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
+ GROUPBOX "Existing file",IDC_STATIC,5,29,278,61
+ ICON "",IDC_EXISTINGICON,14,45,20,20,SS_NOTIFY
+ LTEXT "Size:",IDC_STATIC,40,42,27,8
+ LTEXT "",IDC_EXISTINGSIZE,67,42,35,8
+ RTEXT "Last modified:",IDC_STATIC,103,42,58,8
+ LTEXT "",IDC_EXISTINGDATE,166,42,115,8
+ LTEXT "Type:",IDC_STATIC,40,55,27,8
+ LTEXT "",IDC_EXISTINGTYPE,67,55,214,8
+ PUSHBUTTON "Open file",IDC_OPENFILE,12,70,62,13
+ PUSHBUTTON "Open folder",IDC_OPENFOLDER,82,70,62,13
+ PUSHBUTTON "File properties",IDC_PROPERTIES,201,70,74,13
+ GROUPBOX "File being received",IDC_STATIC,5,95,278,42
+ ICON "",IDC_NEWICON,14,110,20,20,SS_NOTIFY
+ LTEXT "Size:",IDC_STATIC,40,108,27,8
+ LTEXT "",IDC_NEWSIZE,67,108,35,8
+ RTEXT "Last modified:",IDC_STATIC,103,108,58,8
+ LTEXT "",IDC_NEWDATE,166,108,115,8
+ LTEXT "Type:",IDC_STATIC,40,121,27,8
+ LTEXT "",IDC_NEWTYPE,67,121,214,8
+END
+
+IDD_FTMGR DIALOGEX 0, 0, 276, 255
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
+CAPTION "File transfers"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_TABS,"SysTabControl32",WS_TABSTOP,7,7,262,224
+ PUSHBUTTON "Clear completed",IDC_CLEAR,7,234,100,14
+ PUSHBUTTON "Close",IDCANCEL,219,234,50,14
+END
+
+IDD_FTPAGE DIALOGEX 0, 0, 320, 183
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VSCROLL | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT | WS_EX_STATICEDGE
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+END
+
+IDD_OPT_FILETRANSFER DIALOGEX 0, 0, 313, 243
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Receiving files",IDC_STATIC,0,0,313,99
+ LTEXT "Received files folder:",IDC_STATIC,8,15,82,8
+ EDITTEXT IDC_FILEDIR,92,13,190,12,ES_AUTOHSCROLL
+ PUSHBUTTON "...",IDC_FILEDIRBROWSE,287,12,15,13
+ LTEXT "Variables allowed: %userid%, %nick%, %proto%, %miranda_path%, %userprofile%",IDC_STATIC,8,26,294,11,WS_DISABLED
+ CONTROL "Auto-accept incoming files from people on my contact list",IDC_AUTOACCEPT,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,36,294,10
+ CONTROL "Minimize the file transfer window",IDC_AUTOMIN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,17,48,285,10
+ CONTROL "Close window when transfer completes",IDC_AUTOCLOSE,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,60,294,10
+ CONTROL "Clear completed transfers on window closing",IDC_AUTOCLEAR,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,72,294,10
+ CONTROL "Sort file transfers in the reverse order",IDC_REVERSE_ORDER,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,84,286,10
+ GROUPBOX "Virus scanner",IDC_VIRUSSCANNERGROUP,0,100,313,93
+ LTEXT "Scan files:",IDC_STATIC,8,112,43,9,SS_CENTERIMAGE
+ CONTROL "Never, do not use virus scanning",IDC_NOSCANNER,"Button",BS_AUTORADIOBUTTON,52,112,250,10
+ CONTROL "When all files have been downloaded",IDC_SCANAFTERDL,
+ "Button",BS_AUTORADIOBUTTON,52,124,250,10
+ CONTROL "As each file finishes downloading",IDC_SCANDURINGDL,
+ "Button",BS_AUTORADIOBUTTON,52,136,250,10
+ LTEXT "Command line:",IDC_ST_CMDLINE,7,152,62,8
+ COMBOBOX IDC_SCANCMDLINE,70,151,213,71,CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "...",IDC_SCANCMDLINEBROWSE,287,151,15,13
+ LTEXT "%f will be replaced by the file or folder name to be scanned",IDC_ST_CMDLINEHELP,70,165,232,8
+ CONTROL "Warn me before opening a file that has not been scanned",IDC_WARNBEFOREOPENING,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,178,294,10
+ GROUPBOX "If incoming files already exist",IDC_STATIC,0,197,313,41
+ CONTROL "Ask me",IDC_ASK,"Button",BS_AUTORADIOBUTTON,8,210,73,10
+ CONTROL "Resume",IDC_RESUME,"Button",BS_AUTORADIOBUTTON,82,210,125,10
+ CONTROL "Overwrite",IDC_OVERWRITE,"Button",BS_AUTORADIOBUTTON,8,222,73,10
+ CONTROL "Rename (append "" (1)"", etc.)",IDC_RENAME,"Button",BS_AUTORADIOBUTTON,82,222,125,10
+ LTEXT "You will always be asked about files from people not on your contact list",IDC_STATIC,212,208,90,24
+END
+
/////////////////////////////////////////////////////////////////////////////
//
diff --git a/src/mir_app/src/clisttray.cpp b/src/mir_app/src/clisttray.cpp index c10100a1b3..007074cffe 100644 --- a/src/mir_app/src/clisttray.cpp +++ b/src/mir_app/src/clisttray.cpp @@ -27,7 +27,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define TOOLTIP_TOLERANCE 5
-static ITaskbarList3* pTaskbarInterface;
+ITaskbarList3* pTaskbarInterface;
static UINT WM_TASKBARCREATED;
static UINT WM_TASKBARBUTTONCREATED;
diff --git a/src/mir_app/src/file.cpp b/src/mir_app/src/file.cpp new file mode 100644 index 0000000000..c9cc489df4 --- /dev/null +++ b/src/mir_app/src/file.cpp @@ -0,0 +1,436 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org), +Copyright (c) 2000-12 Miranda IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" +#include "file.h" + +HANDLE hDlgSucceeded, hDlgCanceled; + +wchar_t* PFTS_StringToTchar(int flags, const wchar_t* s); +int PFTS_CompareWithTchar(PROTOFILETRANSFERSTATUS* ft, const wchar_t* s, wchar_t *r); + +CMOption<bool> File::bAutoMin(SRFILEMODULE, "AutoMin", false); +CMOption<bool> File::bAutoClear(SRFILEMODULE, "AutoClear", true); +CMOption<bool> File::bAutoClose(SRFILEMODULE, "AutoClose", false); +CMOption<bool> File::bAutoAccept(SRFILEMODULE, "AutoAccept", false); +CMOption<bool> File::bReverseOrder(SRFILEMODULE, "ReverseOrder", false); + +static HGENMENU hSRFileMenuItem; + +static INT_PTR SendFileCommand(WPARAM hContact, LPARAM) +{ + FileSendData fsd; + fsd.hContact = hContact; + fsd.ppFiles = nullptr; + return (INT_PTR)CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FILESEND), NULL, DlgProcSendFile, (LPARAM)&fsd); +} + +static INT_PTR SendSpecificFiles(WPARAM hContact, LPARAM lParam) +{ + FileSendData fsd; + fsd.hContact = hContact; + + char** ppFiles = (char**)lParam; + int count = 0; + while (ppFiles[count] != nullptr) + count++; + + fsd.ppFiles = (const wchar_t**)alloca((count + 1) * sizeof(void*)); + for (int i = 0; i < count; i++) + fsd.ppFiles[i] = mir_a2u(ppFiles[i]); + fsd.ppFiles[count] = nullptr; + + HWND hWnd = CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FILESEND), NULL, DlgProcSendFile, (LPARAM)&fsd); + for (int j = 0; j < count; j++) + mir_free((void*)fsd.ppFiles[j]); + return (INT_PTR)hWnd; +} + +static INT_PTR SendSpecificFilesT(WPARAM hContact, LPARAM lParam) +{ + FileSendData fsd; + fsd.hContact = hContact; + fsd.ppFiles = (const wchar_t**)lParam; + return (INT_PTR)CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FILESEND), NULL, DlgProcSendFile, (LPARAM)&fsd); +} + +static INT_PTR GetReceivedFilesFolder(WPARAM wParam, LPARAM lParam) +{ + wchar_t buf[MAX_PATH]; + GetContactReceivedFilesDir(wParam, buf, MAX_PATH, TRUE); + char *dir = mir_u2a(buf); + mir_strncpy((char *)lParam, dir, MAX_PATH); + mir_free(dir); + return 0; +} + +static INT_PTR GetReceivedFilesFolderW(WPARAM wParam, LPARAM lParam) +{ + wchar_t buf[MAX_PATH]; + GetContactReceivedFilesDir(wParam, buf, MAX_PATH, TRUE); + mir_wstrncpy((wchar_t *)lParam, buf, MAX_PATH); + return 0; +} + +static INT_PTR RecvFileCommand(WPARAM, LPARAM lParam) +{ + LaunchRecvDialog((CLISTEVENT *)lParam); + return 0; +} + +int SRFile_GetRegValue(HKEY hKeyBase, const wchar_t *szSubKey, const wchar_t *szValue, wchar_t *szOutput, int cbOutput) +{ + HKEY hKey; + DWORD cbOut = cbOutput; + + if (RegOpenKeyEx(hKeyBase, szSubKey, 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS) + return 0; + + if (RegQueryValueEx(hKey, szValue, nullptr, nullptr, (uint8_t*)szOutput, &cbOut) != ERROR_SUCCESS) { + RegCloseKey(hKey); + return 0; + } + + RegCloseKey(hKey); + return 1; +} + +void GetSensiblyFormattedSize(__int64 size, wchar_t *szOut, int cchOut, int unitsOverride, int appendUnits, int *unitsUsed) +{ + if (!unitsOverride) { + if (size < 1000) unitsOverride = UNITS_BYTES; + else if (size < 100 * 1024) unitsOverride = UNITS_KBPOINT1; + else if (size < 1024 * 1024) unitsOverride = UNITS_KBPOINT0; + else if (size < 1024 * 1024 * 1024) unitsOverride = UNITS_MBPOINT2; + else unitsOverride = UNITS_GBPOINT3; + } + + if (unitsUsed) + *unitsUsed = unitsOverride; + + switch (unitsOverride) { + case UNITS_BYTES: mir_snwprintf(szOut, cchOut, L"%u%s%s", (int)size, appendUnits ? L" " : L"", appendUnits ? TranslateT("bytes") : L""); break; + case UNITS_KBPOINT1: mir_snwprintf(szOut, cchOut, L"%.1lf%s", size / 1024.0, appendUnits ? L" KB" : L""); break; + case UNITS_KBPOINT0: mir_snwprintf(szOut, cchOut, L"%u%s", (int)(size / 1024), appendUnits ? L" KB" : L""); break; + case UNITS_GBPOINT3: mir_snwprintf(szOut, cchOut, L"%.3f%s", (size >> 20) / 1024.0, appendUnits ? L" GB" : L""); break; + default: mir_snwprintf(szOut, cchOut, L"%.2lf%s", size / 1048576.0, appendUnits ? L" MB" : L""); break; + } +} + +// Tripple redirection sucks but is needed to nullify the array pointer +void FreeFilesMatrix(wchar_t ***files) +{ + if (*files == nullptr) + return; + + // Free each filename in the pointer array + wchar_t **pFile = *files; + while (*pFile != nullptr) { + mir_free(*pFile); + *pFile = nullptr; + pFile++; + } + + // Free the array itself + mir_free(*files); + *files = nullptr; +} + +void FreeProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *fts) +{ + mir_free(fts->szCurrentFile.w); + if (fts->pszFiles.w) { + for (int i = 0; i < fts->totalFiles; i++) mir_free(fts->pszFiles.w[i]); + mir_free(fts->pszFiles.w); + } + mir_free(fts->szWorkingDir.w); +} + +void CopyProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *dest, PROTOFILETRANSFERSTATUS *src) +{ + *dest = *src; + if (src->szCurrentFile.w) dest->szCurrentFile.w = PFTS_StringToTchar(src->flags, src->szCurrentFile.w); + if (src->pszFiles.w) { + dest->pszFiles.w = (wchar_t**)mir_alloc(sizeof(wchar_t*)*src->totalFiles); + for (int i = 0; i < src->totalFiles; i++) + dest->pszFiles.w[i] = PFTS_StringToTchar(src->flags, src->pszFiles.w[i]); + } + if (src->szWorkingDir.w) dest->szWorkingDir.w = PFTS_StringToTchar(src->flags, src->szWorkingDir.w); + dest->flags &= ~PFTS_UTF; + dest->flags |= PFTS_UNICODE; +} + +void UpdateProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *dest, PROTOFILETRANSFERSTATUS *src) +{ + dest->hContact = src->hContact; + dest->flags = src->flags; + if (dest->totalFiles != src->totalFiles) { + for (int i = 0; i < dest->totalFiles; i++) mir_free(dest->pszFiles.w[i]); + mir_free(dest->pszFiles.w); + dest->pszFiles.w = nullptr; + dest->totalFiles = src->totalFiles; + } + if (src->pszFiles.w) { + if (!dest->pszFiles.w) + dest->pszFiles.w = (wchar_t**)mir_calloc(sizeof(wchar_t*)*src->totalFiles); + for (int i = 0; i < src->totalFiles; i++) + if (!dest->pszFiles.w[i] || !src->pszFiles.w[i] || PFTS_CompareWithTchar(src, src->pszFiles.w[i], dest->pszFiles.w[i])) { + mir_free(dest->pszFiles.w[i]); + if (src->pszFiles.w[i]) + dest->pszFiles.w[i] = PFTS_StringToTchar(src->flags, src->pszFiles.w[i]); + else + dest->pszFiles.w[i] = nullptr; + } + } + else if (dest->pszFiles.w) { + for (int i = 0; i < dest->totalFiles; i++) + mir_free(dest->pszFiles.w[i]); + mir_free(dest->pszFiles.w); + dest->pszFiles.w = nullptr; + } + + dest->currentFileNumber = src->currentFileNumber; + dest->totalBytes = src->totalBytes; + dest->totalProgress = src->totalProgress; + if (src->szWorkingDir.w && (!dest->szWorkingDir.w || PFTS_CompareWithTchar(src, src->szWorkingDir.w, dest->szWorkingDir.w))) { + mir_free(dest->szWorkingDir.w); + if (src->szWorkingDir.w) + dest->szWorkingDir.w = PFTS_StringToTchar(src->flags, src->szWorkingDir.w); + else + dest->szWorkingDir.w = nullptr; + } + + if (!dest->szCurrentFile.w || !src->szCurrentFile.w || PFTS_CompareWithTchar(src, src->szCurrentFile.w, dest->szCurrentFile.w)) { + mir_free(dest->szCurrentFile.w); + if (src->szCurrentFile.w) + dest->szCurrentFile.w = PFTS_StringToTchar(src->flags, src->szCurrentFile.w); + else + dest->szCurrentFile.w = nullptr; + } + dest->currentFileSize = src->currentFileSize; + dest->currentFileProgress = src->currentFileProgress; + dest->currentFileTime = src->currentFileTime; + dest->flags &= ~PFTS_UTF; + dest->flags |= PFTS_UNICODE; +} + +static void RemoveUnreadFileEvents(void) +{ + for (auto &hContact : Contacts()) { + MEVENT hDbEvent = db_event_firstUnread(hContact); + while (hDbEvent) { + DBEVENTINFO dbei = {}; + db_event_get(hDbEvent, &dbei); + if (!dbei.markedRead() && dbei.eventType == EVENTTYPE_FILE) + db_event_markRead(hContact, hDbEvent); + hDbEvent = db_event_next(hContact, hDbEvent); + } + } +} + +static int SRFilePreBuildMenu(WPARAM wParam, LPARAM) +{ + bool bEnabled = false; + char *szProto = Proto_GetBaseAccountName(wParam); + if (szProto != nullptr) { + bool isChat = Contact::IsGroupChat(wParam, szProto); + if (CallProtoService(szProto, PS_GETCAPS, isChat ? PFLAGNUM_4 : PFLAGNUM_1, 0) & (isChat ? PF4_GROUPCHATFILES : PF1_FILESEND)) { + if (CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_OFFLINEFILES) + bEnabled = true; + else if (db_get_w(wParam, szProto, "Status", ID_STATUS_OFFLINE) != ID_STATUS_OFFLINE) + bEnabled = true; + } + } + + Menu_ShowItem(hSRFileMenuItem, bEnabled); + return 0; +} + +static int SRFileProtoAck(WPARAM, LPARAM lParam) +{ + ACKDATA *ack = (ACKDATA*)lParam; + if (ack->type == ACKTYPE_FILE) { + int iEvent = 0; + while (auto *cle = Clist_GetEvent(ack->hContact, iEvent++)) + if (cle->lParam == (LPARAM)ack->hProcess) + Clist_RemoveEvent(ack->hContact, cle->hDbEvent); + } + return 0; +} + +static int SRFileModulesLoaded(WPARAM, LPARAM) +{ + CMenuItem mi(&g_plugin); + SET_UID(mi, 0x7f8dcf77, 0xe448, 0x4505, 0xb0, 0x56, 0xb, 0xb1, 0xab, 0xac, 0x64, 0x9d); + mi.position = -2000020000; + mi.hIcolibItem = Skin_GetIconHandle(SKINICON_EVENT_FILE); + mi.name.a = LPGEN("&File"); + mi.pszService = MS_FILE_SENDFILE; + hSRFileMenuItem = Menu_AddContactMenuItem(&mi); + + RemoveUnreadFileEvents(); + return 0; +} + +INT_PTR FtMgrShowCommand(WPARAM, LPARAM) +{ + FtMgr_Show(true, true); + return 0; +} + +INT_PTR openContRecDir(WPARAM hContact, LPARAM) +{ + wchar_t szContRecDir[MAX_PATH]; + GetContactReceivedFilesDir(hContact, szContRecDir, _countof(szContRecDir), TRUE); + ShellExecute(nullptr, L"open", szContRecDir, nullptr, nullptr, SW_SHOW); + return 0; +} + +INT_PTR openRecDir(WPARAM, LPARAM) +{ + wchar_t szContRecDir[MAX_PATH]; + GetReceivedFilesDir(szContRecDir, _countof(szContRecDir)); + ShellExecute(nullptr, L"open", szContRecDir, nullptr, nullptr, SW_SHOW); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR Proto_RecvFileT(WPARAM, LPARAM lParam) +{ + CCSDATA *ccs = (CCSDATA*)lParam; + PROTORECVFILE* pre = (PROTORECVFILE*)ccs->lParam; + if (pre->fileCount == 0) + return 0; + + DB::EventInfo dbei; + dbei.szModule = Proto_GetBaseAccountName(ccs->hContact); + dbei.timestamp = pre->timestamp; + dbei.szId = pre->szId; + dbei.szUserId = pre->szUserId; + dbei.eventType = EVENTTYPE_FILE; + dbei.flags = DBEF_UTF; + if (pre->dwFlags & PRFF_SENT) + dbei.flags |= DBEF_SENT; + if (pre->dwFlags & PRFF_READ) + dbei.flags |= DBEF_READ; + + if ((pre->dwFlags & PRFF_UNICODE) == PRFF_UNICODE) { + CMStringW wszFiles; + + for (int i = 0; i < pre->fileCount; i++) { + if (i != 0) + wszFiles.AppendChar(','); + wszFiles.Append(pre->files.w[i]); + } + + DB::FILE_BLOB blob(wszFiles, pre->descr.w); + CallProtoService(dbei.szModule, PS_PRECREATE_OFFLINEFILE, WPARAM(&blob), pre->lParam); + blob.write(dbei); + } + else { + bool bUtf = (pre->dwFlags & PRFF_UTF) != 0; + CMStringW wszFiles; + + for (int i = 0; i < pre->fileCount; i++) { + if (i != 0) + wszFiles.AppendChar(','); + + if (bUtf) + wszFiles.Append(Utf2T(pre->files.a[i])); + else + wszFiles.Append(_A2T(pre->files.a[i])); + } + + DB::FILE_BLOB blob(wszFiles, bUtf ? Utf2T(pre->descr.a).get() : _A2T(pre->descr.a)); + CallProtoService(dbei.szModule, PS_PRECREATE_OFFLINEFILE, WPARAM(&blob), pre->lParam); + blob.write(dbei); + } + + bool bShow = (pre->dwFlags & (PRFF_SILENT | PRFF_SENT)) == 0; + MEVENT hdbe = db_event_add(ccs->hContact, &dbei); + + CLISTEVENT cle = {}; + cle.hContact = ccs->hContact; + cle.hDbEvent = hdbe; + cle.lParam = pre->lParam; + + if (bShow && File::bAutoAccept && Contact::OnList(ccs->hContact)) + LaunchRecvDialog(&cle); + else { + Skin_PlaySound("RecvFile"); + + if (bShow) { + wchar_t szTooltip[256]; + mir_snwprintf(szTooltip, TranslateT("File from %s"), Clist_GetContactDisplayName(ccs->hContact)); + cle.szTooltip.w = szTooltip; + + cle.flags |= CLEF_UNICODE; + cle.hIcon = Skin_LoadIcon(SKINICON_EVENT_FILE); + cle.pszService = "SRFile/RecvFile"; + g_clistApi.pfnAddEvent(&cle); + } + } + + return hdbe; +} + +int LoadSendRecvFileModule(void) +{ + CreateServiceFunction("FtMgr/Show", FtMgrShowCommand); + + CMenuItem mi(&g_plugin); + SET_UID(mi, 0x75794ab5, 0x2573, 0x48f4, 0xb4, 0xa0, 0x93, 0xd6, 0xf5, 0xe0, 0xf3, 0x32); + mi.hIcolibItem = Skin_GetIconHandle(SKINICON_EVENT_FILE); + mi.position = 1900000000; + mi.name.a = LPGEN("File &transfers..."); + mi.pszService = "FtMgr/Show"; //MS_PROTO_SHOWFTMGR; + Menu_AddMainMenuItem(&mi); + + HookEvent(ME_SYSTEM_MODULESLOADED, SRFileModulesLoaded); + HookEvent(ME_OPT_INITIALISE, FileOptInitialise); + HookEvent(ME_CLIST_PREBUILDCONTACTMENU, SRFilePreBuildMenu); + HookEvent(ME_PROTO_ACK, SRFileProtoAck); + + hDlgSucceeded = CreateHookableEvent(ME_FILEDLG_SUCCEEDED); + hDlgCanceled = CreateHookableEvent(ME_FILEDLG_CANCELED); + + CreateServiceFunction(MS_PROTO_RECVFILET, Proto_RecvFileT); + + CreateServiceFunction(MS_FILE_SENDFILE, SendFileCommand); + CreateServiceFunction(MS_FILE_SENDSPECIFICFILES, SendSpecificFiles); + CreateServiceFunction(MS_FILE_SENDSPECIFICFILEST, SendSpecificFilesT); + CreateServiceFunction(MS_FILE_GETRECEIVEDFILESFOLDER, GetReceivedFilesFolder); + CreateServiceFunction(MS_FILE_GETRECEIVEDFILESFOLDERW, GetReceivedFilesFolderW); + CreateServiceFunction("SRFile/RecvFile", RecvFileCommand); + + CreateServiceFunction("SRFile/OpenContRecDir", openContRecDir); + CreateServiceFunction("SRFile/OpenRecDir", openRecDir); + + g_plugin.addSound("RecvFile", LPGENW("File"), LPGENW("Incoming")); + g_plugin.addSound("FileDone", LPGENW("File"), LPGENW("Complete")); + g_plugin.addSound("FileFailed", LPGENW("File"), LPGENW("Error")); + g_plugin.addSound("FileDenied", LPGENW("File"), LPGENW("Denied")); + return 0; +} diff --git a/src/mir_app/src/file.h b/src/mir_app/src/file.h new file mode 100644 index 0000000000..a394721b50 --- /dev/null +++ b/src/mir_app/src/file.h @@ -0,0 +1,136 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org), +Copyright (c) 2000-12 Miranda IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#pragma once + +#define SRFILEMODULE "SRFile" + +#define VIRUSSCAN_DISABLE 0 +#define VIRUSSCAN_AFTERDL 1 +#define VIRUSSCAN_DURINGDL 2 + +#define FILERESUME_ASK 0 +//1, 2, 3, 4: resume, overwrite, rename, skip: from proto library +#define FILERESUMEF_ALL 0x80 +#define FILERESUME_RESUMEALL (FILERESUME_RESUME|FILERESUMEF_ALL) +#define FILERESUME_OVERWRITEALL (FILERESUME_OVERWRITE|FILERESUMEF_ALL) +#define FILERESUME_RENAMEALL (FILERESUME_RENAME|FILERESUMEF_ALL) +#define FILERESUME_CANCEL 0xFFFFFFFF + +#define M_FILEEXISTSDLGREPLY (WM_USER+200) +#define M_PRESHUTDOWN (WM_USER+201) + +struct FileSendData +{ + MCONTACT hContact; + const wchar_t **ppFiles; +}; + +#define BYTESRECVEDHISTORYCOUNT 10 //the number of bytes recved is sampled once a second and the last 10 are used to get the transfer speed + +struct FileDlgData : public MZeroedObject +{ + ~FileDlgData(); + + HWND hwndTransfer; + HANDLE fs; + MCONTACT hContact; + MEVENT hDbEvent; + HANDLE hNotifyEvent; + HICON hIcon, hIconFolder; + wchar_t **files; + int send; + int closeIfFileChooseCancelled; + int resumeBehaviour; + int bytesRecvedHistory[BYTESRECVEDHISTORYCOUNT]; + int bytesRecvedHistorySize; + int waitingForAcceptance; + PROTOFILETRANSFERSTATUS transferStatus; + int *fileVirusScanned; + HANDLE hPreshutdownEvent; + uint32_t dwTicks; + + wchar_t szSavePath[MAX_PATH]; + wchar_t szMsg[450], szFilenames[1024]; +}; + +//file.c +#define UNITS_BYTES 1 // 0 <= size<1000: "%d bytes" +#define UNITS_KBPOINT1 2 // 1000 <= size<100*1024: "%.1f KB" +#define UNITS_KBPOINT0 3 // 100*1024 <= size<1024*1024: "%d KB" +#define UNITS_MBPOINT2 4 // 1024*1024 <= size: "%.2f MB" +#define UNITS_GBPOINT3 5 // 1024*1024*1024 <= size: "%.3f GB" + +void GetSensiblyFormattedSize(__int64 size, wchar_t *szOut, int cchOut, int unitsOverride, int appendUnits, int *unitsUsed); +void FreeFilesMatrix(wchar_t ***files); //loving that triple indirection +void FreeProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *fts); +void CopyProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *dest, PROTOFILETRANSFERSTATUS *src); +void UpdateProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *dest, PROTOFILETRANSFERSTATUS *src); +int SRFile_GetRegValue(HKEY hKeyBase, const wchar_t *szSubKey, const wchar_t *szValue, wchar_t *szOutput, int cbOutput); + +// filesenddlg.c +INT_PTR CALLBACK DlgProcSendFile(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +// filerecv.c +void LaunchRecvDialog(CLISTEVENT *cle); +void RemoveInvalidFilenameChars(wchar_t *tszString); +void RemoveInvalidPathChars(wchar_t *tszString); +void GetContactReceivedFilesDir(MCONTACT hContact, wchar_t *szDir, int cchDir, BOOL substVars); +void GetReceivedFilesDir(wchar_t *szDir, int cchDir); +int BrowseForFolder(HWND hwnd, wchar_t *szPath); + +// fileexistsdlg.c +struct TDlgProcFileExistsParam +{ + HWND hwndParent; + PROTOFILETRANSFERSTATUS *fts; +}; +INT_PTR CALLBACK DlgProcFileExists(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +// filexferdlg.c +INT_PTR CALLBACK DlgProcFileTransfer(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +// fileopts.c +int FileOptInitialise(WPARAM wParam, LPARAM lParam); + +// ftmanager.c +#define WM_FT_ADD (WM_USER+701) +#define WM_FT_RESIZE (WM_USER+702) +#define WM_FT_REMOVE (WM_USER+703) +#define WM_FT_SELECTPAGE (WM_USER+704) +#define WM_FT_CLEANUP (WM_USER+705) +#define WM_FT_COMPLETED (WM_USER+706) + +#define HM_RECVEVENT (WM_USER+10) + +HWND FtMgr_Show(bool bForceActivate, bool bFromMenu); +void FtMgr_Destroy(); +void FtMgr_AddTransfer(FileDlgData *dat); + +extern HANDLE hDlgSucceeded, hDlgCanceled; + +namespace File +{ + extern CMOption<bool> bAutoMin, bAutoClear, bAutoClose, bAutoAccept, bReverseOrder; +}; diff --git a/src/mir_app/src/fileexistsdlg.cpp b/src/mir_app/src/fileexistsdlg.cpp new file mode 100644 index 0000000000..d75e2f6eae --- /dev/null +++ b/src/mir_app/src/fileexistsdlg.cpp @@ -0,0 +1,340 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org), +Copyright (c) 2000-12 Miranda IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include "stdafx.h" +#include "file.h" + +static void SetControlToUnixTime(HWND hwndDlg, UINT idCtrl, time_t unixTime) +{ + LARGE_INTEGER liFiletime; + FILETIME filetime; + SYSTEMTIME st; + char szTime[64], szDate[64], szOutput[128]; + + liFiletime.QuadPart = (11644473600ll + (__int64)unixTime) * 10000000; + filetime.dwHighDateTime = liFiletime.HighPart; + filetime.dwLowDateTime = liFiletime.LowPart; + FileTimeToSystemTime(&filetime, &st); + GetTimeFormatA(LOCALE_USER_DEFAULT, 0, &st, NULL, szTime, _countof(szTime)); + GetDateFormatA(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, szDate, _countof(szDate)); + mir_snprintf(szOutput, "%s %s", szDate, szTime); + SetDlgItemTextA(hwndDlg, idCtrl, szOutput); +} + +#define C_CONTEXTMENU 0 +#define C_PROPERTIES 1 +// not defined in VC++ 6.0 SE +#ifndef CMF_EXTENDEDVERBS +#define CMF_EXTENDEDVERBS 0x00000100 +#endif +static void DoAnnoyingShellCommand(HWND hwnd, const wchar_t *szFilename, int cmd, POINT *ptCursor) +{ + IShellFolder *pDesktopFolder; + if (SHGetDesktopFolder(&pDesktopFolder) == NOERROR) { + ITEMIDLIST *pCurrentIdl; + wchar_t *wszFilename = (LPWSTR)szFilename; + + if (pDesktopFolder->ParseDisplayName(nullptr, nullptr, wszFilename, nullptr, &pCurrentIdl, nullptr) == NOERROR) { + if (pCurrentIdl->mkid.cb) { + ITEMIDLIST *pidl, *pidlNext, *pidlFilename; + IShellFolder *pFileFolder; + + for (pidl = pCurrentIdl;;) { + pidlNext = (ITEMIDLIST *)((uint8_t*)pidl + pidl->mkid.cb); + if (pidlNext->mkid.cb == 0) { + pidlFilename = (ITEMIDLIST *)CoTaskMemAlloc(pidl->mkid.cb + sizeof(pidl->mkid.cb)); + memcpy(pidlFilename, pidl, pidl->mkid.cb + sizeof(pidl->mkid.cb)); + pidl->mkid.cb = 0; + break; + } + pidl = pidlNext; + } + if (pDesktopFolder->BindToObject(pCurrentIdl, nullptr, IID_IShellFolder, (void **)&pFileFolder) == NOERROR) { + IContextMenu *pContextMenu; + if (pFileFolder->GetUIObjectOf(nullptr, 1, (LPCITEMIDLIST *)&pidlFilename, IID_IContextMenu, nullptr, (void **)&pContextMenu) == NOERROR) { + switch (cmd) { + case C_PROPERTIES: + { + CMINVOKECOMMANDINFO ici = { 0 }; + ici.cbSize = sizeof(ici); + ici.hwnd = hwnd; + ici.lpVerb = "properties"; + ici.nShow = SW_SHOW; + pContextMenu->InvokeCommand(&ici); + } + break; + + case C_CONTEXTMENU: + HMENU hMenu = CreatePopupMenu(); + if (SUCCEEDED(pContextMenu->QueryContextMenu(hMenu, 0, 1000, 65535, (GetKeyState(VK_SHIFT) & 0x8000 ? CMF_EXTENDEDVERBS : 0) | CMF_NORMAL))) { + int ret = TrackPopupMenu(hMenu, TPM_RETURNCMD, ptCursor->x, ptCursor->y, 0, hwnd, nullptr); + if (ret) { + CMINVOKECOMMANDINFO ici = { 0 }; + ici.cbSize = sizeof(ici); + ici.hwnd = hwnd; + ici.lpVerb = MAKEINTRESOURCEA(ret - 1000); + ici.nShow = SW_SHOW; + pContextMenu->InvokeCommand(&ici); + } + } + DestroyMenu(hMenu); + break; + } + pContextMenu->Release(); + } + pFileFolder->Release(); + } + CoTaskMemFree(pidlFilename); + } + CoTaskMemFree(pCurrentIdl); + } + pDesktopFolder->Release(); + } +} + +static LRESULT CALLBACK IconCtrlSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + PROTOFILETRANSFERSTATUS *pft = (PROTOFILETRANSFERSTATUS *)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA); + + switch (msg) { + case WM_LBUTTONDBLCLK: + ShellExecute(hwnd, nullptr, pft->szCurrentFile.w, nullptr, nullptr, SW_SHOW); + break; + case WM_RBUTTONUP: + POINT pt; + pt.x = (short)LOWORD(lParam); pt.y = (short)HIWORD(lParam); + ClientToScreen(hwnd, &pt); + DoAnnoyingShellCommand(hwnd, pft->szCurrentFile.w, C_CONTEXTMENU, &pt); + return 0; + } + return mir_callNextSubclass(hwnd, IconCtrlSubclassProc, msg, wParam, lParam); +} + +struct loadiconsstartinfo +{ + HWND hwndDlg; + wchar_t *szFilename; +}; + +void __cdecl LoadIconsAndTypesThread(void *param) +{ + loadiconsstartinfo *info = (loadiconsstartinfo *)param; + SHFILEINFO fileInfo; + + if (SHGetFileInfo(info->szFilename, 0, &fileInfo, sizeof(fileInfo), SHGFI_TYPENAME | SHGFI_ICON | SHGFI_LARGEICON)) { + wchar_t szExtension[64], szIconFile[MAX_PATH]; + + wchar_t *pszFilename = wcsrchr(info->szFilename, '\\'); + if (pszFilename == nullptr) + pszFilename = info->szFilename; + + wchar_t *pszExtension = wcsrchr(pszFilename, '.'); + if (pszExtension) + mir_wstrncpy(szExtension, pszExtension + 1, _countof(szExtension)); + else { + pszExtension = L"."; + szExtension[0] = '\0'; + } + CharUpper(szExtension); + if (fileInfo.szTypeName[0] == '\0') + mir_snwprintf(fileInfo.szTypeName, TranslateT("%s file"), szExtension); + SetDlgItemText(info->hwndDlg, IDC_EXISTINGTYPE, fileInfo.szTypeName); + SetDlgItemText(info->hwndDlg, IDC_NEWTYPE, fileInfo.szTypeName); + SendDlgItemMessage(info->hwndDlg, IDC_EXISTINGICON, STM_SETICON, (WPARAM)fileInfo.hIcon, 0); + szIconFile[0] = '\0'; + if (!mir_wstrcmp(szExtension, L"EXE")) + SRFile_GetRegValue(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Icons", L"2", szIconFile, _countof(szIconFile)); + else { + wchar_t szTypeName[MAX_PATH]; + if (SRFile_GetRegValue(HKEY_CLASSES_ROOT, pszExtension, NULL, szTypeName, _countof(szTypeName))) { + mir_wstrcat(szTypeName, L"\\DefaultIcon"); + if (SRFile_GetRegValue(HKEY_CLASSES_ROOT, szTypeName, NULL, szIconFile, _countof(szIconFile))) { + if (wcsstr(szIconFile, L"%1")) + SRFile_GetRegValue(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Icons", L"0", szIconFile, _countof(szIconFile)); + else szIconFile[0] = '\0'; + } + } + } + + if (szIconFile[0]) { + wchar_t *pszComma = wcsrchr(szIconFile, ','); + int iconIndex; + if (pszComma) { + iconIndex = _wtoi(pszComma + 1); + *pszComma = '\0'; + } + else + iconIndex = 0; + HICON hIcon = ExtractIcon(g_plugin.getInst(), szIconFile, iconIndex); + if (hIcon) + fileInfo.hIcon = hIcon; + } + SendDlgItemMessage(info->hwndDlg, IDC_NEWICON, STM_SETICON, (WPARAM)fileInfo.hIcon, 0); + } + mir_free(info->szFilename); + mir_free(info); +} + +INT_PTR CALLBACK DlgProcFileExists(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + PROTOFILETRANSFERSTATUS *fts = (PROTOFILETRANSFERSTATUS *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { + wchar_t szSize[64]; + struct _stati64 statbuf; + auto *dat = (TDlgProcFileExistsParam *)lParam; + + SetPropA(hwndDlg, "Miranda.Preshutdown", HookEventMessage(ME_SYSTEM_PRESHUTDOWN, hwndDlg, M_PRESHUTDOWN)); + SetPropA(hwndDlg, "Miranda.ParentWnd", dat->hwndParent); + + fts = (PROTOFILETRANSFERSTATUS *)mir_alloc(sizeof(PROTOFILETRANSFERSTATUS)); + CopyProtoFileTransferStatus(fts, dat->fts); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)fts); + SetDlgItemText(hwndDlg, IDC_FILENAME, fts->szCurrentFile.w); + SetControlToUnixTime(hwndDlg, IDC_NEWDATE, fts->currentFileTime); + GetSensiblyFormattedSize(fts->currentFileSize, szSize, _countof(szSize), 0, 1, NULL); + SetDlgItemText(hwndDlg, IDC_NEWSIZE, szSize); + + mir_subclassWindow(GetDlgItem(hwndDlg, IDC_EXISTINGICON), IconCtrlSubclassProc); + + HWND hwndFocus = GetDlgItem(hwndDlg, IDC_RESUME); + if (_wstat64(fts->szCurrentFile.w, &statbuf) == 0) { + SetControlToUnixTime(hwndDlg, IDC_EXISTINGDATE, statbuf.st_mtime); + GetSensiblyFormattedSize(statbuf.st_size, szSize, _countof(szSize), 0, 1, NULL); + SetDlgItemText(hwndDlg, IDC_EXISTINGSIZE, szSize); + if (statbuf.st_size > (int)fts->currentFileSize) { + EnableWindow(GetDlgItem(hwndDlg, IDC_RESUME), FALSE); + hwndFocus = GetDlgItem(hwndDlg, IDC_OVERWRITE); + } + } + + loadiconsstartinfo *lisi = (loadiconsstartinfo *)mir_alloc(sizeof(loadiconsstartinfo)); + lisi->hwndDlg = hwndDlg; + lisi->szFilename = mir_wstrdup(fts->szCurrentFile.w); + //can be a little slow, so why not? + mir_forkthread(LoadIconsAndTypesThread, lisi); + SetFocus(hwndFocus); + SetWindowLongPtr(hwndFocus, GWL_STYLE, GetWindowLongPtr(hwndFocus, GWL_STYLE) | BS_DEFPUSHBUTTON); + } + return FALSE; + + case WM_COMMAND: + { + PROTOFILERESUME pfr = {}; + switch (LOWORD(wParam)) { + case IDC_OPENFILE: + ShellExecute(hwndDlg, NULL, fts->szCurrentFile.w, NULL, NULL, SW_SHOW); + return FALSE; + + case IDC_OPENFOLDER: + { + wchar_t szFile[MAX_PATH]; + mir_wstrncpy(szFile, fts->szCurrentFile.w, _countof(szFile)); + wchar_t *pszLastBackslash = wcsrchr(szFile, '\\'); + if (pszLastBackslash) + *pszLastBackslash = '\0'; + ShellExecute(hwndDlg, NULL, szFile, NULL, NULL, SW_SHOW); + } + return FALSE; + case IDC_PROPERTIES: + DoAnnoyingShellCommand(hwndDlg, fts->szCurrentFile.w, C_PROPERTIES, NULL); + return FALSE; + case IDC_RESUME: + pfr.action = FILERESUME_RESUME; + break; + case IDC_RESUMEALL: + pfr.action = FILERESUME_RESUMEALL; + break; + case IDC_OVERWRITE: + pfr.action = FILERESUME_OVERWRITE; + break; + case IDC_OVERWRITEALL: + pfr.action = FILERESUME_OVERWRITEALL; + break; + + case IDC_AUTORENAME: + pfr.action = FILERESUME_RENAMEALL; + break; + + case IDC_SAVEAS: + wchar_t str[MAX_PATH]; + mir_wstrncpy(str, fts->szCurrentFile.w, _countof(str)); + + wchar_t filter[512]; + mir_snwprintf(filter, L"%s (*)%c*%c", TranslateT("All files"), 0, 0); + { + OPENFILENAME ofn = {}; + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = hwndDlg; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY; + ofn.lpstrFilter = filter; + ofn.lpstrFile = str; + ofn.nMaxFile = _countof(str); + ofn.nMaxFileTitle = MAX_PATH; + if (!GetSaveFileName(&ofn)) + return FALSE; + + pfr.szFilename = mir_wstrdup(str); + pfr.action = FILERESUME_RENAME; + } + break; + + case IDC_SKIP: + pfr.action = FILERESUME_SKIP; + break; + + case IDCANCEL: + pfr.action = FILERESUME_CANCEL; + break; + + default: + return FALSE; + } + + PostMessage((HWND)GetPropA(hwndDlg, "Miranda.ParentWnd"), M_FILEEXISTSDLGREPLY, (WPARAM)mir_wstrdup(fts->szCurrentFile.w), (LPARAM)new PROTOFILERESUME(pfr)); + DestroyWindow(hwndDlg); + } + break; + + case WM_CLOSE: + PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDCANCEL, BN_CLICKED), (LPARAM)GetDlgItem(hwndDlg, IDCANCEL)); + break; + + case M_PRESHUTDOWN: + PostMessage(hwndDlg, WM_CLOSE, 0, 0); + break; + + case WM_DESTROY: + UnhookEvent(GetPropA(hwndDlg, "Miranda.Preshutdown")); // GetProp() will return NULL if it couldnt find anything + RemovePropA(hwndDlg, "Miranda.Preshutdown"); + RemovePropA(hwndDlg, "Miranda.ParentWnd"); + DestroyIcon((HICON)SendDlgItemMessage(hwndDlg, IDC_EXISTINGICON, STM_GETICON, 0, 0)); + DestroyIcon((HICON)SendDlgItemMessage(hwndDlg, IDC_NEWICON, STM_GETICON, 0, 0)); + FreeProtoFileTransferStatus(fts); + mir_free(fts); + break; + } + return FALSE; +} diff --git a/src/mir_app/src/fileopts.cpp b/src/mir_app/src/fileopts.cpp new file mode 100644 index 0000000000..d3a94c5b18 --- /dev/null +++ b/src/mir_app/src/fileopts.cpp @@ -0,0 +1,241 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org), +Copyright (c) 2000-12 Miranda IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include "stdafx.h" +#include "file.h" + +#define VSCAN_MCAFEE 1 +#define VSCAN_DRSOLOMON 2 +#define VSCAN_NORTON 3 +#define VSCAN_CA 4 + +struct { + const wchar_t *szProductName; + const wchar_t *szExeRegPath; + const wchar_t *szExeRegValue; + const wchar_t *szCommandLine; +} +static virusScanners[] = +{ + {L"Network Associates/McAfee VirusScan", L"SOFTWARE\\McAfee\\VirusScan", L"Scan32EXE", L"\"%s\" %%f /nosplash /comp /autoscan /autoexit /noboot"}, + {L"Dr Solomon's VirusScan (Network Associates)", L"SOFTWARE\\Network Associates\\TVD\\VirusScan\\AVConsol\\General", L"szScannerExe", L"\"%s\" %%f /uinone /noboot /comp /prompt /autoexit"}, + {L"Norton AntiVirus", L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Navw32.exe", nullptr, L"\"%s\" %%f /b- /m- /s+ /noresults"}, + {L"Computer Associates/Inoculate IT", L"Software\\Antivirus", L"ImageFilename", L"\"%s\" %%f /display = progress /exit"}, + {L"Computer Associates eTrust", L"SOFTWARE\\ComputerAssociates\\Anti-Virus\\Resident", L"VetPath", L"\"%s\" %%f /display = progress /exit"}, + {L"Kaspersky Anti-Virus", L"SOFTWARE\\KasperskyLab\\Components\\101", L"EXEName", L"\"%s\" /S /Q %%f"}, + {L"Kaspersky Anti-Virus", L"SOFTWARE\\KasperskyLab\\SetupFolders", L"KAV8", L"\"%savp.exe\" SCAN %%f"}, + {L"Kaspersky Anti-Virus", L"SOFTWARE\\KasperskyLab\\SetupFolders", L"KAV9", L"\"%savp.exe\" SCAN %%f"}, + {L"AntiVir PersonalEdition Classic", L"SOFTWARE\\Avira\\AntiVir PersonalEdition Classic", L"Path", L"\"%savscan.exe\" /GUIMODE = 2 /PATH = \"%%f\""}, + {L"ESET NOD32 Antivirus", L"SOFTWARE\\ESET\\ESET Security\\CurrentVersion\\Info", L"InstallDir", L"\"%secls.exe\" /log-all /aind /no-boots /adware /sfx /unsafe /unwanted /heur /adv-heur /action = clean \"%%f\""}, +}; + +#ifndef SHACF_FILESYS_DIRS + #define SHACF_FILESYS_DIRS 0x00000020 +#endif + +class CFileOptsDlg : public CDlgBase +{ + CCtrlButton btnFileDir, btnScanCmdLine; + CCtrlCheck chkAutoMin, chkAutoClear, chkAutoClose, chkAutoAccept, chkReverseOrder; + CCtrlCheck chkNoScanner, chkScanDuringDl, chkScanAfterDl; + CCtrlCombo cmbScanCmdLine; + +public: + CFileOptsDlg() : + CDlgBase(g_plugin, IDD_OPT_FILETRANSFER), + btnFileDir(this, IDC_FILEDIRBROWSE), + btnScanCmdLine(this, IDC_SCANCMDLINEBROWSE), + chkAutoMin(this, IDC_AUTOMIN), + chkAutoClear(this, IDC_AUTOCLEAR), + chkAutoClose(this, IDC_AUTOCLOSE), + chkAutoAccept(this, IDC_AUTOACCEPT), + chkReverseOrder(this, IDC_REVERSE_ORDER), + + chkNoScanner(this, IDC_NOSCANNER), + chkScanAfterDl(this, IDC_SCANAFTERDL), + chkScanDuringDl(this, IDC_SCANDURINGDL), + + cmbScanCmdLine(this, IDC_SCANCMDLINE) + { + CreateLink(chkAutoMin, File::bAutoMin); + CreateLink(chkAutoClear, File::bAutoClear); + CreateLink(chkAutoClose, File::bAutoClose); + CreateLink(chkAutoAccept, File::bAutoAccept); + CreateLink(chkReverseOrder, File::bReverseOrder); + + btnFileDir.OnClick = Callback(this, &CFileOptsDlg::onClick_FileDir); + btnScanCmdLine.OnClick = Callback(this, &CFileOptsDlg::onClick_ScanCmdLine); + + chkNoScanner.OnChange = Callback(this, &CFileOptsDlg::onChange_NoScanner); + chkAutoAccept.OnChange = Callback(this, &CFileOptsDlg::onChange_AutoAccept); + cmbScanCmdLine.OnSelChanged = Callback(this, &CFileOptsDlg::onSelChanged_Combo); + } + + bool OnInitDialog() override + { + SHAutoComplete(GetDlgItem(m_hwnd, IDC_FILEDIR), SHACF_FILESYS_DIRS); + + wchar_t str[MAX_PATH]; + GetContactReceivedFilesDir(NULL, str, _countof(str), FALSE); + SetDlgItemText(m_hwnd, IDC_FILEDIR, str); + + switch (g_plugin.getByte("UseScanner", VIRUSSCAN_DISABLE)) { + case VIRUSSCAN_AFTERDL: chkScanAfterDl.SetState(true); break; + case VIRUSSCAN_DURINGDL: chkScanDuringDl.SetState(true); break; + default: chkNoScanner.SetState(true); break; + } + CheckDlgButton(m_hwnd, IDC_WARNBEFOREOPENING, g_plugin.getByte("WarnBeforeOpening", 1) ? BST_CHECKED : BST_UNCHECKED); + + for (int i = 0; i < _countof(virusScanners); i++) { + wchar_t szScanExe[MAX_PATH]; + if (SRFile_GetRegValue(HKEY_LOCAL_MACHINE, virusScanners[i].szExeRegPath, virusScanners[i].szExeRegValue, szScanExe, _countof(szScanExe))) + cmbScanCmdLine.AddString(virusScanners[i].szProductName, i); + } + + if (!cmbScanCmdLine.GetCount()) + cmbScanCmdLine.AddString(L"", -1); + + DBVARIANT dbv; + if (g_plugin.getWString("ScanCmdLine", &dbv) == 0) { + cmbScanCmdLine.SetText(dbv.pwszVal); + db_free(&dbv); + } + else if (cmbScanCmdLine.GetCount()) { + cmbScanCmdLine.SetCurSel(0); + onSelChanged_Combo(0); + } + + switch (g_plugin.getByte("IfExists", FILERESUME_ASK)) { + case FILERESUME_RESUMEALL: CheckDlgButton(m_hwnd, IDC_RESUME, BST_CHECKED); break; + case FILERESUME_OVERWRITEALL: CheckDlgButton(m_hwnd, IDC_OVERWRITE, BST_CHECKED); break; + case FILERESUME_RENAMEALL: CheckDlgButton(m_hwnd, IDC_RENAME, BST_CHECKED); break; + default: CheckDlgButton(m_hwnd, IDC_ASK, BST_CHECKED); break; + } + + return true; + } + + bool OnApply() override + { + wchar_t str[512]; + GetDlgItemText(m_hwnd, IDC_FILEDIR, str, _countof(str)); + RemoveInvalidPathChars(str); + g_plugin.setWString("RecvFilesDirAdv", str); + + cmbScanCmdLine.GetText(str, _countof(str)); + g_plugin.setWString("ScanCmdLine", str); + + g_plugin.setByte("UseScanner", chkScanAfterDl.GetState() ? VIRUSSCAN_AFTERDL : (chkScanDuringDl.GetState() ? VIRUSSCAN_DURINGDL : VIRUSSCAN_DISABLE)); + g_plugin.setByte("WarnBeforeOpening", (uint8_t)IsDlgButtonChecked(m_hwnd, IDC_WARNBEFOREOPENING)); + g_plugin.setByte("IfExists", (uint8_t)(IsDlgButtonChecked(m_hwnd, IDC_ASK) ? FILERESUME_ASK : + (IsDlgButtonChecked(m_hwnd, IDC_RESUME) ? FILERESUME_RESUMEALL : + (IsDlgButtonChecked(m_hwnd, IDC_OVERWRITE) ? FILERESUME_OVERWRITEALL : FILERESUME_RENAMEALL)))); + return TRUE; + } + + void onChange_AutoAccept(CCtrlCheck *) + { + chkAutoMin.Enable(chkAutoAccept.GetState()); + } + + void onChange_NoScanner(CCtrlCheck *) + { + bool bEnabled = chkNoScanner.GetState(); + btnScanCmdLine.Enable(bEnabled); + cmbScanCmdLine.Enable(bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_ST_CMDLINE), bEnabled); + EnableWindow(GetDlgItem(m_hwnd, IDC_ST_CMDLINEHELP), bEnabled); + } + + void onSelChanged_Combo(CCtrlCombo*) + { + int iScanner = cmbScanCmdLine.GetCurData(); + if (iScanner >= _countof(virusScanners) || iScanner < 0) + return; + + wchar_t szScanExe[MAX_PATH], str[512]; + if (SRFile_GetRegValue(HKEY_LOCAL_MACHINE, virusScanners[iScanner].szExeRegPath, virusScanners[iScanner].szExeRegValue, szScanExe, _countof(szScanExe))) + mir_snwprintf(str, virusScanners[iScanner].szCommandLine, szScanExe); + else + str[0] = 0; + cmbScanCmdLine.SetText(str); + } + + void onClick_FileDir(CCtrlButton*) + { + wchar_t str[MAX_PATH]; + GetDlgItemText(m_hwnd, IDC_FILEDIR, str, _countof(str)); + if (BrowseForFolder(m_hwnd, str)) + SetDlgItemText(m_hwnd, IDC_FILEDIR, str); + } + + void onClick_ScanCmdLine(CCtrlButton*) + { + wchar_t str[MAX_PATH + 2]; + cmbScanCmdLine.GetText(str, _countof(str)); + + CMStringW tszFilter; + tszFilter.AppendFormat(L"%s (*.exe)%c*.exe%c", TranslateT("Executable files"), 0, 0); + tszFilter.AppendFormat(L"%s (*)%c*%c", TranslateT("All files"), 0, 0); + + OPENFILENAME ofn = { 0 }; + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = m_hwnd; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_DONTADDTORECENT; + ofn.lpstrFilter = tszFilter; + ofn.lpstrFile = str; + ofn.nMaxFile = _countof(str) - 2; + if (str[0] == '"') { + wchar_t *pszQuote = wcschr(str + 1, '"'); + if (pszQuote) + *pszQuote = 0; + memmove(str, str + 1, (mir_wstrlen(str) * sizeof(wchar_t))); + } + else { + wchar_t *pszSpace = wcschr(str, ' '); + if (pszSpace) *pszSpace = 0; + } + ofn.nMaxFileTitle = MAX_PATH; + if (!GetOpenFileName(&ofn)) + return; + + if (wcschr(str, ' ') != nullptr) { + memmove(str + 1, str, ((_countof(str) - 2) * sizeof(wchar_t))); + str[0] = '"'; + mir_wstrcat(str, L"\""); + } + cmbScanCmdLine.SetText(str); + } +}; + +int FileOptInitialise(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = {}; + odp.position = 900000000; + odp.szTitle.a = LPGEN("File transfers"); + odp.szGroup.a = LPGEN("Events"); + odp.pDialog = new CFileOptsDlg(); + odp.flags = ODPF_BOLDGROUPS; + g_plugin.addOptions(wParam, &odp); + return 0; +} diff --git a/src/mir_app/src/filerecvdlg.cpp b/src/mir_app/src/filerecvdlg.cpp new file mode 100644 index 0000000000..f1b32d9e1e --- /dev/null +++ b/src/mir_app/src/filerecvdlg.cpp @@ -0,0 +1,411 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org), +Copyright (c) 2000-12 Miranda IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" +#include "file.h" + +#define MAX_MRU_DIRS 5 + +static BOOL CALLBACK ClipSiblingsChildEnumProc(HWND hwnd, LPARAM) +{ + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE)|WS_CLIPSIBLINGS); + return TRUE; +} + +static void GetLowestExistingDirName(const wchar_t *szTestDir, wchar_t *szExistingDir, int cchExistingDir) +{ + uint32_t dwAttributes; + wchar_t *pszLastBackslash; + + mir_wstrncpy(szExistingDir, szTestDir, cchExistingDir); + while ((dwAttributes = GetFileAttributes(szExistingDir)) != INVALID_FILE_ATTRIBUTES && !(dwAttributes&FILE_ATTRIBUTE_DIRECTORY)) { + pszLastBackslash = wcsrchr(szExistingDir, '\\'); + if (pszLastBackslash == nullptr) { *szExistingDir = '\0'; break; } + *pszLastBackslash = '\0'; + } + if (szExistingDir[0] == '\0') + GetCurrentDirectory(cchExistingDir, szExistingDir); +} + +static const wchar_t InvalidFilenameChars[] = L"\\/:*?\"<>|"; +void RemoveInvalidFilenameChars(wchar_t *tszString) +{ + size_t i; + if (tszString) { + for (i = wcscspn(tszString, InvalidFilenameChars); tszString[i]; i+=wcscspn(tszString+i+1, InvalidFilenameChars)+1) + tszString[i] = '_'; + } +} + +static const wchar_t InvalidPathChars[] = L"*?\"<>|"; // "\/:" are excluded as they are allowed in file path +void RemoveInvalidPathChars(wchar_t *tszString) +{ + if (tszString) + for (size_t i = wcscspn(tszString, InvalidPathChars); tszString[i]; i += wcscspn(tszString + i + 1, InvalidPathChars) + 1) + tszString[i] = '_'; +} + +static INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) +{ + wchar_t szDir[MAX_PATH]; + switch (uMsg) { + case BFFM_INITIALIZED: + SendMessage(hwnd, BFFM_SETSELECTION, TRUE, pData); + break; + case BFFM_SELCHANGED: + if (SHGetPathFromIDList((LPITEMIDLIST)lp, szDir)) + SendMessage(hwnd, BFFM_SETSTATUSTEXT, 0, (LPARAM)szDir); + break; + } + return 0; +} + +int BrowseForFolder(HWND hwnd, wchar_t *szPath) +{ + BROWSEINFO bi = {}; + bi.hwndOwner = hwnd; + bi.pszDisplayName = szPath; + bi.lpszTitle = TranslateT("Select folder"); + bi.ulFlags = BIF_NEWDIALOGSTYLE|BIF_EDITBOX|BIF_RETURNONLYFSDIRS; // Use this combo instead of BIF_USENEWUI + bi.lpfn = BrowseCallbackProc; + bi.lParam = (LPARAM)szPath; + + LPITEMIDLIST pidlResult = SHBrowseForFolder(&bi); + if (pidlResult) { + SHGetPathFromIDList(pidlResult, szPath); + mir_wstrcat(szPath, L"\\"); + CoTaskMemFree(pidlResult); + } + return pidlResult != nullptr; +} + +static REPLACEVARSARRAY sttVarsToReplace[] = +{ + { "///", "//" }, + { "//", "/" }, + { "()", "" }, + { nullptr, nullptr } +}; + +static void patchDir(wchar_t *str, size_t strSize) +{ + wchar_t *result = Utils_ReplaceVarsW(str, 0, sttVarsToReplace); + if (result) { + wcsncpy(str, result, strSize); + mir_free(result); + } + + size_t len = mir_wstrlen(str); + if (len + 1 < strSize && str[len - 1] != '\\') + mir_wstrcpy(str + len, L"\\"); +} + +void GetContactReceivedFilesDir(MCONTACT hContact, wchar_t *szDir, int cchDir, BOOL patchVars) +{ + wchar_t tszTemp[MAX_PATH]; + + ptrW tszRecvPath(g_plugin.getWStringA("RecvFilesDirAdv")); + if (tszRecvPath) + wcsncpy_s(tszTemp, tszRecvPath, _TRUNCATE); + else + mir_snwprintf(tszTemp, L"%%mydocuments%%\\%s\\%%userid%%", TranslateT("My received files")); + + if (hContact) { + hContact = db_mc_tryMeta(hContact); + + REPLACEVARSARRAY rvaVarsToReplace[4]; + rvaVarsToReplace[0].key.w = L"nick"; + rvaVarsToReplace[0].value.w = mir_wstrdup(Clist_GetContactDisplayName(hContact)); + rvaVarsToReplace[1].key.w = L"userid"; + rvaVarsToReplace[1].value.w = Contact::GetInfo(CNF_UNIQUEID, hContact); + rvaVarsToReplace[2].key.w = L"proto"; + rvaVarsToReplace[2].value.w = mir_a2u(Proto_GetBaseAccountName(hContact)); + rvaVarsToReplace[3].key.w = nullptr; + rvaVarsToReplace[3].value.w = nullptr; + for (int i = 0; i < (_countof(rvaVarsToReplace) - 1); i++) + RemoveInvalidFilenameChars(rvaVarsToReplace[i].value.w); + + wchar_t *result = Utils_ReplaceVarsW(tszTemp, hContact, rvaVarsToReplace); + if (result) { + wcsncpy(tszTemp, result, _countof(tszTemp)); + mir_free(result); + for (int i = 0; i < (_countof(rvaVarsToReplace) - 1); i++) + mir_free(rvaVarsToReplace[i].value.w); + } + } + + if (patchVars) + patchDir(tszTemp, _countof(tszTemp)); + RemoveInvalidPathChars(tszTemp); + mir_wstrncpy(szDir, tszTemp, cchDir); +} + +void GetReceivedFilesDir(wchar_t *szDir, int cchDir) +{ + wchar_t tszTemp[MAX_PATH]; + + ptrW tszRecvPath(g_plugin.getWStringA("RecvFilesDirAdv")); + if (tszRecvPath) + wcsncpy_s(tszTemp, tszRecvPath, _TRUNCATE); + else + mir_snwprintf(tszTemp, L"%%mydocuments%%\\%s\\%%userid%%", TranslateT("My received files")); + + patchDir(tszTemp, _countof(tszTemp)); + RemoveInvalidPathChars(tszTemp); + mir_wstrncpy(szDir, tszTemp, cchDir); +} + +class CRecvFileDlg : public CDlgBase +{ + FileDlgData *dat; + LPARAM m_lParam; + + CCtrlButton btnCancel, btnBrowse; + CCtrlMButton btnAdd, btnUserMenu, btnDetails, btnHistory; + +public: + CRecvFileDlg(CLISTEVENT *cle) : + CDlgBase(g_plugin, IDD_FILERECV), + btnAdd(this, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("Add contact permanently to list")), + btnCancel(this, IDCANCEL), + btnBrowse(this, IDC_FILEDIRBROWSE), + btnDetails(this, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View user's details")), + btnHistory(this, IDC_HISTORY, SKINICON_OTHER_HISTORY, LPGEN("View user's history")), + btnUserMenu(this, IDC_USERMENU, SKINICON_OTHER_DOWNARROW, LPGEN("User menu")) + { + dat = new FileDlgData(); + dat->hContact = cle->hContact; + dat->hDbEvent = cle->hDbEvent; + dat->dwTicks = GetTickCount(); + m_lParam = cle->lParam; + + btnAdd.OnClick = Callback(this, &CRecvFileDlg::onClick_Add); + btnCancel.OnClick = Callback(this, &CRecvFileDlg::onClick_Cancel); + btnBrowse.OnClick = Callback(this, &CRecvFileDlg::onClick_Browse); + btnDetails.OnClick = Callback(this, &CRecvFileDlg::onClick_Details); + btnHistory.OnClick = Callback(this, &CRecvFileDlg::onClick_History); + btnUserMenu.OnClick = Callback(this, &CRecvFileDlg::onClick_UserMenu); + } + + bool OnInitDialog() override + { + char *szProto = Proto_GetBaseAccountName(dat->hContact); + + dat->hNotifyEvent = HookEventMessage(ME_PROTO_ACK, m_hwnd, HM_RECVEVENT); + dat->hPreshutdownEvent = HookEventMessage(ME_SYSTEM_PRESHUTDOWN, m_hwnd, M_PRESHUTDOWN); + + SetWindowLongPtr(m_hwnd, GWLP_USERDATA, (LONG_PTR)dat); + + EnumChildWindows(m_hwnd, ClipSiblingsChildEnumProc, 0); + + Window_SetSkinIcon_IcoLib(m_hwnd, SKINICON_EVENT_FILE); + SendDlgItemMessage(m_hwnd, IDC_PROTOCOL, STM_SETICON, LPARAM(Skin_LoadProtoIcon(szProto, ID_STATUS_ONLINE)), 0); + + wchar_t *contactName = Clist_GetContactDisplayName(dat->hContact); + SetDlgItemText(m_hwnd, IDC_FROM, contactName); + + wchar_t szPath[450]; + GetContactReceivedFilesDir(dat->hContact, szPath, _countof(szPath), TRUE); + SetDlgItemText(m_hwnd, IDC_FILEDIR, szPath); + SHAutoComplete(GetWindow(GetDlgItem(m_hwnd, IDC_FILEDIR), GW_CHILD), 1); + + for (int i = 0; i < MAX_MRU_DIRS; i++) { + char idstr[32]; + mir_snprintf(idstr, "MruDir%d", i); + + DBVARIANT dbv; + if (g_plugin.getWString(idstr, &dbv)) + break; + SendDlgItemMessage(m_hwnd, IDC_FILEDIR, CB_ADDSTRING, 0, (LPARAM)dbv.pwszVal); + db_free(&dbv); + } + + db_event_markRead(dat->hContact, dat->hDbEvent); + + DB::EventInfo dbei(dat->hDbEvent); + if (!dbei) + return false; + + dat->fs = (HANDLE)m_lParam; + + DB::FILE_BLOB blob(dbei); + SetDlgItemText(m_hwnd, IDC_FILENAMES, blob.getName()); + if (mir_wstrlen(blob.getDescr())) + SetDlgItemText(m_hwnd, IDC_MSG, blob.getDescr()); + + wchar_t datetimestr[64]; + TimeZone_PrintTimeStamp(NULL, dbei.timestamp, L"t d", datetimestr, _countof(datetimestr), 0); + SetDlgItemText(m_hwnd, IDC_DATE, datetimestr); + + ptrW info(Contact::GetInfo(CNF_UNIQUEID, dat->hContact)); + SetDlgItemText(m_hwnd, IDC_NAME, (info) ? info : contactName); + + if (!Contact::OnList(dat->hContact)) { + RECT rcBtn1, rcBtn2, rcDateCtrl; + GetWindowRect(GetDlgItem(m_hwnd, IDC_ADD), &rcBtn1); + GetWindowRect(GetDlgItem(m_hwnd, IDC_USERMENU), &rcBtn2); + GetWindowRect(GetDlgItem(m_hwnd, IDC_DATE), &rcDateCtrl); + SetWindowPos(GetDlgItem(m_hwnd, IDC_DATE), 0, 0, 0, rcDateCtrl.right - rcDateCtrl.left - (rcBtn2.left - rcBtn1.left), rcDateCtrl.bottom - rcDateCtrl.top, SWP_NOZORDER | SWP_NOMOVE); + } + else if (File::bAutoAccept) { + //don't check auto-min here to fix BUG#647620 + PostMessage(m_hwnd, WM_COMMAND, MAKEWPARAM(IDOK, BN_CLICKED), (LPARAM)GetDlgItem(m_hwnd, IDOK)); + } + if (Contact::OnList(dat->hContact)) + btnAdd.Hide(); + return true; + } + + bool OnApply() override + { + // most recently used directories + wchar_t szRecvDir[MAX_PATH], szDefaultRecvDir[MAX_PATH]; + GetDlgItemText(m_hwnd, IDC_FILEDIR, szRecvDir, _countof(szRecvDir)); + RemoveInvalidPathChars(szRecvDir); + GetContactReceivedFilesDir(NULL, szDefaultRecvDir, _countof(szDefaultRecvDir), TRUE); + if (wcsnicmp(szRecvDir, szDefaultRecvDir, mir_wstrlen(szDefaultRecvDir))) { + char idstr[32]; + int i; + DBVARIANT dbv; + for (i = MAX_MRU_DIRS - 2; i >= 0; i--) { + mir_snprintf(idstr, "MruDir%d", i); + if (g_plugin.getWString(idstr, &dbv)) continue; + mir_snprintf(idstr, "MruDir%d", i + 1); + g_plugin.setWString(idstr, dbv.pwszVal); + db_free(&dbv); + } + g_plugin.setWString(idstr, szRecvDir); + } + + EnableWindow(GetDlgItem(m_hwnd, IDC_FILENAMES), FALSE); + EnableWindow(GetDlgItem(m_hwnd, IDC_MSG), FALSE); + EnableWindow(GetDlgItem(m_hwnd, IDC_FILEDIR), FALSE); + EnableWindow(GetDlgItem(m_hwnd, IDC_FILEDIRBROWSE), FALSE); + + GetDlgItemText(m_hwnd, IDC_FILEDIR, dat->szSavePath, _countof(dat->szSavePath)); + GetDlgItemText(m_hwnd, IDC_FILE, dat->szFilenames, _countof(dat->szFilenames)); + GetDlgItemText(m_hwnd, IDC_MSG, dat->szMsg, _countof(dat->szMsg)); + FtMgr_AddTransfer(dat); + SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0); + dat = nullptr; + + // check for auto-minimize here to fix BUG#647620 + if (File::bAutoAccept && File::bAutoMin) { + ShowWindow(m_hwnd, SW_HIDE); + ShowWindow(m_hwnd, SW_SHOWMINNOACTIVE); + } + return true; + } + + void OnDestroy() override + { + Window_FreeIcon_IcoLib(m_hwnd); + + delete dat; dat = nullptr; + SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0); + } + + INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override + { + switch (msg) { + case WM_COMMAND: + if (Clist_MenuProcessCommand(LOWORD(wParam), MPCF_CONTACTMENU, dat->hContact)) + return 1; + break; + + case HM_RECVEVENT: + ACKDATA *ack = (ACKDATA *)lParam; + if (ack && dat) { + if (ack->hProcess != dat->fs || ack->type != ACKTYPE_FILE || ack->hContact != dat->hContact) + break; + + if (ack->result == ACKRESULT_DENIED || ack->result == ACKRESULT_FAILED) { + EnableWindow(GetDlgItem(m_hwnd, IDOK), FALSE); + EnableWindow(GetDlgItem(m_hwnd, IDC_FILEDIR), FALSE); + EnableWindow(GetDlgItem(m_hwnd, IDC_FILEDIRBROWSE), FALSE); + SetDlgItemText(m_hwnd, IDC_MSG, TranslateT("This file transfer has been canceled by the other side")); + Skin_PlaySound("FileDenied"); + FlashWindow(m_hwnd, TRUE); + } + else if (ack->result != ACKRESULT_FILERESUME) { + btnCancel.Click(); + } + } + break; + } + + return CDlgBase::DlgProc(msg, wParam, lParam); + } + + void onClick_Browse(CCtrlButton *) + { + wchar_t szDirName[MAX_PATH], szExistingDirName[MAX_PATH]; + + GetDlgItemText(m_hwnd, IDC_FILEDIR, szDirName, _countof(szDirName)); + GetLowestExistingDirName(szDirName, szExistingDirName, _countof(szExistingDirName)); + if (BrowseForFolder(m_hwnd, szExistingDirName)) + SetDlgItemText(m_hwnd, IDC_FILEDIR, szExistingDirName); + } + + void onClick_Cancel(CCtrlButton *) + { + if (dat->fs) { + ProtoChainSend(dat->hContact, PSS_FILEDENY, (WPARAM)dat->fs, (LPARAM)TranslateT("Canceled")); + dat->fs = nullptr; /* the protocol will free the handle */ + } + } + + void onClick_Add(CCtrlButton *) + { + Contact::Add(dat->hContact, m_hwnd); + + if (Contact::OnList(dat->hContact)) + ShowWindow(GetDlgItem(m_hwnd, IDC_ADD), SW_HIDE); + } + + void onClick_UserMenu(CCtrlButton *pButton) + { + RECT rc; + GetWindowRect(pButton->GetHwnd(), &rc); + HMENU hMenu = Menu_BuildContactMenu(dat->hContact); + TrackPopupMenu(hMenu, 0, rc.left, rc.bottom, 0, m_hwnd, NULL); + DestroyMenu(hMenu); + } + + void onClick_Details(CCtrlButton *) + { + CallService(MS_USERINFO_SHOWDIALOG, dat->hContact, 0); + } + + void onClick_History(CCtrlButton *) + { + CallService(MS_HISTORY_SHOWCONTACTHISTORY, dat->hContact, 0); + } +}; + +void LaunchRecvDialog(CLISTEVENT *cle) +{ + auto *pDlg = new CRecvFileDlg(cle); + pDlg->Show(); +} diff --git a/src/mir_app/src/filesenddlg.cpp b/src/mir_app/src/filesenddlg.cpp new file mode 100644 index 0000000000..1c1a1af606 --- /dev/null +++ b/src/mir_app/src/filesenddlg.cpp @@ -0,0 +1,334 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org), +Copyright (c) 2000-12 Miranda IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" +#include <sys/types.h> +#include <sys/stat.h> +#include "file.h" + +static void SetFileListAndSizeControls(HWND hwndDlg, FileDlgData *dat) +{ + int fileCount = 0, dirCount = 0, i; + __int64 totalSize = 0; + struct _stati64 statbuf; + wchar_t str[64]; + + for (i = 0; dat->files[i]; i++) { + if (_wstat64(dat->files[i], &statbuf) == 0) { + if (statbuf.st_mode & _S_IFDIR) + dirCount++; + else + fileCount++; + totalSize += statbuf.st_size; + } + } + + GetSensiblyFormattedSize(totalSize, str, _countof(str), 0, 1, NULL); + SetDlgItemText(hwndDlg, IDC_TOTALSIZE, str); + if (i > 1) { + wchar_t szFormat[32]; + if (fileCount && dirCount) { + mir_snwprintf(szFormat, L"%s, %s", TranslateW(fileCount == 1 ? L"%d file" : L"%d files"), TranslateW(dirCount == 1 ? L"%d directory" : L"%d directories")); + mir_snwprintf(str, szFormat, fileCount, dirCount); + } + else if (fileCount) { + mir_wstrcpy(szFormat, TranslateT("%d files")); + mir_snwprintf(str, szFormat, fileCount); + } + else { + mir_wstrcpy(szFormat, TranslateT("%d directories")); + mir_snwprintf(str, szFormat, dirCount); + } + SetDlgItemText(hwndDlg, IDC_FILE, str); + } + else SetDlgItemText(hwndDlg, IDC_FILE, dat->files[0]); + + EnableWindow(GetDlgItem(hwndDlg, IDOK), fileCount || dirCount); +} + +static void FilenameToFileList(HWND hwndDlg, FileDlgData *dat, const wchar_t *buf) +{ + // Make sure that the file matrix is empty (the user may select files several times) + FreeFilesMatrix(&dat->files); + + // Get the file attributes of selection + uint32_t dwFileAttributes = GetFileAttributes(buf); + if (dwFileAttributes == INVALID_FILE_ATTRIBUTES) + return; + + // Check if the selection is a directory or a file + if (GetFileAttributes(buf) & FILE_ATTRIBUTE_DIRECTORY) { + const wchar_t *pBuf; + int nNumberOfFiles = 0; + int nTemp; + + // :NOTE: The first string in the buffer is the directory, followed by a + // NULL separated list of all files + + // fileOffset is the offset to the first file. + size_t fileOffset = mir_wstrlen(buf) + 1; + + // Count number of files + pBuf = buf + fileOffset; + while (*pBuf) { + pBuf += mir_wstrlen(pBuf) + 1; + nNumberOfFiles++; + } + + // Allocate memory for a pointer array + if ((dat->files = (wchar_t**)mir_alloc((nNumberOfFiles + 1) * sizeof(wchar_t*))) == nullptr) + return; + + // Fill the array + pBuf = buf + fileOffset; + nTemp = 0; + while (*pBuf) { + // Allocate space for path+filename + size_t cbFileNameLen = mir_wstrlen(pBuf); + dat->files[nTemp] = (wchar_t*)mir_alloc(sizeof(wchar_t)*(fileOffset + cbFileNameLen + 1)); + + // Add path to filename and copy into array + memcpy(dat->files[nTemp], buf, (fileOffset - 1) * sizeof(wchar_t)); + dat->files[nTemp][fileOffset - 1] = '\\'; + mir_wstrcpy(dat->files[nTemp] + fileOffset - (buf[fileOffset - 2] == '\\' ? 1 : 0), pBuf); + + // Move pointers to next file... + pBuf += cbFileNameLen + 1; + nTemp++; + } + // Terminate array + dat->files[nNumberOfFiles] = nullptr; + } + // ...the selection is a single file + else { + if ((dat->files = (wchar_t **)mir_alloc(2 * sizeof(wchar_t*))) == nullptr) // Leaks when aborted + return; + + dat->files[0] = mir_wstrdup(buf); + dat->files[1] = nullptr; + } + + // Update dialog text with new file selection + SetFileListAndSizeControls(hwndDlg, dat); +} + +#define M_FILECHOOSEDONE (WM_USER+100) +void __cdecl ChooseFilesThread(void *param) +{ + HWND hwndDlg = (HWND)param; + FileDlgData *dat = (FileDlgData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + wchar_t *buf = (wchar_t *)mir_alloc(sizeof(wchar_t) * 32767); + if (buf == nullptr) { + PostMessage(hwndDlg, M_FILECHOOSEDONE, 0, NULL); + return; + } + + wchar_t filter[128]; + mir_wstrcpy(filter, TranslateT("All files")); + mir_wstrcat(filter, L" (*)"); + wchar_t *pfilter = filter + mir_wstrlen(filter) + 1; + mir_wstrcpy(pfilter, L"*"); + pfilter = filter + mir_wstrlen(filter) + 1; + pfilter[0] = '\0'; + + OPENFILENAME ofn = { 0 }; + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = hwndDlg; + ofn.lpstrFilter = filter; + ofn.lpstrFile = buf; *buf = 0; + ofn.nMaxFile = 32767; + ofn.Flags = OFN_NOCHANGEDIR | OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_DONTADDTORECENT; + + char *szProto = Proto_GetBaseAccountName(dat->hContact); + if (!(CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_SINGLEFILEONLY)) + ofn.Flags |= OFN_ALLOWMULTISELECT; + + if (GetOpenFileName(&ofn)) + PostMessage(hwndDlg, M_FILECHOOSEDONE, 0, (LPARAM)buf); + else { + mir_free(buf); + PostMessage(hwndDlg, M_FILECHOOSEDONE, 0, NULL); + } +} + +static BOOL CALLBACK ClipSiblingsChildEnumProc(HWND hwnd, LPARAM) +{ + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | WS_CLIPSIBLINGS); + return TRUE; +} + +static LRESULT CALLBACK SendEditSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_CHAR: + if (wParam == '\n' && GetKeyState(VK_CONTROL) & 0x8000) { + PostMessage(GetParent(hwnd), WM_COMMAND, IDOK, 0); + return 0; + } + break; + case WM_SYSCHAR: + if ((wParam == 's' || wParam == 'S') && GetKeyState(VK_MENU) & 0x8000) { + PostMessage(GetParent(hwnd), WM_COMMAND, IDOK, 0); + return 0; + } + break; + } + return mir_callNextSubclass(hwnd, SendEditSubclassProc, msg, wParam, lParam); +} + +INT_PTR CALLBACK DlgProcSendFile(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + FileDlgData *dat = (FileDlgData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { + FileSendData *fsd = (FileSendData *)lParam; + + dat = new FileDlgData(); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + dat->hContact = fsd->hContact; + dat->send = 1; + dat->hPreshutdownEvent = HookEventMessage(ME_SYSTEM_PRESHUTDOWN, hwndDlg, M_PRESHUTDOWN); + dat->fs = nullptr; + dat->dwTicks = GetTickCount(); + + EnumChildWindows(hwndDlg, ClipSiblingsChildEnumProc, 0); + mir_subclassWindow(GetDlgItem(hwndDlg, IDC_MSG), SendEditSubclassProc); + + Window_SetSkinIcon_IcoLib(hwndDlg, SKINICON_EVENT_FILE); + Button_SetSkin_IcoLib(hwndDlg, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View user's details")); + Button_SetSkin_IcoLib(hwndDlg, IDC_HISTORY, SKINICON_OTHER_HISTORY, LPGEN("View user's history")); + Button_SetSkin_IcoLib(hwndDlg, IDC_USERMENU, SKINICON_OTHER_DOWNARROW, LPGEN("User menu")); + + char *szProto = Proto_GetBaseAccountName(dat->hContact); + SendDlgItemMessage(hwndDlg, IDC_PROTOCOL, STM_SETICON, LPARAM(Skin_LoadProtoIcon(szProto, ID_STATUS_ONLINE)), 0); + + EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE); + + if (fsd->ppFiles != nullptr && fsd->ppFiles[0] != nullptr) { + int totalCount, i; + for (totalCount = 0; fsd->ppFiles[totalCount]; totalCount++); + dat->files = (wchar_t **)mir_alloc(sizeof(wchar_t *) * (totalCount + 1)); // Leaks + for (i = 0; i < totalCount; i++) + dat->files[i] = mir_wstrdup(fsd->ppFiles[i]); + dat->files[totalCount] = nullptr; + SetFileListAndSizeControls(hwndDlg, dat); + } + + wchar_t *contactName = Clist_GetContactDisplayName(dat->hContact); + SetDlgItemText(hwndDlg, IDC_TO, contactName); + + ptrW id(Contact::GetInfo(CNF_UNIQUEID, dat->hContact)); + SetDlgItemText(hwndDlg, IDC_NAME, (id) ? id : contactName); + + if (fsd->ppFiles == nullptr) { + EnableWindow(hwndDlg, FALSE); + dat->closeIfFileChooseCancelled = 1; + PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_CHOOSE, BN_CLICKED), (LPARAM)GetDlgItem(hwndDlg, IDC_CHOOSE)); + } + } + return TRUE; + + case WM_MEASUREITEM: + return Menu_MeasureItem(lParam); + + case WM_DRAWITEM: + return Menu_DrawItem(lParam); + + case M_FILECHOOSEDONE: + if (lParam != 0) { + FilenameToFileList(hwndDlg, dat, (wchar_t *)lParam); + mir_free((wchar_t *)lParam); + dat->closeIfFileChooseCancelled = 0; + } + else if (dat->closeIfFileChooseCancelled) + PostMessage(hwndDlg, WM_COMMAND, IDCANCEL, 0); + + EnableWindow(hwndDlg, TRUE); + break; + + case WM_COMMAND: + if (Clist_MenuProcessCommand(LOWORD(wParam), MPCF_CONTACTMENU, dat->hContact)) + break; + + switch (LOWORD(wParam)) { + case IDC_CHOOSE: + EnableWindow(hwndDlg, FALSE); + mir_forkthread(ChooseFilesThread, hwndDlg); + break; + + case IDOK: + NotifyEventHooks(hDlgSucceeded, dat->hContact, (LPARAM)hwndDlg); + + EnableWindow(GetDlgItem(hwndDlg, IDC_FILENAME), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_MSG), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_CHOOSE), FALSE); + + GetDlgItemText(hwndDlg, IDC_FILEDIR, dat->szSavePath, _countof(dat->szSavePath)); + GetDlgItemText(hwndDlg, IDC_FILE, dat->szFilenames, _countof(dat->szFilenames)); + GetDlgItemText(hwndDlg, IDC_MSG, dat->szMsg, _countof(dat->szMsg)); + FtMgr_AddTransfer(dat); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + DestroyWindow(hwndDlg); + return TRUE; + + case IDCANCEL: + NotifyEventHooks(hDlgCanceled, dat->hContact, (LPARAM)hwndDlg); + DestroyWindow(hwndDlg); + return TRUE; + + case IDC_USERMENU: + { + RECT rc; + GetWindowRect((HWND)lParam, &rc); + HMENU hMenu = Menu_BuildContactMenu(dat->hContact); + TrackPopupMenu(hMenu, 0, rc.left, rc.bottom, 0, hwndDlg, NULL); + DestroyMenu(hMenu); + } + break; + + case IDC_DETAILS: + CallService(MS_USERINFO_SHOWDIALOG, (WPARAM)dat->hContact, 0); + return TRUE; + + case IDC_HISTORY: + CallService(MS_HISTORY_SHOWCONTACTHISTORY, (WPARAM)dat->hContact, 0); + return TRUE; + } + break; + + case WM_DESTROY: + Window_FreeIcon_IcoLib(hwndDlg); + Button_FreeIcon_IcoLib(hwndDlg, IDC_DETAILS); + Button_FreeIcon_IcoLib(hwndDlg, IDC_HISTORY); + Button_FreeIcon_IcoLib(hwndDlg, IDC_USERMENU); + + delete dat; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + return TRUE; + } + return FALSE; +} diff --git a/src/mir_app/src/filexferdlg.cpp b/src/mir_app/src/filexferdlg.cpp new file mode 100644 index 0000000000..5743c9279e --- /dev/null +++ b/src/mir_app/src/filexferdlg.cpp @@ -0,0 +1,752 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org), +Copyright (c) 2000-12 Miranda IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" +#include <io.h> +#include "file.h" + +static int CheckVirusScanned(HWND hwnd, FileDlgData *dat, int i) +{ + if (dat->send) return 1; + if (dat->fileVirusScanned == nullptr) return 0; + if (dat->fileVirusScanned[i]) return 1; + if (g_plugin.getByte("WarnBeforeOpening", 1) == 0) return 1; + + return IDYES == MessageBox(hwnd, + TranslateT("This file has not yet been scanned for viruses. Are you certain you want to open it?"), + TranslateT("File received"), + MB_YESNO | MB_DEFBUTTON2); +} + +#define M_VIRUSSCANDONE (WM_USER+100) +struct virusscanthreadstartinfo +{ + wchar_t *szFile; + int returnCode; + HWND hwndReply; +}; + +wchar_t* PFTS_StringToTchar(int flags, const wchar_t *s) +{ + if (flags & PFTS_UTF) + return mir_utf8decodeW((char*)s); + if (flags & PFTS_UNICODE) + return mir_wstrdup(s); + return mir_a2u((char*)s); +} + +int PFTS_CompareWithTchar(PROTOFILETRANSFERSTATUS *ft, const wchar_t *s, wchar_t *r) +{ + if (ft->flags & PFTS_UTF) { + wchar_t *ts = mir_utf8decodeW((char*)s); + int res = mir_wstrcmp(ts, r); + mir_free(ts); + return res; + } + if (ft->flags & PFTS_UNICODE) + return mir_wstrcmp(s, r); + + wchar_t *ts = mir_a2u((char*)s); + int res = mir_wstrcmp(ts, r); + mir_free(ts); + return res; +} + +static void SetOpenFileButtonStyle(HWND hwndButton, int enabled) +{ + EnableWindow(hwndButton, enabled); +} + +static void __cdecl RunVirusScannerThread(virusscanthreadstartinfo *info) +{ + DBVARIANT dbv; + if (!g_plugin.getWString("ScanCmdLine", &dbv)) { + if (dbv.pwszVal[0]) { + STARTUPINFO si = { 0 }; + si.cb = sizeof(si); + wchar_t *pszReplace = wcsstr(dbv.pwszVal, L"%f"); + wchar_t szCmdLine[768]; + if (pszReplace) { + if (info->szFile[mir_wstrlen(info->szFile) - 1] == '\\') + info->szFile[mir_wstrlen(info->szFile) - 1] = '\0'; + *pszReplace = 0; + mir_snwprintf(szCmdLine, L"%s\"%s\"%s", dbv.pwszVal, info->szFile, pszReplace + 2); + } + else + wcsncpy_s(szCmdLine, dbv.pwszVal, _TRUNCATE); + + PROCESS_INFORMATION pi; + if (CreateProcess(nullptr, szCmdLine, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) { + if (WaitForSingleObject(pi.hProcess, 3600 * 1000) == WAIT_OBJECT_0) + PostMessage(info->hwndReply, M_VIRUSSCANDONE, info->returnCode, 0); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + } + db_free(&dbv); + } + mir_free(info->szFile); + mir_free(info); +} + +static void SetFilenameControls(HWND hwndDlg, FileDlgData *dat, PROTOFILETRANSFERSTATUS *fts) +{ + wchar_t msg[MAX_PATH]; + wchar_t *fnbuf = nullptr, *fn = nullptr; + SHFILEINFO shfi = {}; + + if (fts->szCurrentFile.w) { + fnbuf = mir_wstrdup(fts->szCurrentFile.w); + if ((fn = wcsrchr(fnbuf, '\\')) == nullptr) + fn = fnbuf; + else fn++; + } + + if (dat->hIcon) DestroyIcon(dat->hIcon); dat->hIcon = nullptr; + + if (fn && (fts->totalFiles > 1)) { + mir_snwprintf(msg, L"%s: %s (%d %s %d)", Clist_GetContactDisplayName(fts->hContact), fn, fts->currentFileNumber + 1, TranslateT("of"), fts->totalFiles); + + SHGetFileInfo(fn, FILE_ATTRIBUTE_DIRECTORY, &shfi, sizeof(shfi), SHGFI_USEFILEATTRIBUTES | SHGFI_ICON | SHGFI_SMALLICON); + dat->hIcon = shfi.hIcon; + } + else if (fn) { + mir_snwprintf(msg, L"%s: %s", Clist_GetContactDisplayName(fts->hContact), fn); + + SHGetFileInfo(fn, FILE_ATTRIBUTE_NORMAL, &shfi, sizeof(shfi), SHGFI_USEFILEATTRIBUTES | SHGFI_ICON | SHGFI_SMALLICON); + dat->hIcon = shfi.hIcon; + } + else { + mir_wstrncpy(msg, Clist_GetContactDisplayName(fts->hContact), _countof(msg)); + HICON hIcon = Skin_LoadIcon(SKINICON_OTHER_DOWNARROW); + dat->hIcon = CopyIcon(hIcon); + IcoLib_ReleaseIcon(hIcon, NULL); + } + + mir_free(fnbuf); + + SendDlgItemMessage(hwndDlg, IDC_FILEICON, STM_SETIMAGE, IMAGE_ICON, (LPARAM)dat->hIcon); + SetDlgItemText(hwndDlg, IDC_CONTACTNAME, msg); +} + +enum { FTS_TEXT, FTS_PROGRESS, FTS_OPEN }; +static void SetFtStatus(HWND hwndDlg, wchar_t *text, int mode) +{ + SetDlgItemText(hwndDlg, IDC_STATUS, TranslateW(text)); + SetDlgItemText(hwndDlg, IDC_TRANSFERCOMPLETED, TranslateW(text)); + + ShowWindow(GetDlgItem(hwndDlg, IDC_STATUS), (mode == FTS_TEXT) ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_ALLFILESPROGRESS), (mode == FTS_PROGRESS) ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_TRANSFERCOMPLETED), (mode == FTS_OPEN) ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_FILEICON), (mode == FTS_OPEN) ? SW_SHOW : SW_HIDE); +} + +static void HideProgressControls(HWND hwndDlg) +{ + RECT rc; + char buf[64]; + + GetWindowRect(GetDlgItem(hwndDlg, IDC_ALLPRECENTS), &rc); + MapWindowPoints(nullptr, hwndDlg, (LPPOINT)&rc, 2); + SetWindowPos(hwndDlg, nullptr, 0, 0, 100, rc.bottom + 3, SWP_NOMOVE | SWP_NOZORDER); + ShowWindow(GetDlgItem(hwndDlg, IDC_ALLTRANSFERRED), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_ALLSPEED), SW_HIDE); + + _strtime(buf); + SetDlgItemTextA(hwndDlg, IDC_ALLPRECENTS, buf); + + PostMessage(GetParent(hwndDlg), WM_FT_RESIZE, 0, (LPARAM)hwndDlg); +} + +static int FileTransferDlgResizer(HWND, LPARAM param, UTILRESIZECONTROL *urc) +{ + auto *dat = (FileDlgData *)param; + + switch (urc->wId) { + case IDC_CONTACTNAME: + case IDC_STATUS: + case IDC_ALLFILESPROGRESS: + case IDC_TRANSFERCOMPLETED: + return RD_ANCHORX_WIDTH | RD_ANCHORY_TOP; + + case IDC_FRAME: + return RD_ANCHORX_WIDTH | RD_ANCHORY_BOTTOM; + case IDC_ALLPRECENTS: + case IDCANCEL: + case IDC_OPENFILE: + case IDC_OPENFOLDER: + return RD_ANCHORX_RIGHT | RD_ANCHORY_TOP; + + case IDC_ALLTRANSFERRED: + if (dat->waitingForAcceptance) + return RD_ANCHORX_WIDTH | RD_ANCHORY_TOP; + + urc->rcItem.right = urc->rcItem.left + (urc->rcItem.right - urc->rcItem.left - urc->dlgOriginalSize.cx + urc->dlgNewSize.cx) / 3; + return RD_ANCHORX_CUSTOM | RD_ANCHORY_TOP; + + case IDC_ALLSPEED: + if (dat->waitingForAcceptance) + return RD_ANCHORX_RIGHT | RD_ANCHORY_TOP; + + urc->rcItem.right = urc->rcItem.right - urc->dlgOriginalSize.cx + urc->dlgNewSize.cx; + urc->rcItem.left = urc->rcItem.left + (urc->rcItem.right - urc->rcItem.left) / 3; + return RD_ANCHORX_CUSTOM | RD_ANCHORY_TOP; + } + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; +} + +INT_PTR CALLBACK DlgProcFileTransfer(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + FileDlgData *dat = (FileDlgData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + dat = (FileDlgData *)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + dat->hNotifyEvent = HookEventMessage(ME_PROTO_ACK, hwndDlg, HM_RECVEVENT); + dat->transferStatus.currentFileNumber = -1; + if (dat->send) { + if (db_mc_isMeta(dat->hContact)) + dat->hContact = db_mc_getMostOnline(dat->hContact); + dat->fs = (HANDLE)ProtoChainSend(dat->hContact, PSS_FILE, (WPARAM)dat->szMsg, (LPARAM)dat->files); + SetFtStatus(hwndDlg, LPGENW("Request sent, waiting for acceptance..."), FTS_TEXT); + SetOpenFileButtonStyle(GetDlgItem(hwndDlg, IDC_OPENFILE), 1); + dat->waitingForAcceptance = 1; + // hide "open" button since it may cause potential access violations... + ShowWindow(GetDlgItem(hwndDlg, IDC_OPENFILE), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_OPENFOLDER), SW_HIDE); + } + else { //recv + CreateDirectoryTreeW(dat->szSavePath); + dat->fs = (HANDLE)ProtoChainSend(dat->hContact, PSS_FILEALLOW, (WPARAM)dat->fs, (LPARAM)dat->szSavePath); + dat->transferStatus.szWorkingDir.w = mir_wstrdup(dat->szSavePath); + if (!Contact::OnList(dat->hContact)) + dat->resumeBehaviour = FILERESUME_ASK; + else + dat->resumeBehaviour = g_plugin.getByte("IfExists", FILERESUME_ASK); + SetFtStatus(hwndDlg, LPGENW("Waiting for connection..."), FTS_TEXT); + } + + /* check we actually got an fs handle back from the protocol */ + if (!dat->fs) { + SetFtStatus(hwndDlg, LPGENW("Unable to initiate transfer."), FTS_TEXT); + dat->waitingForAcceptance = 0; + } + { + LOGFONT lf; + HFONT hFont = (HFONT)SendDlgItemMessage(hwndDlg, IDC_CONTACTNAME, WM_GETFONT, 0, 0); + GetObject(hFont, sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + hFont = CreateFontIndirect(&lf); + SendDlgItemMessage(hwndDlg, IDC_CONTACTNAME, WM_SETFONT, (WPARAM)hFont, 0); + + SHFILEINFO shfi = {}; + SHGetFileInfo(L"", FILE_ATTRIBUTE_DIRECTORY, &shfi, sizeof(shfi), SHGFI_USEFILEATTRIBUTES | SHGFI_ICON | SHGFI_SMALLICON); + dat->hIconFolder = shfi.hIcon; + } + dat->hIcon = nullptr; + { + char *szProto = Proto_GetBaseAccountName(dat->hContact); + uint16_t status = db_get_w(dat->hContact, szProto, "Status", ID_STATUS_ONLINE); + SendDlgItemMessage(hwndDlg, IDC_CONTACT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)Skin_LoadProtoIcon(szProto, status)); + } + + SendDlgItemMessage(hwndDlg, IDC_CONTACT, BUTTONADDTOOLTIP, (WPARAM)LPGEN("Contact menu"), 0); + SendDlgItemMessage(hwndDlg, IDC_CONTACT, BUTTONSETASFLATBTN, TRUE, 0); + + Button_SetSkin_IcoLib(hwndDlg, IDC_OPENFILE, SKINICON_OTHER_DOWNARROW, LPGEN("Open...")); + SendDlgItemMessage(hwndDlg, IDC_OPENFILE, BUTTONSETASPUSHBTN, TRUE, 0); + + SendDlgItemMessage(hwndDlg, IDC_OPENFOLDER, BM_SETIMAGE, IMAGE_ICON, (LPARAM)dat->hIconFolder); + SendDlgItemMessage(hwndDlg, IDC_OPENFOLDER, BUTTONADDTOOLTIP, (WPARAM)LPGEN("Open folder"), 0); + SendDlgItemMessage(hwndDlg, IDC_OPENFOLDER, BUTTONSETASFLATBTN, TRUE, 0); + + Button_SetSkin_IcoLib(hwndDlg, IDCANCEL, SKINICON_OTHER_DELETE, LPGEN("Cancel")); + + SetDlgItemText(hwndDlg, IDC_CONTACTNAME, Clist_GetContactDisplayName(dat->hContact)); + + if (!dat->waitingForAcceptance) + SetTimer(hwndDlg, 1, 1000, nullptr); + return TRUE; + + case WM_TIMER: + memmove(dat->bytesRecvedHistory + 1, dat->bytesRecvedHistory, sizeof(dat->bytesRecvedHistory) - sizeof(dat->bytesRecvedHistory[0])); + dat->bytesRecvedHistory[0] = dat->transferStatus.totalProgress; + if (dat->bytesRecvedHistorySize < _countof(dat->bytesRecvedHistory)) + dat->bytesRecvedHistorySize++; + + wchar_t szSpeed[32], szTime[32], szDisplay[96]; + SYSTEMTIME st; + ULARGE_INTEGER li; + FILETIME ft; + + GetSensiblyFormattedSize((dat->bytesRecvedHistory[0] - dat->bytesRecvedHistory[dat->bytesRecvedHistorySize - 1]) / dat->bytesRecvedHistorySize, szSpeed, _countof(szSpeed), 0, 1, NULL); + if (dat->bytesRecvedHistory[0] == dat->bytesRecvedHistory[dat->bytesRecvedHistorySize - 1]) + mir_wstrcpy(szTime, L"??:??:??"); + else { + li.QuadPart = 10000000ll * (dat->transferStatus.currentFileSize - dat->transferStatus.currentFileProgress) * dat->bytesRecvedHistorySize / (dat->bytesRecvedHistory[0] - dat->bytesRecvedHistory[dat->bytesRecvedHistorySize - 1]); + ft.dwHighDateTime = li.HighPart; ft.dwLowDateTime = li.LowPart; + FileTimeToSystemTime(&ft, &st); + GetTimeFormat(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER, &st, NULL, szTime, _countof(szTime)); + } + if (dat->bytesRecvedHistory[0] != dat->bytesRecvedHistory[dat->bytesRecvedHistorySize - 1]) { + li.QuadPart = 10000000ll * (dat->transferStatus.totalBytes - dat->transferStatus.totalProgress) * dat->bytesRecvedHistorySize / (dat->bytesRecvedHistory[0] - dat->bytesRecvedHistory[dat->bytesRecvedHistorySize - 1]); + ft.dwHighDateTime = li.HighPart; ft.dwLowDateTime = li.LowPart; + FileTimeToSystemTime(&ft, &st); + GetTimeFormat(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER, &st, NULL, szTime, _countof(szTime)); + } + + mir_snwprintf(szDisplay, L"%s/%s (%s %s)", szSpeed, TranslateT("sec"), szTime, TranslateT("remaining")); + SetDlgItemText(hwndDlg, IDC_ALLSPEED, szDisplay); + break; + + case WM_MEASUREITEM: + return Menu_MeasureItem(lParam); + + case WM_DRAWITEM: + return Menu_DrawItem(lParam); + + case WM_FT_CLEANUP: + if (!dat->fs) { + PostMessage(GetParent(hwndDlg), WM_FT_REMOVE, 0, (LPARAM)hwndDlg); + DestroyWindow(hwndDlg); + } + break; + + case WM_COMMAND: + if (!dat) + break; + + if (Clist_MenuProcessCommand(LOWORD(wParam), MPCF_CONTACTMENU, dat->hContact)) + break; + + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + PostMessage(GetParent(hwndDlg), WM_FT_REMOVE, 0, (LPARAM)hwndDlg); + DestroyWindow(hwndDlg); + break; + + case IDC_CONTACT: + { + RECT rc; + HMENU hMenu = Menu_BuildContactMenu(dat->hContact); + GetWindowRect((HWND)lParam, &rc); + TrackPopupMenu(hMenu, 0, rc.left, rc.bottom, 0, hwndDlg, NULL); + DestroyMenu(hMenu); + } + break; + + case IDC_TRANSFERCOMPLETED: + if (dat->transferStatus.currentFileNumber <= 1 && CheckVirusScanned(hwndDlg, dat, 0)) { + ShellExecute(NULL, NULL, dat->files[0], NULL, NULL, SW_SHOW); + break; + } + + case IDC_OPENFOLDER: + { + wchar_t *path = dat->transferStatus.szWorkingDir.w; + if (!path || !path[0]) { + path = NEWWSTR_ALLOCA(dat->transferStatus.szCurrentFile.w); + wchar_t *p = wcsrchr(path, '\\'); if (p) *p = 0; + } + + if (path) ShellExecute(NULL, L"open", path, NULL, NULL, SW_SHOW); + } + break; + + case IDC_OPENFILE: + wchar_t **files; + if (dat->send) { + if (dat->files == nullptr) + files = dat->transferStatus.pszFiles.w; + else + files = dat->files; + } + else files = dat->files; + + HMENU hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING, 1, TranslateT("Open folder")); + AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr); + + if (files && *files) { + int limit; + wchar_t *pszFilename, *pszNewFileName; + + if (dat->send) + limit = dat->transferStatus.totalFiles; + else + limit = dat->transferStatus.currentFileNumber; + + // Loop over all transfered files and add them to the menu + for (int i = 0; i < limit; i++) { + pszFilename = wcsrchr(files[i], '\\'); + if (pszFilename == nullptr) + pszFilename = files[i]; + else + pszFilename++; + + if (pszFilename) { + size_t cbFileNameLen = mir_wstrlen(pszFilename); + + pszNewFileName = (wchar_t*)mir_alloc(cbFileNameLen * 2 * sizeof(wchar_t)); + wchar_t *p = pszNewFileName; + for (size_t pszlen = 0; pszlen < cbFileNameLen; pszlen++) { + *p++ = pszFilename[pszlen]; + if (pszFilename[pszlen] == '&') + *p++ = '&'; + } + *p = '\0'; + AppendMenu(hMenu, MF_STRING, i + 10, pszNewFileName); + mir_free(pszNewFileName); + } + } + } + + RECT rc; + GetWindowRect((HWND)lParam, &rc); + CheckDlgButton(hwndDlg, IDC_OPENFILE, BST_CHECKED); + int ret = TrackPopupMenu(hMenu, TPM_RETURNCMD | TPM_RIGHTALIGN, rc.right, rc.bottom, 0, hwndDlg, nullptr); + CheckDlgButton(hwndDlg, IDC_OPENFILE, BST_UNCHECKED); + DestroyMenu(hMenu); + + if (ret == 1) { + wchar_t *path = dat->transferStatus.szWorkingDir.w; + if (!path || !path[0]) { + path = NEWWSTR_ALLOCA(dat->transferStatus.szCurrentFile.w); + wchar_t *p = wcsrchr(path, '\\'); + if (p) + *p = 0; + } + + if (path) ShellExecute(nullptr, L"open", path, nullptr, nullptr, SW_SHOW); + } + else if (ret && CheckVirusScanned(hwndDlg, dat, ret)) + ShellExecute(nullptr, nullptr, files[ret - 10], nullptr, nullptr, SW_SHOW); + } + break; + + case M_FILEEXISTSDLGREPLY: + EnableWindow(hwndDlg, TRUE); + { + PROTOFILERESUME *pfr = (PROTOFILERESUME *)lParam; + wchar_t *szOriginalFilename = (wchar_t *)wParam; + char *szProto = Proto_GetBaseAccountName(dat->hContact); + + switch (pfr->action) { + case FILERESUME_CANCEL: + if (dat->fs) ProtoChainSend(dat->hContact, PSS_FILECANCEL, (WPARAM)dat->fs, 0); + dat->fs = nullptr; + mir_free(szOriginalFilename); + if (pfr->szFilename) mir_free((char *)pfr->szFilename); + mir_free(pfr); + return 0; + case FILERESUME_RESUMEALL: + case FILERESUME_OVERWRITEALL: + dat->resumeBehaviour = pfr->action; + pfr->action &= ~FILERESUMEF_ALL; + break; + case FILERESUME_RENAMEALL: + pfr->action = FILERESUME_RENAME; + { + wchar_t *pszExtension, *pszFilename; + if ((pszFilename = wcsrchr(szOriginalFilename, '\\')) == nullptr) pszFilename = szOriginalFilename; + if ((pszExtension = wcsrchr(pszFilename + 1, '.')) == nullptr) pszExtension = pszFilename + mir_wstrlen(pszFilename); + if (pfr->szFilename) mir_free((wchar_t *)pfr->szFilename); + size_t size = (pszExtension - szOriginalFilename) + 21 + mir_wstrlen(pszExtension); + pfr->szFilename = (wchar_t *)mir_alloc(sizeof(wchar_t) * size); + for (int i = 1;; i++) { + mir_snwprintf((wchar_t *)pfr->szFilename, size, L"%.*s (%u)%s", pszExtension - szOriginalFilename, szOriginalFilename, i, pszExtension); + if (_waccess(pfr->szFilename, 0) != 0) + break; + } + } + break; + } + mir_free(szOriginalFilename); + CallProtoService(szProto, PS_FILERESUME, (WPARAM)dat->fs, (LPARAM)pfr); + delete pfr; + } + break; + + case HM_RECVEVENT: + { + ACKDATA *ack = (ACKDATA *)lParam; + if (ack->hProcess != dat->fs) break; + if (ack->type != ACKTYPE_FILE) break; + if (ack->hContact != dat->hContact) break; + + if (dat->waitingForAcceptance) { + SetTimer(hwndDlg, 1, 1000, nullptr); + dat->waitingForAcceptance = 0; + } + + switch (ack->result) { + case ACKRESULT_SENTREQUEST: SetFtStatus(hwndDlg, LPGENW("Decision sent"), FTS_TEXT); break; + case ACKRESULT_CONNECTING: SetFtStatus(hwndDlg, LPGENW("Connecting..."), FTS_TEXT); break; + case ACKRESULT_CONNECTPROXY: SetFtStatus(hwndDlg, LPGENW("Connecting to proxy..."), FTS_TEXT); break; + case ACKRESULT_CONNECTED: SetFtStatus(hwndDlg, LPGENW("Connected"), FTS_TEXT); break; + case ACKRESULT_LISTENING: SetFtStatus(hwndDlg, LPGENW("Waiting for connection..."), FTS_TEXT); break; + case ACKRESULT_INITIALISING: SetFtStatus(hwndDlg, LPGENW("Initializing..."), FTS_TEXT); break; + case ACKRESULT_NEXTFILE: + SetFtStatus(hwndDlg, LPGENW("Moving to next file..."), FTS_TEXT); + SetDlgItemTextA(hwndDlg, IDC_FILENAME, ""); + if (dat->transferStatus.currentFileNumber == 1 && dat->transferStatus.totalFiles > 1 && !dat->send) + SetOpenFileButtonStyle(GetDlgItem(hwndDlg, IDC_OPENFILE), 1); + if (dat->transferStatus.currentFileNumber != -1 && dat->files && !dat->send && g_plugin.getByte("UseScanner", VIRUSSCAN_DISABLE) == VIRUSSCAN_DURINGDL) { + if (GetFileAttributes(dat->files[dat->transferStatus.currentFileNumber]) & FILE_ATTRIBUTE_DIRECTORY) + PostMessage(hwndDlg, M_VIRUSSCANDONE, dat->transferStatus.currentFileNumber, 0); + else { + virusscanthreadstartinfo *vstsi = (virusscanthreadstartinfo *)mir_alloc(sizeof(virusscanthreadstartinfo)); + vstsi->hwndReply = hwndDlg; + vstsi->szFile = mir_wstrdup(dat->files[dat->transferStatus.currentFileNumber]); + vstsi->returnCode = dat->transferStatus.currentFileNumber; + mir_forkThread<virusscanthreadstartinfo>(RunVirusScannerThread, vstsi); + } + } + break; + + case ACKRESULT_FILERESUME: + UpdateProtoFileTransferStatus(&dat->transferStatus, (PROTOFILETRANSFERSTATUS *)ack->lParam); + { + PROTOFILETRANSFERSTATUS *fts = &dat->transferStatus; + SetFilenameControls(hwndDlg, dat, fts); + if (_waccess(fts->szCurrentFile.w, 0)) + break; + + SetFtStatus(hwndDlg, LPGENW("File already exists"), FTS_TEXT); + if (dat->resumeBehaviour == FILERESUME_ASK) { + TDlgProcFileExistsParam param = { hwndDlg, fts }; + ShowWindow(hwndDlg, SW_SHOWNORMAL); + CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FILEEXISTS), hwndDlg, DlgProcFileExists, (LPARAM)¶m); + EnableWindow(hwndDlg, FALSE); + } + else { + PROTOFILERESUME *pfr = new PROTOFILERESUME(); + pfr->action = dat->resumeBehaviour; + pfr->szFilename = nullptr; + PostMessage(hwndDlg, M_FILEEXISTSDLGREPLY, (WPARAM)mir_wstrdup(fts->szCurrentFile.w), (LPARAM)pfr); + } + } + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); + return TRUE; + + case ACKRESULT_DATA: + { + PROTOFILETRANSFERSTATUS *fts = (PROTOFILETRANSFERSTATUS *)ack->lParam; + wchar_t str[64], str2[64], szSizeDone[32], szSizeTotal[32];//, *contactName; + + if (dat->fileVirusScanned == nullptr) + dat->fileVirusScanned = (int *)mir_calloc(sizeof(int) * fts->totalFiles); + + // This needs to be here - otherwise we get holes in the files array + if (!dat->send) { + if (dat->files == nullptr) + dat->files = (wchar_t **)mir_calloc((fts->totalFiles + 1) * sizeof(wchar_t *)); + if (fts->currentFileNumber < fts->totalFiles && dat->files[fts->currentFileNumber] == nullptr) + dat->files[fts->currentFileNumber] = PFTS_StringToTchar(fts->flags, fts->szCurrentFile.w); + } + + /* HACK: for 0.3.3, limit updates to around 1.1 ack per second */ + if (fts->totalProgress != fts->totalBytes && GetTickCount() < (dat->dwTicks + 650)) + break; // the last update was less than a second ago! + dat->dwTicks = GetTickCount(); + + // Update local transfer status with data from protocol + UpdateProtoFileTransferStatus(&dat->transferStatus, fts); + fts = &dat->transferStatus; + + bool firstTime = false; + if ((GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_ALLFILESPROGRESS), GWL_STYLE) & WS_VISIBLE) == 0) { + SetFtStatus(hwndDlg, (fts->flags & PFTS_SENDING) ? LPGENW("Sending...") : LPGENW("Receiving..."), FTS_PROGRESS); + SetFilenameControls(hwndDlg, dat, fts); + firstTime = true; + } + + const unsigned long lastPos = SendDlgItemMessage(hwndDlg, IDC_ALLFILESPROGRESS, PBM_GETPOS, 0, 0); + const unsigned long nextPos = fts->totalBytes ? (100ll * fts->totalProgress / fts->totalBytes) : 0; + if (lastPos != nextPos || firstTime) { + SendDlgItemMessage(hwndDlg, IDC_ALLFILESPROGRESS, PBM_SETPOS, nextPos, 0); + mir_snwprintf(str, L"%u%%", nextPos); + SetDlgItemText(hwndDlg, IDC_ALLPRECENTS, str); + } + + int units; + GetSensiblyFormattedSize(fts->totalBytes, szSizeTotal, _countof(szSizeTotal), 0, 1, &units); + GetSensiblyFormattedSize(fts->totalProgress, szSizeDone, _countof(szSizeDone), units, 0, NULL); + mir_snwprintf(str, L"%s/%s", szSizeDone, szSizeTotal); + str2[0] = 0; + GetDlgItemText(hwndDlg, IDC_ALLTRANSFERRED, str2, _countof(str2)); + if (mir_wstrcmp(str, str2)) + SetDlgItemText(hwndDlg, IDC_ALLTRANSFERRED, str); + } + break; + + case ACKRESULT_SUCCESS: + case ACKRESULT_FAILED: + case ACKRESULT_DENIED: + HideProgressControls(hwndDlg); + KillTimer(hwndDlg, 1); + if (!dat->send) + SetOpenFileButtonStyle(GetDlgItem(hwndDlg, IDC_OPENFILE), 1); + SetDlgItemText(hwndDlg, IDCANCEL, TranslateT("Close")); + if (dat->hNotifyEvent) + UnhookEvent(dat->hNotifyEvent); + dat->hNotifyEvent = nullptr; + + if (ack->result == ACKRESULT_DENIED) { + dat->fs = nullptr; /* protocol will free structure */ + Skin_PlaySound("FileDenied"); + SetFtStatus(hwndDlg, LPGENW("File transfer denied"), FTS_TEXT); + } + else if (ack->result == ACKRESULT_FAILED) { + dat->fs = nullptr; /* protocol will free structure */ + Skin_PlaySound("FileFailed"); + SetFtStatus(hwndDlg, LPGENW("File transfer failed"), FTS_TEXT); + } + else { + Skin_PlaySound("FileDone"); + if (dat->send) { + dat->fs = nullptr; /* protocol will free structure */ + SetFtStatus(hwndDlg, LPGENW("Transfer completed."), FTS_TEXT); + + DB::EventInfo dbei; + dbei.szModule = Proto_GetBaseAccountName(dat->hContact); + dbei.eventType = EVENTTYPE_FILE; + dbei.flags = DBEF_SENT; + dbei.timestamp = time(0); + dbei.flags |= DBEF_UTF; + + DB::FILE_BLOB blob(dat->szFilenames, dat->szMsg); + blob.write(dbei); + db_event_add(dat->hContact, &dbei); + + dat->files = nullptr; //protocol library frees this + } + else { + SetFtStatus(hwndDlg, + (dat->transferStatus.totalFiles == 1) ? + LPGENW("Transfer completed, open file.") : + LPGENW("Transfer completed, open folder."), + FTS_OPEN); + + int useScanner = g_plugin.getByte("UseScanner", VIRUSSCAN_DISABLE); + if (useScanner != VIRUSSCAN_DISABLE) { + auto *vstsi = (virusscanthreadstartinfo *)mir_alloc(sizeof(virusscanthreadstartinfo)); + vstsi->hwndReply = hwndDlg; + if (useScanner == VIRUSSCAN_DURINGDL) { + vstsi->returnCode = dat->transferStatus.currentFileNumber; + if (GetFileAttributes(dat->files[dat->transferStatus.currentFileNumber]) & FILE_ATTRIBUTE_DIRECTORY) { + PostMessage(hwndDlg, M_VIRUSSCANDONE, vstsi->returnCode, 0); + mir_free(vstsi); + vstsi = nullptr; + } + else vstsi->szFile = mir_wstrdup(dat->files[dat->transferStatus.currentFileNumber]); + } + else { + vstsi->szFile = mir_wstrdup(dat->transferStatus.szWorkingDir.w); + vstsi->returnCode = -1; + } + SetFtStatus(hwndDlg, LPGENW("Scanning for viruses..."), FTS_TEXT); + if (vstsi) + mir_forkThread<virusscanthreadstartinfo>(RunVirusScannerThread, vstsi); + } + else dat->fs = nullptr; /* protocol will free structure */ + + dat->transferStatus.currentFileNumber = dat->transferStatus.totalFiles; + } + } + + PostMessage(GetParent(hwndDlg), WM_FT_COMPLETED, ack->result, (LPARAM)hwndDlg); + break; + } + } + break; + + case M_VIRUSSCANDONE: + { + int done = 1; + if ((int)wParam == -1) { + for (int i = 0; i < dat->transferStatus.totalFiles; i++) + dat->fileVirusScanned[i] = 1; + } + else { + dat->fileVirusScanned[wParam] = 1; + for (int i = 0; i < dat->transferStatus.totalFiles; i++) + if (!dat->fileVirusScanned[i]) { + done = 0; + break; + } + } + if (done) { + dat->fs = nullptr; /* protocol will free structure */ + SetFtStatus(hwndDlg, LPGENW("Transfer and virus scan complete"), FTS_TEXT); + } + } + break; + + case WM_SIZE: + Utils_ResizeDialog(hwndDlg, g_plugin.getInst(), MAKEINTRESOURCEA(IDD_FILETRANSFERINFO), FileTransferDlgResizer, LPARAM(dat)); + + RedrawWindow(GetDlgItem(hwndDlg, IDC_ALLTRANSFERRED), NULL, NULL, RDW_INVALIDATE | RDW_NOERASE); + RedrawWindow(GetDlgItem(hwndDlg, IDC_ALLSPEED), NULL, NULL, RDW_INVALIDATE | RDW_NOERASE); + RedrawWindow(GetDlgItem(hwndDlg, IDC_CONTACTNAME), NULL, NULL, RDW_INVALIDATE | RDW_NOERASE); + RedrawWindow(GetDlgItem(hwndDlg, IDC_STATUS), NULL, NULL, RDW_INVALIDATE | RDW_NOERASE); + break; + + case WM_DESTROY: + KillTimer(hwndDlg, 1); + + HFONT hFont = (HFONT)SendDlgItemMessage(hwndDlg, IDC_CONTACTNAME, WM_GETFONT, 0, 0); + DeleteObject(hFont); + + Button_FreeIcon_IcoLib(hwndDlg, IDC_CONTACT); + Button_FreeIcon_IcoLib(hwndDlg, IDC_OPENFILE); + Button_FreeIcon_IcoLib(hwndDlg, IDCANCEL); + + delete dat; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + break; + } + return FALSE; +} + +FileDlgData::~FileDlgData() +{ + if (fs) + ProtoChainSend(hContact, PSS_FILECANCEL, (WPARAM)fs, 0); + + UnhookEvent(hPreshutdownEvent); + UnhookEvent(hNotifyEvent); + + FreeProtoFileTransferStatus(&transferStatus); + FreeFilesMatrix(&files); + + mir_free(fileVirusScanned); + if (hIcon) + DestroyIcon(hIcon); + if (hIconFolder) + DestroyIcon(hIconFolder); +} diff --git a/src/mir_app/src/ftmanager.cpp b/src/mir_app/src/ftmanager.cpp new file mode 100644 index 0000000000..04221b7956 --- /dev/null +++ b/src/mir_app/src/ftmanager.cpp @@ -0,0 +1,516 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org), +Copyright (c) 2000-12 Miranda IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" +#include "file.h" + +extern ITaskbarList3 *pTaskbarInterface; + +static HWND hwndFtMgr = nullptr; + +struct TFtMgrData +{ + HWND hwndIncoming; + HWND hwndOutgoing; + + HANDLE hhkPreshutdown; + TBPFLAG errorState; +}; + +#define M_CALCPROGRESS (WM_USER + 200) + +struct TFtProgressData +{ + unsigned int init, run, scan; + unsigned __int64 totalBytes, totalProgress; +}; + +struct TLayoutWindowInfo +{ + TLayoutWindowInfo(HWND _hwnd) : + hwnd(_hwnd) + { + ::GetWindowRect(_hwnd, &rc); + } + + HWND hwnd; + RECT rc; +}; + +struct TFtPageData +{ + TFtPageData() : + arWindows(1) + {} + + OBJLIST<TLayoutWindowInfo> arWindows; + int runningCount = 0; + int height = 0, dataHeight = 0, scrollPos = 0; +}; + +static void LayoutTransfers(HWND hwnd, TFtPageData *dat) +{ + int top = 0; + RECT rc; + GetClientRect(hwnd, &rc); + + dat->scrollPos = GetScrollPos(hwnd, SB_VERT); + dat->height = rc.bottom - rc.top; + + if (dat->arWindows.getCount()) { + HDWP hdwp = BeginDeferWindowPos(dat->arWindows.getCount()); + top -= dat->scrollPos; + for (auto &it : dat->arWindows) { + int height = it->rc.bottom - it->rc.top; + if (nullptr != it->hwnd) /* Wine fix. */ + hdwp = DeferWindowPos(hdwp, it->hwnd, nullptr, 0, top, rc.right, height, SWP_NOZORDER); + top += height; + } + top += dat->scrollPos; + EndDeferWindowPos(hdwp); + } + + dat->dataHeight = top; + + SCROLLINFO si = { 0 }; + si.cbSize = sizeof(si); + si.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_RANGE; + si.nPage = dat->height; + si.nMin = 0; + si.nMax = dat->dataHeight; + SetScrollInfo(hwnd, SB_VERT, &si, TRUE); +} + +static INT_PTR CALLBACK FtMgrPageDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + TFtPageData *dat = (TFtPageData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + { + // Force scrollbar visibility + SCROLLINFO si = {}; + si.cbSize = sizeof(si); + si.fMask = SIF_DISABLENOSCROLL; + SetScrollInfo(hwnd, SB_VERT, &si, TRUE); + + dat = new TFtPageData(); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)dat); + } + break; + + case WM_FT_ADD: + { + TLayoutWindowInfo *wnd = new TLayoutWindowInfo((HWND)lParam); + if (File::bReverseOrder) + dat->arWindows.insert(wnd, 0); + else + dat->arWindows.insert(wnd); + LayoutTransfers(hwnd, dat); + dat->runningCount++; + PostMessage(GetParent(hwnd), WM_TIMER, 1, NULL); + } + break; + + case WM_FT_RESIZE: + for (auto &it : dat->arWindows) + if (it->hwnd == (HWND)lParam) { + GetWindowRect(it->hwnd, &it->rc); + break; + } + LayoutTransfers(hwnd, dat); + break; + + case WM_FT_REMOVE: + for (auto &it : dat->arWindows) + if (it->hwnd == (HWND)lParam) { + dat->arWindows.removeItem(&it); + break; + } + LayoutTransfers(hwnd, dat); + break; + + case WM_FT_COMPLETED: + //wParam: { ACKRESULT_SUCCESS | ACKRESULT_FAILED | ACKRESULT_DENIED } + dat->runningCount--; + { + bool bFound = false; + for (auto &it : dat->arWindows) + // no error when canceling (WM_FT_REMOVE is send first, check if hwnd is still registered) + if (it->hwnd == (HWND)lParam) { + bFound = true; + SendMessage(GetParent(hwnd), WM_TIMER, 1, (LPARAM)wParam); + break; + } + + if (!bFound) + PostMessage(GetParent(hwnd), WM_TIMER, 1, NULL); + } + + if(dat->runningCount == 0 && wParam == ACKRESULT_SUCCESS && File::bAutoClose) + ShowWindow(hwndFtMgr, SW_HIDE); + break; + + case WM_FT_CLEANUP: + for (auto &it : dat->arWindows) + SendMessage(it->hwnd, WM_FT_CLEANUP, wParam, lParam); + break; + + case WM_SIZE: + LayoutTransfers(hwnd, dat); + break; + + case WM_MOUSEWHEEL: + if (int zDelta = GET_WHEEL_DELTA_WPARAM(wParam)) { + int nScrollLines = 0; + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &nScrollLines, 0); + for (int i=0; i < (nScrollLines + 1) / 2; i++) + SendMessage(hwnd, WM_VSCROLL, (zDelta < 0) ? SB_LINEDOWN : SB_LINEUP, 0); + } + + SetWindowLongPtr(hwnd, DWLP_MSGRESULT, 0); + return TRUE; + + case WM_VSCROLL: + { + int pos = dat->scrollPos; + switch (LOWORD(wParam)) { + case SB_LINEDOWN: + pos += 15; + break; + case SB_LINEUP: + pos -= 15; + break; + case SB_PAGEDOWN: + pos += dat->height - 10; + break; + case SB_PAGEUP: + pos -= dat->height - 10; + break; + case SB_THUMBTRACK: + pos = HIWORD(wParam); + break; + } + + if (pos > dat->dataHeight - dat->height) + pos = dat->dataHeight - dat->height; + if (pos < 0) + pos = 0; + + if (dat->scrollPos != pos) { + ScrollWindow(hwnd, 0, dat->scrollPos - pos, nullptr, nullptr); + SetScrollPos(hwnd, SB_VERT, pos, TRUE); + dat->scrollPos = pos; + } + } + break; + + case M_PRESHUTDOWN: + for (auto &it : dat->arWindows) + PostMessage(it->hwnd, WM_COMMAND, MAKEWPARAM(IDCANCEL, BN_CLICKED), 0); + break; + + case M_CALCPROGRESS: + { + TFtProgressData *prg = (TFtProgressData *)wParam; + for (auto &it : dat->arWindows) { + FileDlgData *trdat = (FileDlgData *)GetWindowLongPtr(it->hwnd, GWLP_USERDATA); + if (trdat->transferStatus.totalBytes && trdat->fs && !trdat->send && (trdat->transferStatus.totalBytes == trdat->transferStatus.totalProgress)) + prg->scan++; + else if (trdat->transferStatus.totalBytes && trdat->fs) { // in progress + prg->run++; + prg->totalBytes += trdat->transferStatus.totalBytes; + prg->totalProgress += trdat->transferStatus.totalProgress; + } + else if (trdat->fs) // starting + prg->init++; + } + } + break; + + case WM_DESTROY: + delete dat; + break; + } + + return FALSE; +} + +static INT_PTR CALLBACK FtMgrDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + auto *dat = (TFtMgrData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + HWND hwndTab = GetDlgItem(hwnd, IDC_TABS); + + switch (msg) { + case WM_INITDIALOG: + { + TranslateDialogDefault(hwnd); + Window_SetSkinIcon_IcoLib(hwnd, SKINICON_EVENT_FILE); + + dat = (TFtMgrData *)mir_calloc(sizeof(struct TFtMgrData)); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)dat); + + dat->hhkPreshutdown = HookEventMessage(ME_SYSTEM_PRESHUTDOWN, hwnd, M_PRESHUTDOWN); + + dat->hwndIncoming = CreateDialog(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FTPAGE), hwnd, FtMgrPageDlgProc); + dat->hwndOutgoing = CreateDialog(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FTPAGE), hwnd, FtMgrPageDlgProc); + ShowWindow(dat->hwndIncoming, SW_SHOW); + + TCITEM tci = {}; + tci.mask = TCIF_PARAM|TCIF_TEXT; + tci.pszText = TranslateT("Incoming"); + tci.lParam = (LPARAM)dat->hwndIncoming; + TabCtrl_InsertItem(hwndTab, 0, &tci); + tci.pszText = TranslateT("Outgoing"); + tci.lParam = (LPARAM)dat->hwndOutgoing; + TabCtrl_InsertItem(hwndTab, 1, &tci); + + Utils_RestoreWindowPosition(hwnd, NULL, SRFILEMODULE, "FtMgrDlg_", RWPF_NOACTIVATE); + // Fall through to setup initial placement + __fallthrough; + } + case WM_SIZE: + { + RECT rc, rcButton; + GetWindowRect(GetDlgItem(hwnd, IDCANCEL), &rcButton); + OffsetRect(&rcButton, -rcButton.left, -rcButton.top); + + GetClientRect(hwnd, &rc); + InflateRect(&rc, -6, -6); + + HDWP hdwp = BeginDeferWindowPos(3); + + hdwp = DeferWindowPos(hdwp, GetDlgItem(hwnd, IDC_CLEAR), NULL, rc.left, rc.bottom-rcButton.bottom, 0, 0, SWP_NOZORDER|SWP_NOSIZE); + hdwp = DeferWindowPos(hdwp, GetDlgItem(hwnd, IDCANCEL), nullptr, rc.right-rcButton.right, rc.bottom-rcButton.bottom, 0, 0, SWP_NOZORDER|SWP_NOSIZE); + + rc.bottom -= rcButton.bottom + 5; + + if (nullptr != hwndTab) /* Wine fix. */ + hdwp = DeferWindowPos(hdwp, hwndTab, nullptr, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, SWP_NOZORDER); + + EndDeferWindowPos(hdwp); + + GetWindowRect(hwndTab, &rc); + MapWindowPoints(nullptr, hwnd, (LPPOINT)&rc, 2); + TabCtrl_AdjustRect(hwndTab, FALSE, &rc); + InflateRect(&rc, -5, -5); + + hdwp = BeginDeferWindowPos(2); + + if (nullptr != dat->hwndIncoming) /* Wine fix. */ + hdwp = DeferWindowPos(hdwp, dat->hwndIncoming, HWND_TOP, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, 0); + if (nullptr != dat->hwndOutgoing) /* Wine fix. */ + hdwp = DeferWindowPos(hdwp, dat->hwndOutgoing, HWND_TOP, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, 0); + + EndDeferWindowPos(hdwp); + } + break; + + case WM_MOUSEWHEEL: + if (IsWindowVisible(dat->hwndIncoming)) + SendMessage(dat->hwndIncoming, msg, wParam, lParam); + if (IsWindowVisible(dat->hwndOutgoing)) + SendMessage(dat->hwndOutgoing, msg, wParam, lParam); + break; + + case WM_FT_SELECTPAGE: + if (TabCtrl_GetCurSel(hwndTab) != (int)wParam) { + TCITEM tci = {}; + tci.mask = TCIF_PARAM; + TabCtrl_GetItem(hwndTab, TabCtrl_GetCurSel(hwndTab), &tci); + + ShowWindow((HWND)tci.lParam, SW_HIDE); + TabCtrl_SetCurSel(hwndTab, wParam); + TabCtrl_GetItem(hwndTab, TabCtrl_GetCurSel(hwndTab), &tci); + ShowWindow((HWND)tci.lParam, SW_SHOW); + } + break; + + case WM_GETMINMAXINFO: + { + LPMINMAXINFO lpmmi = (LPMINMAXINFO)lParam; + lpmmi->ptMinTrackSize.x = 300; + lpmmi->ptMinTrackSize.y = 400; + } + return 0; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDCANCEL: + PostMessage(hwnd, WM_CLOSE, 0, 0); + break; + + case IDC_CLEAR: + PostMessage(dat->hwndIncoming, WM_FT_CLEANUP, 0, 0); + PostMessage(dat->hwndOutgoing, WM_FT_CLEANUP, 0, 0); + break; + } + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case IDC_TABS: + TCITEM tci = {}; + switch (((LPNMHDR)lParam)->code) { + case TCN_SELCHANGING: + tci.mask = TCIF_PARAM; + TabCtrl_GetItem(hwndTab, TabCtrl_GetCurSel(hwndTab), &tci); + ShowWindow((HWND)tci.lParam, SW_HIDE); + break; + + case TCN_SELCHANGE: + tci.mask = TCIF_PARAM; + TabCtrl_GetItem(hwndTab, TabCtrl_GetCurSel(hwndTab), &tci); + ShowWindow((HWND)tci.lParam, SW_SHOW); + break; + } + break; + } + break; + + case M_PRESHUTDOWN: + SendMessage(dat->hwndIncoming, M_PRESHUTDOWN, 0, 0); + SendMessage(dat->hwndOutgoing, M_PRESHUTDOWN, 0, 0); + DestroyWindow(hwnd); + break; + + case WM_CLOSE: + ShowWindow(hwnd, SW_HIDE); + if (File::bAutoClear) { + PostMessage(dat->hwndIncoming, WM_FT_CLEANUP, 0, 0); + PostMessage(dat->hwndOutgoing, WM_FT_CLEANUP, 0, 0); + } + return TRUE; /* Disable default IDCANCEL notification */ + + case WM_DESTROY: + UnhookEvent(dat->hhkPreshutdown); + Window_FreeIcon_IcoLib(hwnd); + DestroyWindow(dat->hwndIncoming); + DestroyWindow(dat->hwndOutgoing); + mir_free(dat); + SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + Utils_SaveWindowPosition(hwnd, NULL, SRFILEMODULE, "FtMgrDlg_"); + break; + + case WM_ACTIVATE: + dat->errorState = TBPF_NOPROGRESS; + wParam = 1; + break; + + case WM_SHOWWINDOW: + if (!wParam) { // hiding + KillTimer(hwnd, 1); + break; + } + lParam = 0; + + case WM_TIMER: + if (pTaskbarInterface) { + SetTimer(hwnd, 1, 400, nullptr); + if ((lParam == ACKRESULT_FAILED) || (lParam == ACKRESULT_DENIED)) + dat->errorState = TBPF_ERROR; + + TFtProgressData prg = { 0 }; + SendMessage(dat->hwndIncoming, M_CALCPROGRESS, (WPARAM)&prg, 0); + SendMessage(dat->hwndOutgoing, M_CALCPROGRESS, (WPARAM)&prg, 0); + if (dat->errorState) { + pTaskbarInterface->SetProgressState(hwnd, dat->errorState); + if (!prg.run) + pTaskbarInterface->SetProgressValue(hwnd, 1, 1); + } + else if (prg.run) + pTaskbarInterface->SetProgressState(hwnd, TBPF_NORMAL); + else if (prg.init || prg.scan) + pTaskbarInterface->SetProgressState(hwnd, TBPF_INDETERMINATE); + else { + pTaskbarInterface->SetProgressState(hwnd, TBPF_NOPROGRESS); + KillTimer(hwnd, 1); + } + + if (prg.run) + pTaskbarInterface->SetProgressValue(hwnd, prg.totalProgress, prg.totalBytes); + } + break; + } + + return FALSE; +} + +HWND FtMgr_Show(bool bForceActivate, bool bFromMenu) +{ + bool bJustCreated = (hwndFtMgr == nullptr); + if (bJustCreated) + hwndFtMgr = CreateDialog(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FTMGR), NULL, FtMgrDlgProc); + + if (bFromMenu) { /* lqbe */ + ShowWindow(hwndFtMgr, SW_RESTORE); + ShowWindow(hwndFtMgr, SW_SHOW); + SetForegroundWindow(hwndFtMgr); + return hwndFtMgr; + } + if (File::bAutoMin && bJustCreated) { /* lqbe */ + ShowWindow(hwndFtMgr, SW_HIDE); + ShowWindow(hwndFtMgr, SW_MINIMIZE); + return hwndFtMgr; + } + if (bForceActivate) { /* lqbe */ + ShowWindow(hwndFtMgr, SW_RESTORE); + ShowWindow(hwndFtMgr, SW_SHOWNOACTIVATE); + SetForegroundWindow(hwndFtMgr); + return hwndFtMgr; + } + if (!bJustCreated && IsWindowVisible(hwndFtMgr)) + return hwndFtMgr; + + ShowWindow(hwndFtMgr, File::bAutoMin ? SW_SHOWMINNOACTIVE : SW_SHOWNOACTIVATE); + return hwndFtMgr; +} + +void FtMgr_Destroy() +{ + if (hwndFtMgr) + DestroyWindow(hwndFtMgr); +} + +void FtMgr_ShowPage(int page) +{ + if (hwndFtMgr) + SendMessage(hwndFtMgr, WM_FT_SELECTPAGE, page, 0); +} + +void FtMgr_AddTransfer(FileDlgData *fdd) +{ + bool bForceActivate = fdd->send || !File::bAutoAccept; + TFtMgrData *dat = (TFtMgrData *)GetWindowLongPtr(FtMgr_Show(bForceActivate, false), GWLP_USERDATA); + if (dat == nullptr) + return; + + HWND hwndBox = fdd->send ? dat->hwndOutgoing : dat->hwndIncoming; + HWND hwndFt = CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FILETRANSFERINFO), hwndBox, DlgProcFileTransfer, (LPARAM)fdd); + ShowWindow(hwndFt, SW_SHOWNA); + SendMessage(hwndBox, WM_FT_ADD, 0, (LPARAM)hwndFt); + FtMgr_ShowPage(fdd->send ? 1 : 0); + fdd->hwndTransfer = hwndFt; +} diff --git a/src/mir_app/src/modules.cpp b/src/mir_app/src/modules.cpp index 0db467d25f..d1bbdedf90 100644 --- a/src/mir_app/src/modules.cpp +++ b/src/mir_app/src/modules.cpp @@ -32,6 +32,7 @@ INT_PTR CheckRestart(); // core: IDD_WAITRESTART int LoadSystemModule(void); // core: m_system.h services
int LoadNewPluginsModuleInfos(void); // core: preloading plugins
int LoadSendRecvAuthModule(void); // core: auth dialogs
+int LoadSendRecvFileModule(void); // code: file send/recv
int LoadNewPluginsModule(void); // core: N.O. plugins
int LoadNetlibModule(void); // core: network
int LoadProtocolsModule(void); // core: protocol manager
@@ -127,6 +128,7 @@ int LoadDefaultModules(void) if (LoadChatModule()) return 1;
if (LoadSendRecvAuthModule()) return 1;
if (LoadDescButtonModule()) return 1;
+ if (LoadSendRecvFileModule()) return 1;
if (LoadOptionsModule()) return 1;
if (LoadProtocolsModule()) return 1;
diff --git a/src/mir_app/src/newplugins.cpp b/src/mir_app/src/newplugins.cpp index 01b7de292c..372ea90d9b 100644 --- a/src/mir_app/src/newplugins.cpp +++ b/src/mir_app/src/newplugins.cpp @@ -114,6 +114,7 @@ static const MUUID pluginBannedList[] = { 0x8d0a046d, 0x8ea9, 0x4c55, { 0xb5, 0x68, 0x38, 0xda, 0x52, 0x05, 0x64, 0xfd } }, // stdauth
{ 0x9d6c3213, 0x02b4, 0x4fe1, { 0x92, 0xe6, 0x52, 0x6d, 0xe1, 0x4f, 0x8d, 0x65 } }, // stdchat
{ 0x1e64fd80, 0x299e, 0x48a0, { 0x94, 0x41, 0xde, 0x28, 0x68, 0x56, 0x3b, 0x6f } }, // stdhelp
+ { 0x39698dce, 0x7ed4, 0x4334, { 0xac, 0x4c, 0xba, 0x8b, 0x37, 0xa8, 0x6f, 0x13 } }, // stdfile
{ 0x53ac190b, 0xe223, 0x4341, { 0x82, 0x5f, 0x70, 0x9d, 0x85, 0x20, 0x21, 0x5b } }, // stdidle
{ 0x312C4F84, 0x75BE, 0x4404, { 0xBC, 0xB1, 0xC1, 0x03, 0xDB, 0xE5, 0xA3, 0xB8 } }, // stdssl
{ 0x621f886b, 0xa7f6, 0x457f, { 0x9d, 0x62, 0x8e, 0xe8, 0x4c, 0x27, 0x59, 0x93 } }, // modernopt
@@ -151,12 +152,11 @@ static MuuidReplacement pluginDefault[] = { MIID_SRMM, L"stdmsg", nullptr }, // 1
{ MIID_UIUSERINFO, L"stduserinfo", nullptr }, // 2
{ MIID_SREMAIL, L"stdemail", nullptr }, // 3
- { MIID_SRFILE, L"stdfile", nullptr }, // 4
- { MIID_UIHISTORY, L"stduihist", nullptr }, // 5
- { MIID_AUTOAWAY, L"stdautoaway", nullptr }, // 6
- { MIID_USERONLINE, L"stduseronline", nullptr }, // 7
- { MIID_SRAWAY, L"stdaway", nullptr }, // 8
- { MIID_POPUP, L"stdpopup", nullptr }, // 9
+ { MIID_UIHISTORY, L"stduihist", nullptr }, // 4
+ { MIID_AUTOAWAY, L"stdautoaway", nullptr }, // 5
+ { MIID_USERONLINE, L"stduseronline", nullptr }, // 6
+ { MIID_SRAWAY, L"stdaway", nullptr }, // 7
+ { MIID_POPUP, L"stdpopup", nullptr }, // 8
};
int getDefaultPluginIdx(const MUUID &muuid)
|
