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 12e11385597..5de32e83248 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj
@@ -46,10 +46,6 @@
.\AppxPackagingTlb.dll
True
-
- .\ShObjIdlTlb.dll
- True
-
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
index 22f4aea592f..bd09cd9c61a 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
@@ -228,7 +228,6 @@ private void DisableProgram(IProgram programToDelete)
public static void StartProcess(Func runProcess, ProcessStartInfo info)
{
- bool hide;
try
{
runProcess(info);
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs
new file mode 100644
index 00000000000..4b8423ed6fe
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ApplicationActivationHelper.cs
@@ -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);
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs
new file mode 100644
index 00000000000..4ded3412a66
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs
@@ -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
+ {
+ /// Retrieves the path and file name of a Shell link object
+ void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
+ /// Retrieves the list of item identifiers for a Shell link object
+ void GetIDList(out IntPtr ppidl);
+ /// Sets the pointer to an item identifier list (PIDL) for a Shell link object.
+ void SetIDList(IntPtr pidl);
+ /// Retrieves the description string for a Shell link object
+ void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
+ /// Sets the description for a Shell link object. The description can be any application-defined string
+ void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
+ /// Retrieves the name of the working directory for a Shell link object
+ void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
+ /// Sets the name of the working directory for a Shell link object
+ void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
+ /// Retrieves the command-line arguments associated with a Shell link object
+ void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
+ /// Sets the command-line arguments for a Shell link object
+ void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
+ /// Retrieves the hot key for a Shell link object
+ void GetHotkey(out short pwHotkey);
+ /// Sets a hot key for a Shell link object
+ void SetHotkey(short wHotkey);
+ /// Retrieves the show command for a Shell link object
+ void GetShowCmd(out int piShowCmd);
+ /// Sets the show command for a Shell link object. The show command sets the initial show state of the window.
+ void SetShowCmd(int iShowCmd);
+ /// Retrieves the location (path and index) of the icon for a Shell link object
+ void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath,
+ int cchIconPath, out int piIcon);
+ /// Sets the location (path and index) of the icon for a Shell link object
+ void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
+ /// Sets the relative path to the Shell link object
+ void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
+ /// Attempts to find the target of a Shell link, even if it has been moved or renamed
+ void Resolve(ref Accessibility._RemotableHandle hwnd, SLR_FLAGS fFlags);
+ /// Sets the path and file name of a Shell link object
+ 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;
+ }
+ }
+}
\ 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 5db26aa70e6..209b8f92ce4 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs
@@ -13,7 +13,6 @@
using Windows.ApplicationModel;
using Windows.Management.Deployment;
using AppxPackaing;
-using Shell;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.Program.Logger;
using IStream = AppxPackaing.IStream;
@@ -350,10 +349,10 @@ public List ContextMenus(IPublicAPI api)
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
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
index fd994aeb347..372d365245f 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs
@@ -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.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 @@ public class Win32 : IProgram
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 @@ public class Win32 : IProgram
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(), 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();
+ }
var result = new Result
{
Title = title,
- SubTitle = FullPath,
+ SubTitle = LnkResolvedPath ?? FullPath,
IcoPath = IcoPath,
Score = matchResult.Score,
TitleHighlightData = matchResult.MatchData,
@@ -82,7 +99,7 @@ public Result Result(string query, IPublicAPI api)
{
var info = new ProcessStartInfo
{
- FileName = FullPath,
+ FileName = LnkResolvedPath ?? FullPath,
WorkingDirectory = ParentDirectory,
UseShellExecute = true
};
@@ -144,19 +161,19 @@ public List ContextMenus(IPublicAPI api)
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 @@ public List ContextMenus(IPublicAPI api)
}
-
public override string ToString()
{
- return ExecutableName;
+ return Name;
}
private static Win32 Win32Program(string path)
@@ -193,7 +209,7 @@ private static Win32 Win32Program(string path)
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 @@ private static Win32 LnkProgram(string path)
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 @@ private static Win32 LnkProgram(string path)
}
}
}
+
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 @@ private static Win32 ExeProgram(string path)
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 @@ private static Win32 ExeProgram(string path)
private static IEnumerable 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();
- return paths;
- }
- catch (DirectoryNotFoundException e)
- {
- ProgramLogger.LogException($"Directory not found {directory}", e);
- return new string[] { };
- }
- catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
+ return Directory.EnumerateFiles(directory, "*", new EnumerationOptions
{
- 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 @@ private static string Extension(string path)
}
}
- private static ParallelQuery UnregisteredPrograms(List sources, string[] suffixes)
+ private static IEnumerable UnregisteredPrograms(List 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 @@ private static ParallelQuery UnregisteredPrograms(List StartMenuPrograms(string[] suffixes)
+ private static IEnumerable StartMenuPrograms(string[] suffixes)
{
var disabledProgramsList = Main._settings.DisabledProgramSources;
@@ -350,53 +346,49 @@ private static ParallelQuery StartMenuPrograms(string[] suffixes)
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 AppPathsPrograms(string[] suffixes)
+ private static IEnumerable 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();
- using (var root = Registry.LocalMachine.OpenSubKey(appPaths))
+
+ IEnumerable toFilter = Enumerable.Empty();
+
+ using var rootMachine = Registry.LocalMachine.OpenSubKey(appPaths);
+ using var rootUser = Registry.CurrentUser.OpenSubKey(appPaths);
+
+ if (rootMachine != null)
{
- if (root != null)
- {
- programs.AddRange(GetProgramsFromRegistry(root));
- }
+ toFilter = toFilter.Concat(GetPathFromRegistry(rootMachine));
}
- using (var root = Registry.CurrentUser.OpenSubKey(appPaths))
+
+ if (rootUser != null)
{
- if (root != null)
- {
- programs.AddRange(GetProgramsFromRegistry(root));
- }
+ toFilter = toFilter.Concat(GetPathFromRegistry(rootUser));
}
- var disabledProgramsList = Main._settings.DisabledProgramSources;
- var toFilter = programs.AsParallel().Where(p => suffixes.Contains(Extension(p.ExecutableName)));
- var filtered = toFilter.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)).Select(t1 => t1);
+ toFilter = toFilter.Distinct().Where(p => suffixes.Contains(Extension(p)));
+
+ var filtered = ExceptDisabledSource(toFilter);
- return filtered;
+ return filtered.Select(GetProgramFromPath).ToList(); // ToList due to disposing issue
}
- private static IEnumerable GetProgramsFromRegistry(RegistryKey root)
+ private static IEnumerable 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 @@ private static string GetProgramPathFromRegistrySubKeys(RegistryKey root, string
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 @@ private static string GetProgramPathFromRegistrySubKeys(RegistryKey root, string
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 ExceptDisabledSource(IEnumerable sources)
+ {
+ return ExceptDisabledSource(sources, x => x);
+ }
+
+ public static IEnumerable ExceptDisabledSource(IEnumerable sources,
+ Func keySelector)
+ {
+ return Main._settings.DisabledProgramSources.Count == 0
+ ? sources
+ : ExceptDisabledSourceEnumerable(sources, keySelector);
+
+ static IEnumerable ExceptDisabledSourceEnumerable(IEnumerable elements,
+ Func 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 DistinctBy(IEnumerable source, Func selector)
+ {
+ var set = new HashSet();
+ foreach (var item in source)
+ {
+ if (set.Add(selector(item)))
+ yield return item;
+ }
+ }
+
+ private static Win32[] ProgramsHasher(IEnumerable 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().AsParallel();
+ var programs = Enumerable.Empty();
var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes);
programs = programs.Concat(unregistered);
+
if (settings.EnableRegistrySource)
{
var appPaths = AppPathsPrograms(settings.ProgramSuffixes);
@@ -464,7 +503,8 @@ public static Win32[] All(Settings settings)
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 @@ public static Win32[] All(Settings settings)
{
ProgramLogger.LogException("|Win32|All|Not available|An unexpected error occurred", e);
- return new Win32[0];
+ return Array.Empty();
}
#endif
}
}
-}
+}
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Program/ShObjIdlTlb.dll b/Plugins/Flow.Launcher.Plugin.Program/ShObjIdlTlb.dll
deleted file mode 100644
index 83815e40a43..00000000000
Binary files a/Plugins/Flow.Launcher.Plugin.Program/ShObjIdlTlb.dll and /dev/null differ
diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json
index f713a33ece5..93e5079c2d5 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.4.0",
+ "Version": "1.4.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",