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) public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
{ {
bool hide;
try try
{ {
runProcess(info); 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) private async void Launch(IPublicAPI api)
{ {
var appManager = new ApplicationActivationManager(); var appManager = new ApplicationActivationHelper.ApplicationActivationManager();
uint unusedPid; uint unusedPid;
const string noArgs = ""; const string noArgs = "";
const ACTIVATEOPTIONS noFlags = ACTIVATEOPTIONS.AO_NONE; const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
await Task.Run(() => await Task.Run(() =>
{ {
try try

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -8,11 +7,13 @@ using System.Security;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Win32; using Microsoft.Win32;
using Shell;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.Program.Logger; using Flow.Launcher.Plugin.Program.Logger;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin.SharedModels; 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 namespace Flow.Launcher.Plugin.Program.Programs
{ {
@ -23,6 +24,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
public string UniqueIdentifier { get; set; } public string UniqueIdentifier { get; set; }
public string IcoPath { get; set; } public string IcoPath { get; set; }
public string FullPath { get; set; } public string FullPath { get; set; }
public string LnkResolvedPath { get; set; }
public string ParentDirectory { get; set; } public string ParentDirectory { get; set; }
public string ExecutableName { get; set; } public string ExecutableName { get; set; }
public string Description { get; set; } public string Description { get; set; }
@ -31,49 +33,64 @@ namespace Flow.Launcher.Plugin.Program.Programs
public string Location => ParentDirectory; public string Location => ParentDirectory;
private const string ShortcutExtension = "lnk"; private const string ShortcutExtension = "lnk";
private const string ApplicationReferenceExtension = "appref-ms";
private const string ExeExtension = "exe"; private const string ExeExtension = "exe";
public Result Result(string query, IPublicAPI api) public Result Result(string query, IPublicAPI api)
{ {
string title; 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 // We suppose Name won't be null
if (Description == null || Name.StartsWith(Description)) if (Description == null || Name.StartsWith(Description))
{ {
title = Name; title = Name;
matchResult = StringMatcher.FuzzySearch(query, title);
} }
else if (Description.StartsWith(Name)) else if (Description.StartsWith(Name))
{ {
title = Description; title = Description;
matchResult = StringMatcher.FuzzySearch(query, Description);
} }
else else
{ {
title = $"{Name}: {Description}"; title = $"{Name}: {Description}";
var nameMatch = StringMatcher.FuzzySearch(query, Name);
var desciptionMatch = StringMatcher.FuzzySearch(query, Description); if (matchResult == descriptionMatchResult)
if (desciptionMatch.Score > nameMatch.Score)
{ {
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 var result = new Result
{ {
Title = title, Title = title,
SubTitle = FullPath, SubTitle = LnkResolvedPath ?? FullPath,
IcoPath = IcoPath, IcoPath = IcoPath,
Score = matchResult.Score, Score = matchResult.Score,
TitleHighlightData = matchResult.MatchData, TitleHighlightData = matchResult.MatchData,
@ -82,7 +99,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
{ {
var info = new ProcessStartInfo var info = new ProcessStartInfo
{ {
FileName = FullPath, FileName = LnkResolvedPath ?? FullPath,
WorkingDirectory = ParentDirectory, WorkingDirectory = ParentDirectory,
UseShellExecute = true UseShellExecute = true
}; };
@ -144,19 +161,19 @@ namespace Flow.Launcher.Plugin.Program.Programs
Action = _ => Action = _ =>
{ {
var args = !string.IsNullOrWhiteSpace(Main._settings.CustomizedArgs) var args = !string.IsNullOrWhiteSpace(Main._settings.CustomizedArgs)
? Main._settings.CustomizedArgs ? Main._settings.CustomizedArgs
.Replace("%s",$"\"{ParentDirectory}\"") .Replace("%s", $"\"{ParentDirectory}\"")
.Replace("%f",$"\"{FullPath}\"") .Replace("%f", $"\"{FullPath}\"")
: Main._settings.CustomizedExplorer==Settings.Explorer : Main._settings.CustomizedExplorer == Settings.Explorer
? $"/select,\"{FullPath}\"" ? $"/select,\"{FullPath}\""
: Settings.ExplorerArgs; : Settings.ExplorerArgs;
Main.StartProcess(Process.Start, Main.StartProcess(Process.Start,
new ProcessStartInfo( new ProcessStartInfo(
!string.IsNullOrWhiteSpace(Main._settings.CustomizedExplorer) !string.IsNullOrWhiteSpace(Main._settings.CustomizedExplorer)
? Main._settings.CustomizedExplorer ? Main._settings.CustomizedExplorer
: Settings.Explorer, : Settings.Explorer,
args)); args));
return true; return true;
}, },
@ -167,10 +184,9 @@ namespace Flow.Launcher.Plugin.Program.Programs
} }
public override string ToString() public override string ToString()
{ {
return ExecutableName; return Name;
} }
private static Win32 Win32Program(string path) 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) catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{ {
ProgramLogger.LogException($"|Win32|Win32Program|{path}" + 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 }; return new Win32() { Valid = false, Enabled = false };
} }
@ -204,27 +220,21 @@ namespace Flow.Launcher.Plugin.Program.Programs
var program = Win32Program(path); var program = Win32Program(path);
try 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; const int MAX_PATH = 260;
StringBuilder buffer = new StringBuilder(MAX_PATH); 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)) if (!string.IsNullOrEmpty(target))
{ {
var extension = Extension(target); var extension = Extension(target);
if (extension == ExeExtension && File.Exists(target)) if (extension == ExeExtension && File.Exists(target))
{ {
buffer = new StringBuilder(MAX_PATH); program.LnkResolvedPath = program.FullPath;
link.GetDescription(buffer, MAX_PATH); program.FullPath = Path.GetFullPath(target).ToLower();
var description = buffer.ToString(); program.ExecutableName = Path.GetFileName(target);
var description = _helper.description;
if (!string.IsNullOrEmpty(description)) if (!string.IsNullOrEmpty(description))
{ {
program.Description = description; program.Description = description;
@ -239,13 +249,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
} }
} }
} }
return program; return program;
} }
catch (COMException e) catch (COMException e)
{ {
// C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\MiracastView.lnk always cause exception // C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\MiracastView.lnk always cause exception
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" + 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; program.Valid = false;
return program; return program;
@ -275,7 +287,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException) catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{ {
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" + 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 }; 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) private static IEnumerable<string> ProgramPaths(string directory, string[] suffixes)
{ {
if (!Directory.Exists(directory)) if (!Directory.Exists(directory))
return new string[] { }; return Enumerable.Empty<string>();
try
{
var paths = Directory.EnumerateFiles(directory, "*", new EnumerationOptions
{
IgnoreInaccessible = true,
RecurseSubdirectories = true
})
.Where(x => suffixes.Contains(Extension(x)));
return paths; return Directory.EnumerateFiles(directory, "*", new EnumerationOptions
}
catch (DirectoryNotFoundException e)
{ {
ProgramLogger.LogException($"Directory not found {directory}", e); IgnoreInaccessible = true,
return new string[] { }; RecurseSubdirectories = true
} }).Where(x => suffixes.Contains(Extension(x)));
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
ProgramLogger.LogException($"Permission denied {directory}", e);
return new string[] { };
}
} }
private static string Extension(string path) 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) var paths = ExceptDisabledSource(sources.Where(s => Directory.Exists(s.Location) && s.Enabled)
.SelectMany(s => ProgramPaths(s.Location, suffixes)) .SelectMany(s => ProgramPaths(s.Location, suffixes)), x => x)
.Where(t1 => !Main._settings.DisabledProgramSources.Any(x => t1 == x.UniqueIdentifier))
.Distinct(); .Distinct();
var programs = paths.AsParallel().Select(x => Extension(x) switch var programs = paths.Select(x => Extension(x) switch
{ {
ExeExtension => ExeProgram(x), ExeExtension => ExeProgram(x),
ShortcutExtension => LnkProgram(x), ShortcutExtension => LnkProgram(x),
@ -339,7 +335,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
return programs; return programs;
} }
private static ParallelQuery<Win32> StartMenuPrograms(string[] suffixes) private static IEnumerable<Win32> StartMenuPrograms(string[] suffixes)
{ {
var disabledProgramsList = Main._settings.DisabledProgramSources; var disabledProgramsList = Main._settings.DisabledProgramSources;
@ -350,53 +346,49 @@ namespace Flow.Launcher.Plugin.Program.Programs
var toFilter = paths1.Concat(paths2); var toFilter = paths1.Concat(paths2);
var programs = toFilter var programs = ExceptDisabledSource(toFilter.Distinct())
.AsParallel() .Select(x => Extension(x) switch
.Where(t1 => !disabledProgramsList.Any(x => x.UniqueIdentifier == t1)) {
.Distinct() ShortcutExtension => LnkProgram(x),
.Select(x => Extension(x) switch _ => Win32Program(x)
{ }).Where(x => x.Valid);
ShortcutExtension => LnkProgram(x),
_ => Win32Program(x)
}).Where(x => x.Valid);
return programs; 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 // https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths"; 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) toFilter = toFilter.Concat(GetPathFromRegistry(rootMachine));
{
programs.AddRange(GetProgramsFromRegistry(root));
}
}
using (var root = Registry.CurrentUser.OpenSubKey(appPaths))
{
if (root != null)
{
programs.AddRange(GetProgramsFromRegistry(root));
}
} }
var disabledProgramsList = Main._settings.DisabledProgramSources; if (rootUser != null)
var toFilter = programs.AsParallel().Where(p => suffixes.Contains(Extension(p.ExecutableName))); {
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 return root
.GetSubKeyNames() .GetSubKeyNames()
.Select(x => GetProgramPathFromRegistrySubKeys(root, x)) .Select(x => GetProgramPathFromRegistrySubKeys(root, x))
.Distinct() .Distinct();
.Select(x => GetProgramFromPath(x));
} }
private static string GetProgramPathFromRegistrySubKeys(RegistryKey root, string subkey) 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) catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{ {
ProgramLogger.LogException($"|Win32|GetProgramPathFromRegistrySubKeys|{path}" + 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; return string.Empty;
} }
@ -431,27 +423,74 @@ namespace Flow.Launcher.Plugin.Program.Programs
private static Win32 GetProgramFromPath(string path) private static Win32 GetProgramFromPath(string path)
{ {
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
return new Win32(); return null;
path = Environment.ExpandEnvironmentVariables(path); path = Environment.ExpandEnvironmentVariables(path);
if (!File.Exists(path)) if (!File.Exists(path))
return new Win32(); return null;
var entry = Win32Program(path); var entry = Win32Program(path);
entry.ExecutableName = Path.GetFileName(path);
return entry; 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) public static Win32[] All(Settings settings)
{ {
try try
{ {
var programs = new List<Win32>().AsParallel(); var programs = Enumerable.Empty<Win32>();
var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes); var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes);
programs = programs.Concat(unregistered); programs = programs.Concat(unregistered);
if (settings.EnableRegistrySource) if (settings.EnableRegistrySource)
{ {
var appPaths = AppPathsPrograms(settings.ProgramSuffixes); var appPaths = AppPathsPrograms(settings.ProgramSuffixes);
@ -464,7 +503,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
programs = programs.Concat(startMenu); 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. #if DEBUG //This is to make developer aware of any unhandled exception and add in handling.
catch (Exception e) catch (Exception e)
@ -478,9 +518,9 @@ namespace Flow.Launcher.Plugin.Program.Programs
{ {
ProgramLogger.LogException("|Win32|All|Not available|An unexpected error occurred", e); ProgramLogger.LogException("|Win32|All|Not available|An unexpected error occurred", e);
return new Win32[0]; return Array.Empty<Win32>();
} }
#endif #endif
} }
} }
} }

View file

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