mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into fix_loading_uwp
This commit is contained in:
commit
37afe141d5
7 changed files with 331 additions and 116 deletions
|
|
@ -228,7 +228,6 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
|
||||
{
|
||||
bool hide;
|
||||
try
|
||||
{
|
||||
runProcess(info);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs
Normal file
132
Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue