diff --git a/Flow.Launcher.Core/Resource/LocalizationConverter.cs b/Flow.Launcher.Core/Resource/LocalizationConverter.cs index 81600e023e1..fdda33926d5 100644 --- a/Flow.Launcher.Core/Resource/LocalizationConverter.cs +++ b/Flow.Launcher.Core/Resource/LocalizationConverter.cs @@ -6,6 +6,7 @@ namespace Flow.Launcher.Core.Resource { + [Obsolete("LocalizationConverter is obsolete. Use with Flow.Launcher.Localization NuGet package instead.")] public class LocalizationConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs b/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs index 52a23233441..3e1a19a7686 100644 --- a/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs +++ b/Flow.Launcher.Core/Resource/LocalizedDescriptionAttribute.cs @@ -1,15 +1,19 @@ using System.ComponentModel; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Core.Resource { public class LocalizedDescriptionAttribute : DescriptionAttribute { - private readonly Internationalization _translator; + // 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 readonly string _resourceKey; public LocalizedDescriptionAttribute(string resourceKey) { - _translator = InternationalizationManager.Instance; _resourceKey = resourceKey; } @@ -17,7 +21,7 @@ public override string Description { get { - string description = _translator.GetTranslation(_resourceKey); + string description = API.GetTranslation(_resourceKey); return string.IsNullOrWhiteSpace(description) ? string.Format("[[{0}]]", _resourceKey) : description; } diff --git a/Flow.Launcher.Core/Resource/TranslationConverter.cs b/Flow.Launcher.Core/Resource/TranslationConverter.cs index ebab99e5b81..eb0032758b4 100644 --- a/Flow.Launcher.Core/Resource/TranslationConverter.cs +++ b/Flow.Launcher.Core/Resource/TranslationConverter.cs @@ -1,19 +1,25 @@ using System; using System.Globalization; using System.Windows.Data; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Core.Resource { public class TranslationConverter : IValueConverter { + // 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(); + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var key = value.ToString(); - if (String.IsNullOrEmpty(key)) - return key; - return InternationalizationManager.Instance.GetTranslation(key); + if (string.IsNullOrEmpty(key)) return key; + return API.GetTranslation(key); } - public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => + throw new InvalidOperationException(); } } diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index b91da711480..f02b2297f64 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -67,6 +67,7 @@ + diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index ddbab4ef0b1..b8c12868bfb 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using System.Windows.Media; using BitFaster.Caching.Lfu; @@ -55,7 +53,6 @@ public bool TryGetValue(string key, bool isFullImage, out ImageSource image) return image != null; } - image = null; return false; } diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 1483c58654e..9e31d2b4ebf 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -10,6 +10,8 @@ using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin; +using SharpVectors.Converters; +using SharpVectors.Renderers.Wpf; namespace Flow.Launcher.Infrastructure.Image { @@ -32,8 +34,10 @@ public static class ImageLoader public static ImageSource LoadingImage { get; } = new BitmapImage(new Uri(Constant.LoadingImgIcon)); public const int SmallIconSize = 64; public const int FullIconSize = 256; + public const int FullImageSize = 320; private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" }; + private static readonly string SvgExtension = ".svg"; public static async Task InitializeAsync() { @@ -235,10 +239,11 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag image = LoadFullImage(path); type = ImageType.FullImageFile; } - catch (NotSupportedException) + catch (NotSupportedException ex) { image = Image; type = ImageType.Error; + API.LogException(ClassName, $"Failed to load image file from path {path}: {ex.Message}", ex); } } else @@ -251,6 +256,20 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag image = GetThumbnail(path, ThumbnailOptions.ThumbnailOnly); } } + else if (extension == SvgExtension) + { + try + { + image = LoadSvgImage(path, loadFullImage); + type = ImageType.FullImageFile; + } + catch (System.Exception ex) + { + image = Image; + type = ImageType.Error; + API.LogException(ClassName, $"Failed to load SVG image from path {path}: {ex.Message}", ex); + } + } else { type = ImageType.File; @@ -324,7 +343,7 @@ public static async ValueTask LoadAsync(string path, bool loadFullI return img; } - private static BitmapImage LoadFullImage(string path) + private static ImageSource LoadFullImage(string path) { BitmapImage image = new BitmapImage(); image.BeginInit(); @@ -333,24 +352,24 @@ private static BitmapImage LoadFullImage(string path) image.CreateOptions = BitmapCreateOptions.IgnoreColorProfile; image.EndInit(); - if (image.PixelWidth > 320) + if (image.PixelWidth > FullImageSize) { BitmapImage resizedWidth = new BitmapImage(); resizedWidth.BeginInit(); resizedWidth.CacheOption = BitmapCacheOption.OnLoad; resizedWidth.UriSource = new Uri(path); resizedWidth.CreateOptions = BitmapCreateOptions.IgnoreColorProfile; - resizedWidth.DecodePixelWidth = 320; + resizedWidth.DecodePixelWidth = FullImageSize; resizedWidth.EndInit(); - if (resizedWidth.PixelHeight > 320) + if (resizedWidth.PixelHeight > FullImageSize) { BitmapImage resizedHeight = new BitmapImage(); resizedHeight.BeginInit(); resizedHeight.CacheOption = BitmapCacheOption.OnLoad; resizedHeight.UriSource = new Uri(path); resizedHeight.CreateOptions = BitmapCreateOptions.IgnoreColorProfile; - resizedHeight.DecodePixelHeight = 320; + resizedHeight.DecodePixelHeight = FullImageSize; resizedHeight.EndInit(); return resizedHeight; } @@ -360,5 +379,50 @@ private static BitmapImage LoadFullImage(string path) return image; } + + private static ImageSource LoadSvgImage(string path, bool loadFullImage = false) + { + // Set up drawing settings + var desiredHeight = loadFullImage ? FullImageSize : SmallIconSize; + var drawingSettings = new WpfDrawingSettings + { + IncludeRuntime = true, + // Set IgnoreRootViewbox to false to respect the SVG's viewBox + IgnoreRootViewbox = false + }; + + // Load and render the SVG + var converter = new FileSvgReader(drawingSettings); + var drawing = converter.Read(new Uri(path)); + + // Calculate scale to achieve desired height + var drawingBounds = drawing.Bounds; + if (drawingBounds.Height <= 0) + { + throw new InvalidOperationException($"Invalid SVG dimensions: Height must be greater than zero in {path}"); + } + var scale = desiredHeight / drawingBounds.Height; + var scaledWidth = drawingBounds.Width * scale; + var scaledHeight = drawingBounds.Height * scale; + + // Convert the Drawing to a Bitmap + var drawingVisual = new DrawingVisual(); + using (DrawingContext drawingContext = drawingVisual.RenderOpen()) + { + drawingContext.PushTransform(new ScaleTransform(scale, scale)); + drawingContext.DrawDrawing(drawing); + } + + // Create a RenderTargetBitmap to hold the rendered image + var bitmap = new RenderTargetBitmap( + (int)Math.Ceiling(scaledWidth), + (int)Math.Ceiling(scaledHeight), + 96, // DpiX + 96, // DpiY + PixelFormats.Pbgra32); + bitmap.Render(drawingVisual); + + return bitmap; + } } } diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index 363ecb9d002..18b20602213 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -11,11 +11,6 @@ GetModuleHandle GetKeyState VIRTUAL_KEY -WM_KEYDOWN -WM_KEYUP -WM_SYSKEYDOWN -WM_SYSKEYUP - EnumWindows DwmSetWindowAttribute diff --git a/Flow.Launcher.Infrastructure/UI/EnumBindingSource.cs b/Flow.Launcher.Infrastructure/UI/EnumBindingSource.cs index 350c892cf32..f9504e6d926 100644 --- a/Flow.Launcher.Infrastructure/UI/EnumBindingSource.cs +++ b/Flow.Launcher.Infrastructure/UI/EnumBindingSource.cs @@ -3,6 +3,7 @@ namespace Flow.Launcher.Infrastructure.UI { + [Obsolete("EnumBindingSourceExtension is obsolete. Use with Flow.Launcher.Localization NuGet package instead.")] public class EnumBindingSourceExtension : MarkupExtension { private Type _enumType; diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 55f09dc3b4d..a73ead81423 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -142,15 +142,47 @@ public interface IPublicAPI List GetAllPlugins(); /// - /// Register a callback for Global Keyboard Event - /// - /// + /// Registers a callback function for global keyboard events. + /// + /// + /// The callback function to invoke when a global keyboard event occurs. + /// + /// Parameters: + /// + /// int: The type of (key down, key up, etc.) + /// int: The virtual key code of the pressed/released key + /// : The state of modifier keys (Ctrl, Alt, Shift, etc.) + /// + /// + /// + /// Returns: true to allow normal system processing of the key event, + /// or false to intercept and prevent default handling. + /// + /// + /// + /// This callback will be invoked for all keyboard events system-wide. + /// Use with caution as intercepting system keys may affect normal system operation. + /// public void RegisterGlobalKeyboardCallback(Func callback); - + /// /// Remove a callback for Global Keyboard Event /// - /// + /// + /// The callback function to invoke when a global keyboard event occurs. + /// + /// Parameters: + /// + /// int: The type of (key down, key up, etc.) + /// int: The virtual key code of the pressed/released key + /// : The state of modifier keys (Ctrl, Alt, Shift, etc.) + /// + /// + /// + /// Returns: true to allow normal system processing of the key event, + /// or false to intercept and prevent default handling. + /// + /// public void RemoveGlobalKeyboardCallback(Func callback); /// @@ -372,6 +404,7 @@ public interface IPublicAPI /// public bool SetCurrentTheme(ThemeData theme); + /// /// Save all Flow's plugins caches /// void SavePluginCaches(); @@ -404,7 +437,10 @@ public interface IPublicAPI /// Task SaveCacheBinaryStorageAsync(string cacheName, string cacheDirectory) where T : new(); - /// Load image from path. Support local, remote and data:image url. + /// + /// Load image from path. + /// Support local, remote and data:image url. + /// Support png, jpg, jpeg, gif, bmp, tiff, ico, svg image files. /// If image path is missing, it will return a missing icon. /// /// The path of the image. @@ -418,6 +454,7 @@ public interface IPublicAPI /// ValueTask LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true); + /// /// Update the plugin manifest /// /// diff --git a/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs b/Flow.Launcher.Plugin/KeyEvent.cs similarity index 61% rename from Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs rename to Flow.Launcher.Plugin/KeyEvent.cs index 95bb258377b..321f17cc18e 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs +++ b/Flow.Launcher.Plugin/KeyEvent.cs @@ -1,7 +1,12 @@ using Windows.Win32; -namespace Flow.Launcher.Infrastructure.Hotkey +namespace Flow.Launcher.Plugin { + /// + /// Enumeration of key events for + /// + /// and + /// public enum KeyEvent { /// diff --git a/Flow.Launcher.Plugin/NativeMethods.txt b/Flow.Launcher.Plugin/NativeMethods.txt index e3e2b705eb0..0596691cc7f 100644 --- a/Flow.Launcher.Plugin/NativeMethods.txt +++ b/Flow.Launcher.Plugin/NativeMethods.txt @@ -1,3 +1,8 @@ EnumThreadWindows GetWindowText -GetWindowTextLength \ No newline at end of file +GetWindowTextLength + +WM_KEYDOWN +WM_KEYUP +WM_SYSKEYDOWN +WM_SYSKEYUP \ No newline at end of file diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs index 84d8a2ff926..fd2c8e09fa1 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs @@ -32,7 +32,7 @@ private async Task RefreshExternalPluginsAsync() public bool SatisfiesFilter(PluginStoreItemViewModel plugin) { return string.IsNullOrEmpty(FilterText) || - StringMatcher.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() || - StringMatcher.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet(); + App.API.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() || + App.API.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet(); } } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs index 4958bb7b748..b89e970e99b 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs @@ -106,8 +106,8 @@ public SettingsPanePluginsViewModel(Settings settings) public List FilteredPluginViewModels => PluginViewModels .Where(v => string.IsNullOrEmpty(FilterText) || - StringMatcher.FuzzySearch(FilterText, v.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet() || - StringMatcher.FuzzySearch(FilterText, v.PluginPair.Metadata.Description).IsSearchPrecisionScoreMet() + App.API.FuzzySearch(FilterText, v.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet() || + App.API.FuzzySearch(FilterText, v.PluginPair.Metadata.Description).IsSearchPrecisionScoreMet() ) .ToList(); diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index 48acf61090b..66be0890388 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -1,13 +1,23 @@ -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Text.Json; -using Flow.Launcher.Infrastructure.Logger; +using System; +using Flow.Launcher.Plugin.BrowserBookmark.Models; +using Microsoft.Data.Sqlite; namespace Flow.Launcher.Plugin.BrowserBookmark; public abstract class ChromiumBookmarkLoader : IBookmarkLoader { + private static readonly string ClassName = nameof(ChromiumBookmarkLoader); + + private readonly string _faviconCacheDir; + + protected ChromiumBookmarkLoader() + { + _faviconCacheDir = Main._faviconCacheDir; + } + public abstract List GetBookmarks(); protected List LoadBookmarks(string browserDataPath, string name) @@ -22,16 +32,36 @@ protected List LoadBookmarks(string browserDataPath, string name) if (!File.Exists(bookmarkPath)) continue; - Main.RegisterBookmarkFile(bookmarkPath); + // Register bookmark file monitoring (direct call to Main.RegisterBookmarkFile) + try + { + if (File.Exists(bookmarkPath)) + { + Main.RegisterBookmarkFile(bookmarkPath); + } + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to register bookmark file monitoring: {bookmarkPath}", ex); + } var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); - bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source)); + var profileBookmarks = LoadBookmarksFromFile(bookmarkPath, source); + + // Load favicons after loading bookmarks + var faviconDbPath = Path.Combine(profile, "Favicons"); + if (File.Exists(faviconDbPath)) + { + LoadFaviconsFromDb(faviconDbPath, profileBookmarks); + } + + bookmarks.AddRange(profileBookmarks); } return bookmarks; } - protected List LoadBookmarksFromFile(string path, string source) + protected static List LoadBookmarksFromFile(string path, string source) { var bookmarks = new List(); @@ -45,15 +75,14 @@ protected List LoadBookmarksFromFile(string path, string source) return bookmarks; } - private void EnumerateRoot(JsonElement rootElement, ICollection bookmarks, string source) + private static void EnumerateRoot(JsonElement rootElement, ICollection bookmarks, string source) { foreach (var folder in rootElement.EnumerateObject()) { if (folder.Value.ValueKind != JsonValueKind.Object) continue; - // Fix for Opera. It stores bookmarks slightly different than chrome. See PR and bug report for this change for details. - // If various exceptions start to build up here consider splitting this Loader into multiple separate ones. + // Fix for Opera. It stores bookmarks slightly different than chrome. if (folder.Name == "custom_root") EnumerateRoot(folder.Value, bookmarks, source); else @@ -61,7 +90,7 @@ private void EnumerateRoot(JsonElement rootElement, ICollection bookma } } - private void EnumerateFolderBookmark(JsonElement folderElement, ICollection bookmarks, + private static void EnumerateFolderBookmark(JsonElement folderElement, ICollection bookmarks, string source) { if (!folderElement.TryGetProperty("children", out var childrenElement)) @@ -86,9 +115,107 @@ private void EnumerateFolderBookmark(JsonElement folderElement, ICollection bookmarks) + { + // Use a copy to avoid lock issues with the original file + var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.db"); + + try + { + File.Copy(dbPath, tempDbPath, true); + } + catch (Exception ex) + { + File.Delete(tempDbPath); + Main._context.API.LogException(ClassName, $"Failed to copy favicon DB: {dbPath}", ex); + return; + } + + try + { + using var connection = new SqliteConnection($"Data Source={tempDbPath}"); + connection.Open(); + + foreach (var bookmark in bookmarks) + { + try + { + var url = bookmark.Url; + if (string.IsNullOrEmpty(url)) continue; + + // Extract domain from URL + if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) + continue; + + var domain = uri.Host; + + using var cmd = connection.CreateCommand(); + cmd.CommandText = @" + SELECT f.id, b.image_data + FROM favicons f + JOIN favicon_bitmaps b ON f.id = b.icon_id + JOIN icon_mapping m ON f.id = m.icon_id + WHERE m.page_url LIKE @url + ORDER BY b.width DESC + LIMIT 1"; + + cmd.Parameters.AddWithValue("@url", $"%{domain}%"); + + using var reader = cmd.ExecuteReader(); + if (!reader.Read() || reader.IsDBNull(1)) + continue; + + var iconId = reader.GetInt64(0).ToString(); + var imageData = (byte[])reader["image_data"]; + + if (imageData is not { Length: > 0 }) + continue; + + var faviconPath = Path.Combine(_faviconCacheDir, $"chromium_{domain}_{iconId}.png"); + SaveBitmapData(imageData, faviconPath); + + bookmark.FaviconPath = faviconPath; + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to extract bookmark favicon: {bookmark.Url}", ex); + } } + + // https://github.com/dotnet/efcore/issues/26580 + SqliteConnection.ClearPool(connection); + connection.Close(); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to connect to SQLite: {tempDbPath}", ex); + } + + // Delete temporary file + try + { + File.Delete(tempDbPath); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex); + } + } + + private static void SaveBitmapData(byte[] imageData, string outputPath) + { + try + { + File.WriteAllBytes(outputPath, imageData); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to save image: {outputPath}", ex); } } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs index 3468015ebaf..758ce68ae78 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.SharedModels; @@ -10,11 +9,11 @@ internal static class BookmarkLoader { internal static MatchResult MatchProgram(Bookmark bookmark, string queryString) { - var match = StringMatcher.FuzzySearch(queryString, bookmark.Name); + var match = Main._context.API.FuzzySearch(queryString, bookmark.Name); if (match.IsSearchPrecisionScoreMet()) return match; - return StringMatcher.FuzzySearch(queryString, bookmark.Url); + return Main._context.API.FuzzySearch(queryString, bookmark.Url); } internal static List LoadAllBookmarks(Settings setting) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 35ad32fb3d4..75f26d32252 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -1,16 +1,26 @@ -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using Microsoft.Data.Sqlite; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Flow.Launcher.Plugin.BrowserBookmark.Models; +using Microsoft.Data.Sqlite; namespace Flow.Launcher.Plugin.BrowserBookmark; public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader { + private static readonly string ClassName = nameof(FirefoxBookmarkLoaderBase); + + private readonly string _faviconCacheDir; + + protected FirefoxBookmarkLoaderBase() + { + _faviconCacheDir = Main._faviconCacheDir; + } + public abstract List GetBookmarks(); + // Updated query - removed favicon_id column private const string QueryAllBookmarks = """ SELECT moz_places.url, moz_bookmarks.title FROM moz_places @@ -20,35 +30,193 @@ INNER JOIN moz_bookmarks ON ( ORDER BY moz_places.visit_count DESC """; - private const string DbPathFormat = "Data Source ={0}"; + private const string DbPathFormat = "Data Source={0}"; - protected static List GetBookmarksFromPath(string placesPath) + protected List GetBookmarksFromPath(string placesPath) { - // Return empty list if the places.sqlite file cannot be found + // Variable to store bookmark list + var bookmarks = new List(); + + // Return empty list if places.sqlite file doesn't exist if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath)) - return new List(); - - Main.RegisterBookmarkFile(placesPath); - - // create the connection string and init the connection - string dbPath = string.Format(DbPathFormat, placesPath); - using var dbConnection = new SqliteConnection(dbPath); - // Open connection to the database file and execute the query - dbConnection.Open(); - var reader = new SqliteCommand(QueryAllBookmarks, dbConnection).ExecuteReader(); - - // return results in List format - return reader - .Select( - x => new Bookmark( - x["title"] is DBNull ? string.Empty : x["title"].ToString(), - x["url"].ToString() + return bookmarks; + + var tempDbPath = Path.Combine(_faviconCacheDir, $"tempplaces_{Guid.NewGuid()}.sqlite"); + + try + { + // Try to register file monitoring + try + { + Main.RegisterBookmarkFile(placesPath); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to register Firefox bookmark file monitoring: {placesPath}", ex); + } + + // Use a copy to avoid lock issues with the original file + File.Copy(placesPath, tempDbPath, true); + + // Connect to database and execute query + string dbPath = string.Format(DbPathFormat, tempDbPath); + using var dbConnection = new SqliteConnection(dbPath); + dbConnection.Open(); + var reader = new SqliteCommand(QueryAllBookmarks, dbConnection).ExecuteReader(); + + // Create bookmark list + bookmarks = reader + .Select( + x => new Bookmark( + x["title"] is DBNull ? string.Empty : x["title"].ToString(), + x["url"].ToString(), + "Firefox" + ) ) - ) - .ToList(); + .ToList(); + + // Path to favicon database + var faviconDbPath = Path.Combine(Path.GetDirectoryName(placesPath), "favicons.sqlite"); + if (File.Exists(faviconDbPath)) + { + LoadFaviconsFromDb(faviconDbPath, bookmarks); + } + // https://github.com/dotnet/efcore/issues/26580 + SqliteConnection.ClearPool(dbConnection); + dbConnection.Close(); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to load Firefox bookmarks: {placesPath}", ex); + } + + // Delete temporary file + try + { + File.Delete(tempDbPath); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex); + } + + return bookmarks; } -} + private void LoadFaviconsFromDb(string faviconDbPath, List bookmarks) + { + var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.sqlite"); + + try + { + // Use a copy to avoid lock issues with the original file + File.Copy(faviconDbPath, tempDbPath, true); + + var defaultIconPath = Path.Combine( + Path.GetDirectoryName(typeof(FirefoxBookmarkLoaderBase).Assembly.Location), + "bookmark.png"); + + string dbPath = string.Format(DbPathFormat, tempDbPath); + using var connection = new SqliteConnection(dbPath); + connection.Open(); + + // Get favicons based on bookmark URLs + foreach (var bookmark in bookmarks) + { + try + { + if (string.IsNullOrEmpty(bookmark.Url)) + continue; + + // Extract domain from URL + if (!Uri.TryCreate(bookmark.Url, UriKind.Absolute, out Uri uri)) + continue; + + var domain = uri.Host; + + // Query for latest Firefox version favicon structure + using var cmd = connection.CreateCommand(); + cmd.CommandText = @" + SELECT i.data + FROM moz_icons i + JOIN moz_icons_to_pages ip ON i.id = ip.icon_id + JOIN moz_pages_w_icons p ON ip.page_id = p.id + WHERE p.page_url LIKE @url + AND i.data IS NOT NULL + ORDER BY i.width DESC -- Select largest icon available + LIMIT 1"; + + cmd.Parameters.AddWithValue("@url", $"%{domain}%"); + + using var reader = cmd.ExecuteReader(); + if (!reader.Read() || reader.IsDBNull(0)) + continue; + + var imageData = (byte[])reader["data"]; + + if (imageData is not { Length: > 0 }) + continue; + + string faviconPath; + if (IsSvgData(imageData)) + { + faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.svg"); + } + else + { + faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.png"); + } + SaveBitmapData(imageData, faviconPath); + + bookmark.FaviconPath = faviconPath; + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to extract Firefox favicon: {bookmark.Url}", ex); + } + } + + // https://github.com/dotnet/efcore/issues/26580 + SqliteConnection.ClearPool(connection); + connection.Close(); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to load Firefox favicon DB: {faviconDbPath}", ex); + } + + // Delete temporary file + try + { + File.Delete(tempDbPath); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex); + } + } + + private static void SaveBitmapData(byte[] imageData, string outputPath) + { + try + { + File.WriteAllBytes(outputPath, imageData); + } + catch (Exception ex) + { + Main._context.API.LogException(ClassName, $"Failed to save image: {outputPath}", ex); + } + } + + private static bool IsSvgData(byte[] data) + { + if (data.Length < 5) + return false; + string start = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(100, data.Length)); + return start.Contains(" GetBookmarks() /// /// Path to places.sqlite /// - private string PlacesPath + private static string PlacesPath { get { @@ -77,33 +245,6 @@ private string PlacesPath using var sReader = new StreamReader(profileIni); var ini = sReader.ReadToEnd(); - /* - Current profiles.ini structure example as of Firefox version 69.0.1 - - [Install736426B0AF4A39CB] - Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile - Locked=1 - - [Profile2] - Name=newblahprofile - IsRelative=0 - Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code. - - [Profile1] - Name=default - IsRelative=1 - Path=Profiles/cydum7q4.default - Default=1 - - [Profile0] - Name=default-release - IsRelative=1 - Path=Profiles/7789f565.default-release - - [General] - StartWithLastProfile=1 - Version=2 - */ var lines = ini.Split("\r\n").ToList(); var defaultProfileFolderNameRaw = lines.FirstOrDefault(x => x.Contains("Default=") && x != "Default=1") ?? string.Empty; diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index b4e42fbcdc3..49ae2656495 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -1,4 +1,4 @@ - + Library diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index a48d70f2d46..3bee49bf20a 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Windows.Controls; -using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.BrowserBookmark.Commands; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.BrowserBookmark.Views; @@ -10,25 +9,34 @@ using System.Threading.Channels; using System.Threading.Tasks; using System.Threading; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.BrowserBookmark; public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable { - private static PluginInitContext _context; + internal static string _faviconCacheDir; - private static List _cachedBookmarks = new List(); + internal static PluginInitContext _context; + + private static List _cachedBookmarks = new(); private static Settings _settings; private static bool _initialized = false; - + public void Init(PluginInitContext context) { _context = context; _settings = context.API.LoadSettingJsonStorage(); + _faviconCacheDir = Path.Combine( + context.CurrentPluginMetadata.PluginCacheDirectoryPath, + "FaviconCache"); + + FilesFolders.ValidateDirectory(_faviconCacheDir); + LoadBookmarksIfEnabled(); } @@ -58,7 +66,6 @@ public List Query(Query query) // Should top results be returned? (true if no search parameters have been passed) var topResults = string.IsNullOrEmpty(param); - if (!topResults) { // Since we mixed chrome and firefox bookmarks, we should order them again @@ -68,7 +75,9 @@ public List Query(Query query) { Title = c.Name, SubTitle = c.Url, - IcoPath = @"Images\bookmark.png", + IcoPath = !string.IsNullOrEmpty(c.FaviconPath) && File.Exists(c.FaviconPath) + ? c.FaviconPath + : @"Images\bookmark.png", Score = BookmarkLoader.MatchProgram(c, param).Score, Action = _ => { @@ -90,7 +99,9 @@ public List Query(Query query) { Title = c.Name, SubTitle = c.Url, - IcoPath = @"Images\bookmark.png", + IcoPath = !string.IsNullOrEmpty(c.FaviconPath) && File.Exists(c.FaviconPath) + ? c.FaviconPath + : @"Images\bookmark.png", Score = 5, Action = _ => { @@ -104,10 +115,9 @@ public List Query(Query query) } } + private static readonly Channel _refreshQueue = Channel.CreateBounded(1); - private static Channel _refreshQueue = Channel.CreateBounded(1); - - private static SemaphoreSlim _fileMonitorSemaphore = new(1, 1); + private static readonly SemaphoreSlim _fileMonitorSemaphore = new(1, 1); private static async Task MonitorRefreshQueueAsync() { @@ -141,12 +151,13 @@ internal static void RegisterBookmarkFile(string path) return; } - var watcher = new FileSystemWatcher(directory!); - watcher.Filter = Path.GetFileName(path); - - watcher.NotifyFilter = NotifyFilters.FileName | - NotifyFilters.LastWrite | - NotifyFilters.Size; + var watcher = new FileSystemWatcher(directory!) + { + Filter = Path.GetFileName(path), + NotifyFilter = NotifyFilters.FileName | + NotifyFilters.LastWrite | + NotifyFilters.Size + }; watcher.Changed += static (_, _) => { @@ -195,7 +206,7 @@ public List LoadContextMenus(Result selectedResult) { return new List() { - new Result + new() { Title = _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"), SubTitle = _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"), @@ -210,7 +221,7 @@ public List LoadContextMenus(Result selectedResult) catch (Exception e) { var message = "Failed to set url in clipboard"; - Log.Exception("Main", message, e, "LoadContextMenus"); + _context.API.LogException(nameof(Main), message, e); _context.API.ShowMsg(message); diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs index c738da389c5..caab16b65e8 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs @@ -18,4 +18,5 @@ public virtual bool Equals(Bookmark other) } public List CustomBrowsers { get; set; } = new(); + public string FaviconPath { get; set; } = string.Empty; } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs index 4bdab89a945..3182adfed65 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs @@ -9,9 +9,14 @@ namespace Flow.Launcher.Plugin.BrowserBookmark.Views; public partial class SettingsControl : INotifyPropertyChanged { public Settings Settings { get; } - public CustomBrowser SelectedCustomBrowser { get; set; } + public SettingsControl(Settings settings) + { + Settings = settings; + InitializeComponent(); + } + public bool LoadChromeBookmark { get => Settings.LoadChromeBookmark; @@ -52,12 +57,6 @@ public bool OpenInNewBrowserWindow } } - public SettingsControl(Settings settings) - { - Settings = settings; - InitializeComponent(); - } - public event PropertyChangedEventHandler PropertyChanged; private void NewCustomBrowser(object sender, RoutedEventArgs e) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 3f3b7cb5846..f479078242e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -2,13 +2,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Threading.Tasks; using System.Windows; -using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; -using System.Linq; using Flow.Launcher.Plugin.Explorer.Helper; using Flow.Launcher.Plugin.Explorer.ViewModels; @@ -394,7 +393,7 @@ private Result CreateAddToIndexSearchExclusionListResult(SearchResult record) { Title = Context.API.GetTranslation("plugin_explorer_excludefromindexsearch"), SubTitle = Context.API.GetTranslation("plugin_explorer_path") + " " + record.FullPath, - Action = _ => + Action = c_ => { if (!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase))) Settings.IndexSearchExcludedSubdirectoryPaths.Add(new AccessLink @@ -402,7 +401,7 @@ private Result CreateAddToIndexSearchExclusionListResult(SearchResult record) Path = record.FullPath }); - Task.Run(() => + _ = Task.Run(() => { Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_excludedfromindexsearch_msg"), Context.API.GetTranslation("plugin_explorer_path") + @@ -470,22 +469,16 @@ private Result CreateOpenWithMenu(SearchResult record) private void LogException(string message, Exception e) { - Log.Exception($"|Flow.Launcher.Plugin.Folder.ContextMenu|{message}", e); + Context.API.LogException(nameof(ContextMenu), message, e); } - private bool CanRunAsDifferentUser(string path) + private static bool CanRunAsDifferentUser(string path) { - switch (Path.GetExtension(path)) + return Path.GetExtension(path) switch { - case ".exe": - case ".bat": - case ".msi": - return true; - - default: - return false; - - } + ".exe" or ".bat" or ".msi" => true, + _ => false, + }; } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 9fd495f4910..1a0d3bd15d4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -1,10 +1,9 @@ -using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Plugin.SharedCommands; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; +using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { @@ -76,7 +75,7 @@ private static IEnumerable DirectorySearch(EnumerationOptions enum } catch (Exception e) { - Log.Exception(nameof(DirectoryInfoSearch), "Error occurred while searching path", e); + Main.Context.API.LogException(nameof(DirectoryInfoSearch), "Error occurred while searching path", e); throw; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 1add8476577..5c4accdc05f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -1,16 +1,14 @@ -using Flow.Launcher.Core.Resource; -using Flow.Launcher.Infrastructure; -using Flow.Launcher.Plugin.SharedCommands; -using System; +using System; using System.IO; using System.Linq; using System.Threading.Tasks; -using Flow.Launcher.Plugin.Explorer.Search.Everything; -using System.Windows.Input; -using Path = System.IO.Path; using System.Windows.Controls; +using System.Windows.Input; +using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.Views; +using Flow.Launcher.Plugin.SharedCommands; using Peter; +using Path = System.IO.Path; namespace Flow.Launcher.Plugin.Explorer.Search { @@ -66,7 +64,7 @@ public static Result CreateResult(Query query, SearchResult result) CreateFolderResult(Path.GetFileName(result.FullPath), result.FullPath, result.FullPath, query, result.Score, result.WindowsIndexed), ResultType.File => CreateFileResult(result.FullPath, query, result.Score, result.WindowsIndexed), - _ => throw new ArgumentOutOfRangeException() + _ => throw new ArgumentOutOfRangeException(null) }; } @@ -99,7 +97,7 @@ internal static Result CreateFolderResult(string title, string subtitle, string IcoPath = path, SubTitle = subtitle, AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder), - TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData, + TitleHighlightData = Context.API.FuzzySearch(query.Search, title).MatchData, CopyText = path, Preview = new Result.PreviewInfo { @@ -164,7 +162,7 @@ internal static Result CreateFolderResult(string title, string subtitle, string return false; }, Score = score, - TitleToolTip = InternationalizationManager.Instance.GetTranslation("plugin_explorer_plugin_ToolTipOpenDirectory"), + TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenDirectory"), SubTitleToolTip = path, ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed } }; @@ -286,7 +284,7 @@ internal static Result CreateFileResult(string filePath, Query query, int score FilePath = filePath, }, AutoCompleteText = GetAutoCompleteText(title, query, filePath, ResultType.File), - TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData, + TitleHighlightData = Context.API.FuzzySearch(query.Search, title).MatchData, Score = score, CopyText = filePath, PreviewPanel = new Lazy(() => new PreviewPanel(Settings, filePath)), @@ -319,7 +317,7 @@ internal static Result CreateFileResult(string filePath, Query query, int score return true; }, - TitleToolTip = InternationalizationManager.Instance.GetTranslation("plugin_explorer_plugin_ToolTipOpenContainingFolder"), + TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenContainingFolder"), SubTitleToolTip = filePath, ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed } }; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs index 66230937cd3..2aeb421e003 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs @@ -1,6 +1,4 @@ -using Flow.Launcher.Infrastructure.Logger; -using Microsoft.Search.Interop; -using System; +using System; using System.Collections.Generic; using System.Data.OleDb; using System.Linq; @@ -9,11 +7,13 @@ using System.Text.RegularExpressions; using System.Threading; using Flow.Launcher.Plugin.Explorer.Exceptions; +using Microsoft.Search.Interop; namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { internal static class WindowsIndex { + private static readonly string ClassName = nameof(WindowsIndex); // Reserved keywords in oleDB private static Regex _reservedPatternMatcher = new(@"^[`\@\@\#\#\*\^,\&\&\/\\\$\%_;\[\]]+$", RegexOptions.Compiled); @@ -33,7 +33,7 @@ private static async IAsyncEnumerable ExecuteWindowsIndexSearchAsy } catch (OleDbException e) { - Log.Exception($"|WindowsIndex.ExecuteWindowsIndexSearchAsync|Failed to execute windows index search query: {indexQueryString}", e); + Main.Context.API.LogException(ClassName, $"Failed to execute windows index search query: {indexQueryString}", e); yield break; } await using var dataReader = dataReaderAttempt; diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj index 21d964c1126..1e662de9ef9 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj @@ -41,8 +41,6 @@ - - diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs index aea0d77a1f6..05e8d960fb0 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs @@ -1,20 +1,20 @@ using System.Collections.Generic; using System.Linq; -using Flow.Launcher.Core.Plugin; namespace Flow.Launcher.Plugin.PluginIndicator { public class Main : IPlugin, IPluginI18n { - private PluginInitContext context; + internal PluginInitContext Context { get; private set; } public List Query(Query query) { + var nonGlobalPlugins = GetNonGlobalPlugins(); var results = - from keyword in PluginManager.NonGlobalPlugins.Keys - let plugin = PluginManager.NonGlobalPlugins[keyword].Metadata - let keywordSearchResult = context.API.FuzzySearch(query.Search, keyword) - let searchResult = keywordSearchResult.IsSearchPrecisionScoreMet() ? keywordSearchResult : context.API.FuzzySearch(query.Search, plugin.Name) + from keyword in nonGlobalPlugins.Keys + let plugin = nonGlobalPlugins[keyword].Metadata + let keywordSearchResult = Context.API.FuzzySearch(query.Search, keyword) + let searchResult = keywordSearchResult.IsSearchPrecisionScoreMet() ? keywordSearchResult : Context.API.FuzzySearch(query.Search, plugin.Name) let score = searchResult.Score where (searchResult.IsSearchPrecisionScoreMet() || string.IsNullOrEmpty(query.Search)) // To list all available action keywords @@ -22,32 +22,51 @@ from keyword in PluginManager.NonGlobalPlugins.Keys select new Result { Title = keyword, - SubTitle = string.Format(context.API.GetTranslation("flowlauncher_plugin_pluginindicator_result_subtitle"), plugin.Name), + SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_result_subtitle"), plugin.Name), Score = score, IcoPath = plugin.IcoPath, AutoCompleteText = $"{keyword}{Plugin.Query.TermSeparator}", Action = c => { - context.API.ChangeQuery($"{keyword}{Plugin.Query.TermSeparator}"); + Context.API.ChangeQuery($"{keyword}{Plugin.Query.TermSeparator}"); return false; } }; return results.ToList(); } + private Dictionary GetNonGlobalPlugins() + { + var nonGlobalPlugins = new Dictionary(); + foreach (var plugin in Context.API.GetAllPlugins()) + { + foreach (var actionKeyword in plugin.Metadata.ActionKeywords) + { + // Skip global keywords + if (actionKeyword == Plugin.Query.GlobalPluginWildcardSign) continue; + + // Skip dulpicated keywords + if (nonGlobalPlugins.ContainsKey(actionKeyword)) continue; + + nonGlobalPlugins.Add(actionKeyword, plugin); + } + } + return nonGlobalPlugins; + } + public void Init(PluginInitContext context) { - this.context = context; + Context = context; } public string GetTranslatedPluginTitle() { - return context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_name"); + return Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_name"); } public string GetTranslatedPluginDescription() { - return context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_description"); + return Context.API.GetTranslation("flowlauncher_plugin_pluginindicator_plugin_description"); } } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index f03040d68b1..16b6bcab203 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -18,6 +18,8 @@ namespace Flow.Launcher.Plugin.Program { public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, IAsyncReloadable, IDisposable { + private static readonly string ClassName = nameof(Main); + private const string Win32CacheName = "Win32"; private const string UwpCacheName = "UWP"; @@ -30,8 +32,6 @@ public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, I internal static PluginInitContext Context { get; private set; } - private static readonly string ClassName = nameof(Main); - private static readonly List emptyResults = new(); private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 }; @@ -274,7 +274,7 @@ static void MoveFile(string sourcePath, string destinationPath) static void WatchProgramUpdate() { Win32.WatchProgramUpdate(_settings); - _ = UWPPackage.WatchPackageChange(); + _ = UWPPackage.WatchPackageChangeAsync(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs index bf100ed7ee3..cb33250e15e 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs @@ -8,7 +8,6 @@ using System.Windows.Media.Imaging; using Windows.ApplicationModel; using Windows.Management.Deployment; -using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.Program.Logger; using Flow.Launcher.Plugin.SharedModels; using System.Threading.Channels; @@ -290,9 +289,9 @@ private static IEnumerable CurrentUserPackages() } } - private static Channel PackageChangeChannel = Channel.CreateBounded(1); + private static readonly Channel PackageChangeChannel = Channel.CreateBounded(1); - public static async Task WatchPackageChange() + public static async Task WatchPackageChangeAsync() { if (Environment.OSVersion.Version.Major >= 10) { @@ -403,13 +402,13 @@ public Result Result(string query, IPublicAPI api) if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || Name.Equals(Description)) { title = Name; - matchResult = StringMatcher.FuzzySearch(query, Name); + matchResult = Main.Context.API.FuzzySearch(query, Name); } else { title = $"{Name}: {Description}"; - var nameMatch = StringMatcher.FuzzySearch(query, Name); - var descriptionMatch = StringMatcher.FuzzySearch(query, Description); + var nameMatch = Main.Context.API.FuzzySearch(query, Name); + var descriptionMatch = Main.Context.API.FuzzySearch(query, Description); if (descriptionMatch.Score > nameMatch.Score) { for (int i = 0; i < descriptionMatch.MatchData.Count; i++) @@ -477,7 +476,7 @@ public List ContextMenus(IPublicAPI api) { var contextMenus = new List { - new Result + new() { Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"), Action = _ => @@ -496,9 +495,9 @@ public List ContextMenus(IPublicAPI api) contextMenus.Add(new Result { Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"), - Action = _ => + Action = c => { - Task.Run(() => Launch(true)).ConfigureAwait(false); + _ = Task.Run(() => Launch(true)).ConfigureAwait(false); return true; }, IcoPath = "Images/cmd.png", @@ -539,7 +538,7 @@ internal string LogoPathFromUri(string uri, (int, int) desiredSize) { ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" + $"|{UserModelId} 's logo uri is null or empty: {Location}", - new ArgumentException("uri")); + new ArgumentException(null, nameof(uri))); return string.Empty; } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 06be2a628cf..a87b002d414 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -6,7 +6,6 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Win32; -using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.Program.Logger; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedModels; @@ -73,7 +72,7 @@ public string UniqueIdentifier private const string ExeExtension = "exe"; private string _uid = string.Empty; - private static readonly Win32 Default = new Win32() + private static readonly Win32 Default = new() { Name = string.Empty, Description = string.Empty, @@ -92,7 +91,7 @@ private static MatchResult Match(string query, IReadOnlyCollection candi if (candidates.Count == 0) return null; - var match = candidates.Select(candidate => StringMatcher.FuzzySearch(query, candidate)) + var match = candidates.Select(candidate => Main.Context.API.FuzzySearch(query, candidate)) .MaxBy(match => match.Score); return match?.IsSearchPrecisionScoreMet() ?? false ? match : null; @@ -112,14 +111,14 @@ public Result Result(string query, IPublicAPI api) resultName.Equals(Description)) { title = resultName; - matchResult = StringMatcher.FuzzySearch(query, resultName); + matchResult = Main.Context.API.FuzzySearch(query, resultName); } else { // Search in both title = $"{resultName}: {Description}"; - var nameMatch = StringMatcher.FuzzySearch(query, resultName); - var descriptionMatch = StringMatcher.FuzzySearch(query, Description); + var nameMatch = Main.Context.API.FuzzySearch(query, resultName); + var descriptionMatch = Main.Context.API.FuzzySearch(query, Description); if (descriptionMatch.Score > nameMatch.Score) { for (int i = 0; i < descriptionMatch.MatchData.Count; i++) @@ -219,27 +218,27 @@ public List ContextMenus(IPublicAPI api) { var contextMenus = new List { - new Result + new() { Title = api.GetTranslation("flowlauncher_plugin_program_run_as_different_user"), - Action = _ => + Action = c => { var info = new ProcessStartInfo { FileName = FullPath, WorkingDirectory = ParentDirectory, UseShellExecute = true }; - Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info)); + _ = Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info)); return true; }, IcoPath = "Images/user.png", Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe7ee"), }, - new Result + new() { Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"), - Action = _ => + Action = c => { var info = new ProcessStartInfo { @@ -249,14 +248,14 @@ public List ContextMenus(IPublicAPI api) UseShellExecute = true }; - Task.Run(() => Main.StartProcess(Process.Start, info)); + _ = Task.Run(() => Main.StartProcess(Process.Start, info)); return true; }, IcoPath = "Images/cmd.png", Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe7ef"), }, - new Result + new() { Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"), Action = _ => @@ -296,7 +295,7 @@ public override string ToString() return Name; } - private static List Watchers = new List(); + private static readonly List Watchers = new(); private static Win32 Win32Program(string path) { @@ -402,7 +401,7 @@ private static Win32 UrlProgram(string path, string[] protocols) var data = parser.ReadFile(path); var urlSection = data["InternetShortcut"]; var url = urlSection?["URL"]; - if (String.IsNullOrEmpty(url)) + if (string.IsNullOrEmpty(url)) { return program; } @@ -418,12 +417,12 @@ private static Win32 UrlProgram(string path, string[] protocols) } var iconPath = urlSection?["IconFile"]; - if (!String.IsNullOrEmpty(iconPath)) + if (!string.IsNullOrEmpty(iconPath)) { program.IcoPath = iconPath; } } - catch (Exception e) + catch (Exception) { // Many files do not have the required fields, so no logging is done. } @@ -474,7 +473,7 @@ private static string Extension(string path) var extension = Path.GetExtension(path)?.ToLowerInvariant(); if (!string.IsNullOrEmpty(extension)) { - return extension.Substring(1); // remove dot + return extension[1..]; // remove dot } else { @@ -785,7 +784,7 @@ public static void WatchProgramUpdate(Settings settings) _ = Task.Run(MonitorDirectoryChangeAsync); } - private static Channel indexQueue = Channel.CreateBounded(1); + private static readonly Channel indexQueue = Channel.CreateBounded(1); public static async Task MonitorDirectoryChangeAsync() { diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj index 8f443214bba..c7ea7cdd550 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -37,7 +37,6 @@ - diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index 53479b81fc3..0d395c0537a 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -7,20 +7,21 @@ using System.Threading.Tasks; using WindowsInput; using WindowsInput.Native; -using Flow.Launcher.Infrastructure.Hotkey; -using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.SharedCommands; using Control = System.Windows.Controls.Control; using Keys = System.Windows.Forms.Keys; namespace Flow.Launcher.Plugin.Shell { - public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu + public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, IDisposable { + private static readonly string ClassName = nameof(Main); + + internal PluginInitContext Context { get; private set; } + private const string Image = "Images/shell.png"; - private PluginInitContext context; private bool _winRStroked; - private readonly KeyboardSimulator _keyboardSimulator = new KeyboardSimulator(new InputSimulator()); + private readonly KeyboardSimulator _keyboardSimulator = new(new InputSimulator()); private Settings _settings; @@ -53,7 +54,7 @@ public List Query(Query query) { basedir = Path.GetDirectoryName(excmd); var dirName = Path.GetDirectoryName(cmd); - dir = (dirName.EndsWith("/") || dirName.EndsWith(@"\")) ? dirName : cmd.Substring(0, dirName.Length + 1); + dir = (dirName.EndsWith("/") || dirName.EndsWith(@"\")) ? dirName : cmd[..(dirName.Length + 1)]; } if (basedir != null) @@ -88,7 +89,7 @@ public List Query(Query query) } catch (Exception e) { - Log.Exception($"|Flow.Launcher.Plugin.Shell.Main.Query|Exception when query for <{query}>", e); + Context.API.LogException(ClassName, $"Exception when query for <{query}>", e); } return results; } @@ -102,14 +103,14 @@ private List GetHistoryCmds(string cmd, Result result) { if (m.Key == cmd) { - result.SubTitle = string.Format(context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value); + result.SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value); return null; } var ret = new Result { Title = m.Key, - SubTitle = string.Format(context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value), + SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value), IcoPath = Image, Action = c => { @@ -139,7 +140,7 @@ private Result GetCurrentCmd(string cmd) { Title = cmd, Score = 5000, - SubTitle = context.API.GetTranslation("flowlauncher_plugin_cmd_execute_through_shell"), + SubTitle = Context.API.GetTranslation("flowlauncher_plugin_cmd_execute_through_shell"), IcoPath = Image, Action = c => { @@ -164,7 +165,7 @@ private List ResultsFromHistory() .Select(m => new Result { Title = m.Key, - SubTitle = string.Format(context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value), + SubTitle = string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_cmd_has_been_executed_times"), m.Value), IcoPath = Image, Action = c => { @@ -211,7 +212,7 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin info.FileName = "cmd.exe"; } - info.ArgumentList.Add($"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command} {(_settings.CloseShellAfterPress ? $"&& echo {context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")} && pause > nul /c" : "")}"); + info.ArgumentList.Add($"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command} {(_settings.CloseShellAfterPress ? $"&& echo {Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")} && pause > nul /c" : "")}"); break; } @@ -234,7 +235,7 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin else { info.ArgumentList.Add("-Command"); - info.ArgumentList.Add($"{command}\\; {(_settings.CloseShellAfterPress ? $"Write-Host '{context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'\\; [System.Console]::ReadKey()\\; exit" : "")}"); + info.ArgumentList.Add($"{command}\\; {(_settings.CloseShellAfterPress ? $"Write-Host '{Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'\\; [System.Console]::ReadKey()\\; exit" : "")}"); } break; } @@ -255,7 +256,7 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin info.ArgumentList.Add("-NoExit"); } info.ArgumentList.Add("-Command"); - info.ArgumentList.Add($"{command}\\; {(_settings.CloseShellAfterPress ? $"Write-Host '{context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'\\; [System.Console]::ReadKey()\\; exit" : "")}"); + info.ArgumentList.Add($"{command}\\; {(_settings.CloseShellAfterPress ? $"Write-Host '{Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'\\; [System.Console]::ReadKey()\\; exit" : "")}"); break; } @@ -309,17 +310,17 @@ private void Execute(Func startProcess, ProcessStartI { var name = "Plugin: Shell"; var message = $"Command not found: {e.Message}"; - context.API.ShowMsg(name, message); + Context.API.ShowMsg(name, message); } catch (Win32Exception e) { var name = "Plugin: Shell"; var message = $"Error running the command: {e.Message}"; - context.API.ShowMsg(name, message); + Context.API.ShowMsg(name, message); } } - private bool ExistInPath(string filename) + private static bool ExistInPath(string filename) { if (File.Exists(filename)) { @@ -350,14 +351,14 @@ private bool ExistInPath(string filename) public void Init(PluginInitContext context) { - this.context = context; + Context = context; _settings = context.API.LoadSettingJsonStorage(); context.API.RegisterGlobalKeyboardCallback(API_GlobalKeyboardEvent); } bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state) { - if (!context.CurrentPluginMetadata.Disabled && _settings.ReplaceWinR) + if (!Context.CurrentPluginMetadata.Disabled && _settings.ReplaceWinR) { if (keyevent == (int)KeyEvent.WM_KEYDOWN && vkcode == (int)Keys.R && state.WinPressed) { @@ -380,10 +381,9 @@ private void OnWinRPressed() // show the main window and set focus to the query box _ = Task.Run(() => { - context.API.ShowMainWindow(); - context.API.ChangeQuery($"{context.CurrentPluginMetadata.ActionKeywords[0]}{Plugin.Query.TermSeparator}"); + Context.API.ShowMainWindow(); + Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords[0]}{Plugin.Query.TermSeparator}"); }); - } public Control CreateSettingPanel() @@ -393,12 +393,12 @@ public Control CreateSettingPanel() public string GetTranslatedPluginTitle() { - return context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_name"); + return Context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_name"); } public string GetTranslatedPluginDescription() { - return context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_description"); + return Context.API.GetTranslation("flowlauncher_plugin_cmd_plugin_description"); } public List LoadContextMenus(Result selectedResult) @@ -407,8 +407,8 @@ public List LoadContextMenus(Result selectedResult) { new() { - Title = context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_different_user"), - AsyncAction = async c => + Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_different_user"), + Action = c => { Execute(ShellCommand.RunAsDifferentUser, PrepareProcessStartInfo(selectedResult.Title)); return true; @@ -418,7 +418,7 @@ public List LoadContextMenus(Result selectedResult) }, new() { - Title = context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_administrator"), + Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_run_as_administrator"), Action = c => { Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true)); @@ -429,10 +429,10 @@ public List LoadContextMenus(Result selectedResult) }, new() { - Title = context.API.GetTranslation("flowlauncher_plugin_cmd_copy"), + Title = Context.API.GetTranslation("flowlauncher_plugin_cmd_copy"), Action = c => { - context.API.CopyToClipboard(selectedResult.Title); + Context.API.CopyToClipboard(selectedResult.Title); return true; }, IcoPath = "Images/copy.png", @@ -442,5 +442,10 @@ public List LoadContextMenus(Result selectedResult) return results; } + + public void Dispose() + { + Context.API.RemoveGlobalKeyboardCallback(API_GlobalKeyboardEvent); + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index 266c2417008..dbc36ad424b 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -39,7 +39,6 @@ - diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 94a9d0348ff..043eb7a190a 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -6,7 +6,6 @@ using System.Runtime.InteropServices; using System.Windows; using Flow.Launcher.Infrastructure; -using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Windows.Win32; using Windows.Win32.Foundation; @@ -19,6 +18,8 @@ namespace Flow.Launcher.Plugin.Sys { public class Main : IPlugin, ISettingProvider, IPluginI18n { + private static readonly string ClassName = nameof(Main); + private readonly Dictionary KeywordTitleMappings = new() { {"Shutdown", "flowlauncher_plugin_sys_shutdown_computer_cmd"}, @@ -106,7 +107,7 @@ private string GetTitle(string key) { if (!KeywordTitleMappings.TryGetValue(key, out var translationKey)) { - Log.Error("Flow.Launcher.Plugin.Sys.Main", $"Title not found for: {key}"); + _context.API.LogError(ClassName, $"Title not found for: {key}"); return "Title Not Found"; } @@ -117,7 +118,7 @@ private string GetDescription(string key) { if (!KeywordDescriptionMappings.TryGetValue(key, out var translationKey)) { - Log.Error("Flow.Launcher.Plugin.Sys.Main", $"Description not found for: {key}"); + _context.API.LogError(ClassName, $"Description not found for: {key}"); return "Description Not Found"; } diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj index c2d0a46a094..73726ab37ab 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj @@ -51,7 +51,6 @@ -