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)
|
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
|
||||||
{
|
{
|
||||||
bool hide;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
runProcess(info);
|
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)
|
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
|
@ -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",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue