diff --git a/README.md b/README.md index 1ee589ed..6a8caf64 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ This project exists to serve the users who were left behind by the march of tech - Portable (Single executable file) - Right-to-left language support and bidirectional text - Use of S.O.L.I.D. principles in object-oriented programming (limited due to the legacy .NET 4.0 framework) -- Use of Windows native methods for memory management +- Use of Windows API methods for memory management - Windows retro-compatibility (Windows XP, Server 2003, and later) ### 💭 Where does the app save the settings? @@ -261,12 +261,12 @@ When new versions require translation updates, we may use AI tools to provide a | Language | Contributor(s) | Language | Contributor(s) | |:---|:---|:---|:---| | 🇦🇱 Albanian | [Omer Rustemi](https://github.com/omerrustemicode) | 🇯🇵 Japanese | [dai](https://github.com/dai) | -| 🇸🇦 Arabic | [Abdulmajeed Al-Rajhi](https://github.com/Abdulmajeed-Alrajhi) | 🇰🇷 Korean | [VenusGirl](https://github.com/VenusGirl) | +| 🇸🇦 Arabic | [Abderraouf FELLAHI](https://github.com/flh-raouf), [Abdulmajeed Al-Rajhi](https://github.com/Abdulmajeed-Alrajhi) | 🇰🇷 Korean | [VenusGirl](https://github.com/VenusGirl) | | 🇧🇬 Bulgarian | [Konstantin](https://github.com/constantinejc) | 🇲🇰 Macedonian | [Dimitrij Gjorgji](https://github.com/Cathadox) | | 🇨🇳 Chinese (Simplified) | [KaiHuaDou](https://github.com/KaiHuaDou), [Kun Zhao](https://github.com/kzhdev), [Rayden](https://github.com/raydenake22) | 🇳🇴 Norwegian | [Dan](https://github.com/danorse) | | 🇨🇳 Chinese (Traditional) | [Rayden](https://github.com/raydenake22), [rtyrtyrtyqw](https://github.com/rtyrtyrtyqw) | 🇮🇷 Persian | [Kavian](https://github.com/KavianK) | | 🇳🇱 Dutch | [Jesse](https://github.com/dragonhuntermc), [hax4dazy](https://github.com/hax4dazy) | 🇵🇱 Polish | [Patryk](https://github.com/Fresta56) | -| 🇫🇷 French | [William VINCENT](https://github.com/wixaw) | 🇧🇷 Portuguese | [Igor Mundstein](https://github.com/IgorMundstein) | +| 🇫🇷 French | [William VINCENT](https://github.com/wixaw) | 🇵🇹 Portuguese (PT) | AI | | 🇩🇪 German | [Calvin](https://github.com/Slluxx), [Niklas Englmeier](https://github.com/iamniklas), [Steve](https://github.com/uDEV2019) | 🇷🇺 Russian | [Ruslan](https://github.com/ruslooob) | | 🇬🇷 Greek | [Theodoros Katsageorgis](https://github.com/tkatsageorgis) | 🇷🇸 Serbian | [Dragoš Milošević](https://github.com/DragorMilos) | | 🇮🇱 Hebrew | [Eliezer Bloy](https://github.com/eliezerbloy) | 🇸🇮 Slovenian | [Jadran Rudec](https://github.com/JadranR) | diff --git a/src/App.xaml b/src/App.xaml index 80764aab..35abfe63 100644 --- a/src/App.xaml +++ b/src/App.xaml @@ -1,10 +1,13 @@ - + + + + + + + + @@ -12,6 +15,7 @@ + @@ -29,6 +33,32 @@ Calibri 15 Light + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -354,21 +322,12 @@ - - - + + + - + @@ -403,14 +362,8 @@ - - + + @@ -431,14 +384,7 @@ - + @@ -446,41 +392,12 @@ - - + + - - - + + + @@ -520,20 +437,8 @@ - - + + @@ -554,17 +459,10 @@ - - + + - + @@ -617,25 +515,13 @@ - - - + + + - + @@ -660,11 +546,7 @@ - + @@ -692,14 +574,8 @@ - - + + @@ -722,9 +598,7 @@ - + @@ -732,4 +606,4 @@ - \ No newline at end of file + diff --git a/src/App.xaml.cs b/src/App.xaml.cs index 86f26149..621041fa 100644 --- a/src/App.xaml.cs +++ b/src/App.xaml.cs @@ -6,11 +6,14 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Security.Principal; using System.ServiceProcess; using System.Threading; using System.Windows; using System.Windows.Forms; +using System.Windows.Input; using System.Windows.Threading; +using Microsoft.Win32; namespace WinMemoryCleaner { @@ -54,7 +57,7 @@ public App() /// Gets a value indicating whether this instance is in debug mode. /// /// - /// true if this instance is in debug mode; otherwise, false. + /// true if this instance is in debug mode; otherwise, false. /// public static bool IsInDebugMode { @@ -113,6 +116,15 @@ protected virtual void Dispose(bool disposing) { if (disposing) { + try + { + SystemEvents.PowerModeChanged -= OnPowerModeChanged; + } + catch + { + // ignored + } + if (_mutex != null) { try @@ -210,6 +222,73 @@ private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledEx Logger.Error(e.Exception); } + /// + /// Handles power mode changes (suspend/resume from hibernation). + /// + /// The sender. + /// The instance containing the event data. + private static void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e) + { + try + { + switch (e.Mode) + { + case PowerModes.Resume: + ThreadPool.QueueUserWorkItem(_ => + { + try + { + // XP/2003 need more stabilization time before reinitialization + Thread.Sleep(Environment.OSVersion.Version.Major < 6 ? 10000 : 5000); + + // Retry logic for handling transient failures during system resume + const int maxRetries = 3; + var retryCount = 0; + Exception lastException = null; + + while (retryCount < maxRetries) + { + try + { + var mainViewModel = DependencyInjection.Container.Resolve(); + + if (mainViewModel == null) + throw new InvalidOperationException("MainViewModel could not be resolved from the DI container"); + + mainViewModel.ReinitializeAfterHibernation(); + return; + } + catch (Exception ex) + { + lastException = ex; + retryCount++; + + if (retryCount >= maxRetries) + { + var failureException = new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Failed to reinitialize after hibernation after {0} attempts", maxRetries), lastException); + Logger.Error(failureException.Message + ": " + failureException.GetMessage()); + throw; + } + + // Wait before retrying + Thread.Sleep(5000); + } + } + } + catch (Exception threadEx) + { + Logger.Error("Critical error in power mode resume handling: " + threadEx.GetMessage()); + } + }); + break; + } + } + catch (Exception ex) + { + Logger.Error("Error handling power mode change: " + ex.GetMessage()); + } + } + /// /// Called when [process exit]. /// @@ -237,47 +316,78 @@ private void OnProcessExit(object sender, EventArgs e) /// /// The sender. /// The instance containing the event data. - private void OnNotifyIconClick(object sender, EventArgs e) + private void OnNotifyIconClick(object sender, System.Windows.Forms.MouseEventArgs e) { lock (_showHidelock) { - var mouseEventArgs = e as MouseEventArgs; - - // Show/Hide - if (mouseEventArgs != null && mouseEventArgs.Button == MouseButtons.Left && MainWindow != null) + switch (e.Button) { - if (MainWindow.OwnedWindows.Cast().Where(window => window != null && window.IsDialog).Any()) - { - MainWindow.Activate(); - MainWindow.Focus(); + // Show/Hide + case MouseButtons.Left: + if (MainWindow == null) + return; - return; - } + if (MainWindow.OwnedWindows.Cast().Where(window => window != null && window.IsDialog).Any()) + { + MainWindow.Activate(); + MainWindow.Focus(); - switch (MainWindow.Visibility) - { - case Visibility.Collapsed: - case Visibility.Hidden: - MainWindow.Show(); + return; + } - MainWindow.WindowState = WindowState.Normal; + switch (MainWindow.Visibility) + { + case Visibility.Collapsed: + case Visibility.Hidden: + MainWindow.Show(); - MainWindow.Activate(); - MainWindow.Focus(); + MainWindow.WindowState = WindowState.Normal; - MainWindow.Topmost = true; - MainWindow.Topmost = Settings.AlwaysOnTop; - MainWindow.ShowInTaskbar = true; - break; + MainWindow.Activate(); + MainWindow.Focus(); - case Visibility.Visible: - MainWindow.Hide(); + MainWindow.Topmost = true; + MainWindow.Topmost = Settings.AlwaysOnTop; + MainWindow.ShowInTaskbar = true; - MainWindow.ShowInTaskbar = false; - break; - } + // Focus the Optimize button when restoring from notification area + MainWindow.Dispatcher.BeginInvoke((Action)(() => + { + var mainWindow = MainWindow as MainWindow; + + if (mainWindow != null) + { + var optimizeButton = mainWindow.FindName("Optimize") as UIElement; + + if (optimizeButton != null) + { + Keyboard.Focus(optimizeButton); + FocusManager.SetFocusedElement(mainWindow, optimizeButton); + } + } + }), DispatcherPriority.ApplicationIdle); + break; + + case Visibility.Visible: + MainWindow.Hide(); + + MainWindow.ShowInTaskbar = false; + break; + } + + ReleaseMemory(); + return; + + // Optimize + case MouseButtons.Middle: + if (!Settings.TrayIconOptimizeOnMiddleMouseClick) + return; + + var mainViewModel = DependencyInjection.Container.Resolve(); - ReleaseMemory(); + if (mainViewModel.OptimizeCommand.CanExecute(null)) + mainViewModel.OptimizeCommand.Execute(null); + break; } } } @@ -321,9 +431,6 @@ protected override void OnStartup(StartupEventArgs startupEvent) if (argument.Equals(Constants.App.CommandLineArgument.Install, StringComparison.OrdinalIgnoreCase)) startupType = Enums.StartupType.Installation; - if (argument.Equals(Constants.App.CommandLineArgument.Package, StringComparison.OrdinalIgnoreCase)) - startupType = Enums.StartupType.Package; - if (argument.Equals(Constants.App.CommandLineArgument.Service, StringComparison.OrdinalIgnoreCase)) startupType = Enums.StartupType.Service; @@ -332,13 +439,6 @@ protected override void OnStartup(StartupEventArgs startupEvent) } } - // Update the last executable path when neither installing nor updating via a package manager - if (startupType != Enums.StartupType.Package) - { - Settings.Path = Path; - Settings.Save(); - } - switch (startupType) { case Enums.StartupType.App: @@ -372,7 +472,7 @@ protected override void OnStartup(StartupEventArgs startupEvent) // Notification Areas _notifyIcon = new NotifyIcon(); - _notifyIcon.Click += OnNotifyIconClick; + _notifyIcon.MouseUp += OnNotifyIconClick; // DI/IOC DependencyInjection.Container.Register(_notifyIcon); @@ -382,6 +482,9 @@ protected override void OnStartup(StartupEventArgs startupEvent) if (!Settings.StartMinimized) mainWindow.Show(); + // Subscribe to power events + SystemEvents.PowerModeChanged += OnPowerModeChanged; + // Process notifications foreach (var notification in _notifications) { @@ -399,116 +502,6 @@ protected override void OnStartup(StartupEventArgs startupEvent) Shutdown(); break; - case Enums.StartupType.Package: - commandLineArguments.Remove(string.Format(Localizer.Culture, "/{0}", Constants.App.CommandLineArgument.Package)); - - var exe = AppDomain.CurrentDomain.FriendlyName; - var isUpdate = false; - var sourcePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, exe); - var targetPath = sourcePath; - var wasRunning = false; - - try - { - if (Directory.Exists(System.IO.Path.GetDirectoryName(Settings.Path))) - targetPath = Settings.Path; - } - catch - { - // Ignored - } - - try - { - var runningProcesses = Process.GetProcessesByName(Constants.App.Name) - .Where(p => p != null && p.Id != Process.GetCurrentProcess().Id) - .ToList(); - - wasRunning = runningProcesses.Any(); - - foreach (var process in runningProcesses) - { - try - { - var processPath = process.MainModule.FileName; - - if (!string.IsNullOrEmpty(processPath) && File.Exists(processPath)) - targetPath = processPath; - } - catch - { - // Ignored - } - - try - { - process.Kill(); - process.WaitForExit(); - } - catch - { - // Ignored - } - finally - { - process.Dispose(); - } - } - } - catch - { - // Ignored - } - - try - { - if (File.Exists(targetPath)) - { - var currentVersion = FileVersionInfo.GetVersionInfo(sourcePath); - var targetVersion = FileVersionInfo.GetVersionInfo(targetPath); - - isUpdate = currentVersion.FileVersion != targetVersion.FileVersion; - } - } - catch - { - // Ignored - } - - try - { - var targetDirectory = System.IO.Path.GetDirectoryName(targetPath); - - if (!string.IsNullOrEmpty(targetDirectory) && !Directory.Exists(targetDirectory)) - Directory.CreateDirectory(targetDirectory); - } - catch - { - // Ignored - } - - var restart = !isUpdate || wasRunning; - - _processes.Add(new ProcessStartInfo - { - Arguments = string.Format - ( - CultureInfo.InvariantCulture, - @"/c move ""{0}"" ""{1}"" >nul 2>&1 & if {2} equ 1 if exist ""{1}"" start """" ""{1}"" {3}", - sourcePath, - targetPath, - restart ? "1" : "0", - string.Join(" ", commandLineArguments.Concat(new[] { isUpdate ? string.Format(CultureInfo.InvariantCulture, "/{0}", Version) : string.Empty }).Where(arg => !string.IsNullOrEmpty(arg))) - ).Trim(), - CreateNoWindow = true, - FileName = "cmd", - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden - }); - - Shutdown(); - break; - case Enums.StartupType.Service: using (var service = new WinService()) { @@ -589,57 +582,140 @@ public static void RunOnStartup(bool enable) { if (enable) { - var runLevelArgument = Environment.OSVersion.Version.Major >= 6 ? " /RL HIGHEST" : string.Empty; + var isTaskCreated = false; - var createStartInfo = new ProcessStartInfo("schtasks") + try { - Arguments = string.Format(Localizer.Culture, @"/CREATE /F /IT{0} /SC ONLOGON /TN ""{1}"" /TR ""{2}"" /RU ""{3}""", runLevelArgument, Constants.App.Title, Path, Environment.UserName), - CreateNoWindow = true, - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden, - RedirectStandardError = true - }; + var taskXml = string.Format + ( + CultureInfo.InvariantCulture, + @" + + + {3} + Runs {0} at logon. + {4} + + + + true + + + + + {2} + InteractiveToken + HighestAvailable + + + + IgnoreNew + false + false + true + true + false + + PT10M + false + false + + true + true + false + false + false + PT0S + 7 + + + + ""{1}"" + + + ", + Constants.App.Title, + Path, + WindowsIdentity.GetCurrent().User.Value, + string.Format(CultureInfo.InvariantCulture, "WMC {0} ({1})", string.Format(Localizer.Culture, Constants.App.VersionFormat, Version.Major, Version.Minor, Version.Build), Environment.UserName), + DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture) + ); + + var tempXmlFile = System.IO.Path.GetTempFileName(); + + File.WriteAllText(tempXmlFile, taskXml); + + var createStartInfo = new ProcessStartInfo("schtasks") + { + Arguments = string.Format(CultureInfo.InvariantCulture, @"/CREATE /F /TN ""{0}"" /XML ""{1}""", Constants.App.Title, tempXmlFile), + CreateNoWindow = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden, + RedirectStandardError = true + }; - using (var createProcess = Process.Start(createStartInfo)) + using (var createProcess = Process.Start(createStartInfo)) + { + var errorMessage = createProcess.StandardError.ReadToEnd(); + createProcess.WaitForExit(); + + if (createProcess.ExitCode == Constants.Windows.SystemErrorCode.ErrorSuccess) + isTaskCreated = true; + else + Logger.Error(string.Format(Localizer.Culture, "XML task creation failed (will attempt fallback). Error: {0}", errorMessage)); + } + + try + { + File.Delete(tempXmlFile); + } + catch + { + // ignored + } + } + catch (Exception ex) + { + Logger.Error(string.Format(Localizer.Culture, "An exception occurred during XML task creation (will attempt fallback): {0}", ex.GetMessage())); + } + + if (!isTaskCreated) { - var errorMessage = createProcess.StandardError.ReadToEnd(); - createProcess.WaitForExit(); + Logger.Information("Attempting basic fallback method to create startup task."); - if (createProcess.ExitCode != Constants.Windows.SystemErrorCode.ErrorSuccess) - Logger.Error(string.Format(Localizer.Culture, "Failed to create startup task for '{0}'. Error: {1}", Constants.App.Title, errorMessage)); + var createStartInfo = new ProcessStartInfo("schtasks") + { + Arguments = string.Format(CultureInfo.InvariantCulture, @"/CREATE /F /SC ONLOGON /TN ""{0}"" /TR ""{1}"" /RU ""{2}""", Constants.App.Title, Path, Environment.UserName), + CreateNoWindow = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden, + RedirectStandardError = true + }; + + using (var createProcess = Process.Start(createStartInfo)) + { + var errorMessage = createProcess.StandardError.ReadToEnd(); + createProcess.WaitForExit(); + + if (createProcess.ExitCode != Constants.Windows.SystemErrorCode.ErrorSuccess) + Logger.Error(string.Format(Localizer.Culture, "Fallback task creation also failed for '{0}'. Error: {1}", Constants.App.Title, errorMessage)); + } } } else { - var queryStartInfo = new ProcessStartInfo("schtasks") + var deleteStartInfo = new ProcessStartInfo("schtasks") { - Arguments = string.Format(Localizer.Culture, @"/QUERY /TN ""{0}""", Constants.App.Title), + Arguments = string.Format(CultureInfo.InvariantCulture, @"/DELETE /F /TN ""{0}""", Constants.App.Title), CreateNoWindow = true, UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden, - RedirectStandardError = true, - RedirectStandardOutput = true + WindowStyle = ProcessWindowStyle.Hidden }; - using (var queryProcess = Process.Start(queryStartInfo)) + using (var deleteProcess = Process.Start(deleteStartInfo)) { - queryProcess.WaitForExit(); - - if (queryProcess.ExitCode == Constants.Windows.SystemErrorCode.ErrorSuccess) - { - var deleteStartInfo = new ProcessStartInfo("schtasks") - { - Arguments = string.Format(Localizer.Culture, @"/DELETE /F /TN ""{0}""", Constants.App.Title), - CreateNoWindow = true, - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden - }; - - using (var deleteProcess = Process.Start(deleteStartInfo)) - { - deleteProcess.WaitForExit(); - } - } + deleteProcess.WaitForExit(); } } } diff --git a/src/Converters/PercentageToWidthConverter.cs b/src/Converters/PercentageToWidthConverter.cs new file mode 100644 index 00000000..e59b9aa9 --- /dev/null +++ b/src/Converters/PercentageToWidthConverter.cs @@ -0,0 +1,65 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace WinMemoryCleaner +{ + /// + /// Percentage To Width Converter + /// Converts a percentage value (0-100) and container width to the actual pixel width + /// + /// + public class PercentageToWidthConverter : IMultiValueConverter + { + /// + /// Converts a percentage and container width to pixel width. + /// + /// The array of values: [0] = container width, [1] = percentage (0-100) + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// The calculated width in pixels + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (values == null || values.Length < 2) + return 0.0; + + if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue) + return 0.0; + + var containerWidth = System.Convert.ToDouble(values[0], culture); + var percentage = System.Convert.ToDouble(values[1], culture); + + if (containerWidth <= 0 || percentage < 0) + return 0.0; + + // Clamp percentage to 0-100 + if (percentage > 100) + percentage = 100; + + return containerWidth * (percentage / 100.0); + } + catch + { + return 0.0; + } + } + + /// + /// Converts a width back to percentage (not supported). + /// + /// The value that the binding target produces. + /// The array of types to convert to. + /// The converter parameter to use. + /// The culture to use in the converter. + /// Not supported + /// + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index d491b81f..05c442db 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -44,7 +44,6 @@ public static class Test public static class CommandLineArgument { public const string Install = "Install"; - public const string Package = "Package"; public const string Service = "Service"; public const string Uninstall = "Uninstall"; } @@ -145,6 +144,8 @@ public static class Locale public static class Name { public const string English = "en"; + public const string PortugueseBrazil = "pt-BR"; + public const string PortuguesePortugal = "pt-PT"; public const string SimplifiedChinese = "zh-Hans"; public const string TraditionalChinese = "zh-Hant"; } diff --git a/src/Core/Helper.cs b/src/Core/Helper.cs index 9f085849..9ccb2591 100644 --- a/src/Core/Helper.cs +++ b/src/Core/Helper.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq.Expressions; using System.Reflection; +using System.Text; using System.Web.Script.Serialization; namespace WinMemoryCleaner @@ -13,26 +14,29 @@ namespace WinMemoryCleaner public static class Helper { /// - /// Determines if the current Windows version supports updates via GitHub TLS/SNI. - /// Returns false for legacy Windows versions (XP/2003) that cannot reach GitHub. + /// Appends indentation to the StringBuilder based on the current nesting level. /// - /// True if updates are supported; otherwise, false. - public static bool IsAutoUpdateSupported + /// The StringBuilder to append to. + /// The current indentation level. + /// The string to use for each indentation level. + private static void AppendIndent(StringBuilder sb, int level, string indent) { - get - { - try - { - var os = Environment.OSVersion; - - if (os.Version != null && os.Version.Major < 6) - return false; // Windows XP/2003 and earlier - } - catch - { - } + for (var i = 0; i < level; i++) + sb.Append(indent); + } - return true; + /// + /// Creates a directory exists. Ignores failures. + /// + public static void CreateDirectory(string path) + { + try + { + if (!string.IsNullOrEmpty(path) && !Directory.Exists(path)) + Directory.CreateDirectory(path); + } + catch + { } } @@ -40,26 +44,103 @@ public static bool IsAutoUpdateSupported /// Converts the specified JSON string to an object of type T /// /// - /// The input. + /// The object. /// - public static T Deserialize(string input) + public static T Deserialize(string obj) { - return new JavaScriptSerializer().Deserialize(input); + return new JavaScriptSerializer().Deserialize(obj); } /// - /// Creates a directory exists. Ignores failures. + /// Formats a minified JSON string into a pretty-printed format with proper indentation and line breaks. /// - public static void CreateDirectory(string path) + /// The minified JSON string to format. + /// A formatted JSON string with indentation and line breaks. + private static string FormatJson(string json) { - try - { - if (!string.IsNullOrEmpty(path) && !Directory.Exists(path)) - Directory.CreateDirectory(path); - } - catch + if (string.IsNullOrEmpty(json)) + return string.Empty; + + var sb = new StringBuilder(json.Length * 2); + var indent = " "; + var level = 0; + var inString = false; + var escapeNext = false; + + for (var i = 0; i < json.Length; i++) { + var c = json[i]; + + if (escapeNext) + { + sb.Append(c); + escapeNext = false; + continue; + } + + if (c == '\\') + { + sb.Append(c); + escapeNext = true; + continue; + } + + if (c == '"') + { + sb.Append(c); + inString = !inString; + continue; + } + + if (inString) + { + sb.Append(c); + continue; + } + + switch (c) + { + case '{': + case '[': + sb.Append(c); + sb.Append(Environment.NewLine); + level++; + AppendIndent(sb, level, indent); + break; + + case '}': + case ']': + sb.Append(Environment.NewLine); + level--; + AppendIndent(sb, level, indent); + sb.Append(c); + break; + + case ',': + sb.Append(c); + sb.Append(Environment.NewLine); + AppendIndent(sb, level, indent); + break; + + case ':': + sb.Append(c); + sb.Append(' '); + break; + + case ' ': + case '\t': + case '\r': + case '\n': + // Skip whitespace outside strings + break; + + default: + sb.Append(c); + break; + } } + + return sb.ToString(); } /// @@ -103,6 +184,30 @@ public static string GetExecutablePath() } } + /// + /// Determines if the current Windows version supports updates via GitHub TLS/SNI. + /// Returns false for legacy Windows versions (XP/2003) that cannot reach GitHub. + /// + /// True if updates are supported; otherwise, false. + public static bool IsAutoUpdateSupported + { + get + { + try + { + var os = Environment.OSVersion; + + if (os.Version != null && os.Version.Major < 6) + return false; // Windows XP/2003 and earlier + } + catch + { + } + + return true; + } + } + /// /// Gets the string name of a property or field. /// @@ -136,6 +241,22 @@ public static T ReadEmbeddedResource(string name) } } + /// + /// Converts the specified object to a JSON string + /// + /// The object to serialize. + /// If true, produces compact JSON without formatting; otherwise, formats with indentation for readability. Default is false. + /// A JSON string representation of the object. + public static string Serialize(IJsonSerializable obj, bool minified = false) + { + if (obj == null) + throw new ArgumentNullException("obj"); + + var json = new JavaScriptSerializer().Serialize(obj.ToJson()); + + return minified ? json : FormatJson(json); + } + /// /// Converts to hexcode. /// diff --git a/src/Core/Logger.cs b/src/Core/Logger.cs index 085f13f2..505dc25f 100644 --- a/src/Core/Logger.cs +++ b/src/Core/Logger.cs @@ -245,7 +245,7 @@ public static void Log(Log log) if (log == null) throw new ArgumentNullException("log"); - Log(log.Level, log.ToString()); + Log(log.Level, Helper.Serialize(log)); } catch (Exception e) { diff --git a/src/Core/Migrator.cs b/src/Core/Migrator.cs index f2c57395..19404c91 100644 --- a/src/Core/Migrator.cs +++ b/src/Core/Migrator.cs @@ -19,6 +19,11 @@ public static void Run() MigrateSettingsFromCurrentUserToLocalMachine(); RemoveStartupRegistry(); } + // 3.0+ + if (App.Version >= new Version(3, 0)) + { + RemoveRegistryPath(); + } } #region Classes @@ -152,6 +157,29 @@ private static void MigrateSettingsFromCurrentUserToLocalMachine() } } + /// + /// Removes the registry path. + /// + private static void RemoveRegistryPath() + { + try + { + using (var key = Registry.LocalMachine.OpenSubKey(Constants.App.Registry.Key.Settings, true)) + { + if (key != null && key.GetValue(V30.Registry.Path, null) != null) + { + key.DeleteValue(V30.Registry.Path, false); + + Logger.Information("Migration version update: Removed path registry entry."); + } + } + } + catch (Exception e) + { + Logger.Error(e); + } + } + /// /// Removes the startup registry to avoid UAC warnings. Use a scheduled task instead to run at startup. /// diff --git a/src/Core/NativeMethods.cs b/src/Core/NativeMethods.cs index 9d2bbd39..8d1ff4af 100644 --- a/src/Core/NativeMethods.cs +++ b/src/Core/NativeMethods.cs @@ -7,7 +7,7 @@ namespace WinMemoryCleaner { /// - /// Windows Native Methods + /// Windows API /// internal static class NativeMethods { diff --git a/src/Core/Settings.cs b/src/Core/Settings.cs index 963922bc..fa2540e3 100644 --- a/src/Core/Settings.cs +++ b/src/Core/Settings.cs @@ -50,8 +50,6 @@ static Settings() public static ModifierKeys OptimizationModifiers { get; set; } - public static string Path { get; set; } - public static SortedSet ProcessExclusionList { get; private set; } public static Enums.Priority RunOnPriority { get; set; } @@ -70,6 +68,10 @@ static Settings() public static byte TrayIconDangerLevel { get; set; } + public static bool TrayIconOptimizeOnMiddleMouseClick { get; set; } + + public static Brush TrayIconOptimizingColor { get; set; } + public static bool TrayIconShowMemoryUsage { get; set; } public static Brush TrayIconTextColor { get; set; } @@ -101,7 +103,6 @@ private static void Load(bool loadUserValues = true) MemoryAreas = Enums.Memory.Areas.CombinedPageList | Enums.Memory.Areas.ModifiedFileCache | Enums.Memory.Areas.ModifiedPageList | Enums.Memory.Areas.RegistryCache | Enums.Memory.Areas.StandbyList | Enums.Memory.Areas.SystemFileCache | Enums.Memory.Areas.WorkingSet; OptimizationKey = Key.M; OptimizationModifiers = ModifierKeys.Control | ModifierKeys.Shift; - Path = string.Empty; ProcessExclusionList = new SortedSet(StringComparer.OrdinalIgnoreCase); RunOnPriority = Enums.Priority.Low; RunOnStartup = false; @@ -111,12 +112,14 @@ private static void Load(bool loadUserValues = true) TrayIconBackgroundColor = Brushes.DarkGreen; TrayIconDangerColor = Brushes.DarkRed; TrayIconDangerLevel = 90; + TrayIconOptimizeOnMiddleMouseClick = false; + TrayIconOptimizingColor = Brushes.DimGray; TrayIconShowMemoryUsage = false; TrayIconTextColor = Brushes.White; TrayIconUseTransparentBackground = false; TrayIconWarningColor = Brushes.DarkGoldenrod; TrayIconWarningLevel = 80; - UseHotkey = true; + UseHotkey = false; // User values try @@ -169,8 +172,6 @@ private static void Load(bool loadUserValues = true) if (Enum.TryParse(Convert.ToString(key.GetValue(Helper.NameOf(() => OptimizationModifiers), OptimizationModifiers), _culture), out optimizationModifiers) && optimizationModifiers.IsValid()) OptimizationModifiers = optimizationModifiers; - Path = Convert.ToString(key.GetValue(Helper.NameOf(() => Path), Path), _culture); - Enums.Priority runOnPriority; if (Enum.TryParse(Convert.ToString(key.GetValue(Helper.NameOf(() => RunOnPriority), RunOnPriority), _culture), out runOnPriority) && runOnPriority.IsValid()) @@ -183,6 +184,8 @@ private static void Load(bool loadUserValues = true) TrayIconBackgroundColor = Convert.ToString(key.GetValue(Helper.NameOf(() => TrayIconBackgroundColor), TrayIconBackgroundColor), _culture).ToBrush(TrayIconBackgroundColor); TrayIconDangerColor = Convert.ToString(key.GetValue(Helper.NameOf(() => TrayIconDangerColor), TrayIconDangerColor), _culture).ToBrush(TrayIconDangerColor); TrayIconDangerLevel = Convert.ToByte(key.GetValue(Helper.NameOf(() => TrayIconDangerLevel), TrayIconDangerLevel), _culture); + TrayIconOptimizeOnMiddleMouseClick = Convert.ToBoolean(key.GetValue(Helper.NameOf(() => TrayIconOptimizeOnMiddleMouseClick), TrayIconOptimizeOnMiddleMouseClick), _culture); + TrayIconOptimizingColor = Convert.ToString(key.GetValue(Helper.NameOf(() => TrayIconOptimizingColor), TrayIconOptimizingColor), _culture).ToBrush(TrayIconOptimizingColor); TrayIconShowMemoryUsage = Convert.ToBoolean(key.GetValue(Helper.NameOf(() => TrayIconShowMemoryUsage), TrayIconShowMemoryUsage), _culture); TrayIconTextColor = Convert.ToString(key.GetValue(Helper.NameOf(() => TrayIconTextColor), TrayIconTextColor), _culture).ToBrush(TrayIconTextColor); TrayIconUseTransparentBackground = Convert.ToBoolean(key.GetValue(Helper.NameOf(() => TrayIconUseTransparentBackground), TrayIconUseTransparentBackground), _culture); @@ -265,7 +268,6 @@ public static void Save() key.SetValue(Helper.NameOf(() => MemoryAreas), (int)MemoryAreas); key.SetValue(Helper.NameOf(() => OptimizationKey), (int)OptimizationKey); key.SetValue(Helper.NameOf(() => OptimizationModifiers), (int)OptimizationModifiers); - key.SetValue(Helper.NameOf(() => Path), Path); key.SetValue(Helper.NameOf(() => RunOnPriority), (int)RunOnPriority); key.SetValue(Helper.NameOf(() => RunOnStartup), RunOnStartup ? 1 : 0); key.SetValue(Helper.NameOf(() => ShowOptimizationNotifications), ShowOptimizationNotifications ? 1 : 0); @@ -274,6 +276,8 @@ public static void Save() key.SetValue(Helper.NameOf(() => TrayIconBackgroundColor), TrayIconBackgroundColor.GetHex(true)); key.SetValue(Helper.NameOf(() => TrayIconDangerColor), TrayIconDangerColor.GetHex(true)); key.SetValue(Helper.NameOf(() => TrayIconDangerLevel), TrayIconDangerLevel); + key.SetValue(Helper.NameOf(() => TrayIconOptimizeOnMiddleMouseClick), TrayIconOptimizeOnMiddleMouseClick ? 1 : 0); + key.SetValue(Helper.NameOf(() => TrayIconOptimizingColor), TrayIconOptimizingColor.GetHex(true)); key.SetValue(Helper.NameOf(() => TrayIconShowMemoryUsage), TrayIconShowMemoryUsage ? 1 : 0); key.SetValue(Helper.NameOf(() => TrayIconTextColor), TrayIconTextColor.GetHex(true)); key.SetValue(Helper.NameOf(() => TrayIconUseTransparentBackground), TrayIconUseTransparentBackground ? 1 : 0); diff --git a/src/Core/Updater.cs b/src/Core/Updater.cs index 594e1f50..8b93500e 100644 --- a/src/Core/Updater.cs +++ b/src/Core/Updater.cs @@ -138,7 +138,7 @@ private static void ApplyUpdateAndRelaunch(string targetPath, string backupPath, } catch (Exception e) { - LogCritical("UPD-APPLY-ROLLBACK", e); + Log("UPD-APPLY-ROLLBACK", e); } return; @@ -159,7 +159,7 @@ private static void ApplyUpdateAndRelaunch(string targetPath, string backupPath, } catch (Exception e) { - LogCritical("UPD-RELAUNCH", e); + Log("UPD-RELAUNCH", e); } } @@ -559,7 +559,7 @@ private static bool IsSameVolume(string a, string b) } } - private static void LogCritical(string code, Exception e) + private static void Log(string code, Exception e) { try { @@ -652,7 +652,7 @@ private static bool TryApplyUpdateFromEnvironment() } catch (Exception e) { - LogCritical("UPD-READ-TOKEN", e); + Log("UPD-READ-TOKEN", e); App.Shutdown(true); return true; @@ -838,8 +838,7 @@ public static void Update(List commandLineArguments = null) { var commandLineArgument = arg != null ? arg.Replace("/", string.Empty) : null; - if (string.Equals(commandLineArgument, Constants.App.CommandLineArgument.Package, StringComparison.OrdinalIgnoreCase) || - string.Equals(commandLineArgument, Constants.App.CommandLineArgument.Install, StringComparison.OrdinalIgnoreCase) || + if (string.Equals(commandLineArgument, Constants.App.CommandLineArgument.Install, StringComparison.OrdinalIgnoreCase) || string.Equals(commandLineArgument, Constants.App.CommandLineArgument.Uninstall, StringComparison.OrdinalIgnoreCase) || string.Equals(commandLineArgument, Constants.App.CommandLineArgument.Service, StringComparison.OrdinalIgnoreCase)) return; @@ -967,10 +966,8 @@ public static void Update(List commandLineArguments = null) } } - var relaunchArgs = BuildArgumentString(relaunchArgsList); - var backupPath = BuildBackupPath(exePath); - + var relaunchArgs = BuildArgumentString(relaunchArgsList); var token = Guid.NewGuid().ToString("N"); var tokenFilePath = BuildTokenFilePath(token); @@ -986,7 +983,7 @@ public static void Update(List commandLineArguments = null) } catch (Exception e) { - LogCritical("UPD-WRITE-TOKEN", e); + Log("UPD-WRITE-TOKEN", e); SafeDeleteFile(stagedExePath); SafeDeleteFile(tokenFilePath); @@ -1010,7 +1007,7 @@ public static void Update(List commandLineArguments = null) } catch (Exception e) { - LogCritical("UPD-LAUNCH-STAGED", e); + Log("UPD-LAUNCH-STAGED", e); SafeDeleteFile(stagedExePath); SafeDeleteFile(tokenFilePath); @@ -1022,7 +1019,7 @@ public static void Update(List commandLineArguments = null) } catch (Exception e) { - LogCritical("UPD-UNHANDLED", e); + Log("UPD-UNHANDLED", e); } } #endregion diff --git a/src/Interfaces/IJsonSerializable.cs b/src/Interfaces/IJsonSerializable.cs new file mode 100644 index 00000000..9f8e60d4 --- /dev/null +++ b/src/Interfaces/IJsonSerializable.cs @@ -0,0 +1,14 @@ +namespace WinMemoryCleaner +{ + /// + /// Represents an object that can be serialized to JSON. + /// + public interface IJsonSerializable + { + /// + /// Converts the object to a JSON-serializable representation. + /// + /// An object ready for JSON serialization. + object ToJson(); + } +} diff --git a/src/Interfaces/INotificationService.cs b/src/Interfaces/INotificationService.cs index 0b1f6b80..422b16ef 100644 --- a/src/Interfaces/INotificationService.cs +++ b/src/Interfaces/INotificationService.cs @@ -31,6 +31,7 @@ public interface INotificationService : IDisposable /// Update Info /// /// The memory. - void Update(Memory memory); + /// if set to true [is optimizing]. + void Update(Memory memory, bool isOptimizing = false); } } diff --git a/src/Model/Language.cs b/src/Model/Language.cs index be589146..c3a7e9fd 100644 --- a/src/Model/Language.cs +++ b/src/Model/Language.cs @@ -45,6 +45,12 @@ public string DisplayEnglishName { switch (Name) { + case Constants.Windows.Locale.Name.PortugueseBrazil: + return "Portuguese"; + + case Constants.Windows.Locale.Name.PortuguesePortugal: + return "Portuguese"; + case Constants.Windows.Locale.Name.SimplifiedChinese: return "Chinese (S)"; @@ -57,6 +63,30 @@ public string DisplayEnglishName } } + /// + /// Gets the display name of the native. + /// + /// + /// The display name of the native. + /// + public string DisplayNativeName + { + get + { + switch (Name) + { + case Constants.Windows.Locale.Name.PortugueseBrazil: + return "Português (BR)"; + + case Constants.Windows.Locale.Name.PortuguesePortugal: + return "Português (PT)"; + + default: + return NativeName; + } + } + } + /// /// Gets the english name. /// @@ -134,7 +164,7 @@ public override int GetHashCode() /// public override string ToString() { - return EnglishName == NativeName ? EnglishName : string.Format(Localizer.Culture, "{0} ({1})", DisplayEnglishName, NativeName); + return EnglishName == NativeName ? EnglishName : string.Format(Localizer.Culture, "{0} ({1})", DisplayEnglishName, DisplayNativeName); } } } diff --git a/src/Model/Localization.cs b/src/Model/Localization.cs index 334808da..ac54ff24 100644 --- a/src/Model/Localization.cs +++ b/src/Model/Localization.cs @@ -18,12 +18,13 @@ public class Localization private string _dangerLevel, _donate, _donationMessage, _donationTitle; private string _error, _errorAdminPrivilegeRequired, _errorCanNotSaveLog, _errorMemoryAreaOptimizationNotSupported, _everyHour, _exit, _expand; private string _free; + private string _garbageCollector; private string _help, _hotkeyIsInUseByOperatingSystem; private string _invalid; private string _lowMemory; private string _manual, _memoryAreas, _memoryOptimized, _memoryUsage, _minimize, _modifiedFileCache, _modifiedPageList; private string _no; - private string _optimizationHotkey, _optimize, _optimized; + private string _optimizationHotkey, _optimize, _optimizeOnMiddleMouseClick, _optimizing; private string _physicalMemory, _processExclusionList; private string _reason, _registryCache, _remove, _reset, _resetConfirmation, _runOnLowPriority, _runOnStartup; private string _schedule, _seconds, _securityWarning, _settings, _showMemoryUsage, _showOptimizationNotifications, _showVirtualMemory, _standbyList, _standbyListLowPriority, _startMinimized, _systemFileCache; @@ -205,6 +206,13 @@ public string Free private set { _free = value.Capitalize(); } } + [DataMember] + public string GarbageCollector + { + get { return _garbageCollector; } + private set { _garbageCollector = value.Capitalize(); } + } + [DataMember] public string Help { @@ -304,10 +312,17 @@ public string Optimize } [DataMember] - public string Optimized + public string OptimizeOnMiddleMouseClick + { + get { return _optimizeOnMiddleMouseClick; } + private set { _optimizeOnMiddleMouseClick = value.Capitalize(); } + } + + [DataMember] + public string Optimizing { - get { return _optimized; } - private set { _optimized = value.Capitalize(); } + get { return _optimizing; } + private set { _optimizing = value.Capitalize(); } } [DataMember] diff --git a/src/Model/Log/Log.cs b/src/Model/Log/Log.cs index d2cfbf9e..96fe19b0 100644 --- a/src/Model/Log/Log.cs +++ b/src/Model/Log/Log.cs @@ -1,13 +1,13 @@ using System; +using System.Globalization; using System.Runtime.CompilerServices; -using System.Text; namespace WinMemoryCleaner { /// /// Log Item /// - public class Log + public class Log : IJsonSerializable { /// /// Initializes a new instance of the class. @@ -27,20 +27,20 @@ public Log(Enums.Log.Levels level, string message, ILogData data = null, [Caller } /// - /// Gets or sets the level. + /// Gets the data. /// /// - /// The level. + /// The data. /// - public Enums.Log.Levels Level { get; private set; } + public ILogData Data { get; private set; } /// - /// Gets or sets the method. + /// Gets or sets the level. /// /// - /// The method. + /// The level. /// - public string Method { get; private set; } + public Enums.Log.Levels Level { get; private set; } /// /// Gets or sets the message. @@ -51,12 +51,12 @@ public Log(Enums.Log.Levels level, string message, ILogData data = null, [Caller public string Message { get; private set; } /// - /// Gets the data. + /// Gets or sets the method. /// /// - /// The data. + /// The method. /// - public ILogData Data { get; private set; } + public string Method { get; private set; } /// /// Gets or sets the timestamp. @@ -67,30 +67,29 @@ public Log(Enums.Log.Levels level, string message, ILogData data = null, [Caller public DateTimeOffset Timestamp { get; private set; } /// - /// Converts to string. + /// Converts the log to a JSON-serializable object. /// - /// - /// A that represents this instance. - /// - public override string ToString() + /// An anonymous object ready for JSON serialization. + public object ToJson() { - var sb = new StringBuilder(); - var space = " "; + var jsonSerializable = Data as IJsonSerializable; + var data = jsonSerializable != null ? jsonSerializable.ToJson() : null; - sb.Append('{').Append(Environment.NewLine); - sb.Append(space).Append("\"timestamp\": \"").Append(Timestamp.ToString("yyyy-MM-ddTHH:mm:ss.fffK", Localizer.Culture)).Append("\",").Append(Environment.NewLine); - sb.Append(space).Append("\"level\": \"").Append(Level.ToString()).Append("\",").Append(Environment.NewLine); - sb.Append(space).Append("\"method\": \"").Append(Method).Append("\",").Append(Environment.NewLine); - sb.Append(space).Append("\"message\": \"").Append(Message); + var level = Level.ToString(); + var timestamp = Timestamp.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK", CultureInfo.InvariantCulture); - if (Data != null) - sb.Append("\",").Append(Environment.NewLine).Append(space).Append("\"data\": ").Append(Data.ToString()).Append(Environment.NewLine); - else - sb.Append('\"').Append(Environment.NewLine); - - sb.Append('}'); + return data == null + ? new { timestamp, level, method = Method, message = Message, data = (object)null } + : (object)new { timestamp, level, method = Method, message = Message, data }; + } - return sb.ToString(); + /// + /// Returns a human-readable string representation for debugging. + /// + /// A formatted string showing timestamp, level, and message. + public override string ToString() + { + return string.Format(Localizer.Culture, "[{0}] {1}: {2}", Timestamp.ToString("HH:mm:ss", Localizer.Culture), Level, Message); } } } diff --git a/src/Model/Log/LogOptimizationData.cs b/src/Model/Log/LogOptimizationData.cs index 35021280..f692687b 100644 --- a/src/Model/Log/LogOptimizationData.cs +++ b/src/Model/Log/LogOptimizationData.cs @@ -1,14 +1,12 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; namespace WinMemoryCleaner { /// /// Log Optimization Data /// - public class LogOptimizationData : ILogData + public class LogOptimizationData : ILogData, IJsonSerializable { /// /// Initializes a new instance of the class. @@ -42,42 +40,36 @@ public LogOptimizationData() /// public string Reason { get; set; } - /// - /// Converts to string. - /// - /// - /// A that represents this instance. - /// - public override string ToString() + /// + /// Converts the log optimization data to a JSON-serializable object. + /// + /// An anonymous object ready for JSON serialization. + public object ToJson() { - var sb = new StringBuilder(); - var space = " "; + var memoryAreas = MemoryAreas + .OrderBy(m => m.Name) + .Select(m => string.IsNullOrEmpty(m.Error) + ? new { name = m.Name, duration = m.Duration } + : (object)new { name = m.Name, duration = m.Duration, error = m.Error }) + .ToList(); - sb.Append('{').Append(Environment.NewLine); - sb.Append(space).Append(space).Append("\"reason\": \"").Append(Reason).Append("\",").Append(Environment.NewLine); - sb.Append(space).Append(space).Append("\"duration\": \"").Append(Duration).Append("\",").Append(Environment.NewLine); - sb.Append(space).Append(space).Append("\"memoryAreas\": [").Append(Environment.NewLine); - - foreach (var memoryArea in MemoryAreas.OrderBy(memoryArea => memoryArea.Name)) + return new { - sb.Append(space).Append(space).Append(space).Append("{ "); - sb.Append("\"name\": \"").Append(memoryArea.Name); - sb.Append("\", \"duration\": \"").Append(memoryArea.Duration); - - if (memoryArea.Error != null) - sb.Append("\", \"error\": \"").Append(memoryArea.Error); - - sb.Append("\" }").Append(',').Append(Environment.NewLine); - } - - if (MemoryAreas.Count > 0) - sb.Length -= (1 + Environment.NewLine.Length); - - sb.Append(Environment.NewLine); - sb.Append(space).Append(space).Append(']').Append(Environment.NewLine); - sb.Append(space).Append('}'); + reason = Reason, + duration = Duration, + memoryAreas + }; + } - return sb.ToString(); + /// + /// Converts to string. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return string.Format(Localizer.Culture, "{0} ({1}) - {2} area(s)", Reason, Duration, MemoryAreas != null ? MemoryAreas.Count : 0); } } } diff --git a/src/Model/ObservableItem.cs b/src/Model/ObservableItem.cs index 8dd65acf..0b5fe13f 100644 --- a/src/Model/ObservableItem.cs +++ b/src/Model/ObservableItem.cs @@ -9,6 +9,8 @@ namespace WinMemoryCleaner /// public class ObservableItem : ObservableObject { + private bool _isEnabled; + /// /// Initializes a new instance of the class. /// @@ -19,7 +21,7 @@ public class ObservableItem : ObservableObject public ObservableItem(string name, Func getter, Action setter, bool isEnabled = true) { Getter = getter; - IsEnabled = isEnabled; + _isEnabled = isEnabled; Name = name; Setter = setter; } @@ -33,12 +35,23 @@ public ObservableItem(string name, Func getter, Action setter, bool isEnab public Func Getter { get; private set; } /// - /// Gets a value indicating whether this instance is enabled. + /// Gets or sets a value indicating whether this instance is enabled. /// /// /// true if this instance is enabled; otherwise, false. /// - public bool IsEnabled { get; private set; } + public bool IsEnabled + { + get { return _isEnabled; } + set + { + if (_isEnabled != value) + { + _isEnabled = value; + RaisePropertyChanged(); + } + } + } /// /// Gets the name. diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs index 97525ed0..14072e31 100644 --- a/src/Properties/AssemblyInfo.cs +++ b/src/Properties/AssemblyInfo.cs @@ -11,7 +11,7 @@ [assembly: AssemblyKeyFile(Constants.App.KeyFile)] [assembly: AssemblyProduct(Constants.App.Name)] [assembly: AssemblyTitle(Constants.App.Title)] -[assembly: AssemblyVersion("3.0.2.0")] +[assembly: AssemblyVersion("3.0.3.0")] [assembly: ComVisible(false)] [assembly: CLSCompliant(true)] [assembly: Guid(Constants.App.Id)] diff --git a/src/Resources/Localization/Albanian.json b/src/Resources/Localization/Albanian.json index 0c869006..b0ebe480 100644 --- a/src/Resources/Localization/Albanian.json +++ b/src/Resources/Localization/Albanian.json @@ -23,6 +23,7 @@ "Exit": "dil", "Expand": "zgjero", "Free": "falas", + "GarbageCollector": "mbledhës mbeturinash", "Help": "ndihmë", "HotkeyIsInUseByOperatingSystem": "tasti i shkurtër ({0}) është në përdorim nga sistemi operativ", "Invalid": "e pavlefshme", @@ -37,7 +38,8 @@ "No": "jo", "OptimizationHotkey": "tasti kryesor i optimizimit", "Optimize": "optimizo", - "Optimized": "e optimizuar", + "OptimizeOnMiddleMouseClick": "optimizo me klikimin e butonit të mesëm të miut", + "Optimizing": "duke optimizuar", "PhysicalMemory": "memorie fizike", "ProcessExclusionList": "proceset e përjashtuara nga optimizimi", "Reason": "arsyeja", diff --git a/src/Resources/Localization/Arabic.json b/src/Resources/Localization/Arabic.json index f56c731e..9d838697 100644 --- a/src/Resources/Localization/Arabic.json +++ b/src/Resources/Localization/Arabic.json @@ -3,13 +3,13 @@ "Add": "إضافة", "AlwaysOnTop": "دائما في الأعلى", "AutoOptimization": "التحسين التلقائي", - "AutoOptimizationInterval": "الفاصل الزمني بين تحسين الذاكرة التلقائي هو {0} دقائق", + "AutoOptimizationInterval": "الفاصل الزمني للتحسين التلقائي للذاكرة هو {0} دقيقة", "AutoUpdate": "التحديث التلقائي", "Background": "الخلفية", "Close": "إغلاق", "CloseAfterOptimization": "إغلاق بعد التحسين", - "CloseToTheNotificationArea": "بالقرب من منطقة الإعلام", - "Collapse": "توسيع", + "CloseToTheNotificationArea": "الإغلاق إلى منطقة الإشعارات", + "Collapse": "طي", "CombinedPageList": "قائمة الصفحات المجمعة", "DangerLevel": "مستوى الخطر", "Donate": "تبرع", @@ -21,15 +21,16 @@ "ErrorMemoryAreaOptimizationNotSupported": "تحسين {0} غير مدعوم في إصدار نظام التشغيل هذا", "EveryHour": "كل {0} ساعة", "Exit": "خروج", - "Expand": "الانهيار", - "Free": "تفريغ", + "Expand": "توسيع", + "Free": "متاحة", + "GarbageCollector": "جامع القمامة", "Help": "مساعدة", "HotkeyIsInUseByOperatingSystem": "مفتاح التشغيل السريع ({0}) قيد الاستخدام بواسطة نظام التشغيل", "Invalid": "غير صالح", "LowMemory": "ذاكرة منخفضة", "Manual": "يدوي", "MemoryAreas": "مناطق الذاكرة", - "MemoryOptimized": "الذاكرة المحسنة", + "MemoryOptimized": "تم تحسين الذاكرة", "MemoryUsage": "استخدام الذاكرة", "Minimize": "تصغير", "ModifiedFileCache": "ذاكرة التخزين المؤقت للملفات المعدلة", @@ -37,7 +38,8 @@ "No": "لا", "OptimizationHotkey": "مفتاح التشغيل السريع للتحسين", "Optimize": "تحسين", - "Optimized": "تم التحسين", + "OptimizeOnMiddleMouseClick": "التحسين عند النقر بزر الماوس الأوسط", + "Optimizing": "جارٍ التحسين", "PhysicalMemory": "ذاكرة فعلية", "ProcessExclusionList": "العمليات المستبعدة من التحسين", "Reason": "السبب", @@ -54,17 +56,17 @@ "ShowMemoryUsage": "إظهار استخدام الذاكرة", "ShowOptimizationNotifications": "إظهار إشعارات التحسين", "ShowVirtualMemory": "إظهار الذاكرة الافتراضية", - "StandbyList": "قائمة الانتظار", - "StandbyListLowPriority": "قائمة الانتظار (أولوية منخفضة)", - "StartMinimized": "ابدأ مصغرًا", + "StandbyList": "قائمة الاستعداد", + "StandbyListLowPriority": "قائمة الاستعداد (أولوية منخفضة)", + "StartMinimized": "بدء التشغيل مصغّرًا", "SystemFileCache": "ذاكرة التخزين المؤقت لملفات النظام", "Text": "نص", - "TrayIcon": "أيقونة صينية", + "TrayIcon": "أيقونة منطقة الإشعارات", "UpdatedToVersion": "تم التحديث إلى الإصدار {0}", "Used": "مستخدمة", "UseTransparentBackground": "استخدم خلفية شفافة", "VirtualMemory": "ذاكرة افتراضية", - "WhenFreePhysicalMemoryIsBelow": "عندما تكون الذاكرة الفيزيائية الحرة أقل من {0}%", + "WhenFreePhysicalMemoryIsBelow": "عندما تكون الذاكرة الفعلية الحرة أقل من {0}%", "WarningLevel": "مستوى التحذير", "WorkingSet": "مجموعة العمل", "Yes": "نعم" diff --git a/src/Resources/Localization/Bulgarian.json b/src/Resources/Localization/Bulgarian.json index 8a3b8054..38025258 100644 --- a/src/Resources/Localization/Bulgarian.json +++ b/src/Resources/Localization/Bulgarian.json @@ -23,6 +23,7 @@ "Exit": "изход", "Expand": "разшири", "Free": "свободна", + "GarbageCollector": "боклукчия", "Help": "помощ", "HotkeyIsInUseByOperatingSystem": "клавишът ({0}) е зает от операционната система", "Invalid": "невалиден", @@ -37,7 +38,8 @@ "No": "не", "OptimizationHotkey": "клавиш за оптимизиране", "Optimize": "оптимизирай", - "Optimized": "оптимизиран", + "OptimizeOnMiddleMouseClick": "оптимизирай при щракване със средния бутон на мишката", + "Optimizing": "оптимизиране", "PhysicalMemory": "физическа памет", "ProcessExclusionList": "процесът бе изключен от оптимизацията", "Reason": "причина", diff --git a/src/Resources/Localization/Chinese (Simplified).json b/src/Resources/Localization/Chinese (Simplified).json index 697af42f..8320e075 100644 --- a/src/Resources/Localization/Chinese (Simplified).json +++ b/src/Resources/Localization/Chinese (Simplified).json @@ -23,6 +23,7 @@ "Exit": "退出", "Expand": "展开", "Free": "可用", + "GarbageCollector": "垃圾回收器", "Help": "帮助", "HotkeyIsInUseByOperatingSystem": "操作系统正在使用热键 ({0})", "Invalid": "无效的", @@ -37,7 +38,8 @@ "No": "否", "OptimizationHotkey": "优化热键", "Optimize": "优化", - "Optimized": "已优化", + "OptimizeOnMiddleMouseClick": "鼠标中键单击时优化", + "Optimizing": "优化中", "PhysicalMemory": "物理内存", "ProcessExclusionList": "排除的进程", "Reason": "原因", diff --git a/src/Resources/Localization/Chinese (Traditional).json b/src/Resources/Localization/Chinese (Traditional).json index fbf6012b..4a2a08b3 100644 --- a/src/Resources/Localization/Chinese (Traditional).json +++ b/src/Resources/Localization/Chinese (Traditional).json @@ -23,6 +23,7 @@ "Exit": "結束", "Expand": "展開", "Free": "可用", + "GarbageCollector": "垃圾回收器", "Help": "幫助", "HotkeyIsInUseByOperatingSystem": "作業系統正在使用快速鍵({0})", "Invalid": "無效", @@ -37,7 +38,8 @@ "No": "否", "OptimizationHotkey": "最佳化快速鍵", "Optimize": "最佳化", - "Optimized": "已最佳化", + "OptimizeOnMiddleMouseClick": "滑鼠中鍵點擊時最佳化", + "Optimizing": "最佳化中", "PhysicalMemory": "實體記憶體", "ProcessExclusionList": "排除的處理程序", "Reason": "原因", diff --git a/src/Resources/Localization/Dutch.json b/src/Resources/Localization/Dutch.json index e67840a7..9ac50902 100644 --- a/src/Resources/Localization/Dutch.json +++ b/src/Resources/Localization/Dutch.json @@ -23,6 +23,7 @@ "Exit": "sluiten", "Expand": "uitvouwen", "Free": "vrij", + "GarbageCollector": "garbage collector", "Help": "help", "HotkeyIsInUseByOperatingSystem": "sneltoets ({0}) wordt reeds gebruikt door het besturingssysteem", "Invalid": "ongeldig", @@ -37,7 +38,8 @@ "No": "nee", "OptimizationHotkey": "optimalisatie sneltoets", "Optimize": "optimaliseren", - "Optimized": "geoptimaliseerd", + "OptimizeOnMiddleMouseClick": "optimaliseren bij middelste muisklik", + "Optimizing": "optimaliseren", "PhysicalMemory": "fysiek geheugen", "ProcessExclusionList": "processen uitgesloten van optimalisatie", "Reason": "reden", diff --git a/src/Resources/Localization/English.json b/src/Resources/Localization/English.json index 817a1223..9b1ee81a 100644 --- a/src/Resources/Localization/English.json +++ b/src/Resources/Localization/English.json @@ -18,13 +18,14 @@ "Error": "error", "ErrorAdminPrivilegeRequired": "this operation requires administrator privileges ({0})", "ErrorCanNotSaveLog": "cannot save the log: {0} ({1})", - "ErrorMemoryAreaOptimizationNotSupported": "the {0} optimization is not supported on this version of the operating system", + "ErrorMemoryAreaOptimizationNotSupported": "the memory area {0} optimization is not supported on this version of the operating system", "EveryHour": "every {0}h", "Exit": "exit", "Expand": "expand", "Free": "free", + "GarbageCollector": "garbage collector", "Help": "help", - "HotkeyIsInUseByOperatingSystem": "the hotkey ({0}) is currently in use by the operating system", + "HotkeyIsInUseByOperatingSystem": "the hotkey ({0}) is in use by the operating system", "Invalid": "invalid", "LowMemory": "low memory", "Manual": "manual", @@ -37,7 +38,8 @@ "No": "no", "OptimizationHotkey": "optimization hotkey", "Optimize": "optimize", - "Optimized": "optimized", + "OptimizeOnMiddleMouseClick": "optimize on middle mouse click", + "Optimizing": "optimizing", "PhysicalMemory": "physical memory", "ProcessExclusionList": "processes excluded from optimization", "Reason": "reason", diff --git a/src/Resources/Localization/French.json b/src/Resources/Localization/French.json index e0578f32..b679801d 100644 --- a/src/Resources/Localization/French.json +++ b/src/Resources/Localization/French.json @@ -23,6 +23,7 @@ "Exit": "quitter", "Expand": "développer", "Free": "libre", + "GarbageCollector": "collecteur d'ordures", "Help": "aide", "HotkeyIsInUseByOperatingSystem": "le raccourci clavier ({0}) est utilisé par le système d'exploitation", "Invalid": "invalide", @@ -37,7 +38,8 @@ "No": "non", "OptimizationHotkey": "raccourci clavier d'optimisation", "Optimize": "optimiser", - "Optimized": "optimisé", + "OptimizeOnMiddleMouseClick": "optimiser avec le bouton central de la souris", + "Optimizing": "optimisation", "PhysicalMemory": "mémoire physique", "ProcessExclusionList": "processus exclus de l'optimisation", "Reason": "raison", diff --git a/src/Resources/Localization/German.json b/src/Resources/Localization/German.json index 88c88e73..93607beb 100644 --- a/src/Resources/Localization/German.json +++ b/src/Resources/Localization/German.json @@ -23,6 +23,7 @@ "Exit": "beenden", "Expand": "ausklappen", "Free": "frei", + "GarbageCollector": "garbage collector", "Help": "hilfe", "HotkeyIsInUseByOperatingSystem": "hotkey ({0}) wird vom betriebssystem belegt", "Invalid": "ungültig", @@ -37,7 +38,8 @@ "No": "nein", "OptimizationHotkey": "tastaturkürzel für optimierung", "Optimize": "optimieren", - "Optimized": "optimiert", + "OptimizeOnMiddleMouseClick": "optimieren bei mittlerem Mausklick", + "Optimizing": "optimierung läuft", "PhysicalMemory": "physikalischer speicher", "ProcessExclusionList": "von optimierung ignorierte prozesse", "Reason": "grund", diff --git a/src/Resources/Localization/Greek.json b/src/Resources/Localization/Greek.json index d42cde98..45ef0201 100644 --- a/src/Resources/Localization/Greek.json +++ b/src/Resources/Localization/Greek.json @@ -23,6 +23,7 @@ "Exit": "έξοδος", "Expand": "ανάπτυξη", "Free": "ελεύθερη", + "GarbageCollector": "συλλέκτης απορριμμάτων", "Help": "βοήθεια", "HotkeyIsInUseByOperatingSystem": "το ειδικό πλήκτρο ({0}) χρησιμοποιείται από το λειτουργικό σύστημα", "Invalid": "άκυρο", @@ -37,7 +38,8 @@ "No": "όχι", "OptimizationHotkey": "ειδικό πλήκτρο βελτιστοποίησης", "Optimize": "βελτιστοποίηση", - "Optimized": "βελτιστοποιήθηκε", + "OptimizeOnMiddleMouseClick": "βελτιστοποίηση με μεσαίο κλικ ποντικιού", + "Optimizing": "βελτιστοποίηση", "PhysicalMemory": "φυσική μνήμη", "ProcessExclusionList": "διεργασίες που εξαιρέθηκαν από τη βελτιστοποίηση", "Reason": "αιτία", diff --git a/src/Resources/Localization/Hebrew.json b/src/Resources/Localization/Hebrew.json index 0c56f8ff..76d29c9e 100644 --- a/src/Resources/Localization/Hebrew.json +++ b/src/Resources/Localization/Hebrew.json @@ -23,6 +23,7 @@ "Exit": "יציאה", "Expand": "הרחב", "Free": "פנוי", + "GarbageCollector": "אספן זבל", "Help": "עזרה", "HotkeyIsInUseByOperatingSystem": "מקש קיצור ({0}) נמצא בשימוש על ידי מערכת ההפעלה", "Invalid": "לא חוקי", @@ -37,7 +38,8 @@ "No": "לא", "OptimizationHotkey": "מקש קיצור אופטימיזציה", "Optimize": "בצע אופטימיזציה", - "Optimized": "אופטימיזציה", + "OptimizeOnMiddleMouseClick": "אופטימיזציה בלחיצה על לחצן אמצעי של העכבר", + "Optimizing": "מבצע אופטימיזציה", "PhysicalMemory": "זיכרון פיזי", "ProcessExclusionList": "תהליכים שלא נכללו באופטימיזציה", "Reason": "סיבה", diff --git a/src/Resources/Localization/Hungarian.json b/src/Resources/Localization/Hungarian.json index eb78d178..f5d60a72 100644 --- a/src/Resources/Localization/Hungarian.json +++ b/src/Resources/Localization/Hungarian.json @@ -23,6 +23,7 @@ "Exit": "kilépés", "Expand": "kibontás", "Free": "szabad memória", + "GarbageCollector": "szemétgyűjtő", "Help": "súgó", "HotkeyIsInUseByOperatingSystem": "a gyorsbillentyűt ({0}) jelenleg a rendszer használja", "Invalid": "érvénytelen", @@ -37,7 +38,8 @@ "No": "nem", "OptimizationHotkey": "optimalizálás gyorsbillentyű", "Optimize": "optimalizálás", - "Optimized": "optimalizált", + "OptimizeOnMiddleMouseClick": "optimalizálás középső egérgombra kattintáskor", + "Optimizing": "optimalizálás folyamatban", "PhysicalMemory": "fizikai memória", "ProcessExclusionList": "az optimalizálásból kizárt folyamatok", "Reason": "ok", diff --git a/src/Resources/Localization/Indonesian.json b/src/Resources/Localization/Indonesian.json index ff4ce52d..adedac7b 100644 --- a/src/Resources/Localization/Indonesian.json +++ b/src/Resources/Localization/Indonesian.json @@ -23,6 +23,7 @@ "Exit": "keluar", "Expand": "perluas", "Free": "sisa", + "GarbageCollector": "pengumpul sampah", "Help": "bantuan", "HotkeyIsInUseByOperatingSystem": "kombinasi ({0}) telah digunakan oleh sistem operasi", "Invalid": "tidak sah", @@ -37,7 +38,8 @@ "No": "tidak", "OptimizationHotkey": "tombol pintas optimalisasi", "Optimize": "optimalkan", - "Optimized": "teroptimalkan", + "OptimizeOnMiddleMouseClick": "optimalkan dengan klik tombol tengah mouse", + "Optimizing": "mengoptimalkan", "PhysicalMemory": "memori fisik", "ProcessExclusionList": "daftar pengecualian proses", "Reason": "alasan", diff --git a/src/Resources/Localization/Irish.json b/src/Resources/Localization/Irish.json index dd7453e3..5388c63a 100644 --- a/src/Resources/Localization/Irish.json +++ b/src/Resources/Localization/Irish.json @@ -23,6 +23,7 @@ "Exit": "scoir", "Expand": "leathnaigh", "Free": "saor", + "GarbageCollector": "bailitheoir bruscair", "Help": "cabhair", "HotkeyIsInUseByOperatingSystem": "tá hotkey ({0}) in úsáid ag an gcóras oibriúcháin", "Invalid": "neamhbhailí", @@ -37,7 +38,8 @@ "No": "níl", "OptimizationHotkey": "optamaithe hotkey", "Optimize": "optamaigh", - "Optimized": "optamaithe", + "OptimizeOnMiddleMouseClick": "optamaigh ar chliceáil lárionad na luiche", + "Optimizing": "ag optamú", "PhysicalMemory": "cuimhne fhisiciúil", "ProcessExclusionList": "próisis eisiata ó bharrfheabhsú", "Reason": "cúis", diff --git a/src/Resources/Localization/Italian.json b/src/Resources/Localization/Italian.json index 0eecdb98..80d30243 100644 --- a/src/Resources/Localization/Italian.json +++ b/src/Resources/Localization/Italian.json @@ -23,6 +23,7 @@ "Exit": "esci", "Expand": "espandi", "Free": "libera", + "GarbageCollector": "garbage collector", "Help": "aiuto", "HotkeyIsInUseByOperatingSystem": "il tasto di scelta rapida ({0}) è utilizzato dal sistema operativo", "Invalid": "non valido", @@ -37,7 +38,8 @@ "No": "no", "OptimizationHotkey": "tasto di scelta rapida per l'ottimizzazione", "Optimize": "ottimizza", - "Optimized": "ottimizzato", + "OptimizeOnMiddleMouseClick": "ottimizza al clic del pulsante centrale del mouse", + "Optimizing": "ottimizzazione", "PhysicalMemory": "memoria fisica", "ProcessExclusionList": "processi esclusi dall'ottimizzazione", "Reason": "motivo", diff --git a/src/Resources/Localization/Japanese.json b/src/Resources/Localization/Japanese.json index a1772c48..194a1de9 100644 --- a/src/Resources/Localization/Japanese.json +++ b/src/Resources/Localization/Japanese.json @@ -23,6 +23,7 @@ "Exit": "終了", "Expand": "展開", "Free": "フリー", + "GarbageCollector": "ガベージ コレクター", "Help": "ヘルプ", "HotkeyIsInUseByOperatingSystem": "ホットキー ({0}) は os で使用されています", "Invalid": "無効", @@ -37,7 +38,8 @@ "No": "いいえ", "OptimizationHotkey": "最適化ホットキー", "Optimize": "最適化", - "Optimized": "最適化済", + "OptimizeOnMiddleMouseClick": "マウスの中央ボタンのクリックで最適化", + "Optimizing": "最適化中", "PhysicalMemory": "物理メモリ", "ProcessExclusionList": "最適化から除外したプロセス", "Reason": "理由", diff --git a/src/Resources/Localization/Korean.json b/src/Resources/Localization/Korean.json index 278ed26c..d93731b0 100644 --- a/src/Resources/Localization/Korean.json +++ b/src/Resources/Localization/Korean.json @@ -19,10 +19,11 @@ "ErrorAdminPrivilegeRequired": "이 작업을 수행하려면 관리자 권한({0})이 필요합니다", "ErrorCanNotSaveLog": "로그: {0} (예외: {1})를 저장할 수 없습니다)", "ErrorMemoryAreaOptimizationNotSupported": "이 운영 체제 버전에서는 {0} 최적화가 지원되지 않습니다.", - "EveryHour": "매번 {0}h", + "EveryHour": "매 {0}시간마다", "Exit": "종료", "Expand": "펼치기", "Free": "여유", + "GarbageCollector": "가비지 컬렉터", "Help": "도움말", "HotkeyIsInUseByOperatingSystem": "운영 체제에서 단축키({0})를 사용하고 있습니다", "Invalid": "무효", @@ -37,7 +38,8 @@ "No": "아니요", "OptimizationHotkey": "최적화 단축키", "Optimize": "최적화", - "Optimized": "최적화됨", + "OptimizeOnMiddleMouseClick": "마우스 가운데 버튼 클릭 시 최적화", + "Optimizing": "최적화 중", "PhysicalMemory": "물리적 메모리", "ProcessExclusionList": "최적화에서 제외된 프로세스", "Reason": "이유", diff --git a/src/Resources/Localization/Macedonian.json b/src/Resources/Localization/Macedonian.json index aedffad0..8a50f8cc 100644 --- a/src/Resources/Localization/Macedonian.json +++ b/src/Resources/Localization/Macedonian.json @@ -23,6 +23,7 @@ "Exit": "излез", "Expand": "прошири", "Free": "слободно", + "GarbageCollector": "собирач на ѓубре", "Help": "помош", "HotkeyIsInUseByOperatingSystem": "копчето ({0}) се користи од оперативниот систем", "Invalid": "неважечки", @@ -37,7 +38,8 @@ "No": "не", "OptimizationHotkey": "копчето за оптимизација", "Optimize": "оптимизирајте", - "Optimized": "оптимизирано", + "OptimizeOnMiddleMouseClick": "оптимизирајте со кликнување на средното копче на глушецот", + "Optimizing": "оптимизирање", "PhysicalMemory": "физичка меморија", "ProcessExclusionList": "процеси исклучени од оптимизација", "Reason": "причина", diff --git a/src/Resources/Localization/Norwegian.json b/src/Resources/Localization/Norwegian.json index 8e021fac..f3cda28b 100644 --- a/src/Resources/Localization/Norwegian.json +++ b/src/Resources/Localization/Norwegian.json @@ -23,6 +23,7 @@ "Exit": "avslutt", "Expand": "utvid", "Free": "ledig", + "GarbageCollector": "søppelsamler", "Help": "hjelp", "HotkeyIsInUseByOperatingSystem": "hurtigtast ({0}) er i bruk av operativsystemet", "Invalid": "ugyldig", @@ -37,7 +38,8 @@ "No": "nei", "OptimizationHotkey": "hurtigtast for optimalisering", "Optimize": "optimaliser", - "Optimized": "optimalisert", + "OptimizeOnMiddleMouseClick": "optimaliser ved klikk på midterste museknapp", + "Optimizing": "optimaliserer", "PhysicalMemory": "fysisk minne", "ProcessExclusionList": "prosesser ekskludert fra optimalisering", "Reason": "årsak", diff --git a/src/Resources/Localization/Persian.json b/src/Resources/Localization/Persian.json index 9c2ab68f..91582853 100644 --- a/src/Resources/Localization/Persian.json +++ b/src/Resources/Localization/Persian.json @@ -19,10 +19,11 @@ "ErrorAdminPrivilegeRequired": "این عملیات به امتیازات مدیر نیاز دارد ({0})", "ErrorCanNotSaveLog": "امکان ذخیره لاگ وجود ندارد: {0} (استثنا: {1})", "ErrorMemoryAreaOptimizationNotSupported": "بهینه‌سازی {0} در این نسخه از سیستم عامل پشتیبانی نمی‌شود", - "EveryHour": "هر {0}h", + "EveryHour": "هر {0}س", "Exit": "خروج", "Expand": "گسترش", "Free": "آزاد", + "GarbageCollector": "جمع‌کننده زباله", "Help": "راهنما", "HotkeyIsInUseByOperatingSystem": "کلید میانبر ({0}) توسط سیستم عامل استفاده می‌شود", "Invalid": "نامعتبر", @@ -37,7 +38,8 @@ "No": "خیر", "OptimizationHotkey": "کلید میانبر بهینه‌سازی", "Optimize": "بهینه‌سازی", - "Optimized": "بهینه شد", + "OptimizeOnMiddleMouseClick": "بهینه‌سازی با کلیک دکمه میانی ماوس", + "Optimizing": "در حال بهینه‌سازی", "PhysicalMemory": "حافظه فیزیکی", "ProcessExclusionList": "فرآیندهای مستثنی از بهینه‌سازی", "Reason": "دلیل", diff --git a/src/Resources/Localization/Polish.json b/src/Resources/Localization/Polish.json index 1cfe2224..d41d49cd 100644 --- a/src/Resources/Localization/Polish.json +++ b/src/Resources/Localization/Polish.json @@ -23,6 +23,7 @@ "Exit": "wyjdź", "Expand": "rozwiń", "Free": "wolna", + "GarbageCollector": "odśmiecacz pamięci", "Help": "pomoc", "HotkeyIsInUseByOperatingSystem": "skrót ({0}) jest już używany przez system operacyjny", "Invalid": "nieprawidłowy", @@ -37,7 +38,8 @@ "No": "nie", "OptimizationHotkey": "skrót optymalizacji", "Optimize": "optymalizuj", - "Optimized": "zoptymalizowano", + "OptimizeOnMiddleMouseClick": "optymalizuj kliknięciem środkowego przycisku myszy", + "Optimizing": "optymalizacja", "PhysicalMemory": "pamięć fizyczna", "ProcessExclusionList": "procesy wyłączone z optymalizacji", "Reason": "powód", diff --git a/src/Resources/Localization/Portuguese.json b/src/Resources/Localization/Portuguese (Brazil).json similarity index 76% rename from src/Resources/Localization/Portuguese.json rename to src/Resources/Localization/Portuguese (Brazil).json index 0af63305..c6862304 100644 --- a/src/Resources/Localization/Portuguese.json +++ b/src/Resources/Localization/Portuguese (Brazil).json @@ -1,13 +1,13 @@ { "About": "sobre", "Add": "adicionar", - "AlwaysOnTop": "sempre na frente", + "AlwaysOnTop": "sempre visível", "AutoOptimization": "otimização automática", - "AutoOptimizationInterval": "o intervalo entre as otimizações automáticas subsequentes de memória livre é de {0} minutos", + "AutoOptimizationInterval": "o intervalo entre otimizações automáticas subsequentes de memória livre é de {0} minutos", "AutoUpdate": "atualização automática", "Background": "plano de fundo", "Close": "fechar", - "CloseAfterOptimization": "fechar após a otimização", + "CloseAfterOptimization": "fechar após otimização", "CloseToTheNotificationArea": "fechar para a área de notificação", "Collapse": "recolher", "CombinedPageList": "lista de páginas combinadas", @@ -18,15 +18,16 @@ "Error": "erro", "ErrorAdminPrivilegeRequired": "esta operação requer privilégios de administrador ({0})", "ErrorCanNotSaveLog": "não é possível salvar o log: {0} ({1})", - "ErrorMemoryAreaOptimizationNotSupported": "a otimização {0} não é suportada nesta versão do sistema operacional", + "ErrorMemoryAreaOptimizationNotSupported": "a otimização da área de memória {0} não é suportada nesta versão do sistema operacional", "EveryHour": "a cada {0}h", "Exit": "sair", "Expand": "expandir", "Free": "livre", + "GarbageCollector": "coletor de lixo", "Help": "ajuda", "HotkeyIsInUseByOperatingSystem": "a tecla de atalho ({0}) está atualmente em uso pelo sistema operacional", "Invalid": "inválido", - "LowMemory": "pouca memória", + "LowMemory": "memória baixa", "Manual": "manual", "MemoryAreas": "áreas de memória", "MemoryOptimized": "memória otimizada", @@ -35,9 +36,10 @@ "ModifiedFileCache": "cache de arquivos modificados", "ModifiedPageList": "lista de páginas modificadas", "No": "não", - "OptimizationHotkey": "atalho de teclado para otimização", + "OptimizationHotkey": "tecla de atalho para otimização", "Optimize": "otimizar", - "Optimized": "otimizada", + "OptimizeOnMiddleMouseClick": "otimizar ao clicar no botão do meio do mouse", + "Optimizing": "otimizando", "PhysicalMemory": "memória física", "ProcessExclusionList": "processos excluídos da otimização", "Reason": "motivo", @@ -55,16 +57,16 @@ "ShowOptimizationNotifications": "mostrar notificações de otimização", "ShowVirtualMemory": "mostrar memória virtual", "StandbyList": "lista de espera", - "StandbyListLowPriority": "lista de espera (baixa prioridade)", + "StandbyListLowPriority": "lista de espera (prioridade baixa)", "StartMinimized": "iniciar minimizado", "SystemFileCache": "cache de arquivos do sistema", "Text": "texto", - "TrayIcon": "ícone da bandeja", - "UpdatedToVersion": "atualizado para versão {0}", + "TrayIcon": "ícone da área de notificação", + "UpdatedToVersion": "atualizado para a versão {0}", "Used": "usada", "UseTransparentBackground": "usar plano de fundo transparente", "VirtualMemory": "memória virtual", - "WhenFreePhysicalMemoryIsBelow": "quando a memória física livre for menor que {0}%", + "WhenFreePhysicalMemoryIsBelow": "quando a memória física livre estiver abaixo de {0}%", "WarningLevel": "nível de aviso", "WorkingSet": "conjunto de trabalho", "Yes": "sim" diff --git a/src/Resources/Localization/Portuguese (Portugal).json b/src/Resources/Localization/Portuguese (Portugal).json new file mode 100644 index 00000000..bd1e280c --- /dev/null +++ b/src/Resources/Localization/Portuguese (Portugal).json @@ -0,0 +1,73 @@ +{ + "About": "sobre", + "Add": "adicionar", + "AlwaysOnTop": "sempre visível", + "AutoOptimization": "otimização automática", + "AutoOptimizationInterval": "o intervalo entre otimizações automáticas subsequentes de memória é de {0} minutos", + "AutoUpdate": "atualização automática", + "Background": "plano de fundo", + "Close": "fechar", + "CloseAfterOptimization": "fechar após otimização", + "CloseToTheNotificationArea": "fechar para a área de notificação", + "Collapse": "recolher", + "CombinedPageList": "lista de páginas combinadas", + "DangerLevel": "nível de perigo", + "Donate": "doar", + "DonationMessage": "se considera esta aplicação útil, por favor, considere fazer um donativo. a sua contribuição ajuda a manter o projeto ativo, otimizado e gratuito para todos.", + "DonationTitle": "apoie este projeto", + "Error": "erro", + "ErrorAdminPrivilegeRequired": "esta operação requer privilégios de administrador ({0})", + "ErrorCanNotSaveLog": "não é possível guardar o registo: {0} ({1})", + "ErrorMemoryAreaOptimizationNotSupported": "a otimização da área de memória {0} não é suportada nesta versão do sistema operativo", + "EveryHour": "a cada {0}h", + "Exit": "sair", + "Expand": "expandir", + "Free": "livre", + "GarbageCollector": "coletor de lixo", + "Help": "ajuda", + "HotkeyIsInUseByOperatingSystem": "a tecla de atalho ({0}) está a ser utilizada pelo sistema operativo", + "Invalid": "inválido", + "LowMemory": "memória baixa", + "Manual": "manual", + "MemoryAreas": "áreas de memória", + "MemoryOptimized": "memória otimizada", + "MemoryUsage": "uso de memória", + "Minimize": "minimizar", + "ModifiedFileCache": "cache de ficheiros modificados", + "ModifiedPageList": "lista de páginas modificadas", + "No": "não", + "OptimizationHotkey": "tecla de atalho para otimização", + "Optimize": "otimizar", + "OptimizeOnMiddleMouseClick": "otimizar ao clicar no botão do meio do rato", + "Optimizing": "a otimizar", + "PhysicalMemory": "memória física", + "ProcessExclusionList": "processos excluídos da otimização", + "Reason": "motivo", + "RegistryCache": "cache do registo", + "Remove": "remover", + "Reset": "repor", + "ResetConfirmation": "tem a certeza que deseja repor a configuração padrão?", + "RunOnLowPriority": "executar em baixa prioridade", + "RunOnStartup": "executar na inicialização", + "Schedule": "agendamento", + "Seconds": "segundos", + "SecurityWarning": "esta aplicação pode ter sido comprometida. será redirecionado para o repositório oficial para descarregar uma versão segura.", + "Settings": "definições", + "ShowMemoryUsage": "mostrar uso de memória", + "ShowOptimizationNotifications": "mostrar notificações de otimização", + "ShowVirtualMemory": "mostrar memória virtual", + "StandbyList": "lista de espera", + "StandbyListLowPriority": "lista de espera (prioridade baixa)", + "StartMinimized": "iniciar minimizado", + "SystemFileCache": "cache de ficheiros do sistema", + "Text": "texto", + "TrayIcon": "ícone da área de notificação", + "UpdatedToVersion": "atualizado para a versão {0}", + "Used": "usada", + "UseTransparentBackground": "usar plano de fundo transparente", + "VirtualMemory": "memória virtual", + "WhenFreePhysicalMemoryIsBelow": "quando a memória física livre estiver abaixo de {0}%", + "WarningLevel": "nível de aviso", + "WorkingSet": "conjunto de trabalho", + "Yes": "sim" +} diff --git a/src/Resources/Localization/Russian.json b/src/Resources/Localization/Russian.json index 6300c763..7ca1c202 100644 --- a/src/Resources/Localization/Russian.json +++ b/src/Resources/Localization/Russian.json @@ -23,6 +23,7 @@ "Exit": "выход", "Expand": "развернуть", "Free": "свободно", + "GarbageCollector": "сборщик мусора", "Help": "помощь", "HotkeyIsInUseByOperatingSystem": "горячая клавиша ({0}) уже используется операционной системой", "Invalid": "недопустимо", @@ -37,7 +38,8 @@ "No": "нет", "OptimizationHotkey": "горячая клавиша оптимизации", "Optimize": "оптимизировать", - "Optimized": "оптимизировано", + "OptimizeOnMiddleMouseClick": "оптимизация при нажатии средней кнопки мыши", + "Optimizing": "оптимизация", "PhysicalMemory": "физическая память", "ProcessExclusionList": "процессы, исключённые из оптимизации", "Reason": "причина", diff --git a/src/Resources/Localization/Serbian.json b/src/Resources/Localization/Serbian.json index 3d2c1579..9fd00cf7 100644 --- a/src/Resources/Localization/Serbian.json +++ b/src/Resources/Localization/Serbian.json @@ -23,6 +23,7 @@ "Exit": "излаз", "Expand": "прошири", "Free": "слободно", + "GarbageCollector": "сакупљач смећа", "Help": "помоћ", "HotkeyIsInUseByOperatingSystem": "пречица ({0}) је већ у употреби од стране оперативног система", "Invalid": "неважеће", @@ -37,7 +38,8 @@ "No": "не", "OptimizationHotkey": "пречица за оптимизацију", "Optimize": "оптимизуј", - "Optimized": "оптимизовано", + "OptimizeOnMiddleMouseClick": "оптимизација кликом на средњи тастер миша", + "Optimizing": "оптимизација", "PhysicalMemory": "физичка меморија", "ProcessExclusionList": "процеси искључени из оптимизације", "Reason": "разлог", diff --git a/src/Resources/Localization/Slovenian.json b/src/Resources/Localization/Slovenian.json index 6521545e..46a4fb9b 100644 --- a/src/Resources/Localization/Slovenian.json +++ b/src/Resources/Localization/Slovenian.json @@ -23,6 +23,7 @@ "Exit": "izhod", "Expand": "razširi", "Free": "prosto", + "GarbageCollector": "zbiralnik smeti", "Help": "pomoč", "HotkeyIsInUseByOperatingSystem": "bližnjica ({0}) je že v uporabi v operacijskem sistemu", "Invalid": "neveljavno", @@ -37,7 +38,8 @@ "No": "ne", "OptimizationHotkey": "bližnjica za optimizacijo", "Optimize": "optimiziraj", - "Optimized": "optimizirano", + "OptimizeOnMiddleMouseClick": "optimiziraj s klikom na srednji gumb miške", + "Optimizing": "optimiziranje", "PhysicalMemory": "fizični pomnilnik", "ProcessExclusionList": "procesi izključeni iz optimizacije", "Reason": "razlog", diff --git a/src/Resources/Localization/Spanish.json b/src/Resources/Localization/Spanish.json index c0127b73..bb2aa370 100644 --- a/src/Resources/Localization/Spanish.json +++ b/src/Resources/Localization/Spanish.json @@ -23,6 +23,7 @@ "Exit": "salir", "Expand": "expandir", "Free": "libre", + "GarbageCollector": "recolector de basura", "Help": "ayuda", "HotkeyIsInUseByOperatingSystem": "la tecla de acceso rápido ({0}) está en uso por el sistema operativo", "Invalid": "inválido", @@ -37,7 +38,8 @@ "No": "no", "OptimizationHotkey": "tecla de acceso rápido de optimización", "Optimize": "optimizar", - "Optimized": "optimizado", + "OptimizeOnMiddleMouseClick": "optimizar al hacer clic en el botón central del ratón", + "Optimizing": "optimizando", "PhysicalMemory": "memoria física", "ProcessExclusionList": "procesos excluidos de la optimización", "Reason": "razón", diff --git a/src/Resources/Localization/Thai.json b/src/Resources/Localization/Thai.json index bddd7063..b766327f 100644 --- a/src/Resources/Localization/Thai.json +++ b/src/Resources/Localization/Thai.json @@ -23,6 +23,7 @@ "Exit": "ออก", "Expand": "ขยาย", "Free": "ว่าง", + "GarbageCollector": "ตัวรวบรวมขยะ", "Help": "ช่วยเหลือ", "HotkeyIsInUseByOperatingSystem": "ปุ่มลัด ({0}) ถูกใช้งานโดยระบบปฏิบัติการ", "Invalid": "ไม่ถูกต้อง", @@ -37,7 +38,8 @@ "No": "ไม่", "OptimizationHotkey": "ปุ่มลัดสำหรับเพิ่มประสิทธิภาพ", "Optimize": "เพิ่มประสิทธิภาพ", - "Optimized": "เพิ่มประสิทธิภาพแล้ว", + "OptimizeOnMiddleMouseClick": "เพิ่มประสิทธิภาพเมื่อคลิกปุ่มกลางเมาส์", + "Optimizing": "กำลังเพิ่มประสิทธิภาพ", "PhysicalMemory": "หน่วยความจำจริง", "ProcessExclusionList": "กระบวนการที่ยกเว้นจากการเพิ่มประสิทธิภาพ", "Reason": "เหตุผล", diff --git a/src/Resources/Localization/Turkish.json b/src/Resources/Localization/Turkish.json index 531d099c..677cc555 100644 --- a/src/Resources/Localization/Turkish.json +++ b/src/Resources/Localization/Turkish.json @@ -23,6 +23,7 @@ "Exit": "çıkış", "Expand": "genişlet", "Free": "boş", + "GarbageCollector": "çöp toplayıcı", "Help": "yardım", "HotkeyIsInUseByOperatingSystem": "kısayol tuşu ({0}) işletim sistemi tarafından kullanılıyor", "Invalid": "geçersiz", @@ -37,7 +38,8 @@ "No": "hayır", "OptimizationHotkey": "optimizasyon kısayol tuşu", "Optimize": "optimize et", - "Optimized": "optimize edildi", + "OptimizeOnMiddleMouseClick": "orta fare düğmesine tıklanınca optimize et", + "Optimizing": "optimize ediliyor", "PhysicalMemory": "fiziksel bellek", "ProcessExclusionList": "optimizasyondan hariç tutulan işlemler", "Reason": "sebep", diff --git a/src/Resources/Localization/Ukrainian.json b/src/Resources/Localization/Ukrainian.json index edea24dd..48f7f26e 100644 --- a/src/Resources/Localization/Ukrainian.json +++ b/src/Resources/Localization/Ukrainian.json @@ -23,6 +23,7 @@ "Exit": "вийти", "Expand": "розгорнути", "Free": "вільно", + "GarbageCollector": "збирач сміття", "Help": "допомога", "HotkeyIsInUseByOperatingSystem": "гаряча клавіша ({0}) вже використовується операційною системою", "Invalid": "недійсний", @@ -37,7 +38,8 @@ "No": "ні", "OptimizationHotkey": "гаряча клавіша оптимізації", "Optimize": "оптимізувати", - "Optimized": "оптимізовано", + "OptimizeOnMiddleMouseClick": "оптимізувати при натисканні середньої кнопки миші", + "Optimizing": "оптимізація", "PhysicalMemory": "фізична пам'ять", "ProcessExclusionList": "процеси, виключені з оптимізації", "Reason": "причина", diff --git a/src/Service/ComputerService.cs b/src/Service/ComputerService.cs index eff092ff..a4ca5319 100644 --- a/src/Service/ComputerService.cs +++ b/src/Service/ComputerService.cs @@ -413,6 +413,22 @@ public void Optimize(Enums.Memory.Optimization.Reason reason, Enums.Memory.Areas } } + // Garbage Collector + try + { + if (OnOptimizeProgressUpdate != null) + { + value++; + OnOptimizeProgressUpdate(value, Localizer.String.GarbageCollector); + } + + App.ReleaseMemory(); + } + catch + { + // ignored + } + // Log try { @@ -435,24 +451,6 @@ public void Optimize(Enums.Memory.Optimization.Reason reason, Enums.Memory.Areas { // ignored } - - // App - try - { - App.ReleaseMemory(); - } - catch - { - // ignored - } - finally - { - if (OnOptimizeProgressUpdate != null) - { - value++; - OnOptimizeProgressUpdate(value, Localizer.String.Optimized); - } - } } /// diff --git a/src/Service/NotificationService.cs b/src/Service/NotificationService.cs index f45825c0..69b5aaed 100644 --- a/src/Service/NotificationService.cs +++ b/src/Service/NotificationService.cs @@ -2,9 +2,11 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Text; +using System.Globalization; using System.Reflection; using System.Windows.Forms; using System.Windows.Input; +using System.Windows.Threading; using Application = System.Windows.Application; using Cursors = System.Windows.Input.Cursors; @@ -17,8 +19,10 @@ public class NotificationService : INotificationService { #region Fields + private int _currentRotationAngle; private readonly Icon _imageIcon; private readonly NotifyIcon _notifyIcon; + private DispatcherTimer _rotationTimer; #endregion @@ -30,12 +34,46 @@ public class NotificationService : INotificationService /// Notify Icon public NotificationService(NotifyIcon notifyIcon) { + _currentRotationAngle = 0; _imageIcon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); _notifyIcon = notifyIcon; Initialize(); } + /// + /// Initializes the notification service + /// + public void Initialize() + { + if (_notifyIcon == null) + return; + + // Notification Areas (Menu) + _notifyIcon.ContextMenuStrip = new TrayIconContextMenuControl(); + + // Optimize + _notifyIcon.ContextMenuStrip.Items.Add(Localizer.String.Optimize, null, (sender, args) => + { + var mainViewModel = DependencyInjection.Container.Resolve(); + + if (mainViewModel.OptimizeCommand.CanExecute(null)) + mainViewModel.OptimizeCommand.Execute(null); + }); + + _notifyIcon.ContextMenuStrip.Items.Add(new ToolStripSeparator()); + + // Exit + _notifyIcon.ContextMenuStrip.Items.Add(Localizer.String.Exit, null, (sender, args) => + { + App.Shutdown(); + }); + + Update(new Memory()); + + _notifyIcon.Visible = true; + } + #endregion #region IDisposable @@ -57,28 +95,34 @@ protected virtual void Dispose(bool disposing) { if (disposing) { + try + { + if (_rotationTimer != null) + _rotationTimer = null; + } + catch (Exception ex) + { + Logger.Debug(ex); + } + try { if (_imageIcon != null) - { _imageIcon.Dispose(); - } } - catch + catch (Exception ex) { - // ignored + Logger.Debug(ex); } try { if (_notifyIcon != null) - { _notifyIcon.Dispose(); - } } - catch + catch (Exception ex) { - // ignored + Logger.Debug(ex); } } } @@ -88,42 +132,299 @@ protected virtual void Dispose(bool disposing) #region Methods /// - /// Initializes + /// Cleans up the rotation timer resources and resets the rotation angle /// - public void Initialize() + private void CleanupRotationTimer() { - if (_notifyIcon == null) - return; + try + { + _currentRotationAngle = 0; - // Notification Areas (Menu) - _notifyIcon.ContextMenuStrip = new TrayIconContextMenuControl(); + if (_rotationTimer != null) + { + _rotationTimer.Stop(); + _rotationTimer.Tick -= OnRotationTimerTick; + _rotationTimer = null; + } + } + catch (Exception ex) + { + Logger.Debug(ex); + } + } - // Optimize - _notifyIcon.ContextMenuStrip.Items.Add(Localizer.String.Optimize, null, (sender, args) => + /// + /// Gets the background brush color based on memory usage and optimization state + /// + /// The memory information + /// if set to true the system is optimizing + /// A solid brush with the appropriate background color + private Brush GetBackgroundBrush(Memory memory, bool isOptimizing) + { + try { - var mainViewModel = DependencyInjection.Container.Resolve(); + if (Settings.TrayIconUseTransparentBackground) + return new SolidBrush(Color.Transparent); - if (mainViewModel.OptimizeCommand.CanExecute(null)) - mainViewModel.OptimizeCommand.Execute(null); - }); + if (isOptimizing) + { + var solidBrush = Settings.TrayIconOptimizingColor as SolidBrush; + return new SolidBrush(solidBrush.Color); + } - _notifyIcon.ContextMenuStrip.Items.Add(new ToolStripSeparator()); + if (memory.Physical.Used.Percentage >= Settings.TrayIconDangerLevel) + { + var solidBrush = Settings.TrayIconDangerColor as SolidBrush; + return new SolidBrush(solidBrush.Color); + } - // Exit - _notifyIcon.ContextMenuStrip.Items.Add(Localizer.String.Exit, null, (sender, args) => + if (memory.Physical.Used.Percentage >= Settings.TrayIconWarningLevel) + { + var solidBrush = Settings.TrayIconWarningColor as SolidBrush; + return new SolidBrush(solidBrush.Color); + } + + var backgroundBrush = Settings.TrayIconBackgroundColor as SolidBrush; + return new SolidBrush(backgroundBrush.Color); + } + catch (Exception) { - App.Shutdown(); - }); + return new SolidBrush(Color.Black); + } + } - Update(new Memory()); + /// + /// Gets the appropriate tray icon based on settings and current state + /// + /// The memory information + /// if set to true the system is optimizing + /// The tray icon to display + private Icon GetIcon(Memory memory, bool isOptimizing) + { + try + { + return Settings.TrayIconShowMemoryUsage ? GetMemoryUsageIcon(memory, isOptimizing) : GetImageIcon(isOptimizing); + } + catch + { + return _imageIcon; + } + } - _notifyIcon.Visible = true; + /// + /// Gets the static application icon with optional rotation animation during optimization + /// + /// if set to true the system is optimizing + /// The application icon, optionally rotated + private Icon GetImageIcon(bool isOptimizing) + { + try + { + if (isOptimizing) + { + StartRotationAnimation(); + + if (_currentRotationAngle > 0) + return GetRotatedIcon(_imageIcon, _currentRotationAngle); + } + else + { + StopRotationAnimation(); + } + + return _imageIcon; + } + catch + { + return _imageIcon; + } + } + + /// + /// Gets a custom icon displaying the current memory usage percentage + /// + /// The memory information + /// if set to true the system is optimizing + /// An icon with rendered memory percentage text + private Icon GetMemoryUsageIcon(Memory memory, bool isOptimizing) + { + try + { + using (var image = new Bitmap(16, 16)) + using (var graphics = Graphics.FromImage(image)) + using (var font = new Font("Consolas", 15F, FontStyle.Regular, GraphicsUnit.Pixel)) + using (var format = new StringFormat()) + using (var backgroundBrush = GetBackgroundBrush(memory, isOptimizing)) + using (var textBrush = GetTextBrush(memory, isOptimizing)) + { + // Configure format + format.Alignment = StringAlignment.Center; + format.LineAlignment = StringAlignment.Center; + + // Configure graphics quality + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit; + + // Draw background + if (!Settings.TrayIconUseTransparentBackground) + { + using (var path = new GraphicsPath()) + { + var radius = 10; + + path.AddArc(0, 0, radius, radius, 180, 90); + path.AddArc(16 - radius, 0, radius, radius, 270, 90); + path.AddArc(16 - radius, 16 - radius, radius, radius, 0, 90); + path.AddArc(0, 16 - radius, radius, radius, 90, 90); + path.CloseFigure(); + + graphics.FillPath(backgroundBrush, path); + } + } + + // Draw text + graphics.DrawString(string.Format(CultureInfo.InvariantCulture, "{0:00}", memory.Physical.Used.Percentage == 100 ? 99 : memory.Physical.Used.Percentage), font, textBrush, 8F, 9F, format); + + var handle = image.GetHicon(); + + using (var icon = Icon.FromHandle(handle)) + { + var clonedIcon = (Icon)icon.Clone(); + + NativeMethods.DestroyIcon(handle); + + return clonedIcon; + } + } + } + catch + { + return _imageIcon; + } + } + + /// + /// Gets a rotated version of the specified icon + /// + /// The icon to rotate + /// The rotation angle in degrees + /// A new icon rotated by the specified angle + private Icon GetRotatedIcon(Icon icon, float angle) + { + if (icon == null || angle == 0) + return icon; + + try + { + using (var image = icon.ToBitmap()) + using (var rotatedImage = new Bitmap(image.Width, image.Height)) + using (var graphics = Graphics.FromImage(rotatedImage)) + { + // Configure graphics quality + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.SmoothingMode = SmoothingMode.HighQuality; + + // Rotate around center point + var centerX = image.Width / 2f; + var centerY = image.Height / 2f; + + graphics.TranslateTransform(centerX, centerY); + graphics.RotateTransform(angle); + graphics.TranslateTransform(-centerX, -centerY); + + graphics.DrawImage(image, new Point(0, 0)); + + var handle = rotatedImage.GetHicon(); + + using (var tempIcon = Icon.FromHandle(handle)) + { + var clonedIcon = (Icon)tempIcon.Clone(); + + NativeMethods.DestroyIcon(handle); + + return clonedIcon; + } + } + } + catch + { + return icon; + } + } + + /// + /// Gets the tray icon tooltip text based on memory usage and optimization state + /// + /// The memory information + /// if set to true the system is optimizing + /// The formatted tooltip text + private string GetText(Memory memory, bool isOptimizing) + { + try + { + if (isOptimizing) + return Localizer.String.Optimizing.ToUpper(Localizer.Culture); + + return Settings.ShowVirtualMemory + ? string.Format(Localizer.Culture, "{1}{0}{2}: {3}%{0}{4}: {5}%", Environment.NewLine, Localizer.String.MemoryUsage.ToUpper(Localizer.Culture), Localizer.String.PhysicalMemory, memory.Physical.Used.Percentage, Localizer.String.VirtualMemory, memory.Virtual.Used.Percentage) + : string.Format(Localizer.Culture, "{1}{0}{2}: {3}%", Environment.NewLine, Localizer.String.MemoryUsage.ToUpper(Localizer.Culture), Localizer.String.PhysicalMemory, memory.Physical.Used.Percentage); + } + catch + { + return Constants.App.Title; + } + } + + /// + /// Gets the text brush color based on memory usage and optimization state + /// + /// The memory information + /// if set to true the system is optimizing + /// A solid brush with the appropriate text color + private Brush GetTextBrush(Memory memory, bool isOptimizing) + { + try + { + if (!Settings.TrayIconUseTransparentBackground) + { + var solidBrush = Settings.TrayIconTextColor as SolidBrush; + return new SolidBrush(solidBrush.Color); + } + + if (isOptimizing) + { + var solidBrush = Settings.TrayIconOptimizingColor as SolidBrush; + return new SolidBrush(solidBrush.Color); + } + + if (memory.Physical.Used.Percentage >= Settings.TrayIconDangerLevel) + { + var solidBrush = Settings.TrayIconDangerColor as SolidBrush; + return new SolidBrush(solidBrush.Color); + } + + if (memory.Physical.Used.Percentage >= Settings.TrayIconWarningLevel) + { + var solidBrush = Settings.TrayIconWarningColor as SolidBrush; + return new SolidBrush(solidBrush.Color); + } + + var textBrush = Settings.TrayIconTextColor as SolidBrush; + return new SolidBrush(textBrush.Color); + } + catch (Exception) + { + return new SolidBrush(Color.White); + } } /// - /// Show/Hide Loading + /// Shows or hides the loading cursor and enables/disables the context menu /// - /// True (ON) / False (OFF) + /// if set to true shows loading cursor and disables menu public void Loading(bool running) { if (Application.Current == null || Application.Current.Dispatcher == null) @@ -134,136 +435,133 @@ public void Loading(bool running) { Mouse.OverrideCursor = running ? Cursors.Wait : null; - if (_notifyIcon != null && _notifyIcon.ContextMenuStrip != null) + if (_notifyIcon.ContextMenuStrip != null) _notifyIcon.ContextMenuStrip.Enabled = !running; }); } /// - /// Displays a Notification - /// - /// The text - /// The title - /// The time period, in seconds - /// The icon + /// Displays a balloon tip notification from the system tray + /// /// + /// The notification message text + /// The notification title + /// The time period in seconds to display the notification + /// The notification icon type public void Notify(string message, string title = null, int timeout = 5, Enums.Icon.Notification icon = Enums.Icon.Notification.None) { + if (_notifyIcon == null) + return; + try { - if (_notifyIcon == null) - return; - _notifyIcon.Visible = false; _notifyIcon.Visible = true; _notifyIcon.ShowBalloonTip(timeout * 1000, title, message, (ToolTipIcon)icon); } - catch + catch (Exception ex) { - // ignored + Logger.Debug(ex); } } /// - /// Update Info + /// Handles the rotation timer tick event to animate the icon rotation /// - /// The memory. - public void Update(Memory memory) + /// The event sender + /// The event arguments + private void OnRotationTimerTick(object sender, EventArgs e) { - if (memory == null) - throw new ArgumentNullException("memory"); + try + { + _currentRotationAngle = (_currentRotationAngle + 90) % 360; - if (_notifyIcon == null) + _notifyIcon.Icon = GetRotatedIcon(_imageIcon, _currentRotationAngle); + } + catch (Exception ex) + { + Logger.Debug(ex); + } + } + + /// + /// Starts the icon rotation animation for the optimization state + /// + private void StartRotationAnimation() + { + if (_rotationTimer != null) + return; + + if (Application.Current == null || Application.Current.Dispatcher == null) return; - // Icon try { - if (Settings.TrayIconShowMemoryUsage) + Application.Current.Dispatcher.Invoke((Action)delegate { - using (var image = new Bitmap(16, 16)) - using (var graphics = Graphics.FromImage(image)) - using (var font = new Font("Arial", 14F, GraphicsUnit.Pixel)) - using (var format = new StringFormat()) - using (var path = new GraphicsPath()) + try { - format.Alignment = StringAlignment.Center; - format.LineAlignment = StringAlignment.Center; - - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; - - var backgroundBrush = Settings.TrayIconBackgroundColor; - var textBrush = Settings.TrayIconTextColor; - - if (Settings.TrayIconUseTransparentBackground) - { - backgroundBrush = Brushes.Transparent; - - if (memory.Physical.Used.Percentage >= Settings.TrayIconDangerLevel) - { - textBrush = Settings.TrayIconDangerColor; - } - else if (memory.Physical.Used.Percentage >= Settings.TrayIconWarningLevel) - { - textBrush = Settings.TrayIconWarningColor; - } - } - else - { - if (memory.Physical.Used.Percentage >= Settings.TrayIconDangerLevel) - { - backgroundBrush = Settings.TrayIconDangerColor; - } - else if (memory.Physical.Used.Percentage >= Settings.TrayIconWarningLevel) - { - backgroundBrush = Settings.TrayIconWarningColor; - } - } - - var radius = 3; - - path.AddArc(0, 0, radius, radius, 180, 90); - path.AddArc(16 - radius, 0, radius, radius, 270, 90); - path.AddArc(16 - radius, 16 - radius, radius, radius, 0, 90); - path.AddArc(0, 16 - radius, radius, radius, 90, 90); - path.CloseFigure(); - - graphics.FillPath(backgroundBrush, path); - graphics.DrawString(string.Format(Localizer.Culture, "{0:00}", memory.Physical.Used.Percentage == 100 ? 99 : memory.Physical.Used.Percentage), font, textBrush, 9, 9, format); + _currentRotationAngle = 0; - var handle = image.GetHicon(); - - using (var icon = Icon.FromHandle(handle)) - _notifyIcon.Icon = (Icon)icon.Clone(); - - NativeMethods.DestroyIcon(handle); + _rotationTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) }; + _rotationTimer.Tick += OnRotationTimerTick; + _rotationTimer.Start(); } - } - else - _notifyIcon.Icon = _imageIcon; + catch (Exception ex) + { + Logger.Debug(ex); + } + }); } - catch + catch (Exception ex) { - if (_notifyIcon != null) - _notifyIcon.Icon = _imageIcon; + Logger.Debug(ex); } + } + + /// + /// Stops the icon rotation animation + /// + private void StopRotationAnimation() + { + if (_rotationTimer == null) + return; - // Text try { - _notifyIcon.Text = Settings.ShowVirtualMemory - ? string.Format(Localizer.Culture, "{1}{0}{2}: {3}%{0}{4}: {5}%", Environment.NewLine, Localizer.String.MemoryUsage.ToUpper(Localizer.Culture), Localizer.String.PhysicalMemory, memory.Physical.Used.Percentage, Localizer.String.VirtualMemory, memory.Virtual.Used.Percentage) - : string.Format(Localizer.Culture, "{1}{0}{2}: {3}%", Environment.NewLine, Localizer.String.MemoryUsage.ToUpper(Localizer.Culture), Localizer.String.PhysicalMemory, memory.Physical.Used.Percentage); + if (Application.Current == null || Application.Current.Dispatcher == null) + { + CleanupRotationTimer(); + return; + } + + Application.Current.Dispatcher.Invoke((Action)delegate + { + CleanupRotationTimer(); + }); } - catch + catch (Exception ex) { - if (_notifyIcon != null) - _notifyIcon.Text = string.Empty; + Logger.Debug(ex); } + } + + /// + /// Updates the tray icon and tooltip text based on current memory usage and optimization state + /// + /// The memory information + /// if set to true the system is optimizing + /// memory + public void Update(Memory memory, bool isOptimizing = false) + { + if (memory == null) + throw new ArgumentNullException("memory"); + + if (_notifyIcon == null) + return; + _notifyIcon.Text = GetText(memory, isOptimizing); + _notifyIcon.Icon = GetIcon(memory, isOptimizing); } #endregion diff --git a/src/View/Window/DonationWindow.xaml b/src/View/Window/DonationWindow.xaml index 4f5815a9..7e70f794 100644 --- a/src/View/Window/DonationWindow.xaml +++ b/src/View/Window/DonationWindow.xaml @@ -129,7 +129,7 @@ @@ -209,7 +209,7 @@ diff --git a/src/View/Window/MainWindow.xaml b/src/View/Window/MainWindow.xaml index 0d5a84de..fec35130 100644 --- a/src/View/Window/MainWindow.xaml +++ b/src/View/Window/MainWindow.xaml @@ -281,7 +281,6 @@ + + + + + + + + + + + + + - + { + SetFocusTo(Optimize); + }; + IsVisibleChanged += OnWindowVisibleChanged; _viewModel = DataContext as MainViewModel; @@ -38,7 +44,7 @@ public MainWindow() _viewModel.OnLanguageChangeCompleted += Refresh; _viewModel.OnNavigateUriCommandCompleted += OnNavigateUriCommandCompleted; _viewModel.OnOptimizeCommandCompleted += OnOptimizeCommandCompleted; - _viewModel.OnRemoveProcessFromExclusionListCommandCompleted += SetFocusToProcessExclusionList; + _viewModel.OnRemoveProcessFromExclusionListCommandCompleted += () => SetFocusTo(ProcessExclusionList); } // Slider @@ -63,6 +69,9 @@ public MainWindow() FontSizeSlider.AddHandler(PreviewMouseLeftButtonDownEvent, sliderPreviewMouseLeftButtonDownEvent, true); } + /// + /// Closes the window to the notification area. + /// private void CloseToTheNotificationArea() { if (Visibility == Visibility.Visible) @@ -72,16 +81,24 @@ private void CloseToTheNotificationArea() } } + /// + /// Called when the add process to exclusion list command is completed. + /// private void OnAddProcessToExclusionListCommandCompleted() { ProcessExclusionScrollViewer.ScrollToEnd(); } + /// + /// Called when the close button is clicked. + /// + /// The sender. + /// The instance containing the event data. private void OnCloseButtonClick(object sender, RoutedEventArgs e) { if (Settings.CloseToTheNotificationArea) { - SetFocusToOptimizationButton(); + SetFocusTo(Optimize); CloseToTheNotificationArea(); @@ -91,25 +108,45 @@ private void OnCloseButtonClick(object sender, RoutedEventArgs e) App.Shutdown(); } + /// + /// Called when a combo box is right-clicked. + /// + /// The sender. + /// The instance containing the event data. private void OnComboBoxMouseRightButtonUp(object sender, MouseButtonEventArgs e) { - SetFocusToOptimizationButton(); + SetFocusTo(Optimize); } + /// + /// Called when the compact mode button is clicked. + /// + /// The sender. + /// The instance containing the event data. private void OnCompactModeButtonClick(object sender, RoutedEventArgs e) { - SetFocusToOptimizationButton(); + SetFocusTo(Optimize); } + /// + /// Called when the donate menu item is clicked. + /// + /// The sender. + /// The instance containing the event data. private void OnDonateMenuItemClick(object sender, RoutedEventArgs e) { var window = new DonationWindow(this); window.ShowDialog(); - SetFocusToOptimizationButton(); + SetFocusTo(Optimize); } + /// + /// Called when the help button is clicked. + /// + /// The sender. + /// The instance containing the event data. private void OnHelpButtonClick(object sender, RoutedEventArgs e) { var button = (Button)sender; @@ -123,15 +160,25 @@ private void OnHelpButtonClick(object sender, RoutedEventArgs e) } } + /// + /// Called when the help button receives a right mouse button down event. + /// + /// The sender. + /// The instance containing the event data. private void OnHelpButtonPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) { - SetFocusToOptimizationButton(); + SetFocusTo(Optimize); e.Handled = true; } + /// + /// Called when the minimize button is clicked. + /// + /// The sender. + /// The instance containing the event data. private void OnMinimizeButtonClick(object sender, RoutedEventArgs e) { - SetFocusToOptimizationButton(); + SetFocusTo(Optimize); WindowState = WindowState.Minimized; } @@ -144,19 +191,24 @@ protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) DragMove(); } + /// + /// Called when the navigate URI command is completed. + /// private void OnNavigateUriCommandCompleted() { - SetFocusToOptimizationButton(); + SetFocusTo(Optimize); } + /// + /// Called when the optimize command is completed. + /// private void OnOptimizeCommandCompleted() { if (Settings.CloseAfterOptimization) { if (Settings.CloseToTheNotificationArea) { - SetFocusToOptimizationButton(); - + SetFocusTo(Optimize, force: true); CloseToTheNotificationArea(); } else @@ -168,16 +220,31 @@ private void OnOptimizeCommandCompleted() else { _viewModel.IsBusy = false; - - SetFocusToOptimizationButton(); - } + + // Wait for the button to be enabled before trying to refocus it + // The button is disabled while IsOptimizationRunning = true + Dispatcher.BeginInvoke(new Action(() => + { + SetFocusTo(Optimize, force: true); + }), System.Windows.Threading.DispatcherPriority.Loaded); + } } + /// + /// Called when the processes dropdown is opened. + /// + /// The sender. + /// The instance containing the event data. private void OnProcessesDropDownOpened(object sender, EventArgs e) { _viewModel.RaisePropertyChanged(() => _viewModel.Processes); } + /// + /// Called when the reset settings to default configuration menu item is clicked. + /// + /// The sender. + /// The instance containing the event data. private void OnResetSettingsToDefaultConfigurationClick(object sender, RoutedEventArgs e) { var window = new MessageDialog(this, Localizer.String.ResetConfirmation, Enums.Dialog.Button.No, Enums.Dialog.Button.Yes); @@ -187,14 +254,52 @@ private void OnResetSettingsToDefaultConfigurationClick(object sender, RoutedEve if (result == true && _viewModel.ResetSettingsToDefaultConfigurationCommand.CanExecute(null)) _viewModel.ResetSettingsToDefaultConfigurationCommand.Execute(null); - SetFocusToOptimizationButton(); + SetFocusTo(Optimize); } + /// + /// Called when a tray icon checkbox is checked. + /// + /// The sender. + /// The instance containing the event data. + private void OnTrayIconItemCheckBoxChecked(object sender, RoutedEventArgs e) + { + var checkBox = sender as CheckBox; + if (checkBox == null) + return; + + SetFocusTo(checkBox); + } + + /// + /// Called when a tray icon checkbox is unchecked. + /// + /// The sender. + /// The instance containing the event data. + private void OnTrayIconItemCheckBoxUnchecked(object sender, RoutedEventArgs e) + { + var checkBox = sender as CheckBox; + if (checkBox == null) + return; + + SetFocusTo(checkBox); + } + + /// + /// Called when the window is clicked. + /// + /// The sender. + /// The instance containing the event data. private void OnWindowMouseDown(object sender, MouseButtonEventArgs e) { - SetFocusToOptimizationButton(); + SetFocusTo(Optimize); } + /// + /// Called when the window visibility changes. + /// + /// The sender. + /// The instance containing the event data. private void OnWindowVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { foreach (var window in OwnedWindows.Cast().Where(window => window != null && !window.IsDialog)) @@ -207,24 +312,46 @@ private void OnWindowVisibleChanged(object sender, DependencyPropertyChangedEven window.Visibility = Visibility.Hidden; } } - - SetFocusToOptimizationButton(); } - private void SetFocusToOptimizationButton() + /// + /// Sets the focus to the specified element. + /// + /// The element to focus. + /// If set to true, forces focus even if already focused. + private void SetFocusTo(object element, bool force = false) { - if (Optimize == null) + var uiElement = element as UIElement; + var inputElement = element as IInputElement; + + if (uiElement == null || inputElement == null) return; - Keyboard.ClearFocus(); - FocusManager.SetFocusedElement(this, Optimize); - Optimize.Focus(); - } + if (!uiElement.IsEnabled || !uiElement.IsVisible || !uiElement.Focusable) + return; - private void SetFocusToProcessExclusionList() - { - FocusManager.SetFocusedElement(this, ProcessExclusionList); - ProcessExclusionList.Focus(); + if (!force && uiElement.IsFocused) + return; + + if (force && uiElement.IsFocused) + { + // Move focus to the window itself to ensure button loses focus + Focus(); + + // Then re-focus the element after a brief delay to allow animation state to reset + Dispatcher.BeginInvoke(new Action(() => + { + Keyboard.Focus(uiElement); + FocusManager.SetFocusedElement(this, inputElement); + }), System.Windows.Threading.DispatcherPriority.Input); + } + else + { + // Normal focus - immediate + Keyboard.ClearFocus(); + Keyboard.Focus(uiElement); + FocusManager.SetFocusedElement(this, inputElement); + } } } } diff --git a/src/ViewModel/MainViewModel.cs b/src/ViewModel/MainViewModel.cs index 540eaf06..9d2e8d0d 100644 --- a/src/ViewModel/MainViewModel.cs +++ b/src/ViewModel/MainViewModel.cs @@ -24,13 +24,16 @@ public class MainViewModel : ViewModel, IDisposable private readonly IHotkeyService _hotKeyService; private bool _isOptimizationKeyValid; private bool _isOptimizationRunning; + private bool _isReiniziliating; private DateTimeOffset _lastAutoOptimizationByInterval = DateTimeOffset.Now; private DateTimeOffset _lastAutoOptimizationByMemoryUsage = DateTimeOffset.Now; + private readonly object _lockObject = new object(); private byte _optimizationProgressPercentage; private string _optimizationProgressStep = Localizer.String.Optimize; private byte _optimizationProgressTotal = byte.MaxValue; private byte _optimizationProgressValue = byte.MinValue; private string _selectedProcess; + private ObservableCollection> _trayIconItems; #endregion @@ -63,6 +66,9 @@ public MainViewModel(IComputerService computerService, IHotkeyService hotKeyServ if (App.IsInDesignMode) { + _computerService = new ComputerService(); + _hotKeyService = new HotkeyService(); + Settings.AutoUpdate = true; Computer.OperatingSystem.IsWindows81OrGreater = true; @@ -70,8 +76,6 @@ public MainViewModel(IComputerService computerService, IHotkeyService hotKeyServ Computer.OperatingSystem.IsWindowsVistaOrGreater = true; Computer.OperatingSystem.IsWindowsXpOrGreater = true; IsOptimizationKeyValid = true; - - _hotKeyService = new HotkeyService(); } else { @@ -268,7 +272,7 @@ public bool CanAddProcessToExclusionList /// public bool CanOptimize { - get { return MemoryAreas != Enums.Memory.Areas.None; } + get { return MemoryAreas != Enums.Memory.Areas.None && !IsOptimizationRunning; } } /// @@ -428,6 +432,9 @@ public bool IsOptimizationKeyValid get { return _isOptimizationKeyValid; } set { + if (_isReiniziliating) + return; + _isOptimizationKeyValid = value; RaisePropertyChanged(); } @@ -505,8 +512,10 @@ public Language Language Computer.Memory = _computerService.Memory; RaisePropertyChanged(() => Computer); + _trayIconItems = null; + NotificationService.Initialize(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); } RaisePropertyChanged(string.Empty); @@ -953,7 +962,7 @@ public bool ShowVirtualMemory Settings.ShowVirtualMemory = value; Settings.Save(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); RaisePropertyChanged(); } @@ -1030,11 +1039,12 @@ public string Title { get { + var beta = App.IsInDebugMode ? "\u200E‎ (BETA)\u200E" : null; var version = App.IsInDesignMode ? Assembly.GetExecutingAssembly().GetName().Version : App.Version; return CompactMode - ? Constants.App.Title - : string.Format(Localizer.Culture, "{0} {1}", Constants.App.Title, string.Format(Localizer.Culture, Constants.App.VersionFormat, version.Major, version.Minor, version.Build)); + ? string.Format(Localizer.Culture, "{0}{1}", Constants.App.Title, beta) + : string.Format(Localizer.Culture, "{0} {1}{2}", Constants.App.Title, string.Format(Localizer.Culture, Constants.App.VersionFormat, version.Major, version.Minor, version.Build), beta); } } @@ -1052,12 +1062,13 @@ public Brush TrayIconBackgroundColor return System.Windows.Media.Brushes.White; var brush = Settings.TrayIconBackgroundColor as System.Drawing.SolidBrush; - var fallbackValue = System.Windows.Media.Brushes.White; if (brush == null) - return fallbackValue; + return Brushes.FirstOrDefault(b => b.Color == Colors.White) ?? Brushes.First(); - return Brushes.FirstOrDefault(mediaBrush => mediaBrush.Color.IsEquals(brush.Color)) ?? fallbackValue; + return Brushes.FirstOrDefault(mediaBrush => mediaBrush.Color.IsEquals(brush.Color)) + ?? Brushes.FirstOrDefault(b => b.Color == Colors.White) + ?? Brushes.First(); } set { @@ -1067,7 +1078,7 @@ public Brush TrayIconBackgroundColor Settings.TrayIconBackgroundColor = value.ToBrush(); Settings.Save(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); RaisePropertyChanged(); } @@ -1087,12 +1098,13 @@ public Brush TrayIconDangerColor return System.Windows.Media.Brushes.White; var brush = Settings.TrayIconDangerColor as System.Drawing.SolidBrush; - var fallbackValue = System.Windows.Media.Brushes.DarkRed; if (brush == null) - return fallbackValue; + return Brushes.FirstOrDefault(b => b.Color == Colors.DarkRed) ?? Brushes.First(); - return Brushes.FirstOrDefault(mediaBrush => mediaBrush.Color.IsEquals(brush.Color)) ?? fallbackValue; + return Brushes.FirstOrDefault(mediaBrush => mediaBrush.Color.IsEquals(brush.Color)) + ?? Brushes.FirstOrDefault(b => b.Color == Colors.DarkRed) + ?? Brushes.First(); } set { @@ -1102,7 +1114,7 @@ public Brush TrayIconDangerColor Settings.TrayIconDangerColor = value.ToBrush(); Settings.Save(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); RaisePropertyChanged(); } @@ -1126,7 +1138,7 @@ public byte TrayIconDangerLevel Settings.TrayIconDangerLevel = value; Settings.Save(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); RaisePropertyChanged(); } @@ -1137,6 +1149,69 @@ public byte TrayIconDangerLevel } } + /// + /// Gets or sets a value indicating whether [tray icon optimize on middle mouse click]. + /// + /// + /// true if [tray icon optimize on middle mouse click]; otherwise, false. + /// + public bool TrayIconOptimizeOnMiddleMouseClick + { + get { return Settings.TrayIconOptimizeOnMiddleMouseClick; } + set + { + try + { + IsBusy = true; + + Settings.TrayIconOptimizeOnMiddleMouseClick = value; + Settings.Save(); + + RaisePropertyChanged(); + } + finally + { + IsBusy = false; + } + } + } + + /// + /// Gets or sets the color of the tray icon optimizing. + /// + /// + /// The color of the tray icon optimizing. + /// + public Brush TrayIconOptimizingColor + { + get + { + if (App.IsInDesignMode) + return System.Windows.Media.Brushes.White; + + var brush = Settings.TrayIconOptimizingColor as System.Drawing.SolidBrush; + + if (brush == null) + return Brushes.FirstOrDefault(b => b.Color == Colors.DimGray) ?? Brushes.First(); + + return Brushes.FirstOrDefault(mediaBrush => mediaBrush.Color.IsEquals(brush.Color)) + ?? Brushes.FirstOrDefault(b => b.Color == Colors.DimGray) + ?? Brushes.First(); + } + set + { + if (value == null) + return; + + Settings.TrayIconOptimizingColor = value.ToBrush(); + Settings.Save(); + + NotificationService.Update(Computer.Memory, IsOptimizationRunning); + + RaisePropertyChanged(); + } + } + /// /// Gets the tray icon items. /// @@ -1147,15 +1222,19 @@ public ObservableCollection> TrayIconItems { get { - return new ObservableCollection> - ( - new List> - { - new ObservableItem(Localizer.String.ShowMemoryUsage, () => TrayIconShowMemoryUsage, value => TrayIconShowMemoryUsage = value), - new ObservableItem(Localizer.String.UseTransparentBackground, () => TrayIconUseTransparentBackground, value => TrayIconUseTransparentBackground = value, TrayIconShowMemoryUsage) - } - .OrderBy(item => item.Name) - ); + if (_trayIconItems == null) + { + _trayIconItems = new ObservableCollection> + ( + new List> + { + new ObservableItem(Localizer.String.OptimizeOnMiddleMouseClick, () => TrayIconOptimizeOnMiddleMouseClick, value => TrayIconOptimizeOnMiddleMouseClick = value), + new ObservableItem(Localizer.String.ShowMemoryUsage, () => TrayIconShowMemoryUsage, value => TrayIconShowMemoryUsage = value), + new ObservableItem(Localizer.String.UseTransparentBackground, () => TrayIconUseTransparentBackground, value => TrayIconUseTransparentBackground = value, TrayIconShowMemoryUsage) + } + ); + } + return _trayIconItems; } } @@ -1177,10 +1256,14 @@ public bool TrayIconShowMemoryUsage Settings.TrayIconShowMemoryUsage = value; Settings.Save(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); RaisePropertyChanged(); - RaisePropertyChanged(() => TrayIconItems); + + var useTransparentBackgroundItem = TrayIconItems.FirstOrDefault(item => item.Name == Localizer.String.UseTransparentBackground); + + if (useTransparentBackgroundItem != null) + useTransparentBackgroundItem.IsEnabled = value; } finally { @@ -1203,12 +1286,13 @@ public Brush TrayIconTextColor return System.Windows.Media.Brushes.White; var brush = Settings.TrayIconTextColor as System.Drawing.SolidBrush; - var fallbackValue = System.Windows.Media.Brushes.White; if (brush == null) - return fallbackValue; + return Brushes.FirstOrDefault(b => b.Color == Colors.White) ?? Brushes.First(); - return Brushes.FirstOrDefault(mediaBrush => mediaBrush.Color.IsEquals(brush.Color)) ?? fallbackValue; + return Brushes.FirstOrDefault(mediaBrush => mediaBrush.Color.IsEquals(brush.Color)) + ?? Brushes.FirstOrDefault(b => b.Color == Colors.White) + ?? Brushes.First(); } set { @@ -1218,7 +1302,7 @@ public Brush TrayIconTextColor Settings.TrayIconTextColor = value.ToBrush(); Settings.Save(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); RaisePropertyChanged(); } @@ -1242,7 +1326,7 @@ public bool TrayIconUseTransparentBackground Settings.TrayIconUseTransparentBackground = value; Settings.Save(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); RaisePropertyChanged(); } @@ -1267,12 +1351,13 @@ public Brush TrayIconWarningColor return System.Windows.Media.Brushes.White; var brush = Settings.TrayIconWarningColor as System.Drawing.SolidBrush; - var fallbackValue = System.Windows.Media.Brushes.DarkRed; if (brush == null) - return fallbackValue; + return Brushes.FirstOrDefault(b => b.Color == Colors.DarkRed) ?? Brushes.First(); - return Brushes.FirstOrDefault(mediaBrush => mediaBrush.Color.IsEquals(brush.Color)) ?? fallbackValue; + return Brushes.FirstOrDefault(mediaBrush => mediaBrush.Color.IsEquals(brush.Color)) + ?? Brushes.FirstOrDefault(b => b.Color == Colors.DarkRed) + ?? Brushes.First(); } set { @@ -1282,7 +1367,7 @@ public Brush TrayIconWarningColor Settings.TrayIconWarningColor = value.ToBrush(); Settings.Save(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); RaisePropertyChanged(); } @@ -1306,7 +1391,7 @@ public byte TrayIconWarningLevel Settings.TrayIconWarningLevel = value; Settings.Save(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); RaisePropertyChanged(); } @@ -1508,27 +1593,30 @@ private void MonitorApp() App.SetPriority(Settings.RunOnPriority); // Auto Optimization - if (CanOptimize) + lock (_lockObject) { - // Interval - if (Settings.AutoOptimizationInterval > 0 && - DateTimeOffset.Now.Subtract(_lastAutoOptimizationByInterval).TotalHours >= Settings.AutoOptimizationInterval) + if (CanOptimize) { - OptimizeAsync(Enums.Memory.Optimization.Reason.Schedule); + // Interval + if (Settings.AutoOptimizationInterval > 0 && + DateTimeOffset.Now.Subtract(_lastAutoOptimizationByInterval).TotalHours >= Settings.AutoOptimizationInterval) + { + OptimizeAsync(Enums.Memory.Optimization.Reason.Schedule); - _lastAutoOptimizationByInterval = DateTimeOffset.Now; - continue; - } + _lastAutoOptimizationByInterval = DateTimeOffset.Now; + continue; + } - // Memory usage - if (Settings.AutoOptimizationMemoryUsage > 0 && - Computer.Memory.Physical.Free.Percentage < Settings.AutoOptimizationMemoryUsage && - DateTimeOffset.Now.Subtract(_lastAutoOptimizationByMemoryUsage).TotalMinutes >= Constants.App.AutoOptimizationMemoryUsageInterval) - { - OptimizeAsync(Enums.Memory.Optimization.Reason.LowMemory); + // Memory usage + if (Settings.AutoOptimizationMemoryUsage > 0 && + Computer.Memory.Physical.Free.Percentage < Settings.AutoOptimizationMemoryUsage && + DateTimeOffset.Now.Subtract(_lastAutoOptimizationByMemoryUsage).TotalMinutes >= Constants.App.AutoOptimizationMemoryUsageInterval) + { + OptimizeAsync(Enums.Memory.Optimization.Reason.LowMemory); - _lastAutoOptimizationByMemoryUsage = DateTimeOffset.Now; - continue; + _lastAutoOptimizationByMemoryUsage = DateTimeOffset.Now; + continue; + } } } } @@ -1581,13 +1669,16 @@ private void MonitorComputer() if (IsBusy) continue; - // Update memory info - Computer.Memory = _computerService.Memory; + lock (_lockObject) + { + // Update memory info + Computer.Memory = _computerService.Memory; - RaisePropertyChanged(() => Computer); - RaisePropertyChanged(() => VirtualMemoryHeader); + RaisePropertyChanged(() => Computer); + RaisePropertyChanged(() => VirtualMemoryHeader); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); + } // Delay if (_cancellationTokenSource.Token.WaitHandle.WaitOne(5000)) @@ -1618,50 +1709,66 @@ private void OnOptimizeProgressUpdate(byte value, string step) /// Optimization reason private void Optimize(Enums.Memory.Optimization.Reason reason) { - try + lock (_lockObject) { - IsBusy = true; - IsOptimizationRunning = true; + try + { + IsBusy = true; + IsOptimizationRunning = true; + + NotificationService.Update(Computer.Memory, IsOptimizationRunning); - // App priority - App.SetPriority(Settings.RunOnPriority); + // App priority + App.SetPriority(Settings.RunOnPriority); - // Memory optimize - var tempPhysicalAvailable = Computer.Memory.Physical.Free.Bytes; - var tempVirtualAvailable = Computer.Memory.Virtual.Free.Bytes; + // Memory optimize + var tempPhysicalAvailable = Computer.Memory.Physical.Free.Bytes; + var tempVirtualAvailable = Computer.Memory.Virtual.Free.Bytes; - _computerService.Optimize(reason, Settings.MemoryAreas); + _computerService.Optimize(reason, Settings.MemoryAreas); - // Update memory info - Computer.Memory = _computerService.Memory; - RaisePropertyChanged(() => Computer); + // Update memory info + Computer.Memory = _computerService.Memory; + RaisePropertyChanged(() => Computer); - // Notification - if (Settings.ShowOptimizationNotifications) - { - var physicalReleased = (Computer.Memory.Physical.Free.Bytes > tempPhysicalAvailable ? Computer.Memory.Physical.Free.Bytes - tempPhysicalAvailable : tempPhysicalAvailable - Computer.Memory.Physical.Free.Bytes).ToMemoryUnit(); - var virtualReleased = (Computer.Memory.Virtual.Free.Bytes > tempVirtualAvailable ? Computer.Memory.Virtual.Free.Bytes - tempVirtualAvailable : tempVirtualAvailable - Computer.Memory.Virtual.Free.Bytes).ToMemoryUnit(); + // Notification + if (Settings.ShowOptimizationNotifications) + { + var physicalReleased = (Computer.Memory.Physical.Free.Bytes > tempPhysicalAvailable ? Computer.Memory.Physical.Free.Bytes - tempPhysicalAvailable : tempPhysicalAvailable - Computer.Memory.Physical.Free.Bytes).ToMemoryUnit(); + var virtualReleased = (Computer.Memory.Virtual.Free.Bytes > tempVirtualAvailable ? Computer.Memory.Virtual.Free.Bytes - tempVirtualAvailable : tempVirtualAvailable - Computer.Memory.Virtual.Free.Bytes).ToMemoryUnit(); - var message = Settings.ShowVirtualMemory - ? string.Format(Localizer.Culture, "{1}{0}{0}{2}: {3}{0}{4}: {5:0.#} {6}{0}{7}: {8:0.#} {9}", Environment.NewLine, Localizer.String.MemoryOptimized.ToUpper(Localizer.Culture), Localizer.String.Reason, reason.GetString(), Localizer.String.PhysicalMemory, physicalReleased.Key, physicalReleased.Value, Localizer.String.VirtualMemory, virtualReleased.Key, virtualReleased.Value) - : string.Format(Localizer.Culture, "{1}{0}{0}{2}: {3}{0}{4}: {5:0.#} {6}", Environment.NewLine, Localizer.String.MemoryOptimized.ToUpper(Localizer.Culture), Localizer.String.Reason, reason.GetString(), Localizer.String.PhysicalMemory, physicalReleased.Key, physicalReleased.Value); + var message = Settings.ShowVirtualMemory + ? string.Format(Localizer.Culture, "{1}{0}{0}{2}: {3}{0}{4}: {5:0.#} {6}{0}{7}: {8:0.#} {9}", Environment.NewLine, Localizer.String.MemoryOptimized.ToUpper(Localizer.Culture), Localizer.String.Reason, reason.GetString(), Localizer.String.PhysicalMemory, physicalReleased.Key, physicalReleased.Value, Localizer.String.VirtualMemory, virtualReleased.Key, virtualReleased.Value) + : string.Format(Localizer.Culture, "{1}{0}{0}{2}: {3}{0}{4}: {5:0.#} {6}", Environment.NewLine, Localizer.String.MemoryOptimized.ToUpper(Localizer.Culture), Localizer.String.Reason, reason.GetString(), Localizer.String.PhysicalMemory, physicalReleased.Key, physicalReleased.Value); - Notify(message); + Notify(message); + } } - - if (OnOptimizeCommandCompleted != null) + finally { - System.Windows.Application.Current.Dispatcher.Invoke((Action)delegate + IsOptimizationRunning = false; + IsBusy = false; + + NotificationService.Update(Computer.Memory, IsOptimizationRunning); + + // Raise the event after IsOptimizationRunning is set to false + // Use BeginInvoke to ensure it runs after all property changes propagate + System.Windows.Application.Current.Dispatcher.BeginInvoke((Action)(() => { - OnOptimizeCommandCompleted(); - }); + // Force command manager to re-evaluate CanExecute on all commands + CommandManager.InvalidateRequerySuggested(); + }), System.Windows.Threading.DispatcherPriority.Normal); + + // Raise completion event with lower priority to ensure commands are refreshed first + if (OnOptimizeCommandCompleted != null) + { + System.Windows.Application.Current.Dispatcher.BeginInvoke((Action)(() => + { + OnOptimizeCommandCompleted(); + }), System.Windows.Threading.DispatcherPriority.ApplicationIdle); + } } } - finally - { - IsOptimizationRunning = false; - IsBusy = false; - } } /// @@ -1672,6 +1779,9 @@ private void OptimizeAsync(Enums.Memory.Optimization.Reason reason) { try { + if (IsOptimizationRunning) + return; + OptimizationProgressStep = Localizer.String.Optimize; OptimizationProgressValue = 0; OptimizationProgressTotal = (byte)(new BitArray(new[] { (int)Settings.MemoryAreas }).OfType().Count(x => x) + 1); @@ -1697,9 +1807,10 @@ private void RegisterOptimizationHotkey(ModifierKeys modifiers, Key key) Settings.OptimizationModifiers = modifiers; var hotKey = new Hotkey(Settings.OptimizationModifiers, Settings.OptimizationKey); + IsOptimizationKeyValid = _hotKeyService.Register(hotKey, () => OptimizeAsync(Enums.Memory.Optimization.Reason.Manual)); - if (!IsOptimizationKeyValid) + if (!_isReiniziliating && !IsOptimizationKeyValid) { var message = string.Format(Localizer.Culture, Localizer.String.HotkeyIsInUseByOperatingSystem, hotKey); @@ -1715,6 +1826,39 @@ private void RegisterOptimizationHotkey(ModifierKeys modifiers, Key key) RaisePropertyChanged(() => OptimizationModifiers); } + /// + /// Reinitializes app after system resume from hibernation. + /// + public void ReinitializeAfterHibernation() + { + try + { + lock (_lockObject) + { + _isReiniziliating = true; + + if (UseHotkey) + RegisterOptimizationHotkey(Settings.OptimizationModifiers, Settings.OptimizationKey); + + Computer.Memory = _computerService.Memory; + + NotificationService.Update(Computer.Memory, IsOptimizationRunning); + + RaisePropertyChanged(string.Empty); + + App.ReleaseMemory(); + } + } + catch (Exception e) + { + Logger.Error("Error after system resume from hibernation: " + e.GetMessage()); + } + finally + { + _isReiniziliating = false; + } + } + /// /// Removes the process from exclusion list. /// @@ -1761,8 +1905,10 @@ private void ResetSettingsToDefaultConfiguration() OptimizationKey = Settings.OptimizationKey; OptimizationModifiers = Settings.OptimizationModifiers; + _trayIconItems = null; + NotificationService.Initialize(); - NotificationService.Update(Computer.Memory); + NotificationService.Update(Computer.Memory, IsOptimizationRunning); RaisePropertyChanged(string.Empty); } diff --git a/src/WinMemoryCleaner.csproj b/src/WinMemoryCleaner.csproj index 8ebf7557..2b2ce263 100644 --- a/src/WinMemoryCleaner.csproj +++ b/src/WinMemoryCleaner.csproj @@ -1,4 +1,4 @@ - + @@ -115,6 +115,7 @@ + @@ -134,6 +135,7 @@ + @@ -210,7 +212,8 @@ - + +