diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/de.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/de.xaml index 58bcf7beaf..f5e936d41f 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/de.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/de.xaml @@ -38,5 +38,6 @@ Win32-Anwendung Weblink-Anwendung Web-Anwendung + Run command diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/en.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/en.xaml index 2caa14067c..0beec6253c 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/en.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/en.xaml @@ -49,4 +49,5 @@ Win32 application Internet shortcut application Web application + Run command \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/ja.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/ja.xaml index 364dc4e9b2..50941e1b92 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/ja.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/ja.xaml @@ -39,5 +39,6 @@ Win32 application Internet shortcut application Web application + Run command \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/pl.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/pl.xaml index b739a1d551..37e0ef298d 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/pl.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/pl.xaml @@ -38,5 +38,6 @@ Win32 application Internet shortcut application Web application + Run command \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/tr.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/tr.xaml index e659da4876..a6ddbd3c08 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/tr.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/tr.xaml @@ -40,5 +40,6 @@ Win32 application Internet shortcut application Web application + Run command \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/zh-cn.xaml index 83acbe6148..dd99ae570e 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/zh-cn.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/zh-cn.xaml @@ -40,5 +40,6 @@ Win32 application Internet shortcut application Web application + Run command \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/zh-tw.xaml index c424b7347a..60b136f2e7 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/zh-tw.xaml +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Languages/zh-tw.xaml @@ -38,4 +38,6 @@ Win32 application Internet shortcut application Web application + Run command + diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/Win32.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/Win32.cs index bfbc1438cd..d7c0c12e99 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/Win32.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/Win32.cs @@ -48,7 +48,8 @@ namespace Microsoft.Plugin.Program.Programs { WEB_APPLICATION = 0, INTERNET_SHORTCUT_APPLICATION = 1, - WIN32_APPLICATION = 2 + WIN32_APPLICATION = 2, + RUN_COMMAND = 3 } private int Score(string query) @@ -116,12 +117,27 @@ namespace Microsoft.Plugin.Program.Programs { return api.GetTranslation("powertoys_run_plugin_program_web_application"); } + else if(AppType == (uint)ApplicationTypes.RUN_COMMAND) + { + return api.GetTranslation("powertoys_run_plugin_program_run_command"); + } else { return String.Empty; } } + public bool QueryEqualsNameForRunCommands(string query) + { + if (AppType == (uint)ApplicationTypes.RUN_COMMAND + && !query.Equals(Name, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + public Result Result(string query, IPublicAPI api) { var score = Score(query); @@ -144,6 +160,12 @@ namespace Microsoft.Plugin.Program.Programs } } + // NOTE: This is to display run commands only when there is an exact match, like in start menu + if(!QueryEqualsNameForRunCommands(query)) + { + return null; + } + var result = new Result { SubTitle = SetSubtitle(AppType, api), @@ -431,7 +453,7 @@ namespace Microsoft.Plugin.Program.Programs } } - private static IEnumerable ProgramPaths(string directory, string[] suffixes) + private static IEnumerable ProgramPaths(string directory, string[] suffixes, bool recursiveSearch = true) { if (!Directory.Exists(directory)) { @@ -468,6 +490,12 @@ namespace Microsoft.Plugin.Program.Programs try { + // If the search is set to be non-recursive, then do not enqueue the child directories. + if(!recursiveSearch) + { + continue; + } + foreach (var childDirectory in Directory.EnumerateDirectories(currentDirectory, "*", SearchOption.TopDirectoryOnly)) { folderQueue.Enqueue(childDirectory); @@ -518,6 +546,45 @@ namespace Microsoft.Plugin.Program.Programs return programs1.Concat(programs2).Concat(programs3); } + + // Function to obtain the list of applications, the locations of which have been added to the env variable PATH + private static ParallelQuery PathEnvironmentPrograms(string[] suffixes) + { + + // To get all the locations stored in the PATH env variable + var pathEnvVariable = Environment.GetEnvironmentVariable("PATH"); + string[] searchPaths = pathEnvVariable.Split(Path.PathSeparator); + IEnumerable toFilterAllPaths = new List(); + bool isRecursiveSearch = true; + + foreach(string path in searchPaths) + { + if(path.Length > 0) + { + // to expand any environment variables present in the path + string directory = Environment.ExpandEnvironmentVariables(path); + var paths = ProgramPaths(directory, suffixes, !isRecursiveSearch); + toFilterAllPaths = toFilterAllPaths.Concat(paths); + } + } + + var allPaths = toFilterAllPaths + .Distinct() + .ToArray(); + + var programs1 = allPaths.AsParallel().Where(p => Extension(p).Equals(ShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(LnkProgram); + var programs2 = allPaths.AsParallel().Where(p => Extension(p).Equals(ApplicationReferenceExtension, StringComparison.OrdinalIgnoreCase)).Select(Win32Program); + var programs3 = allPaths.AsParallel().Where(p => Extension(p).Equals(InternetShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(InternetShortcutProgram); + var programs4 = allPaths.AsParallel().Where(p => Extension(p).Equals(ExeExtension, StringComparison.OrdinalIgnoreCase)).Select(ExeProgram); + + var allPrograms = programs1.Concat(programs2).Where(p => p.Valid) + .Concat(programs3).Where(p => p.Valid) + .Concat(programs4).Where(p => p.Valid) + .Select( p => { p.AppType = (uint)ApplicationTypes.RUN_COMMAND; return p; }); + + return allPrograms; + } + private static ParallelQuery IndexPath(string[] suffixes, List IndexLocation) { var disabledProgramsList = Main._settings.DisabledProgramSources; @@ -650,8 +717,8 @@ namespace Microsoft.Plugin.Program.Programs && !string.IsNullOrEmpty(app1.ExecutableName) && !string.IsNullOrEmpty(app2.ExecutableName) && !string.IsNullOrEmpty(app1.FullPath) && !string.IsNullOrEmpty(app2.FullPath)) { - return app1.Name.Equals(app2.Name, StringComparison.OrdinalIgnoreCase) - && app1.ExecutableName.Equals(app2.ExecutableName, StringComparison.OrdinalIgnoreCase) + return app1.Name.Equals(app2.Name, StringComparison.OrdinalIgnoreCase) + && app1.ExecutableName.Equals(app2.ExecutableName, StringComparison.OrdinalIgnoreCase) && app1.FullPath.Equals(app2.FullPath, StringComparison.OrdinalIgnoreCase); } return false; @@ -676,7 +743,7 @@ namespace Microsoft.Plugin.Program.Programs // Deduplication code public static Func, Win32[]> DeduplicatePrograms = (programs) => { - var uniqueExePrograms = programs.Where(x => !string.IsNullOrEmpty(x.LnkResolvedPath) || Extension(x.FullPath) != ExeExtension); + var uniqueExePrograms = programs.Where(x => !(string.IsNullOrEmpty(x.LnkResolvedPath) && (Extension(x.FullPath) == ExeExtension) && !(x.AppType == (uint)ApplicationTypes.RUN_COMMAND))); var uniquePrograms = uniqueExePrograms.Distinct(new removeDuplicatesComparer()); return uniquePrograms.ToArray(); }; @@ -702,6 +769,12 @@ namespace Microsoft.Plugin.Program.Programs programs = programs.Concat(startMenu); } + if (settings.EnablePathEnvironmentVariableSource) + { + var appPathEnvironment = PathEnvironmentPrograms(settings.ProgramSuffixes); + programs = programs.Concat(appPathEnvironment); + } + if (settings.EnableDesktopSource) { var desktop = DesktopPrograms(settings.ProgramSuffixes); diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Settings.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Settings.cs index 79b9a9d32a..cbb3156d4e 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Settings.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Settings.cs @@ -17,6 +17,8 @@ namespace Microsoft.Plugin.Program public bool EnableRegistrySource { get; set; } = true; + public bool EnablePathEnvironmentVariableSource { get; set; } = true; + internal const char SuffixSeparator = ';'; /// diff --git a/src/modules/launcher/Wox.Test/Plugins/ProgramPluginTest.cs b/src/modules/launcher/Wox.Test/Plugins/ProgramPluginTest.cs index 9cdec9566e..983325a957 100644 --- a/src/modules/launcher/Wox.Test/Plugins/ProgramPluginTest.cs +++ b/src/modules/launcher/Wox.Test/Plugins/ProgramPluginTest.cs @@ -126,6 +126,24 @@ namespace Wox.Test.Plugins LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\test proxy.lnk" }; + Win32 cmd_run_command = new Win32 + { + Name = "cmd", + ExecutableName = "cmd.exe", + FullPath = "c:\\windows\\system32\\cmd.exe", + LnkResolvedPath = null, + AppType = 3 // Run command + }; + + Win32 cmder_run_command = new Win32 + { + Name = "Cmder", + ExecutableName = "Cmder.exe", + FullPath = "c:\\tools\\cmder\\cmder.exe", + LnkResolvedPath = null, + AppType = 3 // Run command + }; + Win32 dummy_internetShortcut_app = new Win32 { Name = "Shop Titans", @@ -293,5 +311,27 @@ namespace Wox.Test.Plugins // unreachable code return true; } + + [TestCase("Command Prompt")] + [TestCase("cmd")] + [TestCase("cmd.exe")] + [TestCase("ignoreQueryText")] + public void Win32Applications_ShouldNotBeFiltered_WhenFilteringRunCommands(string query) + { + // Even if there is an exact match in the name or exe name, win32 applications should never be filtered + Assert.IsTrue(command_prompt.QueryEqualsNameForRunCommands(query)); + } + + [TestCase("cmd")] + [TestCase("Cmd")] + [TestCase("CMD")] + public void RunCommands_ShouldNotBeFiltered_OnExactMatch(string query) + { + // Partial matches should be filtered as cmd is not equal to cmder + Assert.IsFalse(cmder_run_command.QueryEqualsNameForRunCommands(query)); + + // the query matches the name (cmd) and is therefore not filtered (case-insensitive) + Assert.IsTrue(cmd_run_command.QueryEqualsNameForRunCommands(query)); + } } }