From 73056534bb413516740982dca7327234edc8436b Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sat, 8 Jan 2022 15:50:27 -0600 Subject: [PATCH 01/28] Reindex when app install or deleted (and uwp update). --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 16 +- .../Programs/UWP.cs | 151 +++++++++++++----- .../Programs/Win32.cs | 83 +++++++++- 3 files changed, 198 insertions(+), 52 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 634382109..cb0a2a4f3 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -18,7 +18,7 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Plugin.Program { - public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable + public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable, IDisposable { internal static Win32[] _win32s { get; set; } internal static UWP.Application[] _uwps { get; set; } @@ -126,6 +126,9 @@ namespace Flow.Launcher.Plugin.Program if (!(_win32s.Any() && _uwps.Any())) await indexTask; + + Win32.WatchProgramUpdate(_settings); + UWP.WatchUWPInstallation(); } public static void IndexWin32Programs() @@ -209,13 +212,11 @@ namespace Flow.Launcher.Plugin.Program return; if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) - .FirstOrDefault() + _uwps.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)! .Enabled = false; if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) - .FirstOrDefault() + _win32s.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)! .Enabled = false; _settings.DisabledProgramSources @@ -248,5 +249,10 @@ namespace Flow.Launcher.Plugin.Program { await IndexPrograms(); } + public void Dispose() + { + Win32.Dispose(); + UWP.Dispose(); + } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index 2a04b2b2e..b3d39bdbc 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -78,7 +78,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { var e = Marshal.GetExceptionForHR((int)hResult); ProgramLogger.LogException($"|UWP|InitializeAppInfo|{path}" + - "|Error caused while trying to get the details of the UWP program", e); + "|Error caused while trying to get the details of the UWP program", e); Apps = new List().ToArray(); } @@ -91,28 +91,29 @@ namespace Flow.Launcher.Plugin.Program.Programs + /// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx private string[] XmlNamespaces(string path) { XDocument z = XDocument.Load(path); if (z.Root != null) { - var namespaces = z.Root.Attributes(). - Where(a => a.IsNamespaceDeclaration). - GroupBy( - a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName, - a => XNamespace.Get(a.Value) - ).Select( - g => g.First().ToString() - ).ToArray(); + var namespaces = z.Root.Attributes().Where(a => a.IsNamespaceDeclaration).GroupBy( + a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName, + a => XNamespace.Get(a.Value) + ).Select( + g => g.First().ToString() + ).ToArray(); return namespaces; } else { ProgramLogger.LogException($"|UWP|XmlNamespaces|{path}" + - $"|Error occured while trying to get the XML from {path}", new ArgumentNullException()); + $"|Error occured while trying to get the XML from {path}", new ArgumentNullException()); - return new string[] { }; + return new string[] + { + }; } } @@ -120,9 +121,15 @@ namespace Flow.Launcher.Plugin.Program.Programs { var versionFromNamespace = new Dictionary { - {"http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10}, - {"http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81}, - {"http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8}, + { + "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10 + }, + { + "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81 + }, + { + "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8 + }, }; foreach (var n in versionFromNamespace.Keys) @@ -135,8 +142,8 @@ namespace Flow.Launcher.Plugin.Program.Programs } ProgramLogger.LogException($"|UWP|XmlNamespaces|{Location}" + - "|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version " - + $"{FullName} from location {Location} is returned.", new FormatException()); + "|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version " + + $"{FullName} from location {Location} is returned.", new FormatException()); Version = PackageVersion.Unknown; } @@ -172,15 +179,17 @@ namespace Flow.Launcher.Plugin.Program.Programs }).ToArray(); var updatedListWithoutDisabledApps = applications - .Where(t1 => !Main._settings.DisabledProgramSources - .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) - .Select(x => x); + .Where(t1 => !Main._settings.DisabledProgramSources + .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) + .Select(x => x); return updatedListWithoutDisabledApps.ToArray(); } else { - return new Application[] { }; + return new Application[] + { + }; } } @@ -216,7 +225,7 @@ namespace Flow.Launcher.Plugin.Program.Programs catch (Exception e) { ProgramLogger.LogException("UWP", "CurrentUserPackages", $"id", "An unexpected error occured and " - + $"unable to verify if package is valid", e); + + $"unable to verify if package is valid", e); return false; } @@ -226,10 +235,30 @@ namespace Flow.Launcher.Plugin.Program.Programs } else { - return new Package[] { }; + return new Package[] + { + }; } } + private static List _watchers = new(); + + private static void GenerateWatcher(string path) + { + var watcher = new FileSystemWatcher(path); + watcher.Created += static (_, _) => Task.Run(Main.IndexUwpPrograms); + watcher.Deleted += static (_, _) => Task.Run(Main.IndexUwpPrograms); + watcher.EnableRaisingEvents = true; + } + + public static void WatchUWPInstallation() + { + PackageCatalog.OpenForCurrentUser().PackageStatusChanged += (_, _) => + { + Task.Delay(10000).ContinueWith(t => Main.IndexUwpPrograms()); + }; + } + public override string ToString() { return FamilyName; @@ -326,7 +355,7 @@ namespace Flow.Launcher.Plugin.Program.Programs e.SpecialKeyState.ShiftPressed && !e.SpecialKeyState.AltPressed && !e.SpecialKeyState.WinPressed - ); + ); if (elevated && CanRunElevated) { @@ -359,14 +388,12 @@ namespace Flow.Launcher.Plugin.Program.Programs new Result { Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"), - Action = _ => { Main.Context.API.OpenDirectory(Package.Location); return true; }, - IcoPath = "Images/folder.png" } }; @@ -415,8 +442,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var info = new ProcessStartInfo(command) { - UseShellExecute = true, - Verb = "runas", + UseShellExecute = true, Verb = "runas", }; Main.StartProcess(Process.Start, info); @@ -493,7 +519,7 @@ namespace Flow.Launcher.Plugin.Program.Programs else { ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Can't load null or empty result " - + $"pri {source} in uwp location {Package.Location}", new NullReferenceException()); + + $"pri {source} in uwp location {Package.Location}", new NullReferenceException()); return string.Empty; } } @@ -533,9 +559,15 @@ namespace Flow.Launcher.Plugin.Program.Programs { var logoKeyFromVersion = new Dictionary { - { PackageVersion.Windows10, "Square44x44Logo" }, - { PackageVersion.Windows81, "Square30x30Logo" }, - { PackageVersion.Windows8, "SmallLogo" }, + { + PackageVersion.Windows10, "Square44x44Logo" + }, + { + PackageVersion.Windows81, "Square30x30Logo" + }, + { + PackageVersion.Windows8, "SmallLogo" + }, }; if (logoKeyFromVersion.ContainsKey(Package.Version)) { @@ -572,14 +604,40 @@ namespace Flow.Launcher.Plugin.Program.Programs { var end = path.Length - extension.Length; var prefix = path.Substring(0, end); - var paths = new List { path }; + var paths = new List + { + path + }; var scaleFactors = new Dictionary> { // scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables, - { PackageVersion.Windows10, new List { 100, 125, 150, 200, 400 } }, - { PackageVersion.Windows81, new List { 100, 120, 140, 160, 180 } }, - { PackageVersion.Windows8, new List { 100 } } + { + PackageVersion.Windows10, new List + { + 100, + 125, + 150, + 200, + 400 + } + }, + { + PackageVersion.Windows81, new List + { + 100, + 120, + 140, + 160, + 180 + } + }, + { + PackageVersion.Windows8, new List + { + 100 + } + } }; if (scaleFactors.ContainsKey(Package.Version)) @@ -598,15 +656,15 @@ namespace Flow.Launcher.Plugin.Program.Programs else { ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + - $"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException()); + $"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException()); return string.Empty; } } else { ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + - $"|Unable to find extension from {uri} for {UserModelId} " + - $"in package location {Package.Location}", new FileNotFoundException()); + $"|Unable to find extension from {uri} for {UserModelId} " + + $"in package location {Package.Location}", new FileNotFoundException()); return string.Empty; } } @@ -633,8 +691,8 @@ namespace Flow.Launcher.Plugin.Program.Programs else { ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Avaliable" : path)}" + - $"|Unable to get logo for {UserModelId} from {path} and" + - $" located in {Package.Location}", new FileNotFoundException()); + $"|Unable to get logo for {UserModelId} from {path} and" + + $" located in {Package.Location}", new FileNotFoundException()); return new BitmapImage(new Uri(Constant.MissingImgIcon)); } } @@ -682,8 +740,8 @@ namespace Flow.Launcher.Plugin.Program.Programs else { ProgramLogger.LogException($"|UWP|PlatedImage|{Package.Location}" + - $"|Unable to convert background string {BackgroundColor} " + - $"to color for {Package.Location}", new InvalidOperationException()); + $"|Unable to convert background string {BackgroundColor} " + + $"to color for {Package.Location}", new InvalidOperationException()); return new BitmapImage(new Uri(Constant.MissingImgIcon)); } @@ -701,6 +759,14 @@ namespace Flow.Launcher.Plugin.Program.Programs } } + public static void Dispose() + { + foreach (var fileSystemWatcher in _watchers) + { + fileSystemWatcher.Dispose(); + } + } + public enum PackageVersion { Windows10, @@ -728,5 +794,6 @@ namespace Flow.Launcher.Plugin.Program.Programs [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] private static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved); + } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 1464bd785..3e568fa19 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -12,9 +12,11 @@ using Flow.Launcher.Plugin.Program.Logger; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Infrastructure.Logger; +using System.Collections; using System.Diagnostics; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; using System.Diagnostics.CodeAnalysis; +using System.Threading.Channels; namespace Flow.Launcher.Plugin.Program.Programs { @@ -109,7 +111,7 @@ namespace Flow.Launcher.Plugin.Program.Programs c.SpecialKeyState.ShiftPressed && !c.SpecialKeyState.AltPressed && !c.SpecialKeyState.WinPressed - ); + ); var info = new ProcessStartInfo { @@ -194,6 +196,9 @@ namespace Flow.Launcher.Plugin.Program.Programs return Name; } + public static List Watchers = new List(); + + private static Win32 Win32Program(string path) { try @@ -216,7 +221,10 @@ namespace Flow.Launcher.Plugin.Program.Programs ProgramLogger.LogException($"|Win32|Win32Program|{path}" + $"|Permission denied when trying to load the program from {path}", e); - return new Win32() { Valid = false, Enabled = false }; + return new Win32() + { + Valid = false, Enabled = false + }; } } @@ -294,7 +302,10 @@ namespace Flow.Launcher.Plugin.Program.Programs ProgramLogger.LogException($"|Win32|ExeProgram|{path}" + $"|Permission denied when trying to load the program from {path}", e); - return new Win32() { Valid = false, Enabled = false }; + return new Win32() + { + Valid = false, Enabled = false + }; } } @@ -305,8 +316,7 @@ namespace Flow.Launcher.Plugin.Program.Programs return Directory.EnumerateFiles(directory, "*", new EnumerationOptions { - IgnoreInaccessible = true, - RecurseSubdirectories = true + IgnoreInaccessible = true, RecurseSubdirectories = true }).Where(x => suffixes.Contains(Extension(x))); } @@ -545,5 +555,68 @@ namespace Flow.Launcher.Plugin.Program.Programs return UniqueIdentifier == other.UniqueIdentifier; } + + private static IEnumerable GetStartMenuPaths() + { + var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs); + var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms); + return new[] + { + directory1, directory2 + }; + } + + public static void WatchProgramUpdate(Settings settings) + { + var paths = new List(); + if (settings.EnableStartMenuSource) + paths.AddRange(GetStartMenuPaths()); + + paths.AddRange(from source in settings.ProgramSources where source.Enabled select source.Location); + + foreach (var directory in from path in paths where Directory.Exists(path) select path) + { + WatchDirectory(directory); + } + + _ = Task.Run(MonitorDirectoryChangeAsync); + } + + private static Channel indexQueue = Channel.CreateBounded(1); + + public static async Task MonitorDirectoryChangeAsync() + { + var reader = indexQueue.Reader; + while (await reader.WaitToReadAsync()) + { + await Task.Delay(500); + while (reader.TryRead(out _)) + { + } + Main.IndexWin32Programs(); + } + } + + public static void WatchDirectory(string directory) + { + if (!Directory.Exists(directory)) + { + throw new ArgumentException("Path Not Exist"); + } + var watcher = new FileSystemWatcher(directory); + + watcher.Created += static (_, _) => indexQueue.Writer.TryWrite(default); + watcher.Deleted += static (_, _) => indexQueue.Writer.TryWrite(default); + watcher.EnableRaisingEvents = true; + watcher.IncludeSubdirectories = true; + } + + public static void Dispose() + { + foreach (var fileSystemWatcher in Watchers) + { + fileSystemWatcher.Dispose(); + } + } } } \ No newline at end of file From 9fc0e03b3dd010bf324009f42646438fb9149978 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sat, 8 Jan 2022 16:03:53 -0600 Subject: [PATCH 02/28] detect windows version --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 2 +- Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index cb0a2a4f3..3f89d63d3 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -128,7 +128,7 @@ namespace Flow.Launcher.Plugin.Program await indexTask; Win32.WatchProgramUpdate(_settings); - UWP.WatchUWPInstallation(); + UWP.WatchPackageChange(); } public static void IndexWin32Programs() diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index b3d39bdbc..86f5f38c4 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -18,6 +18,7 @@ using Flow.Launcher.Plugin.Program.Logger; using Rect = System.Windows.Rect; using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Infrastructure.Logger; +using System.Runtime.Versioning; namespace Flow.Launcher.Plugin.Program.Programs { @@ -251,12 +252,13 @@ namespace Flow.Launcher.Plugin.Program.Programs watcher.EnableRaisingEvents = true; } - public static void WatchUWPInstallation() + public static void WatchPackageChange() { - PackageCatalog.OpenForCurrentUser().PackageStatusChanged += (_, _) => - { - Task.Delay(10000).ContinueWith(t => Main.IndexUwpPrograms()); - }; + if (Environment.OSVersion.Version.Build >= 19041) + PackageCatalog.OpenForCurrentUser().PackageStatusChanged += (_, _) => + { + Task.Delay(10000).ContinueWith(t => Main.IndexUwpPrograms()); + }; } public override string ToString() From 50144ea8f45133d40d3eacb0fd4baf9f19309867 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sat, 8 Jan 2022 16:31:28 -0600 Subject: [PATCH 03/28] Remove firefox bookmark with no title --- .../FirefoxBookmarkLoader.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 3df034781..4d5135b30 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -9,10 +9,10 @@ namespace Flow.Launcher.Plugin.BrowserBookmark { public class FirefoxBookmarkLoader : IBookmarkLoader { - private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title + private const string QueryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title FROM moz_places INNER JOIN moz_bookmarks ON ( - moz_bookmarks.fk NOT NULL AND moz_bookmarks.fk = moz_places.id + moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id ) ORDER BY moz_places.visit_count DESC "; @@ -32,18 +32,16 @@ namespace Flow.Launcher.Plugin.BrowserBookmark // 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(); + 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 - bookmarkList = reader.Select( - x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(), - x["url"].ToString()) - ).ToList(); - } + // return results in List format + bookmarkList = reader.Select( + x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(), + x["url"].ToString()) + ).ToList(); return bookmarkList; } From af68cb5093b38c15b39e69cc1cdeebaa4173cd04 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sat, 8 Jan 2022 16:58:12 -0600 Subject: [PATCH 04/28] detect browserbookmark file change --- .../ChromiumBookmarkLoader.cs | 5 ++ .../Commands/BookmarkLoader.cs | 5 ++ .../FirefoxBookmarkLoader.cs | 2 + .../Main.cs | 88 +++++++++++++++++-- 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index d7b412392..c32f60af3 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -20,6 +20,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark var bookmarkPath = Path.Combine(profile, "Bookmarks"); if (!File.Exists(bookmarkPath)) continue; + + Main.RegisterBookmarkFile(bookmarkPath); var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source)); @@ -31,6 +33,9 @@ namespace Flow.Launcher.Plugin.BrowserBookmark { if (!File.Exists(path)) return new(); + + Main.RegisterBookmarkFile(path); + var bookmarks = new List(); using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs index 40d10b26f..491c5c915 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs @@ -3,6 +3,10 @@ using System.Linq; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.SharedModels; +using Microsoft.AspNetCore.Authentication; +using System.IO; +using System.Threading.Channels; +using System.Threading.Tasks; namespace Flow.Launcher.Plugin.BrowserBookmark.Commands { @@ -17,6 +21,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark.Commands return StringMatcher.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 4d5135b30..9d29b9c41 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -29,6 +29,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark return new List(); var bookmarkList = new List(); + + Main.RegisterBookmarkFile(PlacesPath); // create the connection string and init the connection string dbPath = string.Format(dbPathFormat, PlacesPath); diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index 1d58f84d8..f9127cd3c 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -9,24 +9,29 @@ using Flow.Launcher.Plugin.BrowserBookmark.Commands; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.BrowserBookmark.Views; using Flow.Launcher.Plugin.SharedCommands; +using System.IO; +using System.Threading.Channels; +using System.Threading.Tasks; namespace Flow.Launcher.Plugin.BrowserBookmark { - public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu + public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable { private PluginInitContext context; private List cachedBookmarks = new List(); - private Settings _settings { get; set;} + private Settings _settings { get; set; } public void Init(PluginInitContext context) { this.context = context; - + _settings = context.API.LoadSettingJsonStorage(); cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings); + + _ = MonitorRefreshQueue(); } public List Query(Query query) @@ -52,7 +57,10 @@ namespace Flow.Launcher.Plugin.BrowserBookmark return true; }, - ContextData = new BookmarkAttributes { Url = c.Url } + ContextData = new BookmarkAttributes + { + Url = c.Url + } }).Where(r => r.Score > 0); return returnList.ToList(); } @@ -69,11 +77,64 @@ namespace Flow.Launcher.Plugin.BrowserBookmark context.API.OpenUrl(c.Url); return true; }, - ContextData = new BookmarkAttributes { Url = c.Url } + ContextData = new BookmarkAttributes + { + Url = c.Url + } }).ToList(); } } + + private static Channel refreshQueue = Channel.CreateBounded(1); + + private async Task MonitorRefreshQueue() + { + var reader = refreshQueue.Reader; + while (await reader.WaitToReadAsync()) + { + await Task.Delay(2000); + if (reader.TryRead(out _)) + { + ReloadData(); + } + } + } + + private static readonly List Watchers = new(); + + internal static void RegisterBookmarkFile(string path) + { + var directory = Path.GetDirectoryName(path); + if (!Directory.Exists(directory)) + return; + var watcher = new FileSystemWatcher(directory!); + if (File.Exists(path)) + { + var fileName = Path.GetFileName(path); + watcher.Filter = fileName; + } + + watcher.NotifyFilter = NotifyFilters.FileName | + NotifyFilters.LastAccess | + NotifyFilters.LastWrite | + NotifyFilters.Size; + + watcher.Changed += static (_, _) => + { + refreshQueue.Writer.TryWrite(default); + }; + + watcher.Renamed += static (_, _) => + { + refreshQueue.Writer.TryWrite(default); + }; + + watcher.EnableRaisingEvents = true; + + Watchers.Add(watcher); + } + public void ReloadData() { cachedBookmarks.Clear(); @@ -98,7 +159,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark public List LoadContextMenus(Result selectedResult) { - return new List() { + return new List() + { new Result { Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"), @@ -114,7 +176,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark catch (Exception e) { var message = "Failed to set url in clipboard"; - Log.Exception("Main",message, e, "LoadContextMenus"); + Log.Exception("Main", message, e, "LoadContextMenus"); context.API.ShowMsg(message); @@ -122,12 +184,20 @@ namespace Flow.Launcher.Plugin.BrowserBookmark } }, IcoPath = "Images\\copylink.png" - }}; + } + }; } internal class BookmarkAttributes { internal string Url { get; set; } } + public void Dispose() + { + foreach (var watcher in Watchers) + { + watcher.Dispose(); + } + } } -} +} \ No newline at end of file From f42fd29224623a187476a8e59c42e71602ea16f7 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 27 Jan 2022 20:14:42 +1100 Subject: [PATCH 05/28] formatting --- .../Commands/BookmarkLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs index 491c5c915..284255498 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs @@ -21,7 +21,6 @@ namespace Flow.Launcher.Plugin.BrowserBookmark.Commands return StringMatcher.FuzzySearch(queryString, bookmark.Url); } - internal static List LoadAllBookmarks(Settings setting) { From 0842091f266664854f2f7ce5c84b286307873de6 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 27 Jan 2022 20:22:01 +1100 Subject: [PATCH 06/28] formatting --- Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index 86f5f38c4..4094d8589 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -90,9 +90,6 @@ namespace Flow.Launcher.Plugin.Program.Programs } } - - - /// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx private string[] XmlNamespaces(string path) { From 25cc50b9ac29f475ba56cb5205d518845b18432d Mon Sep 17 00:00:00 2001 From: Jeremy Date: Thu, 27 Jan 2022 20:52:47 +1100 Subject: [PATCH 07/28] version bump Program & BrowserBookmark plugins --- Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json | 2 +- Plugins/Flow.Launcher.Plugin.Program/plugin.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json index afc4f9855..b93630c31 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser Bookmarks", "Description": "Search your browser bookmarks", "Author": "qianlifeng, Ioannis G.", - "Version": "1.6.3", + "Version": "1.7.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json index 993b2adba..a297cb410 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json @@ -4,7 +4,7 @@ "Name": "Program", "Description": "Search programs in Flow.Launcher", "Author": "qianlifeng", - "Version": "1.8.2", + "Version": "1.9.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", From 18acea332159e95859868badd555c3fcac430166 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 7 Feb 2022 08:47:28 +1100 Subject: [PATCH 08/28] remove dup RegisterBookmarkFile & update condition --- .../ChromiumBookmarkLoader.cs | 2 -- .../Commands/BookmarkLoader.cs | 4 ---- .../FirefoxBookmarkLoader.cs | 4 ++-- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 4 ++-- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index c32f60af3..a49f8cb94 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -34,8 +34,6 @@ namespace Flow.Launcher.Plugin.BrowserBookmark if (!File.Exists(path)) return new(); - Main.RegisterBookmarkFile(path); - var bookmarks = new List(); using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs index 284255498..40d10b26f 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs @@ -3,10 +3,6 @@ using System.Linq; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.SharedModels; -using Microsoft.AspNetCore.Authentication; -using System.IO; -using System.Threading.Channels; -using System.Threading.Tasks; namespace Flow.Launcher.Plugin.BrowserBookmark.Commands { diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 9d29b9c41..892e18ddd 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -9,7 +9,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark { public class FirefoxBookmarkLoader : IBookmarkLoader { - private const string QueryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title + private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title FROM moz_places INNER JOIN moz_bookmarks ON ( moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id @@ -37,7 +37,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark 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(); + var reader = new SQLiteCommand(queryAllBookmarks, dbConnection).ExecuteReader(); // return results in List format bookmarkList = reader.Select( diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 3f89d63d3..cf5d68a18 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -212,11 +212,11 @@ namespace Flow.Launcher.Plugin.Program return; if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _uwps.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)! + _uwps.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) .Enabled = false; if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _win32s.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)! + _win32s.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) .Enabled = false; _settings.DisabledProgramSources From 823fa38dcdcc273390162eb051c559e06918dc78 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sun, 6 Feb 2022 17:27:12 -0600 Subject: [PATCH 09/28] Remove unused method --- Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index 4094d8589..e4ce7ddb4 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -241,14 +241,6 @@ namespace Flow.Launcher.Plugin.Program.Programs private static List _watchers = new(); - private static void GenerateWatcher(string path) - { - var watcher = new FileSystemWatcher(path); - watcher.Created += static (_, _) => Task.Run(Main.IndexUwpPrograms); - watcher.Deleted += static (_, _) => Task.Run(Main.IndexUwpPrograms); - watcher.EnableRaisingEvents = true; - } - public static void WatchPackageChange() { if (Environment.OSVersion.Version.Build >= 19041) From 87e7ff5b5386c3183bee169f21f0c99d661b1e6c Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sun, 6 Feb 2022 17:34:50 -0600 Subject: [PATCH 10/28] Remove unneeded logic and add a background reindex at startup --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 75 ++++++++------------ 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index cf5d68a18..5975b989a 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -24,7 +24,6 @@ namespace Flow.Launcher.Plugin.Program internal static UWP.Application[] _uwps { get; set; } internal static Settings _settings { get; set; } - private static bool IsStartupIndexProgramsRequired => _settings.LastIndexTime.AddDays(3) < DateTime.Today; internal static PluginInitContext Context { get; private set; } @@ -51,29 +50,25 @@ namespace Flow.Launcher.Plugin.Program public async Task> QueryAsync(Query query, CancellationToken token) { - - if (IsStartupIndexProgramsRequired) - _ = IndexPrograms(); - var result = await cache.GetOrCreateAsync(query.Search, async entry => - { - var resultList = await Task.Run(() => - _win32s.Cast() - .Concat(_uwps) - .AsParallel() - .WithCancellation(token) - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, Context.API)) - .Where(r => r?.Score > 0) - .ToList()); + { + var resultList = await Task.Run(() => + _win32s.Cast() + .Concat(_uwps) + .AsParallel() + .WithCancellation(token) + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, Context.API)) + .Where(r => r?.Score > 0) + .ToList()); - resultList = resultList.Any() ? resultList : emptyResults; + resultList = resultList.Any() ? resultList : emptyResults; - entry.SetSize(resultList.Count); - entry.SetSlidingExpiration(TimeSpan.FromHours(8)); + entry.SetSize(resultList.Count); + entry.SetSlidingExpiration(TimeSpan.FromHours(8)); - return resultList; - }); + return resultList; + }); return result; } @@ -84,49 +79,35 @@ namespace Flow.Launcher.Plugin.Program _settings = context.API.LoadSettingJsonStorage(); - await Task.Yield(); - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () => { _win32Storage = new BinaryStorage("Win32"); - _win32s = _win32Storage.TryLoad(new Win32[] { }); + _win32s = _win32Storage.TryLoad(new Win32[] + { + }); _uwpStorage = new BinaryStorage("UWP"); - _uwps = _uwpStorage.TryLoad(new UWP.Application[] { }); + _uwps = _uwpStorage.TryLoad(new UWP.Application[] + { + }); }); Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>"); - - bool indexedWinApps = false; - bool indexedUWPApps = false; + bool cacheEmpty = !_win32s.Any() && !_uwps.Any(); var a = Task.Run(() => { - if (IsStartupIndexProgramsRequired || !_win32s.Any()) - { - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs); - indexedWinApps = true; - } + Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs); }); var b = Task.Run(() => { - if (IsStartupIndexProgramsRequired || !_uwps.Any()) - { - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms); - indexedUWPApps = true; - } + Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms); }); - var indexTask = Task.WhenAll(a, b).ContinueWith(t => - { - if (indexedWinApps && indexedUWPApps) - _settings.LastIndexTime = DateTime.Today; - }); + if (cacheEmpty) + await Task.WhenAll(a, b); - if (!(_win32s.Any() && _uwps.Any())) - await indexTask; - Win32.WatchProgramUpdate(_settings); UWP.WatchPackageChange(); } @@ -141,7 +122,9 @@ namespace Flow.Launcher.Plugin.Program { var windows10 = new Version(10, 0); var support = Environment.OSVersion.Version.Major >= windows10.Major; - var applications = support ? UWP.All() : new UWP.Application[] { }; + var applications = support ? UWP.All() : new UWP.Application[] + { + }; _uwps = applications; } From 80eb0e494320f4a73dc7bc37dc050024d9a40721 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Mon, 21 Feb 2022 15:54:46 -0600 Subject: [PATCH 11/28] Reset Cache and rewrite uwp detection logic --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 4 +- .../Programs/UWP.cs | 47 +++++++++++++------ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 5975b989a..e634f5fe7 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -116,6 +116,7 @@ namespace Flow.Launcher.Plugin.Program { var win32S = Win32.All(_settings); _win32s = win32S; + ResetCache(); } public static void IndexUwpPrograms() @@ -126,6 +127,7 @@ namespace Flow.Launcher.Plugin.Program { }; _uwps = applications; + ResetCache(); } public static async Task IndexPrograms() @@ -133,7 +135,6 @@ namespace Flow.Launcher.Plugin.Program var t1 = Task.Run(IndexWin32Programs); var t2 = Task.Run(IndexUwpPrograms); await Task.WhenAll(t1, t2).ConfigureAwait(false); - ResetCache(); _settings.LastIndexTime = DateTime.Today; } @@ -235,7 +236,6 @@ namespace Flow.Launcher.Plugin.Program public void Dispose() { Win32.Dispose(); - UWP.Dispose(); } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index e4ce7ddb4..c843815ac 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -19,6 +19,7 @@ using Rect = System.Windows.Rect; using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Infrastructure.Logger; using System.Runtime.Versioning; +using System.Threading.Channels; namespace Flow.Launcher.Plugin.Program.Programs { @@ -163,8 +164,10 @@ namespace Flow.Launcher.Plugin.Program.Programs catch (Exception e) { ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occured and " - + $"unable to convert Package to UWP for {p.Id.FullName}", e); - return new Application[] { }; + + $"unable to convert Package to UWP for {p.Id.FullName}", e); + return new Application[] + { + }; } #endif #if DEBUG //make developer aware and implement handling @@ -239,15 +242,37 @@ namespace Flow.Launcher.Plugin.Program.Programs } } - private static List _watchers = new(); + private static Channel PackageChangeChannel = Channel.CreateBounded(1); - public static void WatchPackageChange() + public static async Task WatchPackageChange() { - if (Environment.OSVersion.Version.Build >= 19041) - PackageCatalog.OpenForCurrentUser().PackageStatusChanged += (_, _) => + if (Environment.OSVersion.Version.Major >= 10) + { + var catalog = PackageCatalog.OpenForCurrentUser(); + catalog.PackageInstalling += (_, args) => { - Task.Delay(10000).ContinueWith(t => Main.IndexUwpPrograms()); + if (args.IsComplete) + PackageChangeChannel.Writer.TryWrite(default); }; + catalog.PackageUninstalling += (_, args) => + { + if (args.IsComplete) + PackageChangeChannel.Writer.TryWrite(default); + }; + catalog.PackageUpdating += (_, args) => + { + if (args.IsComplete) + PackageChangeChannel.Writer.TryWrite(default); + }; + + while (await PackageChangeChannel.Reader.WaitToReadAsync().ConfigureAwait(false)) + { + await Task.Delay(3000).ConfigureAwait(false); + PackageChangeChannel.Reader.TryRead(out _); + await Task.Run(Main.IndexUwpPrograms); + } + + } } public override string ToString() @@ -750,14 +775,6 @@ namespace Flow.Launcher.Plugin.Program.Programs } } - public static void Dispose() - { - foreach (var fileSystemWatcher in _watchers) - { - fileSystemWatcher.Dispose(); - } - } - public enum PackageVersion { Windows10, From ef4bb121f9f925a1d2d3b039e505686c136684c6 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Wed, 23 Feb 2022 18:19:34 -0600 Subject: [PATCH 12/28] Add FileSystemWatcher to Watcherlist --- Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 3e568fa19..c3b0900a7 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -609,6 +609,8 @@ namespace Flow.Launcher.Plugin.Program.Programs watcher.Deleted += static (_, _) => indexQueue.Writer.TryWrite(default); watcher.EnableRaisingEvents = true; watcher.IncludeSubdirectories = true; + + Watchers.Add(watcher); } public static void Dispose() From 5404303c5f62ced6b8e854d24c060d6a6af2bc8a Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Wed, 23 Feb 2022 18:21:13 -0600 Subject: [PATCH 13/28] Wrap IndexWin32Programs with Task.Run --- Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index c3b0900a7..2f3ea47d0 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -593,7 +593,7 @@ namespace Flow.Launcher.Plugin.Program.Programs while (reader.TryRead(out _)) { } - Main.IndexWin32Programs(); + await Task.Run(Main.IndexWin32Programs); } } From 8b543a2d4cfff439c6a5290e5904fb0d26372235 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sat, 13 Aug 2022 00:16:41 +1000 Subject: [PATCH 14/28] add icons to About tab --- Flow.Launcher/SettingWindow.xaml | 36 +++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index 06c9549e2..7c503340c 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -14,7 +14,7 @@ xmlns:vm="clr-namespace:Flow.Launcher.ViewModel" Title="{DynamicResource flowlauncher_settings}" Width="1000" - Height="650" + Height="700" MinWidth="900" MinHeight="600" d:DataContext="{d:DesignInstance vm:SettingWindowViewModel}" @@ -2487,6 +2487,26 @@ + + + + + + + + + + + + +  + + + + @@ -2513,19 +2533,7 @@ - - - - - - Date: Thu, 18 Aug 2022 09:34:53 -0400 Subject: [PATCH 15/28] Suppress JsonRPC Empty Response Exception and refactor code --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 248 +++++++++++---------- 1 file changed, 126 insertions(+), 122 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index ffba6ce61..8a7fac1b5 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -69,7 +69,7 @@ namespace Flow.Launcher.Core.Plugin private static readonly JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true, -#pragma warning disable SYSLIB0020 +#pragma warning disable SYSLIB0020 // IgnoreNullValues is obsolete, but the replacement JsonIgnoreCondition.WhenWritingNull still // deserializes null, instead of ignoring it and leaving the default (empty list). We can change the behaviour // to accept null and fallback to a default etc, or just keep IgnoreNullValues for now @@ -247,74 +247,75 @@ namespace Flow.Launcher.Core.Plugin protected async Task ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default) { - Process process = null; - using var exitTokenSource = new CancellationTokenSource(); - try + using var process = Process.Start(startInfo); + if (process == null) { - process = Process.Start(startInfo); - if (process == null) - { - Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process"); - return Stream.Null; - } - - - await using var source = process.StandardOutput.BaseStream; - - var buffer = BufferManager.GetStream(); - - token.Register(() => - { - // ReSharper disable once AccessToModifiedClosure - // Manually Check whether disposed - if (!exitTokenSource.IsCancellationRequested && !process.HasExited) - process.Kill(); - }); + Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process"); + return Stream.Null; + } + token.Register(() => + { try { - // token expire won't instantly trigger the exception, - // manually kill process at before - await source.CopyToAsync(buffer, token); + if (!process.HasExited) + process.Kill(); } - catch (OperationCanceledException) + catch (InvalidOperationException) { - await buffer.DisposeAsync(); - return Stream.Null; } + }); - buffer.Seek(0, SeekOrigin.Begin); - token.ThrowIfCancellationRequested(); + var sourceBuffer = BufferManager.GetStream(); + var errorBuffer = BufferManager.GetStream(); - if (buffer.Length == 0) - { - var errorMessage = process.StandardError.EndOfStream ? - "Empty JSONRPC Response" : - await process.StandardError.ReadToEndAsync(); - throw new InvalidDataException($"{context.CurrentPluginMetadata.Name}|{errorMessage}"); - } + var sourceCopyTask = process.StandardOutput.BaseStream.CopyToAsync(sourceBuffer, token); + var errorCopyTask = process.StandardError.BaseStream.CopyToAsync(errorBuffer, token); - if (!process.StandardError.EndOfStream) - { - using var standardError = process.StandardError; - var error = await standardError.ReadToEndAsync(); - if (!string.IsNullOrEmpty(error)) - { - Log.Error($"|{context.CurrentPluginMetadata.Name}.{nameof(ExecuteAsync)}|{error}"); - } - } - - return buffer; - } - finally + try { - exitTokenSource.Cancel(); - process?.Dispose(); + // token expire won't instantly trigger the exception, + // manually kill process at before + await process.WaitForExitAsync(token); + await Task.WhenAll(sourceCopyTask, errorCopyTask); } + catch (OperationCanceledException) + { + await sourceBuffer.DisposeAsync(); + return Stream.Null; + } + + sourceBuffer.Seek(0, SeekOrigin.Begin); + + token.ThrowIfCancellationRequested(); + + if (sourceBuffer.Length == 0) + { + var errorMessage = errorBuffer.Length == 0 ? + "Empty JSONRPC Response" : + await process.StandardError.ReadToEndAsync(); + + Log.Error($"{context.CurrentPluginMetadata.Name}|{errorMessage}"); + } + + if (errorBuffer.Length != 0) + { + using var error = new StreamReader(errorBuffer); + + var errorMessage = await error.ReadToEndAsync(); + + if (!string.IsNullOrEmpty(errorMessage)) + { + Log.Error($"|{context.CurrentPluginMetadata.Name}.{nameof(ExecuteAsync)}|{errorMessage}"); + } + } + + return sourceBuffer; } + public async Task> QueryAsync(Query query, CancellationToken token) { var request = new JsonRPCRequestModel @@ -366,6 +367,7 @@ namespace Flow.Launcher.Core.Plugin private static readonly Thickness settingPanelMargin = new(15, 20, 15, 20); private static readonly Thickness settingTextBlockMargin = new(10, 4, 10, 4); private JsonRpcConfigurationModel _settingsTemplate; + public Control CreateSettingPanel() { if (Settings == null) @@ -397,84 +399,84 @@ namespace Flow.Launcher.Core.Plugin switch (type) { case "textBlock": + { + contentControl = new TextBlock { - contentControl = new TextBlock - { - Text = attribute.Description.Replace("\\r\\n", "\r\n"), - Margin = settingTextBlockMargin, - MaxWidth = 500, - TextWrapping = TextWrapping.WrapWithOverflow - }; - break; - } + Text = attribute.Description.Replace("\\r\\n", "\r\n"), + Margin = settingTextBlockMargin, + MaxWidth = 500, + TextWrapping = TextWrapping.WrapWithOverflow + }; + break; + } case "input": + { + var textBox = new TextBox() { - var textBox = new TextBox() - { - Width = 300, - Text = Settings[attribute.Name] as string ?? string.Empty, - Margin = settingControlMargin, - ToolTip = attribute.Description - }; - textBox.TextChanged += (_, _) => - { - Settings[attribute.Name] = textBox.Text; - }; - contentControl = textBox; - break; - } + Width = 300, + Text = Settings[attribute.Name] as string ?? string.Empty, + Margin = settingControlMargin, + ToolTip = attribute.Description + }; + textBox.TextChanged += (_, _) => + { + Settings[attribute.Name] = textBox.Text; + }; + contentControl = textBox; + break; + } case "textarea": + { + var textBox = new TextBox() { - var textBox = new TextBox() - { - Width = 300, - Height = 120, - Margin = settingControlMargin, - TextWrapping = TextWrapping.WrapWithOverflow, - AcceptsReturn = true, - Text = Settings[attribute.Name] as string ?? string.Empty, - ToolTip = attribute.Description - }; - textBox.TextChanged += (sender, _) => - { - Settings[attribute.Name] = ((TextBox)sender).Text; - }; - contentControl = textBox; - break; - } + Width = 300, + Height = 120, + Margin = settingControlMargin, + TextWrapping = TextWrapping.WrapWithOverflow, + AcceptsReturn = true, + Text = Settings[attribute.Name] as string ?? string.Empty, + ToolTip = attribute.Description + }; + textBox.TextChanged += (sender, _) => + { + Settings[attribute.Name] = ((TextBox)sender).Text; + }; + contentControl = textBox; + break; + } case "passwordBox": + { + var passwordBox = new PasswordBox() { - var passwordBox = new PasswordBox() - { - Width = 300, - Margin = settingControlMargin, - Password = Settings[attribute.Name] as string ?? string.Empty, - PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar, - ToolTip = attribute.Description - }; - passwordBox.PasswordChanged += (sender, _) => - { - Settings[attribute.Name] = ((PasswordBox)sender).Password; - }; - contentControl = passwordBox; - break; - } + Width = 300, + Margin = settingControlMargin, + Password = Settings[attribute.Name] as string ?? string.Empty, + PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar, + ToolTip = attribute.Description + }; + passwordBox.PasswordChanged += (sender, _) => + { + Settings[attribute.Name] = ((PasswordBox)sender).Password; + }; + contentControl = passwordBox; + break; + } case "dropdown": + { + var comboBox = new ComboBox() { - var comboBox = new ComboBox() - { - ItemsSource = attribute.Options, - SelectedItem = Settings[attribute.Name], - Margin = settingControlMargin, - ToolTip = attribute.Description - }; - comboBox.SelectionChanged += (sender, _) => - { - Settings[attribute.Name] = (string)((ComboBox)sender).SelectedItem; - }; - contentControl = comboBox; - break; - } + ItemsSource = attribute.Options, + SelectedItem = Settings[attribute.Name], + Margin = settingControlMargin, + ToolTip = attribute.Description + }; + comboBox.SelectionChanged += (sender, _) => + { + Settings[attribute.Name] = (string)((ComboBox)sender).SelectedItem; + }; + contentControl = comboBox; + break; + } case "checkbox": var checkBox = new CheckBox { @@ -499,6 +501,7 @@ namespace Flow.Launcher.Core.Plugin } return settingWindow; } + public void Save() { if (Settings != null) @@ -541,4 +544,5 @@ namespace Flow.Launcher.Core.Plugin } } } + } From c3dd3b1851b06b65eddc7a57a883f363a6c29da2 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Thu, 18 Aug 2022 09:37:41 -0400 Subject: [PATCH 16/28] Suppress JsonRPC Empty Response Exception and refactor code --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 8a7fac1b5..cf9d6f225 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -254,6 +254,14 @@ namespace Flow.Launcher.Core.Plugin return Stream.Null; } + + + var sourceBuffer = BufferManager.GetStream(); + var errorBuffer = BufferManager.GetStream(); + + var sourceCopyTask = process.StandardOutput.BaseStream.CopyToAsync(sourceBuffer, token); + var errorCopyTask = process.StandardError.BaseStream.CopyToAsync(errorBuffer, token); + token.Register(() => { try @@ -266,14 +274,6 @@ namespace Flow.Launcher.Core.Plugin } }); - - var sourceBuffer = BufferManager.GetStream(); - var errorBuffer = BufferManager.GetStream(); - - var sourceCopyTask = process.StandardOutput.BaseStream.CopyToAsync(sourceBuffer, token); - var errorCopyTask = process.StandardError.BaseStream.CopyToAsync(errorBuffer, token); - - try { // token expire won't instantly trigger the exception, From ddb25a253a88d07c932e5faef9a7af69e591cb01 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sun, 21 Aug 2022 20:58:17 -0400 Subject: [PATCH 17/28] Unregister Cancellation Event by disposing the delegate --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index cf9d6f225..66f2bc952 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -3,6 +3,7 @@ using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; @@ -254,24 +255,17 @@ namespace Flow.Launcher.Core.Plugin return Stream.Null; } - - var sourceBuffer = BufferManager.GetStream(); - var errorBuffer = BufferManager.GetStream(); + using var errorBuffer = BufferManager.GetStream(); var sourceCopyTask = process.StandardOutput.BaseStream.CopyToAsync(sourceBuffer, token); var errorCopyTask = process.StandardError.BaseStream.CopyToAsync(errorBuffer, token); - - token.Register(() => + + await using var registeredEvent = token.Register(() => { - try - { - if (!process.HasExited) - process.Kill(); - } - catch (InvalidOperationException) - { - } + if (!process.HasExited) + process.Kill(); + sourceBuffer.Dispose(); }); try @@ -303,7 +297,7 @@ namespace Flow.Launcher.Core.Plugin if (errorBuffer.Length != 0) { using var error = new StreamReader(errorBuffer); - + var errorMessage = await error.ReadToEndAsync(); if (!string.IsNullOrEmpty(errorMessage)) From 2b60422a6c378baeaba4913ecbe85cfc46e8819e Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sun, 21 Aug 2022 20:59:31 -0400 Subject: [PATCH 18/28] Cleanup Using --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 66f2bc952..bc9c38670 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -1,34 +1,26 @@ -using Accessibility; -using Flow.Launcher.Core.Resource; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using ICSharpCode.SharpZipLib.Zip; -using JetBrains.Annotations; using Microsoft.IO; -using System.Text.Json.Serialization; using System.Windows; using System.Windows.Controls; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; using CheckBox = System.Windows.Controls.CheckBox; using Control = System.Windows.Controls.Control; -using Label = System.Windows.Controls.Label; using Orientation = System.Windows.Controls.Orientation; using TextBox = System.Windows.Controls.TextBox; using UserControl = System.Windows.Controls.UserControl; -using System.Windows.Data; namespace Flow.Launcher.Core.Plugin { From 74d929094481cbc269dcb3dea4b97d9772d61e43 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sun, 21 Aug 2022 22:00:12 -0400 Subject: [PATCH 19/28] Redesign exception stream handling and dispose stream to recycle --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 49 +++++++++------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index bc9c38670..0f32137f1 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -85,12 +86,15 @@ namespace Flow.Launcher.Core.Plugin private async Task> DeserializedResultAsync(Stream output) { - if (output == Stream.Null) return null; + await using (output) + { + if (output == Stream.Null) return null; - var queryResponseModel = - await JsonSerializer.DeserializeAsync(output, options); + var queryResponseModel = + await JsonSerializer.DeserializeAsync(output, options); - return ParseResults(queryResponseModel); + return ParseResults(queryResponseModel); + } } private List DeserializedResult(string output) @@ -132,7 +136,7 @@ namespace Flow.Launcher.Core.Plugin } else { - var actionResponse = await RequestAsync(result.JsonRPCAction); + await using var actionResponse = await RequestAsync(result.JsonRPCAction); if (actionResponse.Length == 0) { @@ -273,31 +277,18 @@ namespace Flow.Launcher.Core.Plugin return Stream.Null; } + switch (sourceBuffer.Length, errorBuffer.Position) + { + case (0, 0): + var errorMessage = Encoding.UTF8.GetString(errorBuffer.GetBuffer(), 0, (int)errorBuffer.Position); + Log.Warn($"|{nameof(JsonRPCPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}"); + break; + case (_, not 0): + throw new InvalidDataException(Encoding.UTF8.GetString(errorBuffer.ToArray())); // The process has exited with an error message + } + sourceBuffer.Seek(0, SeekOrigin.Begin); - - token.ThrowIfCancellationRequested(); - - if (sourceBuffer.Length == 0) - { - var errorMessage = errorBuffer.Length == 0 ? - "Empty JSONRPC Response" : - await process.StandardError.ReadToEndAsync(); - - Log.Error($"{context.CurrentPluginMetadata.Name}|{errorMessage}"); - } - - if (errorBuffer.Length != 0) - { - using var error = new StreamReader(errorBuffer); - - var errorMessage = await error.ReadToEndAsync(); - - if (!string.IsNullOrEmpty(errorMessage)) - { - Log.Error($"|{context.CurrentPluginMetadata.Name}.{nameof(ExecuteAsync)}|{errorMessage}"); - } - } - + return sourceBuffer; } From 793b432d61f2ddc37dc0b8f760d1d6a1441a6ec4 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sun, 21 Aug 2022 22:02:46 -0400 Subject: [PATCH 20/28] Use Length instead of Position to keep thing clear --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 0f32137f1..7003804b8 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -277,7 +277,7 @@ namespace Flow.Launcher.Core.Plugin return Stream.Null; } - switch (sourceBuffer.Length, errorBuffer.Position) + switch (sourceBuffer.Length, errorBuffer.Length) { case (0, 0): var errorMessage = Encoding.UTF8.GetString(errorBuffer.GetBuffer(), 0, (int)errorBuffer.Position); From f6e6cd5a4261fe985c3f611b9f727d85eb11a921 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sun, 21 Aug 2022 22:09:56 -0400 Subject: [PATCH 21/28] Fix Error Message for empty response --- Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 7003804b8..e3efcd296 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -280,7 +280,7 @@ namespace Flow.Launcher.Core.Plugin switch (sourceBuffer.Length, errorBuffer.Length) { case (0, 0): - var errorMessage = Encoding.UTF8.GetString(errorBuffer.GetBuffer(), 0, (int)errorBuffer.Position); + const string errorMessage = "Empty JSON-RPC Response."; Log.Warn($"|{nameof(JsonRPCPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}"); break; case (_, not 0): From a2d9cb6f2dee914afa7a1518ce62dbee47f7742d Mon Sep 17 00:00:00 2001 From: DB p Date: Thu, 1 Sep 2022 21:04:14 +0900 Subject: [PATCH 22/28] - Adjust ResultListItem Layout for Large Window Size - Change Window Max Width to 1920 from 900 --- Flow.Launcher/ResultListBox.xaml | 12 +++++------- Flow.Launcher/SettingWindow.xaml | 14 ++++++-------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index 2a7f9c494..be81a9bf2 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -30,7 +30,7 @@ -