diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs index e0a0434a2..8df2ce9ed 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs @@ -175,5 +175,15 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models { _api.BackToQueryResults(); } + + public void StartLoadingBar() + { + _api.StartLoadingBar(); + } + + public void StopLoadingBar() + { + _api.StopLoadingBar(); + } } } diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 6e7b5ec60..09711051e 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -29,7 +29,9 @@ namespace Flow.Launcher.Core.Plugin public static readonly HashSet GlobalPlugins = new(); public static readonly Dictionary NonGlobalPlugins = new(); - public static IPublicAPI API { get; private set; } = Ioc.Default.GetRequiredService(); + // We should not initialize API in static constructor because it will create another API instance + private static IPublicAPI api = null; + private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); private static PluginsSettings Settings; private static List _metadatas; diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 07fc378c3..4c7af4cd4 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -323,17 +323,26 @@ namespace Flow.Launcher.Plugin public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK); /// - /// Displays a standardised Flow message box. - /// If there is issue when showing the message box, it will return null. + /// Displays a standardised Flow progress box. /// - /// The caption of the message box. + /// The caption of the progress box. /// /// Time-consuming task function, whose input is the action to report progress. /// The input of the action is the progress value which is a double value between 0 and 100. /// If there are any exceptions, this action will be null. /// - /// When user closes the progress box manually by button or esc key, this action will be called. - /// A progress box interface. - public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, Action forceClosed = null); + /// When user cancel the progress, this action will be called. + /// + public Task ShowProgressBoxAsync(string caption, Func, Task> reportProgressAsync, Action cancelProgress = null); + + /// + /// Start the loading bar in main window + /// + public void StartLoadingBar(); + + /// + /// Stop the loading bar in main window + /// + public void StopLoadingBar(); } } diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 8374fc9fe..447eca792 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -35,31 +35,64 @@ namespace Flow.Launcher public App() { // Initialize settings - var storage = new FlowLauncherJsonStorage(); - _settings = storage.Load(); - _settings.SetStorage(storage); - _settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled(); + try + { + var storage = new FlowLauncherJsonStorage(); + _settings = storage.Load(); + _settings.SetStorage(storage); + _settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled(); + } + catch (Exception e) + { + ShowErrorMsgBoxAndFailFast("Cannot load setting storage, please check local data directory", e); + return; + } // Configure the dependency injection container - var host = Host.CreateDefaultBuilder() - .UseContentRoot(AppContext.BaseDirectory) - .ConfigureServices(services => services - .AddSingleton(_ => _settings) - .AddSingleton(sp => new Updater(sp.GetRequiredService(), Launcher.Properties.Settings.Default.GithubRepo)) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - ).Build(); - Ioc.Default.ConfigureServices(host.Services); + try + { + var host = Host.CreateDefaultBuilder() + .UseContentRoot(AppContext.BaseDirectory) + .ConfigureServices(services => services + .AddSingleton(_ => _settings) + .AddSingleton(sp => new Updater(sp.GetRequiredService(), Launcher.Properties.Settings.Default.GithubRepo)) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + ).Build(); + Ioc.Default.ConfigureServices(host.Services); + } + catch (Exception e) + { + ShowErrorMsgBoxAndFailFast("Cannot configure dependency injection container, please open new issue in Flow.Launcher", e); + return; + } // Initialize the public API and Settings first - API = Ioc.Default.GetRequiredService(); - _settings.Initialize(); + try + { + API = Ioc.Default.GetRequiredService(); + _settings.Initialize(); + } + catch (Exception e) + { + ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e); + return; + } + } + + private static void ShowErrorMsgBoxAndFailFast(string message, Exception e) + { + // Firstly show users the message + MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error); + + // Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info. + Environment.FailFast(message, e); } [STAThread] diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 0baa1bef5..33d13614f 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -92,7 +92,7 @@ - + all @@ -104,7 +104,7 @@ - + diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs index 8a42d480f..a3bd83a97 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Text; -using System.Windows.Documents; +using System.Windows; using System.Windows.Media; +using System.Windows.Media.Imaging; using Microsoft.Win32; using Windows.Win32; using Windows.Win32.UI.WindowsAndMessaging; @@ -13,8 +15,70 @@ namespace Flow.Launcher.Helper; public static class WallpaperPathRetrieval { private static readonly int MAX_PATH = 260; + private static readonly int MAX_CACHE_SIZE = 3; - public static unsafe string GetWallpaperPath() + private static readonly Dictionary<(string, DateTime), ImageBrush> wallpaperCache = new(); + + public static Brush GetWallpaperBrush() + { + // Invoke the method on the UI thread + if (!Application.Current.Dispatcher.CheckAccess()) + { + return Application.Current.Dispatcher.Invoke(GetWallpaperBrush); + } + + try + { + var wallpaperPath = GetWallpaperPath(); + if (wallpaperPath is not null && File.Exists(wallpaperPath)) + { + // Since the wallpaper file name can be the same (TranscodedWallpaper), + // we need to add the last modified date to differentiate them + var dateModified = File.GetLastWriteTime(wallpaperPath); + wallpaperCache.TryGetValue((wallpaperPath, dateModified), out var cachedWallpaper); + if (cachedWallpaper != null) + { + return cachedWallpaper; + } + + // We should not dispose the memory stream since the bitmap is still in use + var memStream = new MemoryStream(File.ReadAllBytes(wallpaperPath)); + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.StreamSource = memStream; + bitmap.DecodePixelWidth = 800; + bitmap.DecodePixelHeight = 600; + bitmap.EndInit(); + bitmap.Freeze(); // Make the bitmap thread-safe + var wallpaperBrush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill }; + wallpaperBrush.Freeze(); // Make the brush thread-safe + + // Manage cache size + if (wallpaperCache.Count >= MAX_CACHE_SIZE) + { + // Remove the oldest wallpaper from the cache + var oldestCache = wallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault(); + if (oldestCache != default) + { + wallpaperCache.Remove(oldestCache); + } + } + + wallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush); + return wallpaperBrush; + } + + var wallpaperColor = GetWallpaperColor(); + return new SolidColorBrush(wallpaperColor); + } + catch (Exception ex) + { + App.API.LogException(nameof(WallpaperPathRetrieval), "Error retrieving wallpaper", ex); + return new SolidColorBrush(Colors.Transparent); + } + } + + private static unsafe string GetWallpaperPath() { var wallpaperPtr = stackalloc char[MAX_PATH]; PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, (uint)MAX_PATH, @@ -25,7 +89,7 @@ public static class WallpaperPathRetrieval return wallpaper.ToString(); } - public static Color GetWallpaperColor() + private static Color GetWallpaperColor() { RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", true); var result = key?.GetValue("Background", null); diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 3d987127e..058a51ae3 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -368,6 +368,7 @@ OK Yes No + Background Version diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 264f57cb5..5237bdf38 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -442,7 +442,7 @@ namespace Flow.Launcher if (_settings.FirstLaunch) { _settings.FirstLaunch = false; - PluginManager.API.SaveAppAllSettings(); + App.API.SaveAppAllSettings(); OpenWelcomeWindow(); } } diff --git a/Flow.Launcher/ProgressBoxEx.xaml b/Flow.Launcher/ProgressBoxEx.xaml index 3102cfb72..c17f8b61d 100644 --- a/Flow.Launcher/ProgressBoxEx.xaml +++ b/Flow.Launcher/ProgressBoxEx.xaml @@ -12,6 +12,7 @@ Foreground="{DynamicResource PopupTextColor}" ResizeMode="NoResize" SizeToContent="Height" + Topmost="True" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> @@ -35,9 +36,32 @@ + +