diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index e9f199d00..d10a0313d 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -57,6 +57,7 @@ + diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index b91da7114..ae94afdab 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.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 1472813b8..027fac7eb 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -77,6 +77,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj index 0241a374e..98f25a5f4 100644 --- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj +++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj @@ -55,6 +55,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 1e305d3d9..0a792ef72 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -104,6 +104,7 @@ + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index 48acf6109..acf863bf0 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -3,11 +3,24 @@ using System.Collections.Generic; using System.IO; using System.Text.Json; using Flow.Launcher.Infrastructure.Logger; +using System; +using System.Data.SQLite; +using SkiaSharp; namespace Flow.Launcher.Plugin.BrowserBookmark; public abstract class ChromiumBookmarkLoader : IBookmarkLoader { + private readonly string _faviconCacheDir; + + protected ChromiumBookmarkLoader() + { + _faviconCacheDir = Path.Combine( + Path.GetDirectoryName(typeof(ChromiumBookmarkLoader).Assembly.Location), + "FaviconCache"); + Directory.CreateDirectory(_faviconCacheDir); + } + public abstract List GetBookmarks(); protected List LoadBookmarks(string browserDataPath, string name) @@ -22,10 +35,30 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader 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) + { + Log.Exception($"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; @@ -52,8 +85,7 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader 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 @@ -91,4 +123,108 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader } } } + + private void LoadFaviconsFromDb(string dbPath, List bookmarks) + { + try + { + // 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) + { + Log.Exception($"Failed to copy favicon DB: {dbPath}", ex); + return; + } + + try + { + using var connection = new SQLiteConnection($"Data Source={tempDbPath};Version=3;Read Only=True;"); + 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)) + { + var iconId = reader.GetInt64(0).ToString(); + var imageData = (byte[])reader["image_data"]; + + if (imageData != null && imageData.Length > 0) + { + var faviconPath = Path.Combine(_faviconCacheDir, $"{domain}_{iconId}.png"); + if (!File.Exists(faviconPath)) + { + SaveBitmapData(imageData, faviconPath); + } + bookmark.FaviconPath = faviconPath; + } + } + } + catch (Exception ex) + { + Log.Exception($"Failed to extract bookmark favicon: {bookmark.Url}", ex); + } + } + } + catch (Exception ex) + { + Log.Exception($"Failed to connect to SQLite: {tempDbPath}", ex); + } + + // Delete temporary file + try { File.Delete(tempDbPath); } catch { /* Ignore */ } + } + catch (Exception ex) + { + Log.Exception($"Failed to load favicon DB: {dbPath}", ex); + } + } + + private void SaveBitmapData(byte[] imageData, string outputPath) + { + try + { + using var ms = new MemoryStream(imageData); + using var bitmap = SKBitmap.Decode(ms); + if (bitmap != null) + { + using var image = SKImage.FromBitmap(bitmap); + using var data = image.Encode(SKEncodedImageFormat.Png, 100); + using var fs = File.OpenWrite(outputPath); + data.SaveTo(fs); + } + } + catch (Exception ex) + { + Log.Exception($"Failed to save image: {outputPath}", ex); + } + } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 35ad32fb3..84c343707 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -4,13 +4,26 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Flow.Launcher.Infrastructure.Logger; +using SkiaSharp; namespace Flow.Launcher.Plugin.BrowserBookmark; public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader { + private readonly string _faviconCacheDir; + + protected FirefoxBookmarkLoaderBase() + { + _faviconCacheDir = Path.Combine( + Path.GetDirectoryName(typeof(FirefoxBookmarkLoaderBase).Assembly.Location), + "FaviconCache"); + Directory.CreateDirectory(_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,36 +33,161 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader 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(); + return bookmarks; - Main.RegisterBookmarkFile(placesPath); + try + { + // Try to register file monitoring + try + { + Main.RegisterBookmarkFile(placesPath); + } + catch (Exception ex) + { + Log.Exception($"Failed to register Firefox bookmark file monitoring: {placesPath}", ex); + } - // 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(); + // Use a copy to avoid lock issues with the original file + var tempDbPath = Path.Combine(_faviconCacheDir, $"tempplaces_{Guid.NewGuid()}.sqlite"); + File.Copy(placesPath, tempDbPath, true); - // return results in List format - return reader - .Select( - x => new Bookmark( - x["title"] is DBNull ? string.Empty : x["title"].ToString(), - x["url"].ToString() + // 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); + } + + // Delete temporary file + try { File.Delete(tempDbPath); } catch { /* Ignore */ } + } + catch (Exception ex) + { + Log.Exception($"Failed to load Firefox bookmarks: {placesPath}", ex); + } + + return bookmarks; + } + + private void LoadFaviconsFromDb(string faviconDbPath, List bookmarks) + { + try + { + // Use a copy to avoid lock issues with the original file + var tempDbPath = Path.Combine(_faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.sqlite"); + File.Copy(faviconDbPath, tempDbPath, true); + + 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)) + { + var imageData = (byte[])reader["data"]; + + if (imageData != null && imageData.Length > 0) + { + var faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.png"); + + if (!File.Exists(faviconPath)) + { + SaveBitmapData(imageData, faviconPath); + } + bookmark.FaviconPath = faviconPath; + } + } + } + catch (Exception ex) + { + Log.Exception($"Failed to extract Firefox favicon: {bookmark.Url}", ex); + } + } + + // Delete temporary file + try { File.Delete(tempDbPath); } catch { /* Ignore */ } + } + catch (Exception ex) + { + Log.Exception($"Failed to load Firefox favicon DB: {faviconDbPath}", ex); + } + } + + private void SaveBitmapData(byte[] imageData, string outputPath) + { + try + { + using var ms = new MemoryStream(imageData); + using var bitmap = SKBitmap.Decode(ms); + if (bitmap != null) + { + using var image = SKImage.FromBitmap(bitmap); + using var data = image.Encode(SKEncodedImageFormat.Png, 100); + using var fs = File.OpenWrite(outputPath); + data.SaveTo(fs); + } + } + catch (Exception ex) + { + Log.Exception($"Failed to save image: {outputPath}", ex); + } } } - public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase { /// @@ -77,33 +215,6 @@ public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase 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 b4e42fbcd..4ac1e68b6 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -96,6 +96,9 @@ + + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index a48d70f2d..9d9b9e505 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -68,7 +68,9 @@ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContex { 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 +92,9 @@ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContex { 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 = _ => { diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs index c738da389..caab16b65 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs @@ -18,4 +18,5 @@ public record Bookmark(string Name, string Url, string Source = "") } public List CustomBrowsers { get; set; } = new(); + public string FaviconPath { get; set; } = string.Empty; } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj index 1b985acf9..0bac1040e 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj @@ -63,6 +63,7 @@ + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index 549217027..5b326b524 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -46,6 +46,7 @@ + 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 21d964c11..bb23eeed8 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj @@ -56,5 +56,9 @@ PreserveNewest + + + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index b438305d6..f3f5f268b 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -37,4 +37,8 @@ PreserveNewest + + + + diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index 4e216b7b2..628e349c4 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -55,6 +55,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index 99c1a12e9..ff538828a 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -69,6 +69,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file 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 8f443214b..deef21944 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -60,6 +60,7 @@ + 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 266c24170..f59d596c8 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -64,5 +64,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj index 6d338733e..ec0d3c1ca 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj +++ b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj @@ -56,4 +56,8 @@ + + + + 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 55d69d526..5500096cd 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj @@ -56,4 +56,8 @@ + + + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj index 73fcd9f83..512665753 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj @@ -68,5 +68,8 @@ + + +