Merge branch 'dev' into fix_loading_uwp

This commit is contained in:
Jeremy Wu 2021-02-15 20:00:58 +11:00
commit 37afe141d5
7 changed files with 331 additions and 116 deletions

View file

@ -228,7 +228,6 @@ namespace Flow.Launcher.Plugin.Program
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
{
bool hide;
try
{
runProcess(info);

View file

@ -0,0 +1,44 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace Flow.Launcher.Plugin.Program.Programs
{
public class ApplicationActivationHelper
{
// Reference : https://github.com/MicrosoftEdge/edge-launcher/blob/108e63df0b4cb5cd9d5e45aa7a264690851ec51d/MIcrosoftEdgeLauncherCsharp/Program.cs
public enum ActivateOptions
{
None = 0x00000000,
DesignMode = 0x00000001,
NoErrorUI = 0x00000002,
NoSplashScreen = 0x00000004,
}
/// ApplicationActivationManager
[ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IApplicationActivationManager
{
IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId);
IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId);
IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId);
}
// Application Activation Manager Class
[ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
public class ApplicationActivationManager : IApplicationActivationManager
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/]
public extern IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public extern IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public extern IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId);
}
}
}

View file

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using Accessibility;
using System.Runtime.InteropServices.ComTypes;
using System.Security.Policy;
namespace Flow.Launcher.Plugin.Program.Programs
{
class ShellLinkHelper
{
[Flags()]
public enum SLGP_FLAGS
{
SLGP_SHORTPATH = 0x1,
SLGP_UNCPRIORITY = 0x2,
SLGP_RAWPATH = 0x4
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WIN32_FIND_DATAW
{
public uint dwFileAttributes;
public long ftCreationTime;
public long ftLastAccessTime;
public long ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
[Flags()]
public enum SLR_FLAGS
{
SLR_NO_UI = 0x1,
SLR_ANY_MATCH = 0x2,
SLR_UPDATE = 0x4,
SLR_NOUPDATE = 0x8,
SLR_NOSEARCH = 0x10,
SLR_NOTRACK = 0x20,
SLR_NOLINKINFO = 0x40,
SLR_INVOKE_MSI = 0x80
}
// Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW
/// The IShellLink interface allows Shell links to be created, modified, and resolved
[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")]
interface IShellLinkW
{
/// <summary>Retrieves the path and file name of a Shell link object</summary>
void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
/// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
void GetIDList(out IntPtr ppidl);
/// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary>
void SetIDList(IntPtr pidl);
/// <summary>Retrieves the description string for a Shell link object</summary>
void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
/// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary>
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
/// <summary>Retrieves the name of the working directory for a Shell link object</summary>
void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
/// <summary>Sets the name of the working directory for a Shell link object</summary>
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
/// <summary>Retrieves the command-line arguments associated with a Shell link object</summary>
void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
/// <summary>Sets the command-line arguments for a Shell link object</summary>
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
/// <summary>Retrieves the hot key for a Shell link object</summary>
void GetHotkey(out short pwHotkey);
/// <summary>Sets a hot key for a Shell link object</summary>
void SetHotkey(short wHotkey);
/// <summary>Retrieves the show command for a Shell link object</summary>
void GetShowCmd(out int piShowCmd);
/// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary>
void SetShowCmd(int iShowCmd);
/// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary>
void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath,
int cchIconPath, out int piIcon);
/// <summary>Sets the location (path and index) of the icon for a Shell link object</summary>
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
/// <summary>Sets the relative path to the Shell link object</summary>
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
/// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary>
void Resolve(ref Accessibility._RemotableHandle hwnd, SLR_FLAGS fFlags);
/// <summary>Sets the path and file name of a Shell link object</summary>
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
}
[ComImport(), Guid("00021401-0000-0000-C000-000000000046")]
public class ShellLink
{
}
// To initialize the app description
public String description = String.Empty;
// Retrieve the target path using Shell Link
public string retrieveTargetPath(string path)
{
var link = new ShellLink();
const int STGM_READ = 0;
((IPersistFile)link).Load(path, STGM_READ);
var hwnd = new _RemotableHandle();
((IShellLinkW)link).Resolve(ref hwnd, 0);
const int MAX_PATH = 260;
StringBuilder buffer = new StringBuilder(MAX_PATH);
var data = new WIN32_FIND_DATAW();
((IShellLinkW)link).GetPath(buffer, buffer.Capacity, ref data, SLGP_FLAGS.SLGP_SHORTPATH);
var target = buffer.ToString();
// To set the app description
if (!String.IsNullOrEmpty(target))
{
buffer = new StringBuilder(MAX_PATH);
((IShellLinkW)link).GetDescription(buffer, MAX_PATH);
description = buffer.ToString();
}
return target;
}
}
}

View file

@ -344,10 +344,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
private async void Launch(IPublicAPI api)
{
var appManager = new ApplicationActivationManager();
var appManager = new ApplicationActivationHelper.ApplicationActivationManager();
uint unusedPid;
const string noArgs = "";
const ACTIVATEOPTIONS noFlags = ACTIVATEOPTIONS.AO_NONE;
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
await Task.Run(() =>
{
try

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@ -8,11 +7,13 @@ using System.Security;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Win32;
using Shell;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.Program.Logger;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.Infrastructure.Logger;
using System.Diagnostics;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher.Plugin.Program.Programs
{
@ -23,6 +24,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
public string UniqueIdentifier { get; set; }
public string IcoPath { get; set; }
public string FullPath { get; set; }
public string LnkResolvedPath { get; set; }
public string ParentDirectory { get; set; }
public string ExecutableName { get; set; }
public string Description { get; set; }
@ -31,49 +33,64 @@ namespace Flow.Launcher.Plugin.Program.Programs
public string Location => ParentDirectory;
private const string ShortcutExtension = "lnk";
private const string ApplicationReferenceExtension = "appref-ms";
private const string ExeExtension = "exe";
public Result Result(string query, IPublicAPI api)
{
string title;
MatchResult matchResult;
var nameMatchResult = StringMatcher.FuzzySearch(query, Name);
var descriptionMatchResult = StringMatcher.FuzzySearch(query, Description);
var pathMatchResult = new MatchResult(false, 0, new List<int>(), 0);
if (ExecutableName != null) // only lnk program will need this one
pathMatchResult = StringMatcher.FuzzySearch(query, ExecutableName);
MatchResult matchResult = nameMatchResult;
if (nameMatchResult.Score < descriptionMatchResult.Score)
matchResult = descriptionMatchResult;
if (!matchResult.IsSearchPrecisionScoreMet())
{
if (pathMatchResult.IsSearchPrecisionScoreMet())
matchResult = pathMatchResult;
else return null;
}
// We suppose Name won't be null
if (Description == null || Name.StartsWith(Description))
{
title = Name;
matchResult = StringMatcher.FuzzySearch(query, title);
}
else if (Description.StartsWith(Name))
{
title = Description;
matchResult = StringMatcher.FuzzySearch(query, Description);
}
else
{
title = $"{Name}: {Description}";
var nameMatch = StringMatcher.FuzzySearch(query, Name);
var desciptionMatch = StringMatcher.FuzzySearch(query, Description);
if (desciptionMatch.Score > nameMatch.Score)
if (matchResult == descriptionMatchResult)
{
for (int i = 0; i < desciptionMatch.MatchData.Count; i++)
for (int i = 0; i < descriptionMatchResult.MatchData.Count; i++)
{
desciptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": "
matchResult.MatchData[i] += Name.Length + 2; // 2 is ": "
}
matchResult = desciptionMatch;
}
else matchResult = nameMatch;
}
if (!matchResult.Success) return null;
if (matchResult == pathMatchResult)
{
// path Match won't have valid highlight data
matchResult.MatchData = new List<int>();
}
var result = new Result
{
Title = title,
SubTitle = FullPath,
SubTitle = LnkResolvedPath ?? FullPath,
IcoPath = IcoPath,
Score = matchResult.Score,
TitleHighlightData = matchResult.MatchData,
@ -82,7 +99,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
var info = new ProcessStartInfo
{
FileName = FullPath,
FileName = LnkResolvedPath ?? FullPath,
WorkingDirectory = ParentDirectory,
UseShellExecute = true
};
@ -144,19 +161,19 @@ namespace Flow.Launcher.Plugin.Program.Programs
Action = _ =>
{
var args = !string.IsNullOrWhiteSpace(Main._settings.CustomizedArgs)
? Main._settings.CustomizedArgs
.Replace("%s",$"\"{ParentDirectory}\"")
.Replace("%f",$"\"{FullPath}\"")
: Main._settings.CustomizedExplorer==Settings.Explorer
? $"/select,\"{FullPath}\""
: Settings.ExplorerArgs;
? Main._settings.CustomizedArgs
.Replace("%s", $"\"{ParentDirectory}\"")
.Replace("%f", $"\"{FullPath}\"")
: Main._settings.CustomizedExplorer == Settings.Explorer
? $"/select,\"{FullPath}\""
: Settings.ExplorerArgs;
Main.StartProcess(Process.Start,
new ProcessStartInfo(
!string.IsNullOrWhiteSpace(Main._settings.CustomizedExplorer)
? Main._settings.CustomizedExplorer
: Settings.Explorer,
args));
new ProcessStartInfo(
!string.IsNullOrWhiteSpace(Main._settings.CustomizedExplorer)
? Main._settings.CustomizedExplorer
: Settings.Explorer,
args));
return true;
},
@ -167,10 +184,9 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
public override string ToString()
{
return ExecutableName;
return Name;
}
private static Win32 Win32Program(string path)
@ -193,7 +209,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
$"|Permission denied when trying to load the program from {path}", e);
$"|Permission denied when trying to load the program from {path}", e);
return new Win32() { Valid = false, Enabled = false };
}
@ -204,27 +220,21 @@ namespace Flow.Launcher.Plugin.Program.Programs
var program = Win32Program(path);
try
{
var link = new ShellLink();
const uint STGM_READ = 0;
((IPersistFile)link).Load(path, STGM_READ);
var hwnd = new _RemotableHandle();
link.Resolve(ref hwnd, 0);
const int MAX_PATH = 260;
StringBuilder buffer = new StringBuilder(MAX_PATH);
ShellLinkHelper _helper = new ShellLinkHelper();
string target = _helper.retrieveTargetPath(path);
var data = new _WIN32_FIND_DATAW();
const uint SLGP_SHORTPATH = 1;
link.GetPath(buffer, buffer.Capacity, ref data, SLGP_SHORTPATH);
var target = buffer.ToString();
if (!string.IsNullOrEmpty(target))
{
var extension = Extension(target);
if (extension == ExeExtension && File.Exists(target))
{
buffer = new StringBuilder(MAX_PATH);
link.GetDescription(buffer, MAX_PATH);
var description = buffer.ToString();
program.LnkResolvedPath = program.FullPath;
program.FullPath = Path.GetFullPath(target).ToLower();
program.ExecutableName = Path.GetFileName(target);
var description = _helper.description;
if (!string.IsNullOrEmpty(description))
{
program.Description = description;
@ -239,13 +249,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
}
return program;
}
catch (COMException e)
{
// C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\MiracastView.lnk always cause exception
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
"|Error caused likely due to trying to get the description of the program", e);
"|Error caused likely due to trying to get the description of the program",
e);
program.Valid = false;
return program;
@ -275,7 +287,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
$"|Permission denied when trying to load the program from {path}", e);
$"|Permission denied when trying to load the program from {path}", e);
return new Win32() { Valid = false, Enabled = false };
}
@ -284,28 +296,13 @@ namespace Flow.Launcher.Plugin.Program.Programs
private static IEnumerable<string> ProgramPaths(string directory, string[] suffixes)
{
if (!Directory.Exists(directory))
return new string[] { };
try
{
var paths = Directory.EnumerateFiles(directory, "*", new EnumerationOptions
{
IgnoreInaccessible = true,
RecurseSubdirectories = true
})
.Where(x => suffixes.Contains(Extension(x)));
return Enumerable.Empty<string>();
return paths;
}
catch (DirectoryNotFoundException e)
return Directory.EnumerateFiles(directory, "*", new EnumerationOptions
{
ProgramLogger.LogException($"Directory not found {directory}", e);
return new string[] { };
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
ProgramLogger.LogException($"Permission denied {directory}", e);
return new string[] { };
}
IgnoreInaccessible = true,
RecurseSubdirectories = true
}).Where(x => suffixes.Contains(Extension(x)));
}
private static string Extension(string path)
@ -321,14 +318,13 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
private static ParallelQuery<Win32> UnregisteredPrograms(List<Settings.ProgramSource> sources, string[] suffixes)
private static IEnumerable<Win32> UnregisteredPrograms(List<Settings.ProgramSource> sources, string[] suffixes)
{
var paths = sources.Where(s => Directory.Exists(s.Location) && s.Enabled)
.SelectMany(s => ProgramPaths(s.Location, suffixes))
.Where(t1 => !Main._settings.DisabledProgramSources.Any(x => t1 == x.UniqueIdentifier))
var paths = ExceptDisabledSource(sources.Where(s => Directory.Exists(s.Location) && s.Enabled)
.SelectMany(s => ProgramPaths(s.Location, suffixes)), x => x)
.Distinct();
var programs = paths.AsParallel().Select(x => Extension(x) switch
var programs = paths.Select(x => Extension(x) switch
{
ExeExtension => ExeProgram(x),
ShortcutExtension => LnkProgram(x),
@ -339,7 +335,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
return programs;
}
private static ParallelQuery<Win32> StartMenuPrograms(string[] suffixes)
private static IEnumerable<Win32> StartMenuPrograms(string[] suffixes)
{
var disabledProgramsList = Main._settings.DisabledProgramSources;
@ -350,53 +346,49 @@ namespace Flow.Launcher.Plugin.Program.Programs
var toFilter = paths1.Concat(paths2);
var programs = toFilter
.AsParallel()
.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1))
.Distinct()
.Select(x => Extension(x) switch
{
ShortcutExtension => LnkProgram(x),
_ => Win32Program(x)
}).Where(x => x.Valid);
var programs = ExceptDisabledSource(toFilter.Distinct())
.Select(x => Extension(x) switch
{
ShortcutExtension => LnkProgram(x),
_ => Win32Program(x)
}).Where(x => x.Valid);
return programs;
}
private static ParallelQuery<Win32> AppPathsPrograms(string[] suffixes)
private static IEnumerable<Win32> AppPathsPrograms(string[] suffixes)
{
// https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
var programs = new List<Win32>();
using (var root = Registry.LocalMachine.OpenSubKey(appPaths))
IEnumerable<string> toFilter = Enumerable.Empty<string>();
using var rootMachine = Registry.LocalMachine.OpenSubKey(appPaths);
using var rootUser = Registry.CurrentUser.OpenSubKey(appPaths);
if (rootMachine != null)
{
if (root != null)
{
programs.AddRange(GetProgramsFromRegistry(root));
}
}
using (var root = Registry.CurrentUser.OpenSubKey(appPaths))
{
if (root != null)
{
programs.AddRange(GetProgramsFromRegistry(root));
}
toFilter = toFilter.Concat(GetPathFromRegistry(rootMachine));
}
var disabledProgramsList = Main._settings.DisabledProgramSources;
var toFilter = programs.AsParallel().Where(p => suffixes.Contains(Extension(p.ExecutableName)));
if (rootUser != null)
{
toFilter = toFilter.Concat(GetPathFromRegistry(rootUser));
}
var filtered = toFilter.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)).Select(t1 => t1);
return filtered;
toFilter = toFilter.Distinct().Where(p => suffixes.Contains(Extension(p)));
var filtered = ExceptDisabledSource(toFilter);
return filtered.Select(GetProgramFromPath).ToList(); // ToList due to disposing issue
}
private static IEnumerable<Win32> GetProgramsFromRegistry(RegistryKey root)
private static IEnumerable<string> GetPathFromRegistry(RegistryKey root)
{
return root
.GetSubKeyNames()
.Select(x => GetProgramPathFromRegistrySubKeys(root, x))
.Distinct()
.Select(x => GetProgramFromPath(x));
.GetSubKeyNames()
.Select(x => GetProgramPathFromRegistrySubKeys(root, x))
.Distinct();
}
private static string GetProgramPathFromRegistrySubKeys(RegistryKey root, string subkey)
@ -422,7 +414,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
ProgramLogger.LogException($"|Win32|GetProgramPathFromRegistrySubKeys|{path}" +
$"|Permission denied when trying to load the program from {path}", e);
$"|Permission denied when trying to load the program from {path}", e);
return string.Empty;
}
@ -431,27 +423,74 @@ namespace Flow.Launcher.Plugin.Program.Programs
private static Win32 GetProgramFromPath(string path)
{
if (string.IsNullOrEmpty(path))
return new Win32();
return null;
path = Environment.ExpandEnvironmentVariables(path);
if (!File.Exists(path))
return new Win32();
return null;
var entry = Win32Program(path);
entry.ExecutableName = Path.GetFileName(path);
return entry;
}
public static IEnumerable<string> ExceptDisabledSource(IEnumerable<string> sources)
{
return ExceptDisabledSource(sources, x => x);
}
public static IEnumerable<TSource> ExceptDisabledSource<TSource>(IEnumerable<TSource> sources,
Func<TSource, string> keySelector)
{
return Main._settings.DisabledProgramSources.Count == 0
? sources
: ExceptDisabledSourceEnumerable(sources, keySelector);
static IEnumerable<TSource> ExceptDisabledSourceEnumerable(IEnumerable<TSource> elements,
Func<TSource, string> selector)
{
var set = Main._settings.DisabledProgramSources.Select(x => x.UniqueIdentifier).ToHashSet();
foreach (var element in elements)
{
if (!set.Contains(selector(element)))
yield return element;
}
}
}
public static IEnumerable<T> DistinctBy<T, R>(IEnumerable<T> source, Func<T, R> selector)
{
var set = new HashSet<R>();
foreach (var item in source)
{
if (set.Add(selector(item)))
yield return item;
}
}
private static Win32[] ProgramsHasher(IEnumerable<Win32> programs)
{
return programs.GroupBy(p => p.FullPath.ToLower())
.SelectMany(g =>
{
if (g.Count() > 1)
return DistinctBy(g.Where(p => !string.IsNullOrEmpty(p.Description)), x => x.Description);
return g;
}).ToArray();
}
public static Win32[] All(Settings settings)
{
try
{
var programs = new List<Win32>().AsParallel();
var programs = Enumerable.Empty<Win32>();
var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes);
programs = programs.Concat(unregistered);
if (settings.EnableRegistrySource)
{
var appPaths = AppPathsPrograms(settings.ProgramSuffixes);
@ -464,7 +503,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
programs = programs.Concat(startMenu);
}
return programs.ToArray();
return ProgramsHasher(programs.Where(p => p != null));
}
#if DEBUG //This is to make developer aware of any unhandled exception and add in handling.
catch (Exception e)
@ -478,9 +518,9 @@ namespace Flow.Launcher.Plugin.Program.Programs
{
ProgramLogger.LogException("|Win32|All|Not available|An unexpected error occurred", e);
return new Win32[0];
return Array.Empty<Win32>();
}
#endif
}
}
}
}

View file

@ -4,7 +4,7 @@
"Name": "Program",
"Description": "Search programs in Flow.Launcher",
"Author": "qianlifeng",
"Version": "1.4.0",
"Version": "1.4.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",