1
0
Martin Prikryl 11 жил өмнө
parent
commit
dd3f10cf2a
54 өөрчлөгдсөн 663 нэмэгдсэн , 213 устгасан
  1. 27 0
      deployment/WinSCPnet.nuspec
  2. 7 0
      deployment/install.ps1
  3. 18 4
      deployment/winscpsetup.iss
  4. 2 0
      dotnet/SessionEvents.cs
  5. 4 4
      dotnet/properties/AssemblyInfo.cs
  6. 2 2
      source/Console.cbproj
  7. 1 1
      source/DragExt.cbproj
  8. 2 2
      source/DragExt64.rc
  9. 2 2
      source/WinSCP.cbproj
  10. 77 26
      source/core/Common.cpp
  11. 9 3
      source/core/Common.h
  12. 11 2
      source/core/Exceptions.cpp
  13. 1 0
      source/core/Exceptions.h
  14. 13 0
      source/core/FileOperationProgress.cpp
  15. 2 0
      source/core/FileOperationProgress.h
  16. 12 4
      source/core/FtpFileSystem.cpp
  17. 1 0
      source/core/FtpFileSystem.h
  18. 59 5
      source/core/Queue.cpp
  19. 6 0
      source/core/Queue.h
  20. 1 1
      source/core/ScpFileSystem.cpp
  21. 4 4
      source/core/Script.cpp
  22. 20 0
      source/core/SecureShell.cpp
  23. 2 0
      source/core/SecureShell.h
  24. 1 1
      source/core/SessionData.cpp
  25. 3 8
      source/core/SftpFileSystem.cpp
  26. 0 1
      source/core/SftpFileSystem.h
  27. 29 6
      source/core/Terminal.cpp
  28. 5 4
      source/core/WebDAVFileSystem.cpp
  29. 0 1
      source/filezilla/FileZillaApi.h
  30. 1 0
      source/filezilla/FtpControlSocket.cpp
  31. 1 21
      source/filezilla/ServerPath.cpp
  32. 53 21
      source/forms/CustomScpExplorer.cpp
  33. 1 0
      source/forms/CustomScpExplorer.h
  34. 0 1
      source/forms/ImportSessions.dfm
  35. 27 4
      source/forms/Login.cpp
  36. 1 2
      source/forms/Login.dfm
  37. 2 0
      source/forms/Login.h
  38. 119 0
      source/forms/MessageDlg.cpp
  39. 34 29
      source/forms/Progress.cpp
  40. 2 2
      source/forms/SiteAdvanced.cpp
  41. 30 18
      source/packages/filemng/CustomDirView.pas
  42. 2 1
      source/packages/tb2k/TB2Item.pas
  43. 3 0
      source/resource/TextsCore.h
  44. 1 0
      source/resource/TextsCore1.rc
  45. 1 1
      source/resource/TextsWin.h
  46. 1 1
      source/resource/TextsWin1.rc
  47. 27 17
      source/windows/ConsoleRunner.cpp
  48. 2 2
      source/windows/GUIConfiguration.cpp
  49. 2 2
      source/windows/GUITools.cpp
  50. 1 2
      source/windows/QueueController.cpp
  51. 2 1
      source/windows/Setup.cpp
  52. 6 4
      source/windows/VCLCommon.cpp
  53. 3 2
      source/windows/WinConfiguration.cpp
  54. 20 1
      source/windows/WinInterface.cpp

+ 27 - 0
deployment/WinSCPnet.nuspec

@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<package >
+  <metadata>
+    <id>WinSCPnet</id>
+    <version>$version$</version>
+    <title>WinSCP .NET assembly</title>
+    <authors>Martin Prikryl</authors>
+    <owners>Martin Prikryl</owners>
+    <licenseUrl>http://winscp.net/eng/docs/library#license</licenseUrl>
+    <projectUrl>http://winscp.net/eng/docs/library</projectUrl>
+    <iconUrl>http://winscp.net/pad/winscp.png</iconUrl>
+    <requireLicenseAcceptance>false</requireLicenseAcceptance>
+    <summary>The WinSCP .NET assembly is a .NET wrapper around WinSCP’s scripting interface.</summary>
+    <description>The WinSCP .NET assembly is a .NET wrapper around WinSCP’s scripting interface that allows your code to connect to a remote machine and manipulate remote files over SFTP, SCP, and FTP sessions.
+    
+The library is primarily intended for advanced automation tasks that require conditional processing, loops or other control structures for which the basic scripting interface is too limited. The library is not a general purpose file transfer library. It particularly lacks support for interactive processing and as such it is not well suited for use in GUI applications.
+
+For documentation and examples of use, see project website.</description>
+    <copyright>Copyright © 2012-2014 Martin Prikryl</copyright>
+    <tags>winscp sftp ftp ftps scp transfer</tags>
+  </metadata>
+  <files>
+    <file src="$BuildConfigDir$\WinSCPnet.dll" target="lib"/>
+    <file src="$BuildConfigDir$\WinSCP.exe" target="content"/>
+    <file src="install.ps1" target="tools"/>
+  </files>
+</package>

+ 7 - 0
deployment/install.ps1

@@ -0,0 +1,7 @@
+param($installPath, $toolsPath, $package, $project)
+
+$file1 = $project.ProjectItems.Item("WinSCP.exe")
+
+# set 'Copy To Output Directory' to 'Copy if newer'
+$copyToOutput = $file1.Properties.Item("CopyToOutputDirectory")
+$copyToOutput.Value = 2

+ 18 - 4
deployment/winscpsetup.iss

@@ -274,7 +274,7 @@ Filename: "{app}\WinSCP.exe"; Parameters: "/RegisterForDefaultProtocols"; \
 Filename: "{app}\WinSCP.exe"; Parameters: "/AddSearchPath"; \
   StatusMsg: {cm:AddingSearchPath}; Tasks: searchpath
 Filename: "{app}\WinSCP.exe"; Parameters: "/ImportSitesIfAny"; \
-  StatusMsg: {cm:ImportSites}
+  StatusMsg: {cm:ImportSites}; Flags: skipifsilent
 Filename: "{app}\WinSCP.exe"; Parameters: "/Usage=TypicalInstallation:1"; \
   Check: IsTypicalInstallation
 Filename: "{app}\WinSCP.exe"; Parameters: "/Usage=TypicalInstallation:0"; \
@@ -745,8 +745,6 @@ end;
 
 #endif
 
-#ifdef Chrome
-
 procedure LoadEmbededBitmap(Image: TBitmapImage; Name: string);
 begin
   ExtractTemporaryFile(Name);
@@ -754,6 +752,8 @@ begin
   Image.AutoSize := True;
 end;
 
+#ifdef Chrome
+
 procedure ChromeCheckboxClick(Sender: TObject);
 begin
   ChromeDefaultCheckbox.Enabled := ChromeCheckbox.Checked;
@@ -1334,14 +1334,18 @@ begin
     begin
       LaunchCheckboxTop := WizardForm.RunList.Top;
 #ifdef Donations
+#ifdef Chrome
       DonationPanel.Visible := not IsChromeSelected;
+#endif
 #endif
     end;
 
     LaunchCheckbox.Top := LaunchCheckboxTop;
     OpenGettingStartedCheckbox.Top := LaunchCheckbox.Top + LineHeight;
+#ifdef Chrome
     LaunchChromeCheckbox.Visible := IsChromeSelected;
     LaunchChromeCheckbox.Top := OpenGettingStartedCheckbox.Top + LineHeight;
+#endif    
 
     UpdatePostInstallRunCheckboxes(nil);
   end;
@@ -1444,7 +1448,9 @@ var
   Path: string;
   WebGettingStarted: string;
   OpenGettingStarted: Boolean;
+#ifdef Chrome
   LaunchChrome: Boolean;
+#endif  
 begin
   if CurStep = ssPostInstall then
   begin
@@ -1471,10 +1477,12 @@ begin
         OpenGettingStartedCheckbox.Enabled and
          OpenGettingStartedCheckbox.Checked;
 
+#ifdef Chrome
       LaunchChrome :=
         ChromeAllowed and IsChromeSelected and
          LaunchChromeCheckbox.Visible and // sanity check
          LaunchChromeCheckbox.Checked;
+#endif         
 
       if OpenGettingStarted then
       begin
@@ -1486,7 +1494,11 @@ begin
 
       if LaunchCheckbox.Checked then
       begin
-        if OpenGettingStarted or LaunchChrome then
+        if OpenGettingStarted 
+#ifdef Chrome
+           or LaunchChrome
+#endif
+           then
         begin
           Log('Will launch WinSCP minimized');
           ShowCmd := SW_SHOWMINIMIZED
@@ -1501,6 +1513,7 @@ begin
         ExecAsOriginalUser(Path, '', '', ShowCmd, ewNoWait, ErrorCode)
       end;
 
+#ifdef Chrome
       if LaunchChrome then
       begin
         Log('Launching Chrome');
@@ -1515,6 +1528,7 @@ begin
           MsgBox(ExpandConstant('{cm:ChromeInstallationFailed}'), mbError, MB_OK);
         end;
       end;
+#endif      
     end;
   end;
 

+ 2 - 0
dotnet/SessionEvents.cs

@@ -13,5 +13,7 @@ namespace WinSCP
         void Failed(object sender, FailedEventArgs e);
         [DispId(3)]
         void OutputDataReceived(object sender, OutputDataReceivedEventArgs e);
+        [DispId(4)]
+        void FileTransferProgress(object sender, FileTransferProgressEventArgs e);
     }
 }

+ 4 - 4
dotnet/properties/AssemblyInfo.cs

@@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
 // set of attributes. Change these attribute values to modify the information
 // associated with an assembly.
 [assembly: AssemblyTitle("WinSCPnet")]
-[assembly: AssemblyDescription("WinSCP console interface .NET wrapper")]
+[assembly: AssemblyDescription("WinSCP scripting interface .NET wrapper")]
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("Martin Prikryl")]
 [assembly: AssemblyProduct("WinSCP")]
@@ -19,9 +19,9 @@ using System.Runtime.InteropServices;
 // The following GUID is for the ID of the typelib if this project is exposed to COM
 [assembly: Guid("a0b93468-d98a-4845-a234-8076229ad93f")]
 
-[assembly: AssemblyVersion("1.1.4.0")]
-[assembly: AssemblyFileVersion("1.1.4.0")]
-[assembly: AssemblyInformationalVersionAttribute("5.5.1.0")]
+[assembly: AssemblyVersion("1.1.5.0")]
+[assembly: AssemblyFileVersion("1.1.5.0")]
+[assembly: AssemblyInformationalVersionAttribute("5.5.2.0")]
 
 [assembly: CLSCompliant(true)]
 

+ 2 - 2
source/Console.cbproj

@@ -41,10 +41,10 @@
 			<PackageImports>rtl.bpi;$(PackageImports)</PackageImports>
 			<ProjectType>CppConsoleApplication</ProjectType>
 			<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
-			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Console interface for WinSCP;FileVersion=4.0.2.0;InternalName=console;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.com;ProductName=WinSCP;ProductVersion=5.5.1.0;ReleaseType=stable;WWW=http://winscp.net/</VerInfo_Keys>
+			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Console interface for WinSCP;FileVersion=4.0.3.0;InternalName=console;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.com;ProductName=WinSCP;ProductVersion=5.5.2.0;ReleaseType=stable;WWW=http://winscp.net/</VerInfo_Keys>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_MajorVer>4</VerInfo_MajorVer>
-			<VerInfo_Release>2</VerInfo_Release>
+			<VerInfo_Release>3</VerInfo_Release>
 		</PropertyGroup>
 		<PropertyGroup Condition="'$(Cfg_1)'!=''">
 			<BCC_DebugLineNumbers>true</BCC_DebugLineNumbers>

+ 1 - 1
source/DragExt.cbproj

@@ -42,7 +42,7 @@
 			<ProjectType>CppDynamicLibrary</ProjectType>
 			<VerInfo_DLL>true</VerInfo_DLL>
 			<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
-			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Drag&amp;Drop shell extension for WinSCP (32-bit);FileVersion=1.2.1.0;InternalName=dragext32;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=dragext.dll;ProductName=WinSCP;ProductVersion=5.5.1.0;ReleaseType=stable;WWW=http://winscp.net/</VerInfo_Keys>
+			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Drag&amp;Drop shell extension for WinSCP (32-bit);FileVersion=1.2.1.0;InternalName=dragext32;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=dragext.dll;ProductName=WinSCP;ProductVersion=5.5.2.0;ReleaseType=stable;WWW=http://winscp.net/</VerInfo_Keys>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_MinorVer>2</VerInfo_MinorVer>
 			<VerInfo_Release>1</VerInfo_Release>

+ 2 - 2
source/DragExt64.rc

@@ -1,6 +1,6 @@
 1 VERSIONINFO
 FILEVERSION 1,2,1,0
-PRODUCTVERSION 5,5,1,0
+PRODUCTVERSION 5,5,2,0
 FILEOS 0x4
 FILETYPE 0x2
 {
@@ -16,7 +16,7 @@ FILETYPE 0x2
             VALUE "LegalTrademarks", "\0"
             VALUE "OriginalFilename", "dragext64.dll\0"
             VALUE "ProductName", "WinSCP\0"
-            VALUE "ProductVersion", "5.5.1.0\0"
+            VALUE "ProductVersion", "5.5.2.0\0"
             VALUE "ReleaseType", "stable\0"
             VALUE "WWW", "http://winscp.net/\0"
         }

+ 2 - 2
source/WinSCP.cbproj

@@ -54,11 +54,11 @@
 			<ProjectType>CppVCLApplication</ProjectType>
 			<UsingDelphiRTL>true</UsingDelphiRTL>
 			<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
-			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=WinSCP: SFTP, FTP and SCP client;FileVersion=5.5.1.0;InternalName=winscp;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.exe;ProductName=WinSCP;ProductVersion=5.5.1.0;ReleaseType=stable;WWW=http://winscp.net/</VerInfo_Keys>
+			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=WinSCP: SFTP, FTP and SCP client;FileVersion=5.5.2.0;InternalName=winscp;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.exe;ProductName=WinSCP;ProductVersion=5.5.2.0;ReleaseType=stable;WWW=http://winscp.net/</VerInfo_Keys>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_MajorVer>5</VerInfo_MajorVer>
 			<VerInfo_MinorVer>5</VerInfo_MinorVer>
-			<VerInfo_Release>1</VerInfo_Release>
+			<VerInfo_Release>2</VerInfo_Release>
 		</PropertyGroup>
 		<PropertyGroup Condition="'$(Cfg_1)'!=''">
 			<BCC_DebugLineNumbers>true</BCC_DebugLineNumbers>

+ 77 - 26
source/core/Common.cpp

@@ -367,6 +367,8 @@ UnicodeString __fastcall GetShellFolderPath(int CSIdl)
   return Result;
 }
 //---------------------------------------------------------------------------
+// Particularly needed when using file name selected by TFilenameEdit,
+// as it wraps a path to double-quotes, when there is a space in the path.
 UnicodeString __fastcall StripPathQuotes(const UnicodeString Path)
 {
   if ((Path.Length() >= 2) &&
@@ -838,28 +840,35 @@ bool __fastcall IsHex(wchar_t Ch)
     ((Ch >= 'a') && (Ch <= 'f'));
 }
 //---------------------------------------------------------------------------
-int __fastcall FindCheck(int Result)
+int __fastcall FindCheck(int Result, const UnicodeString & Path)
 {
   if ((Result != ERROR_SUCCESS) &&
       (Result != ERROR_FILE_NOT_FOUND) &&
       (Result != ERROR_NO_MORE_FILES))
   {
-    RaiseLastOSError(Result);
+    throw EOSExtException(FMTLOAD(FIND_FILE_ERROR, (Path)), Result);
   }
   return Result;
 }
 //---------------------------------------------------------------------------
-int __fastcall FindFirstChecked(const UnicodeString & Path, int Attr, TSearchRec & F)
+int __fastcall FindFirstUnchecked(const UnicodeString & Path, int Attr, TSearchRecChecked & F)
 {
-  return FindCheck(FindFirst(Path, Attr, F));
+  F.Path = Path;
+  return FindFirst(Path, Attr, F);
+}
+//---------------------------------------------------------------------------
+int __fastcall FindFirstChecked(const UnicodeString & Path, int Attr, TSearchRecChecked & F)
+{
+  int Result = FindFirstUnchecked(Path, Attr, F);
+  return FindCheck(Result, F.Path);
 }
 //---------------------------------------------------------------------------
 // It can make sense to use FindNextChecked, even if unchecked FindFirst is used.
 // I.e. even if we do not care that FindFirst failed, if FindNext
 // failes after successfull FindFirst, it mean some terrible problem
-int __fastcall FindNextChecked(TSearchRec & F)
+int __fastcall FindNextChecked(TSearchRecChecked & F)
 {
-  return FindCheck(FindNext(F));
+  return FindCheck(FindNext(F), F.Path);
 }
 //---------------------------------------------------------------------------
 bool __fastcall FileSearchRec(const UnicodeString FileName, TSearchRec & Rec)
@@ -882,7 +891,7 @@ void __fastcall ProcessLocalDirectory(UnicodeString DirName,
   {
     FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive;
   }
-  TSearchRec SearchRec;
+  TSearchRecChecked SearchRec;
 
   DirName = IncludeTrailingBackslash(DirName);
   if (FindFirstChecked(DirName + L"*.*", FindAttrs, SearchRec) == 0)
@@ -962,9 +971,26 @@ struct TDateTimeParams
   SYSTEMTIME SystemDaylightDate;
   TDateTime StandardDate;
   TDateTime DaylightDate;
-  bool SummerDST;
+  UnicodeString StandardName;
+  UnicodeString DaylightName;
   // This is actually global, not per-year
   bool DaylightHack;
+
+  bool HasDST() const
+  {
+    // On some systems it occurs that StandardDate is unset, while
+    // DaylightDate is set. MSDN states that this is invalid and
+    // should be treated as if there is no daylight saving.
+    // So check both.
+    return
+      (SystemStandardDate.wMonth != 0) &&
+      (SystemDaylightDate.wMonth != 0);
+  }
+
+  bool SummerDST() const
+  {
+    return HasDST() && (DaylightDate < StandardDate);
+  }
 };
 typedef std::map<int, TDateTimeParams> TYearlyDateTimeParams;
 static TYearlyDateTimeParams YearlyDateTimeParams;
@@ -1055,15 +1081,14 @@ static const TDateTimeParams * __fastcall GetDateTimeParams(unsigned short Year)
     Result->SystemDaylightDate = TZI.DaylightDate;
 
     unsigned short AYear = (Year != 0) ? Year : DecodeYear(Now());
-    if (Result->SystemStandardDate.wMonth != 0)
+    if (Result->HasDST())
     {
       EncodeDSTMargin(Result->SystemStandardDate, AYear, Result->StandardDate);
-    }
-    if (Result->SystemDaylightDate.wMonth != 0)
-    {
       EncodeDSTMargin(Result->SystemDaylightDate, AYear, Result->DaylightDate);
     }
-    Result->SummerDST = (Result->DaylightDate < Result->StandardDate);
+
+    Result->StandardName = TZI.StandardName;
+    Result->DaylightName = TZI.DaylightName;
 
     Result->DaylightHack = !IsWin7();
   }
@@ -1114,15 +1139,14 @@ static bool __fastcall IsDateInDST(const TDateTime & DateTime)
   // DaylightDate is set. MSDN states that this is invalid and
   // should be treated as if there is no daylight saving.
   // So check both.
-  if ((Params->SystemStandardDate.wMonth == 0) ||
-      (Params->SystemDaylightDate.wMonth == 0))
+  if (!Params->HasDST())
   {
     Result = false;
   }
   else
   {
 
-    if (Params->SummerDST)
+    if (Params->SummerDST())
     {
       Result =
         (DateTime >= Params->DaylightDate) &&
@@ -1247,11 +1271,21 @@ FILETIME __fastcall DateTimeToFileTime(const TDateTime DateTime,
   const TDateTimeParams * Params = GetDateTimeParams(DecodeYear(DateTime));
   if (!Params->DaylightHack)
   {
+    // We should probably use reversed code of FileTimeToDateTime here instead of custom implementation
+
+    // We are incrementing and decrementing BaseDifferenceSec because it
+    // can actually change between years
+    // (as it did in Belarus from GMT+2 to GMT+3 between 2011 and 2012)
+
     UnixTimeStamp += (IsDateInDST(DateTime) ?
-      Params->DaylightDifferenceSec : Params->StandardDifferenceSec);
+      Params->DaylightDifferenceSec : Params->StandardDifferenceSec) +
+      Params->BaseDifferenceSec;
 
     const TDateTimeParams * CurrentParams = GetDateTimeParams(0);
-    UnixTimeStamp -= CurrentParams->CurrentDaylightDifferenceSec;
+    UnixTimeStamp -=
+      CurrentParams->CurrentDaylightDifferenceSec +
+      CurrentParams->BaseDifferenceSec;
+
   }
 
   FILETIME Result;
@@ -1500,15 +1534,27 @@ static UnicodeString __fastcall FormatTimeZone(long Sec)
 //---------------------------------------------------------------------------
 UnicodeString __fastcall GetTimeZoneLogString()
 {
-  const TDateTimeParams * Params = GetDateTimeParams(0);
+  const TDateTimeParams * CurrentParams = GetDateTimeParams(0);
 
   UnicodeString Result =
-    FORMAT(L"Current: GMT%s, Standard: GMT%s, DST: GMT%s, DST Start: %s, DST End: %s",
-      (FormatTimeZone(Params->CurrentDifferenceSec),
-       FormatTimeZone(Params->BaseDifferenceSec + Params->StandardDifferenceSec),
-       FormatTimeZone(Params->BaseDifferenceSec + Params->DaylightDifferenceSec),
-       Params->DaylightDate.DateString(),
-       Params->StandardDate.DateString()));
+    FORMAT(L"Current: GMT%s", (FormatTimeZone(CurrentParams->CurrentDifferenceSec)));
+
+  if (!CurrentParams->HasDST())
+  {
+    Result += FORMAT(L" (%s), No DST", (CurrentParams->StandardName));
+  }
+  else
+  {
+    Result +=
+      FORMAT(L", Standard: GMT%s (%s), DST: GMT%s (%s), DST Start: %s, DST End: %s",
+        (FormatTimeZone(CurrentParams->BaseDifferenceSec + CurrentParams->StandardDifferenceSec),
+         CurrentParams->StandardName,
+         FormatTimeZone(CurrentParams->BaseDifferenceSec + CurrentParams->DaylightDifferenceSec),
+         CurrentParams->DaylightName,
+         CurrentParams->DaylightDate.DateString(),
+         CurrentParams->StandardDate.DateString()));
+  }
+
   return Result;
 }
 //---------------------------------------------------------------------------
@@ -1588,9 +1634,14 @@ int __fastcall TimeToMSec(TDateTime T)
   return int(Round(double(T) * double(MSecsPerDay)));
 }
 //---------------------------------------------------------------------------
+int __fastcall TimeToSeconds(TDateTime T)
+{
+  return TimeToMSec(T) / MSecsPerSec;
+}
+//---------------------------------------------------------------------------
 int __fastcall TimeToMinutes(TDateTime T)
 {
-  return TimeToMSec(T) / MSecsPerSec / SecsPerMin;
+  return TimeToSeconds(T) / SecsPerMin;
 }
 //---------------------------------------------------------------------------
 bool __fastcall RecursiveDeleteFile(const UnicodeString FileName, bool ToRecycleBin)

+ 9 - 3
source/core/Common.h

@@ -114,9 +114,14 @@ UnicodeString __fastcall ExtractFileBaseName(const UnicodeString & Path);
 typedef void __fastcall (__closure* TProcessLocalFileEvent)
   (const UnicodeString FileName, const TSearchRec Rec, void * Param);
 bool __fastcall FileSearchRec(const UnicodeString FileName, TSearchRec & Rec);
-int __fastcall FindCheck(int Result);
-int __fastcall FindFirstChecked(const UnicodeString & Path, int Attr, TSearchRec & F);
-int __fastcall FindNextChecked(TSearchRec & F);
+struct TSearchRecChecked : public TSearchRec
+{
+  UnicodeString Path;
+};
+int __fastcall FindCheck(int Result, const UnicodeString & Path);
+int __fastcall FindFirstUnchecked(const UnicodeString & Path, int Attr, TSearchRecChecked & F);
+int __fastcall FindFirstChecked(const UnicodeString & Path, int Attr, TSearchRecChecked & F);
+int __fastcall FindNextChecked(TSearchRecChecked & F);
 void __fastcall ProcessLocalDirectory(UnicodeString DirName,
   TProcessLocalFileEvent CallBackFunc, void * Param = NULL, int FindAttrs = -1);
 //---------------------------------------------------------------------------
@@ -148,6 +153,7 @@ UnicodeString __fastcall GetTimeZoneLogString();
 bool __fastcall AdjustClockForDSTEnabled();
 int __fastcall CompareFileTime(TDateTime T1, TDateTime T2);
 int __fastcall TimeToMSec(TDateTime T);
+int __fastcall TimeToSeconds(TDateTime T);
 int __fastcall TimeToMinutes(TDateTime T);
 //---------------------------------------------------------------------------
 template<class MethodT>

+ 11 - 2
source/core/Exceptions.cpp

@@ -323,9 +323,8 @@ ExtException * __fastcall ExtException::Clone()
   return new ExtException(this, L"");
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall LastSysErrorMessage()
+UnicodeString __fastcall SysErrorMessageForError(int LastError)
 {
-  int LastError = GetLastError();
   UnicodeString Result;
   if (LastError != 0)
   {
@@ -334,11 +333,21 @@ UnicodeString __fastcall LastSysErrorMessage()
   return Result;
 }
 //---------------------------------------------------------------------------
+UnicodeString __fastcall LastSysErrorMessage()
+{
+  return SysErrorMessageForError(GetLastError());
+}
+//---------------------------------------------------------------------------
 __fastcall EOSExtException::EOSExtException(UnicodeString Msg) :
   ExtException(Msg, LastSysErrorMessage())
 {
 }
 //---------------------------------------------------------------------------
+__fastcall EOSExtException::EOSExtException(UnicodeString Msg, int LastError) :
+  ExtException(Msg, SysErrorMessageForError(LastError))
+{
+}
+//---------------------------------------------------------------------------
 __fastcall EFatal::EFatal(Exception* E, UnicodeString Msg, UnicodeString HelpKeyword) :
   ExtException(Msg, E, HelpKeyword),
   FReopenQueried(false)

+ 1 - 0
source/core/Exceptions.h

@@ -120,6 +120,7 @@ class EOSExtException : public ExtException
 public:
   __fastcall EOSExtException();
   __fastcall EOSExtException(UnicodeString Msg);
+  __fastcall EOSExtException(UnicodeString Msg, int LastError);
 };
 //---------------------------------------------------------------------------
 class EFatal : public ExtException

+ 13 - 0
source/core/FileOperationProgress.cpp

@@ -4,6 +4,7 @@
 
 #include "Common.h"
 #include "FileOperationProgress.h"
+#include "CoreMain.h"
 //---------------------------------------------------------------------------
 #define TRANSFER_BUF_SIZE 4096
 //---------------------------------------------------------------------------
@@ -55,6 +56,7 @@ void __fastcall TFileOperationProgressType::Clear()
   CPSLimit = 0;
   FTicks.clear();
   FTotalTransferredThen.clear();
+  FCounterSet = false;
   ClearTransfer();
 }
 //---------------------------------------------------------------------------
@@ -243,9 +245,20 @@ bool __fastcall TFileOperationProgressType::IsLocallyDone()
   return (LocallyUsed == LocalSize);
 }
 //---------------------------------------------------------------------------
+void __fastcall TFileOperationProgressType::SetSpeedCounters()
+{
+  if ((CPSLimit > 0) && !FCounterSet)
+  {
+    FCounterSet = true;
+    Configuration->Usage->Inc(L"SpeedLimitUses");
+  }
+}
+//---------------------------------------------------------------------------
 unsigned long __fastcall TFileOperationProgressType::AdjustToCPSLimit(
   unsigned long Size)
 {
+  SetSpeedCounters();
+
   if (CPSLimit > 0)
   {
     // we must not return 0, hence, if we reach zero,

+ 2 - 0
source/core/FileOperationProgress.h

@@ -33,6 +33,7 @@ private:
   bool FReset;
   unsigned int FLastSecond;
   unsigned long FRemainingCPS;
+  bool FCounterSet;
   std::vector<unsigned long> FTicks;
   std::vector<__int64> FTotalTransferredThen;
 
@@ -121,6 +122,7 @@ public:
   int __fastcall TransferProgress();
   int __fastcall OverallProgress();
   int __fastcall TotalTransferProgress();
+  void __fastcall SetSpeedCounters();
 };
 //---------------------------------------------------------------------------
 class TSuspendFileOperationProgress

+ 12 - 4
source/core/FtpFileSystem.cpp

@@ -1000,10 +1000,18 @@ void __fastcall TFTPFileSystem::DoFileTransferProgress(__int64 TransferSize,
 
   if (FFileTransferCPSLimit != OperationProgress->CPSLimit)
   {
-    FFileTransferCPSLimit = OperationProgress->CPSLimit;
+    SetCPSLimit(OperationProgress);
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TFTPFileSystem::SetCPSLimit(TFileOperationProgressType * OperationProgress)
+{
+  // Any reason we use separate field intead of directly using OperationProgress->CPSLimit?
+  // Maybe thread-safety?
+  FFileTransferCPSLimit = OperationProgress->CPSLimit;
+  OperationProgress->SetSpeedCounters();
+}
+//---------------------------------------------------------------------------
 void __fastcall TFTPFileSystem::FileTransferProgress(__int64 TransferSize,
   __int64 Bytes)
 {
@@ -1235,7 +1243,7 @@ void __fastcall TFTPFileSystem::Sink(const UnicodeString FileName,
       // ignore file list
       TFileListHelper Helper(this, NULL, true);
 
-      FFileTransferCPSLimit = OperationProgress->CPSLimit;
+      SetCPSLimit(OperationProgress);
       FFileTransferPreserveTime = CopyParam->PreserveTime;
       // not used for downloads anyway
       FFileTransferRemoveBOM = CopyParam->RemoveBOM;
@@ -1479,7 +1487,7 @@ void __fastcall TFTPFileSystem::Source(const UnicodeString FileName,
       // ignore file list
       TFileListHelper Helper(this, NULL, true);
 
-      FFileTransferCPSLimit = OperationProgress->CPSLimit;
+      SetCPSLimit(OperationProgress);
       // not used for uploads anyway
       FFileTransferPreserveTime = CopyParam->PreserveTime;
       FFileTransferRemoveBOM = CopyParam->RemoveBOM;
@@ -1536,7 +1544,7 @@ void __fastcall TFTPFileSystem::DirectorySource(const UnicodeString DirectoryNam
   OperationProgress->SetFile(DirectoryName);
 
   int FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive;
-  TSearchRec SearchRec;
+  TSearchRecChecked SearchRec;
   bool FindOK;
 
   FILE_OPERATION_LOOP (FMTLOAD(LIST_DIR_ERROR, (DirectoryName)),

+ 1 - 0
source/core/FtpFileSystem.h

@@ -182,6 +182,7 @@ protected:
     TDateTime & DateTime, TModificationFmt & ModificationFmt);
   void __fastcall SetLastCode(int Code);
   void __fastcall StoreLastResponse(const UnicodeString & Text);
+  void __fastcall SetCPSLimit(TFileOperationProgressType * OperationProgress);
 
   static bool __fastcall Unquote(UnicodeString & Str);
   static UnicodeString __fastcall ExtractStatusMessage(UnicodeString Status);

+ 59 - 5
source/core/Queue.cpp

@@ -910,6 +910,25 @@ bool __fastcall TTerminalQueue::ItemSetCPSLimit(TQueueItem * Item, unsigned long
   return Result;
 }
 //---------------------------------------------------------------------------
+bool __fastcall TTerminalQueue::ItemGetCPSLimit(TQueueItem * Item, unsigned long & CPSLimit)
+{
+  CPSLimit = 0;
+  // to prevent deadlocks when closing queue from other thread
+  bool Result = !FFinished;
+  if (Result)
+  {
+    TGuard Guard(FItemsSection);
+
+    Result = (FItems->IndexOf(Item) >= 0);
+    if (Result)
+    {
+      CPSLimit = Item->GetCPSLimit();
+    }
+  }
+
+  return Result;
+}
+//---------------------------------------------------------------------------
 void __fastcall TTerminalQueue::Idle()
 {
   TDateTime N = Now();
@@ -1582,15 +1601,17 @@ void __fastcall TQueueItem::SetProgress(
   {
     TGuard Guard(FSection);
 
-    assert(FProgressData != NULL);
-    *FProgressData = ProgressData;
-    FProgressData->Reset();
-
-    if (FCPSLimit >= 0)
+    // do not lose CPS limit override on "calculate size" operation,
+    // wait until the real transfer operation starts
+    if ((FCPSLimit >= 0) && ((ProgressData.Operation == foMove) || (ProgressData.Operation == foCopy)))
     {
       ProgressData.CPSLimit = static_cast<unsigned long>(FCPSLimit);
       FCPSLimit = -1;
     }
+
+    assert(FProgressData != NULL);
+    *FProgressData = ProgressData;
+    FProgressData->Reset();
   }
   FQueue->DoQueueItemUpdate(this);
 }
@@ -1631,6 +1652,29 @@ void __fastcall TQueueItem::SetCPSLimit(unsigned long CPSLimit)
   FCPSLimit = static_cast<long>(CPSLimit);
 }
 //---------------------------------------------------------------------------
+unsigned long __fastcall TQueueItem::DefaultCPSLimit()
+{
+  return 0;
+}
+//---------------------------------------------------------------------------
+unsigned long __fastcall TQueueItem::GetCPSLimit()
+{
+  unsigned long Result;
+  if (FCPSLimit >= 0)
+  {
+    Result = FCPSLimit;
+  }
+  else if (FProgressData != NULL)
+  {
+    Result = FProgressData->CPSLimit;
+  }
+  else
+  {
+    Result = DefaultCPSLimit();
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 // TQueueItemProxy
 //---------------------------------------------------------------------------
 __fastcall TQueueItemProxy::TQueueItemProxy(TTerminalQueue * Queue,
@@ -1744,6 +1788,11 @@ bool __fastcall TQueueItemProxy::ProcessUserAction()
   return Result;
 }
 //---------------------------------------------------------------------------
+bool __fastcall TQueueItemProxy::GetCPSLimit(unsigned long & CPSLimit)
+{
+  return FQueue->ItemGetCPSLimit(FQueueItem, CPSLimit);
+}
+//---------------------------------------------------------------------------
 bool __fastcall TQueueItemProxy::SetCPSLimit(unsigned long CPSLimit)
 {
   return FQueue->ItemSetCPSLimit(FQueueItem, CPSLimit);
@@ -1913,6 +1962,11 @@ __fastcall TTransferQueueItem::~TTransferQueueItem()
   delete FCopyParam;
 }
 //---------------------------------------------------------------------------
+unsigned long __fastcall TTransferQueueItem::DefaultCPSLimit()
+{
+  return FCopyParam->CPSLimit;
+}
+//---------------------------------------------------------------------------
 // TUploadQueueItem
 //---------------------------------------------------------------------------
 __fastcall TUploadQueueItem::TUploadQueueItem(TTerminal * Terminal,

+ 6 - 0
source/core/Queue.h

@@ -128,6 +128,7 @@ protected:
   bool __fastcall ItemDelete(TQueueItem * Item);
   bool __fastcall ItemPause(TQueueItem * Item, bool Pause);
   bool __fastcall ItemSetCPSLimit(TQueueItem * Item, unsigned long CPSLimit);
+  bool __fastcall ItemGetCPSLimit(TQueueItem * Item, unsigned long & CPSLimit);
 
   void __fastcall RetryItem(TQueueItem * Item);
   void __fastcall DeleteItem(TQueueItem * Item, bool CanKeep);
@@ -193,6 +194,8 @@ protected:
   void __fastcall SetProgress(TFileOperationProgressType & ProgressData);
   void __fastcall GetData(TQueueItemProxy * Proxy);
   void __fastcall SetCPSLimit(unsigned long CPSLimit);
+  unsigned long __fastcall GetCPSLimit();
+  virtual unsigned long __fastcall DefaultCPSLimit();
   virtual UnicodeString __fastcall StartupDirectory() = 0;
   void __fastcall Complete();
 };
@@ -213,6 +216,7 @@ public:
   bool __fastcall Pause();
   bool __fastcall Resume();
   bool __fastcall SetCPSLimit(unsigned long CPSLimit);
+  bool __fastcall GetCPSLimit(unsigned long & CPSLimit);
 
   __property TFileOperationProgressType * ProgressData = { read = GetProgressData };
   __property __int64 TotalTransferred = { read = GetTotalTransferred };
@@ -302,6 +306,8 @@ protected:
   UnicodeString FTargetDir;
   TCopyParamType * FCopyParam;
   int FParams;
+
+  virtual unsigned long __fastcall DefaultCPSLimit();
 };
 //---------------------------------------------------------------------------
 class TUploadQueueItem : public TTransferQueueItem

+ 1 - 1
source/core/ScpFileSystem.cpp

@@ -1917,7 +1917,7 @@ void __fastcall TSCPFileSystem::SCPDirectorySource(const UnicodeString Directory
   try
   {
     int FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive;
-    TSearchRec SearchRec;
+    TSearchRecChecked SearchRec;
     bool FindOK;
 
     FILE_OPERATION_LOOP (FMTLOAD(LIST_DIR_ERROR, (DirectoryName)),

+ 4 - 4
source/core/Script.cpp

@@ -613,9 +613,9 @@ TStrings * __fastcall TScript::CreateLocalFileList(TScriptProcParams * Parameter
       UnicodeString FileName = Parameters->Param[i];
       if (FLAGSET(ListType, fltMask))
       {
-        TSearchRec SearchRec;
+        TSearchRecChecked SearchRec;
         int FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive;
-        if (FindFirst(FileName, FindAttrs, SearchRec) == 0)
+        if (FindFirstUnchecked(FileName, FindAttrs, SearchRec) == 0)
         {
           UnicodeString Directory = ExtractFilePath(FileName);
           try
@@ -2377,9 +2377,9 @@ void __fastcall TManagementScript::LLsProc(TScriptProcParams * Parameters)
     Mask = L"*.*";
   }
 
-  TSearchRec SearchRec;
+  TSearchRecChecked SearchRec;
   int FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive;
-  if (FindFirst(IncludeTrailingBackslash(Directory) + Mask, FindAttrs, SearchRec) != 0)
+  if (FindFirstUnchecked(IncludeTrailingBackslash(Directory) + Mask, FindAttrs, SearchRec) != 0)
   {
     throw EOSExtException(FMTLOAD(LIST_DIR_ERROR, (Directory)));
   }

+ 20 - 0
source/core/SecureShell.cpp

@@ -50,6 +50,7 @@ __fastcall TSecureShell::TSecureShell(TSessionUI* UI,
   FActive = false;
   FWaiting = 0;
   FOpened = false;
+  FOpenSSH = false;
   OutPtr = NULL;
   Pending = NULL;
   FBackendHandle = NULL;
@@ -132,6 +133,11 @@ const TSessionInfo & __fastcall TSecureShell::GetSessionInfo()
   }
   return FSessionInfo;
 }
+//---------------------------------------------------------------------------
+bool __fastcall TSecureShell::IsOpenSSH()
+{
+  return FOpenSSH;
+}
 //---------------------------------------------------------------------
 Conf * __fastcall TSecureShell::StoreToConfig(TSessionData * Data, bool Simple)
 {
@@ -408,6 +414,11 @@ void __fastcall TSecureShell::Open()
 
   assert(!FSessionInfo.SshImplementation.IsEmpty());
   FOpened = true;
+
+  FOpenSSH =
+    // Sun SSH is based on OpenSSH (suffers the same bugs)
+    (GetSessionInfo().SshImplementation.Pos(L"OpenSSH") == 1) ||
+    (GetSessionInfo().SshImplementation.Pos(L"Sun_SSH") == 1);
 }
 //---------------------------------------------------------------------------
 bool __fastcall TSecureShell::TryFtp()
@@ -2165,4 +2176,13 @@ void __fastcall TSecureShell::CollectUsage()
   {
     Configuration->Usage->Inc(L"OpenedSessionsSSH2");
   }
+
+  if (FOpenSSH)
+  {
+    Configuration->Usage->Inc(L"OpenedSessionsSSHOpenSSH");
+  }
+  else
+  {
+    Configuration->Usage->Inc(L"OpenedSessionsSSHOther");
+  }
 }

+ 2 - 0
source/core/SecureShell.h

@@ -47,6 +47,7 @@ private:
   bool FNoConnectionResponse;
   bool FCollectPrivateKeyUsage;
   int FWaitingForData;
+  bool FOpenSSH;
 
   unsigned PendLen;
   unsigned PendSize;
@@ -130,6 +131,7 @@ public:
   void __fastcall ClearStdError();
   bool __fastcall GetStoredCredentialsTried();
   void __fastcall CollectUsage();
+  bool __fastcall IsOpenSSH();
 
   void __fastcall RegisterReceiveHandler(TNotifyEvent Handler);
   void __fastcall UnregisterReceiveHandler(TNotifyEvent Handler);

+ 1 - 1
source/core/SessionData.cpp

@@ -1015,7 +1015,7 @@ void __fastcall TSessionData::ImportFromFilezilla(_di_IXMLNode Node, const Unico
     FtpAccount = ReadXmlNode(Node, L"Account", FtpAccount);
   }
 
-  int DefaultTimeDifference = TimeToMSec(TimeDifference) / MSecsPerSec;
+  int DefaultTimeDifference = TimeToSeconds(TimeDifference);
   TimeDifference =
     (double(ReadXmlNode(Node, L"TimezoneOffset", DefaultTimeDifference) / SecsPerDay));
 

+ 3 - 8
source/core/SftpFileSystem.cpp

@@ -2884,15 +2884,10 @@ void __fastcall TSFTPFileSystem::DoStartup()
     FTerminal->LogEvent(L"We will never use UTF-8 strings");
   }
 
-  FOpenSSH =
-    // Sun SSH is based on OpenSSH (suffers the same bugs)
-    (GetSessionInfo().SshImplementation.Pos(L"OpenSSH") == 1) ||
-    (GetSessionInfo().SshImplementation.Pos(L"Sun_SSH") == 1);
-
   FMaxPacketSize = FTerminal->SessionData->SFTPMaxPacketSize;
   if (FMaxPacketSize == 0)
   {
-    if (FOpenSSH && (FVersion == 3) && !FSupport->Loaded)
+    if (FSecureShell->IsOpenSSH() && (FVersion == 3) && !FSupport->Loaded)
     {
       FMaxPacketSize = 4 + (256 * 1024); // len + 256kB payload
       FTerminal->LogEvent(FORMAT(L"Limiting packet size to OpenSSH sftp-server limit of %d bytes",
@@ -3404,7 +3399,7 @@ void __fastcall TSFTPFileSystem::CreateLink(const UnicodeString FileName,
   TSFTPPacket Packet(SSH_FXP_SYMLINK);
 
   bool Buggy = (FTerminal->SessionData->SFTPBug[sbSymlink] == asOn) ||
-    ((FTerminal->SessionData->SFTPBug[sbSymlink] == asAuto) && FOpenSSH);
+    ((FTerminal->SessionData->SFTPBug[sbSymlink] == asAuto) && FSecureShell->IsOpenSSH());
 
   if (!Buggy)
   {
@@ -4826,7 +4821,7 @@ void __fastcall TSFTPFileSystem::SFTPDirectorySource(const UnicodeString Directo
   }
 
   int FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive;
-  TSearchRec SearchRec;
+  TSearchRecChecked SearchRec;
   bool FindOK;
 
   FILE_OPERATION_LOOP (FMTLOAD(LIST_DIR_ERROR, (DirectoryName)),

+ 0 - 1
source/core/SftpFileSystem.h

@@ -101,7 +101,6 @@ protected:
   TSFTPSupport * FSupport;
   bool FUtfStrings;
   bool FSignedTS;
-  bool FOpenSSH;
   TStrings * FFixedPaths;
   unsigned long FMaxPacketSize;
   bool FSupportsStatVfsV2;

+ 29 - 6
source/core/Terminal.cpp

@@ -2001,6 +2001,7 @@ bool __fastcall TTerminal::HandleException(Exception * E)
 //---------------------------------------------------------------------------
 void __fastcall TTerminal::CloseOnCompletion(TOnceDoneOperation Operation, const UnicodeString Message)
 {
+  Configuration->Usage->Inc(L"ClosesOnCompletion");
   LogEvent(L"Closing session after completed operation (as requested by user)");
   Close();
   throw ESshTerminate(NULL,
@@ -2931,14 +2932,20 @@ TUsableCopyParamAttrs __fastcall TTerminal::UsableCopyParamAttrs(int Params)
     FLAGMASK(!IsCapable[fcModeChanging], cpaNoRights) |
     FLAGMASK(!IsCapable[fcModeChanging], cpaNoPreserveReadOnly) |
     FLAGMASK(FLAGSET(Params, cpDelete), cpaNoClearArchive) |
-    FLAGMASK(!IsCapable[fcIgnorePermErrors], cpaNoIgnorePermErrors);
-  Result.Download = Result.General | cpaNoClearArchive | cpaNoRights |
-    cpaNoIgnorePermErrors | cpaNoRemoveCtrlZ | cpaNoRemoveBOM;
-  Result.Upload = Result.General | cpaNoPreserveReadOnly |
+    FLAGMASK(!IsCapable[fcIgnorePermErrors], cpaNoIgnorePermErrors) |
+    // the following three are never supported for download,
+    // so when they are not suppored for upload too,
+    // set them in General flags, so that they do not get enabled on
+    // Synchronize dialog.
     FLAGMASK(!IsCapable[fcModeChangingUpload], cpaNoRights) |
-    FLAGMASK(!IsCapable[fcPreservingTimestampUpload], cpaNoPreserveTime) |
     FLAGMASK(!IsCapable[fcRemoveCtrlZUpload], cpaNoRemoveCtrlZ) |
     FLAGMASK(!IsCapable[fcRemoveBOMUpload], cpaNoRemoveBOM);
+  Result.Download = Result.General | cpaNoClearArchive |
+    cpaNoIgnorePermErrors |
+    // May be already set in General flags, but it's unconditional here
+    cpaNoRights | cpaNoRemoveCtrlZ | cpaNoRemoveBOM;
+  Result.Upload = Result.General | cpaNoPreserveReadOnly |
+    FLAGMASK(!IsCapable[fcPreservingTimestampUpload], cpaNoPreserveTime);
   return Result;
 }
 //---------------------------------------------------------------------------
@@ -4330,7 +4337,7 @@ void __fastcall TTerminal::DoSynchronizeCollectDirectory(const UnicodeString Loc
   try
   {
     bool Found;
-    TSearchRec SearchRec;
+    TSearchRecChecked SearchRec;
     Data.LocalFileList = new TStringList();
     Data.LocalFileList->Sorted = true;
     Data.LocalFileList->CaseSensitive = false;
@@ -5125,6 +5132,7 @@ bool __fastcall TTerminal::CopyToRemote(TStrings * FilesToCopy,
       FilesToCopy->Count, Params & cpTemporary, TargetDir, CopyParam->CPSLimit);
 
     FOperationProgress = &OperationProgress;
+    bool CollectingUsage = false;
     try
     {
       if (CalculatedSize)
@@ -5135,6 +5143,7 @@ bool __fastcall TTerminal::CopyToRemote(TStrings * FilesToCopy,
           Configuration->Usage->Inc(L"Uploads");
           Configuration->Usage->Inc(L"UploadedBytes", CounterSize);
           Configuration->Usage->SetMax(L"MaxUploadSize", CounterSize);
+          CollectingUsage = true;
         }
 
         OperationProgress.SetTotalSize(Size);
@@ -5170,6 +5179,12 @@ bool __fastcall TTerminal::CopyToRemote(TStrings * FilesToCopy,
     }
     __finally
     {
+      if (CollectingUsage)
+      {
+        int CounterTime = TimeToSeconds(OperationProgress.TimeElapsed());
+        Configuration->Usage->Inc(L"UploadTime", CounterTime);
+        Configuration->Usage->SetMax(L"MaxUploadTime", CounterTime);
+      }
       OperationProgress.Stop();
       FOperationProgress = NULL;
     }
@@ -5235,6 +5250,7 @@ bool __fastcall TTerminal::CopyToLocal(TStrings * FilesToCopy,
         FilesToCopy->Count, Params & cpTemporary, TargetDir, CopyParam->CPSLimit);
 
       FOperationProgress = &OperationProgress;
+      bool CollectingUsage = false;
       try
       {
         if (TotalSizeKnown)
@@ -5245,6 +5261,7 @@ bool __fastcall TTerminal::CopyToLocal(TStrings * FilesToCopy,
             Configuration->Usage->Inc(L"Downloads");
             Configuration->Usage->Inc(L"DownloadedBytes", CounterTotalSize);
             Configuration->Usage->SetMax(L"MaxDownloadSize", CounterTotalSize);
+            CollectingUsage = true;
           }
 
           OperationProgress.SetTotalSize(TotalSize);
@@ -5278,6 +5295,12 @@ bool __fastcall TTerminal::CopyToLocal(TStrings * FilesToCopy,
       }
       __finally
       {
+        if (CollectingUsage)
+        {
+          int CounterTime = TimeToSeconds(OperationProgress.TimeElapsed());
+          Configuration->Usage->Inc(L"DownloadTime", CounterTime);
+          Configuration->Usage->SetMax(L"MaxDownloadTime", CounterTime);
+        }
         FOperationProgress = NULL;
         OperationProgress.Stop();
       }

+ 5 - 4
source/core/WebDAVFileSystem.cpp

@@ -13037,13 +13037,14 @@ void __fastcall TWebDAVFileSystem::WebDAVDirectorySource(const UnicodeString Dir
   bool FindOK = false;
   HANDLE findHandle = 0;
 
+  UnicodeString FindPath = DirectoryName + L"*.*";
+
   FILE_OPERATION_LOOP (FMTLOAD(LIST_DIR_ERROR, (DirectoryName.c_str())),
-    UnicodeString path = DirectoryName + L"*.*";
-    findHandle = FindFirstFile(path.c_str(), &SearchRec);
+    findHandle = FindFirstFile(FindPath.c_str(), &SearchRec);
     FindOK = (findHandle != 0);
     if (!FindOK)
     {
-      FindCheck(GetLastError());
+      FindCheck(GetLastError(), FindPath);
     }
   );
 
@@ -13085,7 +13086,7 @@ void __fastcall TWebDAVFileSystem::WebDAVDirectorySource(const UnicodeString Dir
         FindOK = (::FindNextFile(findHandle, &SearchRec) != 0);
         if (!FindOK)
         {
-          FindCheck(GetLastError());
+          FindCheck(GetLastError(), FindPath);
         }
       );
     }

+ 0 - 1
source/filezilla/FileZillaApi.h

@@ -276,7 +276,6 @@ public:
 #define FZ_SERVERTYPE_SUB_FTP_VMS		0x0001
 #define FZ_SERVERTYPE_SUB_FTP_SFTP		0x0002
 #define FZ_SERVERTYPE_SUB_FTP_WINDOWS	0x0004
-#define FZ_SERVERTYPE_SUB_FTP_UNKNOWN	0x0008
 #define FZ_SERVERTYPE_SUB_FTP_MVS		0x0010
 #define FZ_SERVERTYPE_SUB_FTP_BS2000	0x0020
 

+ 1 - 0
source/filezilla/FtpControlSocket.cpp

@@ -3976,6 +3976,7 @@ void CFtpControlSocket::FileTransfer(t_transferfile *transferfile/*=0*/,BOOL bFi
 					}
 					if (!nReplyError && !COptions::GetOptionVal(OPTION_MPEXT_TRANSFER_ACTIVE_IMMEDIATELLY))
 					{
+						m_pTransferSocket->SetActive();
 					}
 				}
 				else if (pData->bPasv && !COptions::GetOptionVal(OPTION_MPEXT_TRANSFER_ACTIVE_IMMEDIATELLY))

+ 1 - 21
source/filezilla/ServerPath.cpp

@@ -70,8 +70,6 @@ CServerPath::CServerPath(CString path)
 		m_nServerType |= FZ_SERVERTYPE_SUB_FTP_MVS;
 	else if (path.GetLength() > 2 && path[0] == _MPT('\'') && path.Right(1) == _T("'") && path.Find(_MPT('/')) == -1 && path.Find(_MPT('\\')) == -1)
 		m_nServerType |= FZ_SERVERTYPE_SUB_FTP_MVS;
-	else if (path.GetLength() >= 2 && path[0] != _MPT('/') && path.Right(1) == _T("."))
-		m_nServerType |= FZ_SERVERTYPE_SUB_FTP_UNKNOWN;
 
 	*this = CServerPath(path, m_nServerType);
 }
@@ -141,8 +139,6 @@ CServerPath::CServerPath(CString path, int nServerType)
 					m_Segments.push_back(path);
 			}
 			break;
-		case FZ_SERVERTYPE_SUB_FTP_UNKNOWN:
-			while (path.Replace(_MPT('.'), _MPT('/')));
 		default:
 			path.Replace( _T("\\"), _T("/") );
 			while (path.Replace( _T("//"), _T("/") ));
@@ -234,8 +230,6 @@ BOOL CServerPath::SetPath(CString &newpath, BOOL bIsFile /*=FALSE*/)
 			m_nServerType |= FZ_SERVERTYPE_SUB_FTP_WINDOWS;
 		else if (path[0] == FTP_MVS_DOUBLE_QUOTA && path[path.GetLength() - 1] == FTP_MVS_DOUBLE_QUOTA)
 			m_nServerType |= FZ_SERVERTYPE_SUB_FTP_MVS;
-		else if (path.GetLength() >= 2 && path[0] != _MPT('/') && path.Right(1) == _T("."))
-			m_nServerType |= FZ_SERVERTYPE_SUB_FTP_UNKNOWN;
 	}
 	m_Segments.clear();
 	m_Prefix = _MPT("");
@@ -326,8 +320,6 @@ BOOL CServerPath::SetPath(CString &newpath, BOOL bIsFile /*=FALSE*/)
 						m_Segments.push_back(path);
 				}
 				break;
-			case FZ_SERVERTYPE_SUB_FTP_UNKNOWN:
-				while (path.Replace(_MPT('.'), _MPT('/')));
 			default:
 				path.Replace( _T("\\"), _T("/") );
 				while(path.Replace( _T("//"), _T("/") ));
@@ -434,10 +426,6 @@ const CString CServerPath::GetPath() const
 			path.TrimRight( _T(".") );
 			path += _MPT("]");
 			break;
-		case FZ_SERVERTYPE_SUB_FTP_UNKNOWN:
-			for (iter=m_Segments.begin(); iter!=m_Segments.end(); iter++)
-				path+=*iter + _T(".");
-			break;
 		default:
 			if (!(m_nServerType & FZ_SERVERTYPE_SUB_FTP_WINDOWS))
 				path=_MPT("/");
@@ -664,6 +652,7 @@ CServerPath::CServerPath(CString subdir, const CServerPath &parent)
 						{
 							m_Segments.push_back(subdir.Left(pos));
 							subdir = subdir.Mid(pos + 1);
+							pos = subdir.Find(_MPT('.'));
 						}
 						if (subdir != _T(""))
 						{
@@ -741,8 +730,6 @@ CServerPath::CServerPath(CString subdir, const CServerPath &parent)
 					m_Segments.push_back(subdir);
 				break;
 			}
-		case FZ_SERVERTYPE_SUB_FTP_UNKNOWN:
-			subdir.Replace(_MPT('.'), _MPT('/'));
 		default:
 			subdir.Replace( _T("\\"), _T("/") );
 			while(subdir.Replace( _T("//"), _T("/") ));
@@ -849,13 +836,6 @@ CString CServerPath::FormatFilename(CString fn, bool omitPath /*=false*/) const
 			path += _MPT("]");
 			path += fn;
 			break;
-		case FZ_SERVERTYPE_SUB_FTP_UNKNOWN:
-			if (omitPath)
-				return fn;
-			for (iter=m_Segments.begin(); iter!=m_Segments.end(); iter++)
-				path+=*iter + _T(".");
-			path += fn;
-			break;
 		default:
 			if (omitPath)
 				return fn;

+ 53 - 21
source/forms/CustomScpExplorer.cpp

@@ -2490,6 +2490,7 @@ void __fastcall TCustomScpExplorerForm::TemporarilyDownloadFiles(
   CopyParam.PreserveReadOnly = false;
   CopyParam.ReplaceInvalidChars = true;
   CopyParam.FileMask = L"";
+  CopyParam.NewerOnly = false;
   if (AllFiles)
   {
     CopyParam.IncludeFileMask = TFileMasks();
@@ -2939,22 +2940,28 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(const UnicodeString
 void __fastcall TCustomScpExplorerForm::ExecutedFileReload(
   const UnicodeString FileName, const TEditedFileData * Data)
 {
+  // Sanity check, we should not be busy othwerwise user would not be able to click Reload button.
+  assert(!NonVisualDataModule->Busy);
+
   if ((Data->Terminal == NULL) || !Data->Terminal->Active)
   {
     throw Exception(FMTLOAD(EDIT_SESSION_CLOSED_RELOAD,
       (ExtractFileName(FileName), Data->SessionName)));
   }
 
-  TRemoteFile * File = NULL;
-  TStrings * FileList = new TStringList();
+  TTerminal * PrevTerminal = TTerminalManager::Instance()->ActiveTerminal;
+  TTerminalManager::Instance()->ActiveTerminal = Data->Terminal;
   try
   {
+    std::unique_ptr<TRemoteFile> File;
     UnicodeString RemoteFileName =
       UnixIncludeTrailingBackslash(Data->RemoteDirectory) + Data->OriginalFileName;
     FTerminal->ExceptionOnFail = true;
     try
     {
-      FTerminal->ReadFile(RemoteFileName, File);
+      TRemoteFile * AFile = NULL;
+      FTerminal->ReadFile(RemoteFileName, AFile);
+      File.reset(AFile);
       if (!File->HaveFullFileName)
       {
         File->FullFileName = RemoteFileName;
@@ -2964,31 +2971,22 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileReload(
     {
       FTerminal->ExceptionOnFail = false;
     }
-    FileList->AddObject(RemoteFileName, File);
+    std::unique_ptr<TStrings> FileList(new TStringList());
+    FileList->AddObject(RemoteFileName, File.get());
 
     UnicodeString RootTempDir = Data->LocalRootDirectory;
     UnicodeString TempDir = ExtractFilePath(FileName);
 
-    TTerminal * PrevTerminal = TTerminalManager::Instance()->ActiveTerminal;
-    TTerminalManager::Instance()->ActiveTerminal = Data->Terminal;
-    try
-    {
-      TemporarilyDownloadFiles(FileList, Data->ForceText, RootTempDir,
-        TempDir, true, true, true);
-    }
-    __finally
-    {
-      // it actually may not exist anymore...
-      TTerminalManager::Instance()->ActiveTerminal = PrevTerminal;
-    }
+    TemporarilyDownloadFiles(FileList.get(), Data->ForceText, RootTempDir,
+      TempDir, true, true, true);
 
     // sanity check, the target file name should be still the same
     assert(ExtractFileName(FileName) == FileList->Strings[0]);
   }
   __finally
   {
-    delete File;
-    delete FileList;
+    // it actually may not exist anymore...
+    TTerminalManager::Instance()->ActiveTerminal = PrevTerminal;
   }
 }
 //---------------------------------------------------------------------------
@@ -5498,6 +5496,26 @@ void __fastcall TCustomScpExplorerForm::WMQueryEndSession(TMessage & Message)
   // msdn.microsoft.com/en-us/library/ms700677.aspx
 }
 //---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::WMEndSession(TWMEndSession & Message)
+{
+  if (Message.EndSession && IsApplicationMinimized())
+  {
+    // WORKAROUND
+    // TApplication.WndProc() calls Application.Terminate() before Halt(),
+    // when it receives WM_ENDSESSION,
+    // but that sometimes (particularly when application is minimized) cause crashes.
+    // Crash popups message that blocks log off.
+    // Obviously application cannot shutdown cleanly after WM_ENDSESSION,
+    // so we call ExitProcess() immediatelly, not even trying to cleanup.
+    // It still causes beep, so there's likely some popup, but it does not block
+    // log off.
+    // Still needs testing on Windows 8 final. On Windows 8 preview it still blocks.
+    // Works on Windows XP and 7.
+    ExitProcess(0);
+  }
+  TForm::Dispatch(&Message);
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::SysResizing(unsigned int /*Cmd*/)
 {
 }
@@ -5540,6 +5558,8 @@ void __fastcall TCustomScpExplorerForm::PopupTrayBalloon(TTerminal * Terminal,
 
   if (Do && WinConfiguration->BalloonNotifications)
   {
+    Message = UnformatMessage(Message);
+
     const ResourceString * Captions[] = { &_SMsgDlgConfirm, &_SMsgDlgWarning,
       &_SMsgDlgError, &_SMsgDlgInformation, NULL };
     UnicodeString Title = LoadResourceString(Captions[Type]);
@@ -7162,6 +7182,10 @@ void __fastcall TCustomScpExplorerForm::Dispatch(void * Message)
       WMQueryEndSession(*M);
       break;
 
+    case WM_ENDSESSION:
+      WMEndSession(*reinterpret_cast<TWMEndSession *>(M));
+      break;
+
     case WM_COMPONENT_HIDE:
       {
         Byte Component = static_cast<Byte>(M->WParam);
@@ -7450,7 +7474,13 @@ void __fastcall TCustomScpExplorerForm::SuspendWindowLock()
     {
       // won't be disabled when conditions in LockWindow() were not satisfied
       FDisabledOnLockSuspend = !Enabled;
-      Enabled = true;
+      // When minimized to tray (or actually when set to SW_HIDE),
+      // setting Enabled makes the window focusable even when there's
+      // modal window over it
+      if (!FTrayIcon->Visible)
+      {
+        Enabled = true;
+      }
     }
     FLockSuspendLevel++;
   }
@@ -7465,8 +7495,10 @@ void __fastcall TCustomScpExplorerForm::ResumeWindowLock()
     // see comment in SuspendWindowLock
     if (ALWAYS_TRUE(FLockSuspendLevel == 0))
     {
-      assert(Enabled);
-      // we should possibly do the same check as in LockWindow(),
+      // Note that window can be enabled here, when we were minized to tray when
+      // was SuspendWindowLock() called.
+
+      // We should possibly do the same check as in LockWindow(),
       // if it is ever possible that the consitions change between
       // SuspendWindowLock() and ResumeWindowLock()
       Enabled = !FDisabledOnLockSuspend;

+ 1 - 0
source/forms/CustomScpExplorer.h

@@ -348,6 +348,7 @@ protected:
   inline void __fastcall WMAppCommand(TMessage & Message);
   inline void __fastcall WMSysCommand(TMessage & Message);
   void __fastcall WMQueryEndSession(TMessage & Message);
+  void __fastcall WMEndSession(TWMEndSession & Message);
   void __fastcall WMCopyData(TMessage & Message);
   virtual void __fastcall SysResizing(unsigned int Cmd);
   DYNAMIC void __fastcall DoShow();

+ 0 - 1
source/forms/ImportSessions.dfm

@@ -57,7 +57,6 @@ object ImportSessionsDialog: TImportSessionsDialog
     Checkboxes = True
     Columns = <
       item
-        Caption = 'Site'
         Width = 240
       end>
     ColumnClick = False

+ 27 - 4
source/forms/Login.cpp

@@ -149,6 +149,11 @@ void __fastcall TLoginDialog::InitControls()
   // The Color 0 is a HACK
   int DisabledImageIndex = ActionImageList->AddMasked(Bitmap.get(), TColor(0));
   LoginButton->DisabledImageIndex = DisabledImageIndex;
+
+  if (SessionTree->Items->Count > 0)
+  {
+    SetNewSiteNodeLabel();
+  }
 }
 //---------------------------------------------------------------------
 void __fastcall TLoginDialog::Init()
@@ -317,6 +322,18 @@ void __fastcall TLoginDialog::DestroySession(TSessionData * Data)
   StoredSessions->Remove(Data);
 }
 //---------------------------------------------------------------------
+TTreeNode * __fastcall TLoginDialog::GetNewSiteNode()
+{
+  TTreeNode * Result = SessionTree->Items->GetFirstNode();
+  assert(IsNewSiteNode(Result));
+  return Result;
+}
+//---------------------------------------------------------------------
+void __fastcall TLoginDialog::SetNewSiteNodeLabel()
+{
+  GetNewSiteNode()->Text = LoadStr(LOGIN_NEW_SITE_NODE);
+}
+//---------------------------------------------------------------------
 void __fastcall TLoginDialog::LoadSessions()
 {
   SessionTree->Items->BeginUpdate();
@@ -324,8 +341,9 @@ void __fastcall TLoginDialog::LoadSessions()
   {
     SessionTree->Items->Clear();
 
-    TTreeNode * Node = SessionTree->Items->AddChild(NULL, LoadStr(LOGIN_NEW_SITE_NODE));
+    TTreeNode * Node = SessionTree->Items->AddChild(NULL, L"");
     Node->Data = FNewSiteData;
+    SetNewSiteNodeLabel();
     SetNodeImage(Node, NewSiteImageIndex);
 
     assert(StoredSessions != NULL);
@@ -353,7 +371,7 @@ void __fastcall TLoginDialog::UpdateFolderNode(TTreeNode * Node)
 //---------------------------------------------------------------------------
 void __fastcall TLoginDialog::NewSite()
 {
-  TTreeNode * NewSiteNode = SessionTree->Items->GetFirstNode();
+  TTreeNode * NewSiteNode = GetNewSiteNode();
   if (ALWAYS_TRUE(IsNewSiteNode(NewSiteNode)))
   {
     SessionTree->Selected = NewSiteNode;
@@ -564,7 +582,7 @@ void __fastcall TLoginDialog::UpdateControls()
     DefaultButton(SaveButton, FEditing);
     EditCancelButton->Cancel = FEditing;
     SiteClonetoNewSiteMenuItem->Default = IsCloneToNewSiteDefault();
-    SiteLoginMenuItem->Default = !SiteClonetoNewSiteMenuItem->Default;
+    SiteLoginMenuItem->Default = LoginButton->Default;
 
     UpdateButtonVisibility(SaveButton);
     UpdateButtonVisibility(EditButton);
@@ -765,6 +783,11 @@ void __fastcall TLoginDialog::SessionTreeKeyPress(TObject * /*Sender*/, System::
         Key = 0;
       }
     }
+    else if ((Key == VK_RETURN) && IsCloneToNewSiteDefault())
+    {
+      CloneToNewSite();
+      Key = 0;
+    }
   }
 }
 //---------------------------------------------------------------------------
@@ -1093,7 +1116,7 @@ void __fastcall TLoginDialog::ActionListUpdate(TBasicAction * BasicAction,
 //---------------------------------------------------------------------------
 bool __fastcall TLoginDialog::IsCloneToNewSiteDefault()
 {
-  return IsSiteNode(SessionTree->Selected) && !FStoredSessions->CanLogin(GetSessionData());
+  return !FEditing && !FRenaming && IsSiteNode(SessionTree->Selected) && !FStoredSessions->CanLogin(GetSessionData());
 }
 //---------------------------------------------------------------------------
 bool __fastcall TLoginDialog::CanLogin()

+ 1 - 2
source/forms/Login.dfm

@@ -313,7 +313,6 @@ object LoginDialog: TLoginDialog
           Height = 25
           Action = EditCancelAction
           Anchors = [akLeft, akBottom]
-          Caption = 'Cancel'
           TabOrder = 13
           OnDropDownClick = SaveButtonDropDownClick
         end
@@ -574,7 +573,7 @@ object LoginDialog: TLoginDialog
     end
     object EditCancelAction: TAction
       Category = 'Session'
-      Caption = '&Cancel'
+      Caption = 'Cancel'
       OnExecute = EditCancelActionExecute
     end
     object SessionAdvancedAction: TAction

+ 2 - 0
source/forms/Login.h

@@ -303,6 +303,8 @@ private:
   inline bool __fastcall IsSessionNode(TTreeNode * Node);
   inline bool __fastcall IsSiteNode(TTreeNode * Node);
   inline bool __fastcall IsNewSiteNode(TTreeNode * Node);
+  TTreeNode * __fastcall GetNewSiteNode();
+  void __fastcall SetNewSiteNodeLabel();
   inline TSessionData * __fastcall GetNodeSession(TTreeNode * Node);
   void __fastcall ExecuteTool(const UnicodeString & Name);
   UnicodeString __fastcall ImportExportIniFilePath();

+ 119 - 0
source/forms/MessageDlg.cpp

@@ -68,6 +68,8 @@ public:
     unsigned int TimeoutAnswer, TButton ** TimeoutButton, const UnicodeString & ImageName,
     const UnicodeString & NeverAskAgainCaption);
 
+  virtual int __fastcall ShowModal();
+
 protected:
   __fastcall TMessageForm(TComponent * AOwner);
   virtual __fastcall ~TMessageForm();
@@ -83,6 +85,7 @@ protected:
   void __fastcall MenuItemClick(TObject * Sender);
   void __fastcall ButtonDropDownClick(TObject * Sender);
   void __fastcall UpdateForShiftStateTimer(TObject * Sender);
+  DYNAMIC void __fastcall SetZOrder(bool TopMost);
 
 private:
   typedef std::map<unsigned int, TButton *> TAnswerButtons;
@@ -92,20 +95,24 @@ private:
   TShiftState FShiftState;
   TTimer * FUpdateForShiftStateTimer;
   TForm * FDummyForm;
+  bool FShowNoActivate;
 
   void __fastcall HelpButtonClick(TObject * Sender);
   void __fastcall ReportButtonClick(TObject * Sender);
   void __fastcall CMDialogKey(TWMKeyDown & Message);
+  void __fastcall CMShowingChanged(TMessage & Message);
   void __fastcall UpdateForShiftState();
   TButton * __fastcall CreateButton(
     UnicodeString Name, UnicodeString Caption, unsigned int Answer,
     TNotifyEvent OnClick, bool IsTimeoutButton,
     int GroupWith, TShiftState GrouppedShiftState,
     TAnswerButtons & AnswerButtons, bool HasMoreMessages, int & ButtonWidth);
+  bool __fastcall ApplicationHook(TMessage & Message);
 };
 //---------------------------------------------------------------------------
 __fastcall TMessageForm::TMessageForm(TComponent * AOwner) : TForm(AOwner, 0)
 {
+  FShowNoActivate = false;
   MessageMemo = NULL;
   FUpdateForShiftStateTimer = NULL;
   Position = poOwnerFormCenter;
@@ -324,6 +331,114 @@ void __fastcall TMessageForm::CMDialogKey(TWMKeyDown & Message)
   }
 }
 //---------------------------------------------------------------------------
+int __fastcall TMessageForm::ShowModal()
+{
+  if (IsApplicationMinimized())
+  {
+    FShowNoActivate = true;
+  }
+
+  int Result = TForm::ShowModal();
+
+  Application->UnhookMainWindow(ApplicationHook);
+
+  return Result;
+}
+//---------------------------------------------------------------------------
+void __fastcall TMessageForm::SetZOrder(bool TopMost)
+{
+  // WORKAROUND: If application is minimized,
+  // swallow call to BringToFront() from TForm::ShowModal()
+  if (FShowNoActivate && TopMost)
+  {
+    // noop
+  }
+  else
+  {
+    TForm::SetZOrder(TopMost);
+  }
+}
+//---------------------------------------------------------------------------
+bool __fastcall TMessageForm::ApplicationHook(TMessage & Message)
+{
+  bool Result = false;
+  // If application is restored, message box is not activated, do it manually.
+  // We cannot do this from TApplication::OnActivate because
+  // TApplication.WndProc resets focus to the last active window afterwards.
+  // So we override CM_ACTIVATE implementation here completelly.
+  if ((Message.Msg == CM_ACTIVATE) && FShowNoActivate)
+  {
+    ::SetFocus(Handle);
+    // VCLCOPY
+    if (Application->OnActivate != NULL)
+    {
+      Application->OnActivate(Application);
+    }
+    Result = true;
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+void __fastcall TMessageForm::CMShowingChanged(TMessage & Message)
+{
+  if (Showing && FShowNoActivate)
+  {
+    // With is same as SendToBack, except for added SWP_NOACTIVATE (VCLCOPY)
+    SetWindowPos(WindowHandle, HWND_BOTTOM, 0, 0, 0, 0,
+      SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+
+    // This replaces TCustomForm::CMShowingChanged()
+    // which calls ShowWindow(Handle, SW_SHOWNORMAL).
+
+    ShowWindow(Handle, SW_SHOWNOACTIVATE);
+
+    // - so we have to call DoShow explicitly.
+    DoShow();
+
+    // - also we skip applying TForm::Position (VCLCOPY)
+    if (ALWAYS_TRUE(Position == poOwnerFormCenter))
+    {
+      TCustomForm * CenterForm = Application->MainForm;
+      TCustomForm * OwnerForm = dynamic_cast<TCustomForm *>(Owner);
+      if (OwnerForm != NULL)
+      {
+        CenterForm = OwnerForm;
+      }
+      int X, Y;
+      if ((CenterForm != NULL) && (CenterForm != this))
+      {
+        TRect Bounds = CenterForm->BoundsRect;
+        X = ((Bounds.Width() - Width) / 2) + CenterForm->Left;
+        Y = ((Bounds.Height() - Height) / 2) + CenterForm->Top;
+      }
+      else
+      {
+        X = (Screen->Width - Width) / 2;
+        Y = (Screen->Height - Height) / 2;
+      }
+      if (X < Screen->DesktopLeft)
+      {
+        X = Screen->DesktopLeft;
+      }
+      if (Y < Screen->DesktopTop)
+      {
+        Y = Screen->DesktopTop;
+      }
+      SetBounds(X, Y, Width, Height);
+      // We cannot call SetWindowToMonitor().
+      // We cannot set FPosition = poDesigned, so worlarea-checking code
+      // in DoFormWindowProc is not triggered
+    }
+
+    // wait for application to be activate to activate ourself
+    Application->HookMainWindow(ApplicationHook);
+  }
+  else
+  {
+    TForm::Dispatch(&Message);
+  }
+}
+//---------------------------------------------------------------------------
 void __fastcall TMessageForm::Dispatch(void * Message)
 {
   TMessage * M = reinterpret_cast<TMessage*>(Message);
@@ -331,6 +446,10 @@ void __fastcall TMessageForm::Dispatch(void * Message)
   {
     CMDialogKey(*((TWMKeyDown *)Message));
   }
+  else if (M->Msg == CM_SHOWINGCHANGED)
+  {
+    CMShowingChanged(*M);
+  }
   else
   {
     TForm::Dispatch(Message);

+ 34 - 29
source/forms/Progress.cpp

@@ -250,37 +250,42 @@ static TDateTime UpdateInterval(static_cast<double>(OneSecond));
 bool __fastcall TProgressForm::ReceiveData(bool Force, int ModalLevelOffset)
 {
   bool Result = false;
-  if (FDataGot && !FDataReceived &&
-      // Never popup over dialog that appeared later than we started
-      // (this can happen from UpdateTimerTimer when application is
-      // restored while overwrite confirmation dialog [or any other]
-      // is already shown).
-      // TODO We should probably take as-modal windows into account too
-      // (for extreme cases like restoring while reconnecting [as-modal TAuthenticateForm]).
-      ((FModalLevel < 0) || (Application->ModalLevel + ModalLevelOffset <= FModalLevel)))
+  if (FDataGot && !FDataReceived)
   {
-    // delay showing the progress until the application is restored,
-    // otherwise the form popups up unminimized.
-    if (!IsApplicationMinimized() &&
-        (Force || ((Now() - FStarted) > DelayStartInterval)))
+    // CPS limit is set set only once from TFileOperationProgressType::Start.
+    // Needs to be set even when data are not accepted yet, otherwise we would
+    // write default value to FData in TProgressForm::SetProgressData
+    FCPSLimit = FData.CPSLimit;
+
+    // Never popup over dialog that appeared later than we started
+    // (this can happen from UpdateTimerTimer when application is
+    // restored while overwrite confirmation dialog [or any other]
+    // is already shown).
+    // TODO We should probably take as-modal windows into account too
+    // (for extreme cases like restoring while reconnecting [as-modal TAuthenticateForm]).
+    if ((FModalLevel < 0) || (Application->ModalLevel + ModalLevelOffset <= FModalLevel))
     {
-      FDataReceived = true;
-      // CPS limit is set set only once from TFileOperationProgressType::Start
-      FCPSLimit = FData.CPSLimit;
-      SpeedCombo->Text = SetSpeedLimit(FCPSLimit);
-      ShowAsModal(this, FShowAsModalStorage);
-      // particularly needed for the case, when we are showing the form delayed
-      // because application was minimized when operation started
-      Result = true;
-    }
-    else if (!FModalBeginHooked && ALWAYS_TRUE(FModalLevel < 0))
-    {
-      // record state as of time, the window should be shown,
-      // had not we implemented delayed show
-      FPrevApplicationModalBegin = Application->OnModalBegin;
-      Application->OnModalBegin = ApplicationModalBegin;
-      FModalBeginHooked = true;
-      FModalLevel = Application->ModalLevel;
+      // delay showing the progress until the application is restored,
+      // otherwise the form popups up unminimized.
+      if (!IsApplicationMinimized() &&
+          (Force || ((Now() - FStarted) > DelayStartInterval)))
+      {
+        FDataReceived = true;
+        SpeedCombo->Text = SetSpeedLimit(FCPSLimit);
+        ShowAsModal(this, FShowAsModalStorage);
+        // particularly needed for the case, when we are showing the form delayed
+        // because application was minimized when operation started
+        Result = true;
+      }
+      else if (!FModalBeginHooked && ALWAYS_TRUE(FModalLevel < 0))
+      {
+        // record state as of time, the window should be shown,
+        // had not we implemented delayed show
+        FPrevApplicationModalBegin = Application->OnModalBegin;
+        Application->OnModalBegin = ApplicationModalBegin;
+        FModalBeginHooked = true;
+        FModalLevel = Application->ModalLevel;
+      }
     }
   }
 

+ 2 - 2
source/forms/SiteAdvanced.cpp

@@ -1249,9 +1249,9 @@ void __fastcall TSiteAdvancedDialog::FormCloseQuery(TObject * /*Sender*/,
 {
   if (ModalResult == DefaultResult(this))
   {
-    VerifyKeyIncludingVersion(PrivateKeyEdit->Text, GetSshProt());
+    VerifyKeyIncludingVersion(StripPathQuotes(PrivateKeyEdit->Text), GetSshProt());
     // for tunnel key do not check SSH version as it is not configurable
-    VerifyKey(TunnelPrivateKeyEdit->Text);
+    VerifyKey(StripPathQuotes(TunnelPrivateKeyEdit->Text));
   }
 }
 //---------------------------------------------------------------------------

+ 30 - 18
source/packages/filemng/CustomDirView.pas

@@ -557,7 +557,7 @@ var
   StdDirIcon: Integer;
   StdDirSelIcon: Integer;
   DropSourceControl: TObject;
-  UnknownFileIcon: Integer;
+  UnknownFileIcon: Integer = 0;
   HasExtendedCOMCTL32: Boolean;
   StdDirTypeName: string;
   DefaultExeIcon: Integer;
@@ -575,6 +575,33 @@ const
   ResBrokenLink = 'BROKEN%2.2d';
   ResPartial = 'PARTIAL%2.2d';
 
+var
+  WinDir: string;
+  TempDir: string;
+  GlobalsInitialized: Boolean = False;
+
+procedure InitGlobals;
+begin
+  if not GlobalsInitialized then
+  begin
+    GlobalsInitialized := True;
+
+    UnknownFileIcon := GetshFileInfo('$#)(.#$)', FILE_ATTRIBUTE_NORMAL,
+      SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES).iIcon;
+    DefaultExeIcon := GetshFileInfo('.COM',
+      FILE_ATTRIBUTE_NORMAL, SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES).iIcon;
+
+    with GetshFileInfo(WinDir, FILE_ATTRIBUTE_NORMAL or FILE_ATTRIBUTE_DIRECTORY,
+      SHGFI_TYPENAME or SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES) do
+    begin
+      StdDirTypeName := szTypeName;
+      StdDirIcon := iIcon;
+    end;
+    StdDirSelIcon := GetIconIndex(WinDir,
+      FILE_ATTRIBUTE_NORMAL or FILE_ATTRIBUTE_DIRECTORY, SHGFI_OPENICON);
+  end;
+end;
+
 type
   TDirViewState = class(TObject)
   public
@@ -589,8 +616,6 @@ type
   end;
 
 var
-  WinDir: string;
-  TempDir: string;
   COMCTL32Version: DWORD;
 
 destructor TDirViewState.Destroy;
@@ -858,6 +883,8 @@ end;
 
 constructor TCustomDirView.Create(AOwner: TComponent);
 begin
+  InitGlobals;
+
   inherited;
 
   FWatchForChanges := False;
@@ -3287,24 +3314,9 @@ initialization
 
   SpecialFolderLocation(CSIDL_PERSONAL, UserDocumentDirectory);
 
-  UnknownFileIcon := GetshFileInfo('$#)(.#$)', FILE_ATTRIBUTE_NORMAL,
-    SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES).iIcon;
-  DefaultExeIcon := GetshFileInfo('.COM',
-    FILE_ATTRIBUTE_NORMAL, SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES).iIcon;
-
-  with GetshFileInfo(WinDir, FILE_ATTRIBUTE_NORMAL or FILE_ATTRIBUTE_DIRECTORY,
-    SHGFI_TYPENAME or SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES) do
-  begin
-    StdDirTypeName := szTypeName;
-    StdDirIcon := iIcon;
-  end;
-  StdDirSelIcon := GetIconIndex(WinDir,
-    FILE_ATTRIBUTE_NORMAL or FILE_ATTRIBUTE_DIRECTORY, SHGFI_OPENICON);
-
   WinDir := IncludeTrailingPathDelimiter(WinDir);
   TempDir := IncludeTrailingPathDelimiter(TempDir);
 
-
 finalization
   SetLength(StdDirTypeName, 0);
   SetLength(WinDir, 0);

+ 2 - 1
source/packages/tb2k/TB2Item.pas

@@ -2737,7 +2737,8 @@ begin
   P := Pos(#9, Result);
   if P <> 0 then
     SetLength(Result, P-1);
-  if IsToolbarStyle then
+  { MP }
+  if IsToolbarStyle and not (vsMenuBar in View.Style) then
     Result := StripAccelChars(StripTrailingPunctuation(Result));
 end;
 

+ 3 - 0
source/resource/TextsCore.h

@@ -223,6 +223,7 @@
 #define NET_TRANSL_PACKET_GARBLED 297
 #define REPORT_ERROR            298
 #define TLS_CERT_DECODE_ERROR   299
+#define FIND_FILE_ERROR         700
 
 #define CORE_CONFIRMATION_STRINGS 300
 #define CONFIRM_PROLONG_TIMEOUT3 301
@@ -411,4 +412,6 @@
 #define PUTTY_LICENSE_URL       625
 #define MAIN_MSG_TAG            631
 
+// 7xxx used by errors as secondary sequence
+
 #endif // TextsCore

+ 1 - 0
source/resource/TextsCore1.rc

@@ -188,6 +188,7 @@ BEGIN
   NET_TRANSL_PACKET_GARBLED, "Incoming packet was garbled on decryption"
   REPORT_ERROR, "%s\n\nPlease help us improving WinSCP by reporting the error on WinSCP support forum."
   TLS_CERT_DECODE_ERROR, "Error decoding TLS/SSL certificate (%s)."
+  FIND_FILE_ERROR, "Error retrieving file list for \"%s\"."
 
   CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION"
   CONFIRM_PROLONG_TIMEOUT3, "Host is not communicating for %d seconds.\n\nWait for another %0:d seconds?"

+ 1 - 1
source/resource/TextsWin.h

@@ -44,7 +44,6 @@
 #define CUSTOM_COMMAND_SELECTED_UNMATCH1 1151
 #define CUSTOM_COMMAND_PAIRS_DOWNLOAD_FAILED 1152
 #define EXTERNAL_CONSOLE_INIT_ERROR 1153
-#define CONSOLE_PRINT_TOO_LONG  1154
 #define CONSOLE_COMM_ERROR      1155
 #define CONSOLE_SEND_TIMEOUT    1156
 #define EXTERNAL_CONSOLE_INCOMPATIBLE 1157
@@ -483,5 +482,6 @@
 #define LOCALES_URL             4011
 #define DOCUMENTATION_SEARCH_URL2 4012
 #define ERROR_REPORT_URL        4013
+#define UPGRADE_URL             4014
 
 #endif // TextsWin

+ 1 - 1
source/resource/TextsWin1.rc

@@ -48,7 +48,6 @@ BEGIN
         CUSTOM_COMMAND_SELECTED_UNMATCH1, "To use selected custom command only one file must be selected in local panel."
         CUSTOM_COMMAND_PAIRS_DOWNLOAD_FAILED, "Some of the selected remote files were not downloaded. The selected custom command must be executed for pairs of file, what is thus not possible."
         EXTERNAL_CONSOLE_INIT_ERROR, "Cannot initialize external console."
-        CONSOLE_PRINT_TOO_LONG, "Message too long (%d bytes) to send to external console."
         CONSOLE_COMM_ERROR, "Cannot open mapping object to start comunication with external console."
         CONSOLE_SEND_TIMEOUT, "Timeout waiting for external console to complete the command."
         EXTERNAL_CONSOLE_INCOMPATIBLE, "Incompatible external console protocol version %d."
@@ -477,5 +476,6 @@ BEGIN
         LOCALES_URL, "http://winscp.net/eng/translations.php"
         DOCUMENTATION_SEARCH_URL2, "http://winscp.net/eng/docs/search.php?q=%s&ver=%s&lang=%s"
         ERROR_REPORT_URL, "http://winscp.net/forum/posting.php?mode=newtopic&f=2&report=%s&ver=%s&lang=%s"
+        UPGRADE_URL, "http://winscp.net/eng/upgrade.php"
 
 END

+ 27 - 17
source/windows/ConsoleRunner.cpp

@@ -626,24 +626,36 @@ void __fastcall TExternalConsole::SendEvent(int Timeout)
 //---------------------------------------------------------------------------
 void __fastcall TExternalConsole::Print(UnicodeString Str, bool FromBeginning)
 {
-  TConsoleCommStruct * CommStruct = GetCommStruct();
-  try
+  // need to do at least one iteration, even when Str is empty (new line)
+  do
   {
-    if (Str.Length() >= static_cast<int>(LENOF(CommStruct->PrintEvent.Message)))
+    TConsoleCommStruct * CommStruct = GetCommStruct();
+    try
     {
-      throw Exception(FMTLOAD(CONSOLE_PRINT_TOO_LONG, (Str.Length())));
+      size_t MaxLen = LENOF(CommStruct->PrintEvent.Message) - 1;
+      UnicodeString Piece = Str.SubString(1, MaxLen);
+      Str.Delete(1, MaxLen);
+
+      CommStruct->Event = TConsoleCommStruct::PRINT;
+      wcscpy(CommStruct->PrintEvent.Message, Piece.c_str());
+      CommStruct->PrintEvent.FromBeginning = FromBeginning;
+
+      // In the next iteration we need to append never overwrite.
+      // Note that this won't work properly for disk/pipe outputs,
+      // when the next line is also FromBeginning,
+      // as !FromBeginning print effectively commits previous FromBeginning print.
+      // On the other hand, FromBeginning print is always initiated by us,
+      // and it's not likely we ever issue print over 10 KiB.
+      FromBeginning = false;
+    }
+    __finally
+    {
+      FreeCommStruct(CommStruct);
     }
 
-    CommStruct->Event = TConsoleCommStruct::PRINT;
-    wcscpy(CommStruct->PrintEvent.Message, Str.c_str());
-    CommStruct->PrintEvent.FromBeginning = FromBeginning;
+    SendEvent(PrintTimeout);
   }
-  __finally
-  {
-    FreeCommStruct(CommStruct);
-  }
-
-  SendEvent(PrintTimeout);
+  while (!Str.IsEmpty());
 }
 //---------------------------------------------------------------------------
 bool __fastcall TExternalConsole::Input(UnicodeString & Str, bool Echo, unsigned int Timer)
@@ -727,10 +739,8 @@ void __fastcall TExternalConsole::SetTitle(UnicodeString Title)
   TConsoleCommStruct * CommStruct = GetCommStruct();
   try
   {
-    if (Title.Length() >= static_cast<int>(LENOF(CommStruct->TitleEvent.Title)))
-    {
-      throw Exception(FMTLOAD(CONSOLE_PRINT_TOO_LONG, (Title.Length())));
-    }
+    // Truncate to maximum allowed. Title over 10 KiB won't fir to screen anyway
+    Title = Title.SubString(1, LENOF(CommStruct->TitleEvent.Title) - 1);
 
     CommStruct->Event = TConsoleCommStruct::TITLE;
     wcscpy(CommStruct->TitleEvent.Title, Title.c_str());

+ 2 - 2
source/windows/GUIConfiguration.cpp

@@ -948,10 +948,10 @@ TStrings * __fastcall TGUIConfiguration::GetLocales()
     Exts->CaseSensitive = false;
 
     int FindAttrs = faReadOnly | faArchive;
-    TSearchRec SearchRec;
+    TSearchRecChecked SearchRec;
     bool Found;
 
-    Found = (bool)(FindFirst(ChangeFileExt(ModuleFileName(), L".*"),
+    Found = (bool)(FindFirstUnchecked(ChangeFileExt(ModuleFileName(), L".*"),
       FindAttrs, SearchRec) == 0);
     try
     {

+ 2 - 2
source/windows/GUITools.cpp

@@ -319,9 +319,9 @@ UnicodeString __fastcall UniqTempDir(const UnicodeString BaseDir, const UnicodeS
 //---------------------------------------------------------------------------
 bool __fastcall DeleteDirectory(const UnicodeString DirName)
 {
-  TSearchRec sr;
+  TSearchRecChecked sr;
   bool retval = true;
-  if (FindFirst(DirName + L"\\*", faAnyFile, sr) == 0) // VCL Function
+  if (FindFirstUnchecked(DirName + L"\\*", faAnyFile, sr) == 0) // VCL Function
   {
     if (FLAGSET(sr.Attr, faDirectory))
     {

+ 1 - 2
source/windows/QueueController.cpp

@@ -151,8 +151,7 @@ bool __fastcall TQueueController::AllowOperation(
         bool Result = (QueueItem != NULL) && (QueueItem->Status != TQueueItem::qsDone);
         if (Result && (Param != NULL))
         {
-          *Param = reinterpret_cast<void *>(QueueItem->ProgressData != NULL ?
-            QueueItem->ProgressData->CPSLimit : 0);
+          Result = QueueItem->GetCPSLimit(*reinterpret_cast<unsigned long *>(Param));
         }
         return Result;
       }

+ 2 - 1
source/windows/Setup.cpp

@@ -942,6 +942,7 @@ void __fastcall GetUpdatesMessage(UnicodeString & Message, bool & New,
   {
     if (Updates.Results.Disabled)
     {
+      New = false;
       if (Force)
       {
         Message = LoadStr(UPDATE_DISABLED);
@@ -1098,7 +1099,7 @@ void __fastcall CheckForUpdates(bool CachedResults)
           if (New)
           {
             Configuration->Usage->Inc(L"UpdateDownloadOpens");
-            OpenBrowser(LoadStr(DOWNLOAD_URL));
+            OpenBrowser(LoadStr(UPGRADE_URL));
           }
           break;
 

+ 6 - 4
source/windows/VCLCommon.cpp

@@ -949,13 +949,15 @@ void __fastcall ResizeForm(TCustomForm * Form, int Width, int Height)
   {
     Left = WorkareaRect.Right - Width;
   }
-  if (Top < 0)
+  // WorkareaRect.Left is not 0, when secondary monitor is placed left of primary one.
+  // Similarly for WorkareaRect.Top.
+  if (Top < WorkareaRect.Top)
   {
-    Top = 0;
+    Top = WorkareaRect.Top;
   }
-  if (Left < 0)
+  if (Left < WorkareaRect.Left)
   {
-    Left = 0;
+    Left = WorkareaRect.Left;
   }
   Form->SetBounds(Left, Top, Width, Height);
   Bounds = Form->BoundsRect;

+ 3 - 2
source/windows/WinConfiguration.cpp

@@ -1789,10 +1789,10 @@ TStrings * __fastcall TWinConfiguration::FindTemporaryFolders()
   TStrings * Result = new TStringList();
   try
   {
-    TSearchRec SRec;
+    TSearchRecChecked SRec;
     UnicodeString Mask = TemporaryDir(true);
     UnicodeString Directory = ExtractFilePath(Mask);
-    if (FindFirst(Mask, faDirectory, SRec) == 0)
+    if (FindFirstUnchecked(Mask, faDirectory, SRec) == 0)
     {
       do
       {
@@ -2260,6 +2260,7 @@ void __fastcall TWinConfiguration::UpdateStaticUsage()
     (Interface == ifExplorer) ?
       ScpExplorer.DriveView :
       (ScpCommander.LocalPanel.DriveView || ScpCommander.RemotePanel.DriveView));
+  Usage->Set(L"MinimizeToTray", MinimizeToTray);
 
   Usage->Set(L"CommanderNortonLikeMode", int(ScpCommander.NortonLikeMode));
   Usage->Set(L"CommanderExplorerKeyboardShortcuts", ScpCommander.ExplorerKeyboardShortcuts);

+ 20 - 1
source/windows/WinInterface.cpp

@@ -988,6 +988,7 @@ void __fastcall ClearGlobalMinimizeHandler(TNotifyEvent OnMinimize)
 //---------------------------------------------------------------------------
 void __fastcall CallGlobalMinimizeHandler(TObject * Sender)
 {
+  Configuration->Usage->Inc(L"OperationMinimizations");
   if (ALWAYS_TRUE(GlobalOnMinimize != NULL))
   {
     GlobalOnMinimize(Sender);
@@ -1166,7 +1167,25 @@ __fastcall ::TTrayIcon::TTrayIcon(unsigned int Id)
   memset(FTrayIcon, 0, sizeof(*FTrayIcon));
   FTrayIcon->cbSize = sizeof(*FTrayIcon);
   FTrayIcon->uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
-  FTrayIcon->hIcon = Application->Icon->Handle;
+
+  // LoadIconMetric is available from Windows Vista only
+  HMODULE ComCtl32Dll = GetModuleHandle(comctl32);
+  if (ALWAYS_TRUE(ComCtl32Dll))
+  {
+    typedef HRESULT WINAPI (* TLoadIconMetric)(HINSTANCE hinst, PCWSTR pszName, int lims, __out HICON *phico);
+    TLoadIconMetric LoadIconMetric = (TLoadIconMetric)GetProcAddress(ComCtl32Dll, "LoadIconMetric");
+    if (LoadIconMetric != NULL)
+    {
+      // Prefer not to use Application->Icon->Handle as that shows 32x32 scaled down to 16x16 for some reason
+      LoadIconMetric(MainInstance, L"MAINICON", LIM_SMALL, &FTrayIcon->hIcon);
+    }
+  }
+
+  if (FTrayIcon->hIcon == 0)
+  {
+    FTrayIcon->hIcon = Application->Icon->Handle;
+  }
+
   FTrayIcon->uID = Id;
   FTrayIcon->hWnd = AllocateHWnd(WndProc);
   FTrayIcon->uCallbackMessage = WM_TRAY_ICON;