Merge pull request #101 from Flow-Launcher/dev

Release 1.1.0 | Plugin 1.1.0 | Explorer Plugin 1.1.0
This commit is contained in:
Jeremy Wu 2020-07-04 18:20:45 +10:00 committed by GitHub
commit d42d35e7e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 753 additions and 597 deletions

32
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View file

@ -0,0 +1,32 @@
---
name: "\U0001F41E Bug report"
about: Create a bug report to help us improve Flow Launcher
title: "[Describe Your Bug]"
labels: 'bug'
assignees: ''
---
**Describe the bug/issue**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. ...
2. ...
3. ...
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Your System**
```
Windows build number: (run "ver" at a command prompt)
Flow Launcher version: (Settings => About)
```
**Flow Launcher Error Log**
<!--
The latest log file can be found here: %AppData%\FlowLauncher\Logs\<version>\<date>.txt
or for portable mode: %LocalAppData%\FlowLauncher\<App-Version>\UserData\Logs\<version>\<date>.txt
Drag and drop the log file below this comment.
-->

21
.github/ISSUE_TEMPLATE/code-review.md vendored Normal file
View file

@ -0,0 +1,21 @@
---
name: "\U0001F50E Code Review"
about: Describe (bad) code that needs to be improved
title: "[Describe Problematic Code]"
labels: 'code review'
assignees: ''
---
**Point to the corresponding file and line number(s) which could be improved.**
Click on the line number in GitHub to create an anchored link to the corresponding code section. For example:
https://github.com/Flow-Launcher/Flow.Launcher/blob/master/Flow.Launcher/Storage/TopMostRecord.cs#L8
Provide your alternative implementation idea:
```
Code
```

14
.github/ISSUE_TEMPLATE/discussion.md vendored Normal file
View file

@ -0,0 +1,14 @@
---
name: "⁉️ Question/Discussion"
about: Create a report to help us improve
title: "[Ask a question or propose a change]"
labels: 'question/discussion'
assignees: ''
---
**Question**
Describe your question.
**Discussion**
Discuss a topic.

View file

@ -0,0 +1,24 @@
---
name: "⭐ Feature request"
about: Suggest a new idea or a feature enhacement
title: "[Describe Your Feature]"
labels: 'enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -32,10 +32,9 @@ namespace Flow.Launcher.Core.Configuration
try
{
MoveUserDataFolder(DataLocation.PortableDataPath, DataLocation.RoamingDataPath);
#if DEBUG
#if !DEBUG
// Create shortcuts and uninstaller are not required in debug mode,
// otherwise will repoint the path of the actual installed production version to the debug version
#else
CreateShortcuts();
CreateUninstallerEntry();
#endif
@ -48,10 +47,7 @@ namespace Flow.Launcher.Core.Configuration
}
catch (Exception e)
{
#if !DEBUG
Log.Exception("Portable", "Error occured while disabling portable mode", e);
#endif
throw;
Log.Exception("|Portable.DisablePortableMode|Error occured while disabling portable mode", e);
}
}
@ -60,10 +56,9 @@ namespace Flow.Launcher.Core.Configuration
try
{
MoveUserDataFolder(DataLocation.RoamingDataPath, DataLocation.PortableDataPath);
#if DEBUG
#if !DEBUG
// Remove shortcuts and uninstaller are not required in debug mode,
// otherwise will delete the actual installed production version
#else
RemoveShortcuts();
RemoveUninstallerEntry();
#endif
@ -76,10 +71,7 @@ namespace Flow.Launcher.Core.Configuration
}
catch (Exception e)
{
#if !DEBUG
Log.Exception("Portable", "Error occured while enabling portable mode", e);
#endif
throw;
Log.Exception("|Portable.EnablePortableMode|Error occured while enabling portable mode", e);
}
}
@ -125,14 +117,13 @@ namespace Flow.Launcher.Core.Configuration
public void CreateUninstallerEntry()
{
var uninstallRegSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall";
// NB: Sometimes the Uninstall key doesn't exist
using (var parentKey =
RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default)
.CreateSubKey("Uninstall", RegistryKeyPermissionCheck.ReadWriteSubTree)) {; }
var key = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default)
.CreateSubKey(uninstallRegSubKey + "\\" + Constant.FlowLauncher, RegistryKeyPermissionCheck.ReadWriteSubTree);
key.SetValue("DisplayIcon", Constant.ApplicationDirectory + "\\app.ico", RegistryValueKind.String);
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default))
using (var subKey1 = baseKey.CreateSubKey(uninstallRegSubKey, RegistryKeyPermissionCheck.ReadWriteSubTree))
using (var subKey2 = subKey1.CreateSubKey(Constant.FlowLauncher, RegistryKeyPermissionCheck.ReadWriteSubTree))
{
subKey2.SetValue("DisplayIcon", Path.Combine(Constant.ApplicationDirectory, "app.ico"), RegistryValueKind.String);
}
using (var portabilityUpdater = NewUpdateManager())
{
@ -142,7 +133,10 @@ namespace Flow.Launcher.Core.Configuration
internal void IndicateDeletion(string filePathTodelete)
{
using (StreamWriter sw = File.CreateText(filePathTodelete + "\\" + DataLocation.DeletionIndicatorFile)){}
var deleteFilePath = Path.Combine(filePathTodelete, DataLocation.DeletionIndicatorFile);
using (var _ = File.CreateText(deleteFilePath))
{
}
}
///<summary>
@ -152,21 +146,18 @@ namespace Flow.Launcher.Core.Configuration
public void PreStartCleanUpAfterPortabilityUpdate()
{
// Specify here so this method does not rely on other environment variables to initialise
var portableDataPath = Path.Combine(Directory.GetParent(Assembly.GetExecutingAssembly().Location.NonNull()).ToString(), "UserData");
var roamingDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FlowLauncher");
var portableDataDir = Path.Combine(Directory.GetParent(Assembly.GetExecutingAssembly().Location.NonNull()).ToString(), "UserData");
var roamingDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FlowLauncher");
bool DataLocationPortableDeleteRequired = false;
bool DataLocationRoamingDeleteRequired = false;
// Get full path to the .dead files for each case
var portableDataDeleteFilePath = Path.Combine(portableDataDir, DataLocation.DeletionIndicatorFile);
var roamingDataDeleteFilePath = Path.Combine(roamingDataDir, DataLocation.DeletionIndicatorFile);
if ((roamingDataPath + "\\" + DataLocation.DeletionIndicatorFile).FileExits())
DataLocationRoamingDeleteRequired = true;
if ((portableDataPath + "\\" + DataLocation.DeletionIndicatorFile).FileExits())
DataLocationPortableDeleteRequired = true;
if (DataLocationRoamingDeleteRequired)
// If the data folder in %appdata% is marked for deletion,
// delete it and prompt the user to pick the portable data location
if (File.Exists(roamingDataDeleteFilePath))
{
FilesFolders.RemoveFolderIfExists(roamingDataPath);
FilesFolders.RemoveFolderIfExists(roamingDataDir);
if (MessageBox.Show("Flow Launcher has detected you enabled portable mode, " +
"would you like to move it to a different location?", string.Empty,
@ -176,18 +167,15 @@ namespace Flow.Launcher.Core.Configuration
Environment.Exit(0);
}
return;
}
if(DataLocationPortableDeleteRequired)
// Otherwise, if the portable data folder is marked for deletion,
// delete it and notify the user about it.
else if (File.Exists(portableDataDeleteFilePath))
{
FilesFolders.RemoveFolderIfExists(portableDataPath);
FilesFolders.RemoveFolderIfExists(portableDataDir);
MessageBox.Show("Flow Launcher has detected you disabled portable mode, " +
"the relevant shortcuts and uninstaller entry have been created");
return;
}
}
@ -196,7 +184,7 @@ namespace Flow.Launcher.Core.Configuration
var roamingLocationExists = DataLocation.RoamingDataPath.LocationExists();
var portableLocationExists = DataLocation.PortableDataPath.LocationExists();
if(roamingLocationExists && portableLocationExists)
if (roamingLocationExists && portableLocationExists)
{
MessageBox.Show(string.Format("Flow Launcher detected your user data exists both in {0} and " +
"{1}. {2}{2}Please delete {1} in order to proceed. No changes have occured.",

View file

@ -2,9 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Flow.Launcher.Infrastructure.Exception;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
@ -13,24 +12,16 @@ namespace Flow.Launcher.Core.Plugin
internal abstract class PluginConfig
{
private const string PluginConfigName = "plugin.json";
private static readonly List<PluginMetadata> PluginMetadatas = new List<PluginMetadata>();
/// <summary>
/// Parse plugin metadata in giving directories
/// Parse plugin metadata in the given directories
/// </summary>
/// <param name="pluginDirectories"></param>
/// <returns></returns>
public static List<PluginMetadata> Parse(string[] pluginDirectories)
{
PluginMetadatas.Clear();
var allPluginMetadata = new List<PluginMetadata>();
var directories = pluginDirectories.SelectMany(Directory.GetDirectories);
ParsePluginConfigs(directories);
return PluginMetadatas;
}
private static void ParsePluginConfigs(IEnumerable<string> directories)
{
// todo use linq when diable plugin is implmented since parallel.foreach + list is not thread saft
foreach (var directory in directories)
{
@ -50,15 +41,17 @@ namespace Flow.Launcher.Core.Plugin
PluginMetadata metadata = GetPluginMetadata(directory);
if (metadata != null)
{
PluginMetadatas.Add(metadata);
allPluginMetadata.Add(metadata);
}
}
}
return allPluginMetadata;
}
private static PluginMetadata GetPluginMetadata(string pluginDirectory)
{
string configPath = Path.Combine(pluginDirectory, PluginConfigName);
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
if (!File.Exists(configPath))
{
Log.Error($"|PluginConfig.GetPluginMetadata|Didn't find config file <{configPath}>");
@ -81,7 +74,6 @@ namespace Flow.Launcher.Core.Plugin
return null;
}
if (!AllowedLanguage.IsAllowed(metadata.Language))
{
Log.Error($"|PluginConfig.GetPluginMetadata|Invalid language <{metadata.Language}> for config <{configPath}>");

View file

@ -1,9 +1,11 @@
using System;
using System;
using System.IO;
using System.Windows;
using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json;
using Flow.Launcher.Plugin;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Core.Plugin
{
@ -13,28 +15,28 @@ namespace Flow.Launcher.Core.Plugin
{
if (File.Exists(path))
{
string tempFoler = Path.Combine(Path.GetTempPath(), "flowlauncher\\plugins");
if (Directory.Exists(tempFoler))
string tempFolder = Path.Combine(Path.GetTempPath(), "flowlauncher", "plugins");
if (Directory.Exists(tempFolder))
{
Directory.Delete(tempFoler, true);
Directory.Delete(tempFolder, true);
}
UnZip(path, tempFoler, true);
UnZip(path, tempFolder, true);
string iniPath = Path.Combine(tempFoler, "plugin.json");
if (!File.Exists(iniPath))
string jsonPath = Path.Combine(tempFolder, Constant.PluginMetadataFileName);
if (!File.Exists(jsonPath))
{
MessageBox.Show("Install failed: plugin config is missing");
return;
}
PluginMetadata plugin = GetMetadataFromJson(tempFoler);
PluginMetadata plugin = GetMetadataFromJson(tempFolder);
if (plugin == null || plugin.Name == null)
{
MessageBox.Show("Install failed: plugin config is invalid");
return;
}
string pluginFolerPath = Infrastructure.UserSettings.DataLocation.PluginsDirectory;
string pluginFolderPath = Infrastructure.UserSettings.DataLocation.PluginsDirectory;
string newPluginName = plugin.Name
.Replace("/", "_")
@ -46,7 +48,9 @@ namespace Flow.Launcher.Core.Plugin
.Replace("*", "_")
.Replace("|", "_")
+ "-" + Guid.NewGuid();
string newPluginPath = Path.Combine(pluginFolerPath, newPluginName);
string newPluginPath = Path.Combine(pluginFolderPath, newPluginName);
string content = $"Do you want to install following plugin?{Environment.NewLine}{Environment.NewLine}" +
$"Name: {plugin.Name}{Environment.NewLine}" +
$"Version: {plugin.Version}{Environment.NewLine}" +
@ -71,8 +75,7 @@ namespace Flow.Launcher.Core.Plugin
File.Create(Path.Combine(existingPlugin.Metadata.PluginDirectory, "NeedDelete.txt")).Close();
}
UnZip(path, newPluginPath, true);
Directory.Delete(tempFoler, true);
Directory.Move(tempFolder, newPluginPath);
//exsiting plugins may be has loaded by application,
//if we try to delelte those kind of plugins, we will get a error that indicate the
@ -94,7 +97,7 @@ namespace Flow.Launcher.Core.Plugin
private static PluginMetadata GetMetadataFromJson(string pluginDirectory)
{
string configPath = Path.Combine(pluginDirectory, "plugin.json");
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
PluginMetadata metadata;
if (!File.Exists(configPath))
@ -107,36 +110,20 @@ namespace Flow.Launcher.Core.Plugin
metadata = JsonConvert.DeserializeObject<PluginMetadata>(File.ReadAllText(configPath));
metadata.PluginDirectory = pluginDirectory;
}
catch (Exception)
catch (Exception e)
{
string error = $"Parse plugin config {configPath} failed: json format is not valid";
#if (DEBUG)
{
throw new Exception(error);
}
#endif
Log.Exception($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: invalid json format", e);
return null;
}
if (!AllowedLanguage.IsAllowed(metadata.Language))
{
string error = $"Parse plugin config {configPath} failed: invalid language {metadata.Language}";
#if (DEBUG)
{
throw new Exception(error);
}
#endif
Log.Error($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: invalid language {metadata.Language}");
return null;
}
if (!File.Exists(metadata.ExecuteFilePath))
{
string error = $"Parse plugin config {configPath} failed: ExecuteFile {metadata.ExecuteFilePath} didn't exist";
#if (DEBUG)
{
throw new Exception(error);
}
#endif
Log.Error($"|PluginInstaller.GetMetadataFromJson|plugin config {configPath} failed: file {metadata.ExecuteFilePath} doesn't exist");
return null;
}
@ -144,58 +131,38 @@ namespace Flow.Launcher.Core.Plugin
}
/// <summary>
/// unzip
/// unzip plugin contents to the given directory.
/// </summary>
/// <param name="zipedFile">The ziped file.</param>
/// <param name="strDirectory">The STR directory.</param>
/// <param name="zipFile">The path to the zip file.</param>
/// <param name="strDirectory">The output directory.</param>
/// <param name="overWrite">overwirte</param>
private static void UnZip(string zipedFile, string strDirectory, bool overWrite)
private static void UnZip(string zipFile, string strDirectory, bool overWrite)
{
if (strDirectory == "")
strDirectory = Directory.GetCurrentDirectory();
if (!strDirectory.EndsWith("\\"))
strDirectory = strDirectory + "\\";
using (ZipInputStream s = new ZipInputStream(File.OpenRead(zipedFile)))
using (ZipInputStream zipStream = new ZipInputStream(File.OpenRead(zipFile)))
{
ZipEntry theEntry;
while ((theEntry = s.GetNextEntry()) != null)
while ((theEntry = zipStream.GetNextEntry()) != null)
{
string directoryName = "";
string pathToZip = "";
pathToZip = theEntry.Name;
var pathToZip = theEntry.Name;
var directoryName = String.IsNullOrEmpty(pathToZip) ? "" : Path.GetDirectoryName(pathToZip);
var fileName = Path.GetFileName(pathToZip);
var destinationDir = Path.Combine(strDirectory, directoryName);
var destinationFile = Path.Combine(destinationDir, fileName);
if (pathToZip != "")
directoryName = Path.GetDirectoryName(pathToZip) + "\\";
Directory.CreateDirectory(destinationDir);
string fileName = Path.GetFileName(pathToZip);
if (String.IsNullOrEmpty(fileName) || (File.Exists(destinationFile) && !overWrite))
continue;
Directory.CreateDirectory(strDirectory + directoryName);
if (fileName != "")
using (FileStream streamWriter = File.Create(destinationFile))
{
if ((File.Exists(strDirectory + directoryName + fileName) && overWrite) || (!File.Exists(strDirectory + directoryName + fileName)))
{
using (FileStream streamWriter = File.Create(strDirectory + directoryName + fileName))
{
byte[] data = new byte[2048];
while (true)
{
int size = s.Read(data, 0, data.Length);
if (size > 0)
streamWriter.Write(data, 0, size);
else
break;
}
streamWriter.Close();
}
}
zipStream.CopyTo(streamWriter);
}
}
s.Close();
}
}
}

View file

@ -19,10 +19,6 @@ namespace Flow.Launcher.Core.Plugin
{
private static IEnumerable<PluginPair> _contextMenuPlugins;
/// <summary>
/// Directories that will hold Flow Launcher plugin directory
/// </summary>
public static List<PluginPair> AllPlugins { get; private set; }
public static readonly List<PluginPair> GlobalPlugins = new List<PluginPair>();
public static readonly Dictionary<string, PluginPair> NonGlobalPlugins = new Dictionary<string, PluginPair>();
@ -32,27 +28,18 @@ namespace Flow.Launcher.Core.Plugin
// todo happlebao, this should not be public, the indicator function should be embeded
public static PluginsSettings Settings;
private static List<PluginMetadata> _metadatas;
private static readonly string[] Directories = { Constant.PreinstalledDirectory, DataLocation.PluginsDirectory };
private static void ValidateUserDirectory()
{
if (!Directory.Exists(DataLocation.PluginsDirectory))
{
Directory.CreateDirectory(DataLocation.PluginsDirectory);
}
}
/// <summary>
/// Directories that will hold Flow Launcher plugin directory
/// </summary>
private static readonly string[] Directories = { Constant.PreinstalledDirectory, DataLocation.PluginsDirectory };
private static void DeletePythonBinding()
{
const string binding = "flowlauncher.py";
var directory = DataLocation.PluginsDirectory;
foreach (var subDirectory in Directory.GetDirectories(directory))
foreach (var subDirectory in Directory.GetDirectories(DataLocation.PluginsDirectory))
{
var path = Path.Combine(subDirectory, binding);
if (File.Exists(path))
{
File.Delete(path);
}
File.Delete(Path.Combine(subDirectory, binding));
}
}
@ -76,7 +63,8 @@ namespace Flow.Launcher.Core.Plugin
static PluginManager()
{
ValidateUserDirectory();
// validate user directory
Directory.CreateDirectory(DataLocation.PluginsDirectory);
// force old plugins use new python binding
DeletePythonBinding();
}
@ -132,9 +120,10 @@ namespace Flow.Launcher.Core.Plugin
GlobalPlugins.Add(plugin);
// Plugins may have multiple ActionKeywords, eg. WebSearch
plugin.Metadata.ActionKeywords.Where(x => x != Query.GlobalPluginWildcardSign)
.ToList()
.ForEach(x => NonGlobalPlugins[x] = plugin);
plugin.Metadata.ActionKeywords
.Where(x => x != Query.GlobalPluginWildcardSign)
.ToList()
.ForEach(x => NonGlobalPlugins[x] = plugin);
}
if (failedPlugins.Any())
@ -164,9 +153,9 @@ namespace Flow.Launcher.Core.Plugin
public static List<Result> QueryForPlugin(PluginPair pair, Query query)
{
var results = new List<Result>();
try
{
List<Result> results = null;
var metadata = pair.Metadata;
var milliseconds = Stopwatch.Debug($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", () =>
{
@ -175,13 +164,12 @@ namespace Flow.Launcher.Core.Plugin
});
metadata.QueryCount += 1;
metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2;
return results;
}
catch (Exception e)
{
Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e);
return new List<Result>();
}
return results;
}
public static void UpdatePluginMetadata(List<Result> results, PluginMetadata metadata, Query query)
@ -221,47 +209,34 @@ namespace Flow.Launcher.Core.Plugin
public static List<Result> GetContextMenusForPlugin(Result result)
{
var results = new List<Result>();
var pluginPair = _contextMenuPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID);
if (pluginPair != null)
{
var metadata = pluginPair.Metadata;
var plugin = (IContextMenu)pluginPair.Plugin;
try
{
var results = plugin.LoadContextMenus(result);
results = plugin.LoadContextMenus(result);
foreach (var r in results)
{
r.PluginDirectory = metadata.PluginDirectory;
r.PluginID = metadata.ID;
r.PluginDirectory = pluginPair.Metadata.PluginDirectory;
r.PluginID = pluginPair.Metadata.ID;
r.OriginQuery = result.OriginQuery;
}
return results;
}
catch (Exception e)
{
Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{metadata.Name}>", e);
return new List<Result>();
Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>", e);
}
}
else
{
return new List<Result>();
}
return results;
}
public static bool ActionKeywordRegistered(string actionKeyword)
{
if (actionKeyword != Query.GlobalPluginWildcardSign &&
NonGlobalPlugins.ContainsKey(actionKeyword))
{
return true;
}
else
{
return false;
}
return actionKeyword != Query.GlobalPluginWildcardSign
&& NonGlobalPlugins.ContainsKey(actionKeyword);
}
/// <summary>
@ -299,7 +274,7 @@ namespace Flow.Launcher.Core.Plugin
GlobalPlugins.Remove(plugin);
}
if(oldActionkeyword != Query.GlobalPluginWildcardSign)
if (oldActionkeyword != Query.GlobalPluginWildcardSign)
NonGlobalPlugins.Remove(oldActionkeyword);

View file

@ -21,7 +21,7 @@ namespace Flow.Launcher.Core.Plugin
public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings)
{
var dotnetPlugins = DotNetPlugins(metadatas).ToList();
var dotnetPlugins = DotNetPlugins(metadatas);
var pythonPlugins = PythonPlugins(metadatas, settings.PythonDirectory);
var executablePlugins = ExecutablePlugins(metadatas);
var plugins = dotnetPlugins.Concat(pythonPlugins).Concat(executablePlugins).ToList();
@ -46,75 +46,58 @@ namespace Flow.Launcher.Core.Plugin
var type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin)));
var plugin = (IPlugin)Activator.CreateInstance(type);
#else
Assembly assembly;
Assembly assembly = null;
IPlugin plugin = null;
try
{
assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(metadata.ExecuteFilePath);
}
catch (Exception e)
{
erroredPlugins.Add(metadata.Name);
Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e);
return;
}
Type type;
try
{
var types = assembly.GetTypes();
type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin)));
var type = types.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin)));
plugin = (IPlugin)Activator.CreateInstance(type);
}
catch (Exception e) when (assembly == null)
{
Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e);
}
catch (InvalidOperationException e)
{
erroredPlugins.Add(metadata.Name);
Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
return;
}
catch (ReflectionTypeLoadException e)
{
erroredPlugins.Add(metadata.Name);
Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
return;
}
IPlugin plugin;
try
{
plugin = (IPlugin)Activator.CreateInstance(type);
}
catch (Exception e)
{
erroredPlugins.Add(metadata.Name);
Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
}
if (plugin == null)
{
erroredPlugins.Add(metadata.Name);
return;
}
#endif
PluginPair pair = new PluginPair
plugins.Add(new PluginPair
{
Plugin = plugin,
Metadata = metadata
};
plugins.Add(pair);
});
});
metadata.InitTime += milliseconds;
}
if (erroredPlugins.Count > 0)
{
var errorPluginString = "";
var errorPluginString = String.Join(Environment.NewLine, erroredPlugins);
var errorMessage = "The following "
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
+ "errored and cannot be loaded:";
erroredPlugins.ForEach(x => errorPluginString += x + Environment.NewLine);
Task.Run(() =>
{
MessageBox.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
@ -127,65 +110,74 @@ namespace Flow.Launcher.Core.Plugin
return plugins;
}
public static IEnumerable<PluginPair> PythonPlugins(List<PluginMetadata> source, string pythonDirecotry)
public static IEnumerable<PluginPair> PythonPlugins(List<PluginMetadata> source, string pythonDirectory)
{
var metadatas = source.Where(o => o.Language.ToUpper() == AllowedLanguage.Python);
string filename;
if (string.IsNullOrEmpty(pythonDirecotry))
// try to set Constant.PythonPath, either from
// PATH or from the given pythonDirectory
if (string.IsNullOrEmpty(pythonDirectory))
{
var paths = Environment.GetEnvironmentVariable(PATH);
if (paths != null)
{
var pythonPaths = paths.Split(';').Where(p => p.ToLower().Contains(Python));
if (pythonPaths.Any())
var pythonInPath = paths
.Split(';')
.Where(p => p.ToLower().Contains(Python))
.Any();
if (pythonInPath)
{
filename = PythonExecutable;
Constant.PythonPath = PythonExecutable;
}
else
{
Log.Error("|PluginsLoader.PythonPlugins|Python can't be found in PATH.");
return new List<PluginPair>();
}
}
else
{
Log.Error("|PluginsLoader.PythonPlugins|PATH environment variable is not set.");
return new List<PluginPair>();
}
}
else
{
var path = Path.Combine(pythonDirecotry, PythonExecutable);
var path = Path.Combine(pythonDirectory, PythonExecutable);
if (File.Exists(path))
{
filename = path;
Constant.PythonPath = path;
}
else
{
Log.Error("|PluginsLoader.PythonPlugins|Can't find python executable in <b ");
return new List<PluginPair>();
Log.Error($"|PluginsLoader.PythonPlugins|Can't find python executable in {path}");
}
}
Constant.PythonPath = filename;
var plugins = metadatas.Select(metadata => new PluginPair
// if we have a path to the python executable,
// load every python plugin pair.
if (String.IsNullOrEmpty(Constant.PythonPath))
{
Plugin = new PythonPlugin(filename),
Metadata = metadata
});
return plugins;
return new List<PluginPair>();
}
else
{
return source
.Where(o => o.Language.ToUpper() == AllowedLanguage.Python)
.Select(metadata => new PluginPair
{
Plugin = new PythonPlugin(Constant.PythonPath),
Metadata = metadata
});
}
}
public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
{
var metadatas = source.Where(o => o.Language.ToUpper() == AllowedLanguage.Executable);
var plugins = metadatas.Select(metadata => new PluginPair
{
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath),
Metadata = metadata
});
return plugins;
return source
.Where(o => o.Language.ToUpper() == AllowedLanguage.Executable)
.Select(metadata => new PluginPair
{
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath),
Metadata = metadata
});
}
}

View file

@ -86,8 +86,8 @@ namespace Flow.Launcher.Core
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
FilesFolders.Copy(DataLocation.PortableDataPath, targetDestination);
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination))
MessageBox.Show(string.Format("Flow Launcher was not able to move your user profile data to the new update version. Please manually" +
"move your profile data folder from {0} to {1}", DataLocation.PortableDataPath, targetDestination));
MessageBox.Show("Flow Launcher was not able to move your user profile data to the new update version. Please manually " +
$"move your profile data folder from {DataLocation.PortableDataPath} to {targetDestination}");
}
else
{

View file

@ -8,6 +8,7 @@ namespace Flow.Launcher.Infrastructure
{
public const string FlowLauncher = "Flow.Launcher";
public const string Plugins = "Plugins";
public const string PluginMetadataFileName = "plugin.json";
public const string ApplicationFileName = FlowLauncher + ".exe";

View file

@ -83,9 +83,18 @@ namespace Flow.Launcher.Infrastructure
bool allSubstringsContainedInCompareString = true;
var indexList = new List<int>();
List<int> spaceIndices = new List<int>();
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
{
// To maintain a list of indices which correspond to spaces in the string to compare
// To populate the list only for the first query substring
if (fullStringToCompareWithoutCase[compareStringIndex].Equals(' ') && currentQuerySubstringIndex == 0)
{
spaceIndices.Add(compareStringIndex);
}
if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex])
{
matchFoundInPreviousLoop = false;
@ -147,15 +156,31 @@ namespace Flow.Launcher.Infrastructure
// proceed to calculate score if every char or substring without whitespaces matched
if (allQuerySubstringsMatched)
{
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex);
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
return new MatchResult(true, UserSettingSearchPrecision, indexList, score);
}
return new MatchResult (false, UserSettingSearchPrecision);
return new MatchResult(false, UserSettingSearchPrecision);
}
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
// To get the index of the closest space which preceeds the first matching index
private int CalculateClosestSpaceIndex(List<int> spaceIndices, int firstMatchIndex)
{
if (spaceIndices.Count == 0)
{
return -1;
}
else
{
int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault();
int closestSpaceIndex = ind ?? -1;
return closestSpaceIndex;
}
}
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
string fullStringToCompareWithoutCase, string currentQuerySubstring)
{
var allMatch = true;
@ -299,13 +324,13 @@ namespace Flow.Launcher.Infrastructure
public class MatchOption
{
/// <summary>
/// prefix of match char, use for hightlight
/// prefix of match char, use for highlight
/// </summary>
[Obsolete("this is never used")]
public string Prefix { get; set; } = "";
/// <summary>
/// suffix of match char, use for hightlight
/// suffix of match char, use for highlight
/// </summary>
[Obsolete("this is never used")]
public string Suffix { get; set; } = "";

View file

@ -14,10 +14,10 @@
</PropertyGroup>
<PropertyGroup>
<Version>1.0.0</Version>
<PackageVersion>1.0.0-beta3</PackageVersion>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<Version>1.1.0</Version>
<PackageVersion>1.1.0</PackageVersion>
<AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.1.0</FileVersion>
<PackageId>Flow.Launcher.Plugin</PackageId>
<Authors>Flow-Launcher</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>

View file

@ -130,5 +130,15 @@ namespace Flow.Launcher.Plugin
/// Plugin ID that generated this result
/// </summary>
public string PluginID { get; internal set; }
/// <summary>
/// Show message as ToolTip on result Title hover over
/// </summary>
public string TitleToolTip { get; set; }
/// <summary>
/// Show message as ToolTip on result SubTitle hover over
/// </summary>
public string SubTitleToolTip { get; set; }
}
}

View file

@ -8,6 +8,9 @@ namespace Flow.Launcher.Plugin.SharedCommands
public static class FilesFolders
{
private const string FileExplorerProgramName = "explorer";
private const string FileExplorerProgramEXE = "explorer.exe";
public static void Copy(this string sourcePath, string targetPath)
{
// Get the subdirectories for the specified directory.
@ -128,6 +131,11 @@ namespace Flow.Launcher.Plugin.SharedCommands
}
}
public static void OpenContainingFolder(string path)
{
Process.Start(FileExplorerProgramEXE, $" /select,\"{path}\"");
}
///<summary>
/// This checks whether a given string is a directory path or network location string.
/// It does not check if location actually exists.

View file

@ -77,7 +77,7 @@ namespace Flow.Launcher.Test
}
[TestCase("Chrome")]
public void WhenGivenNotAllCharactersFoundInSearchStringThenShouldReturnZeroScore(string searchString)
public void WhenNotAllCharactersFoundInSearchString_ThenShouldReturnZeroScore(string searchString)
{
var compareString = "Can have rum only in my glass";
var matcher = new StringMatcher();
@ -92,7 +92,7 @@ namespace Flow.Launcher.Test
[TestCase("cand")]
[TestCase("cpywa")]
[TestCase("ccs")]
public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm)
public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm)
{
var results = new List<Result>();
var matcher = new StringMatcher();
@ -107,7 +107,10 @@ namespace Flow.Launcher.Test
foreach (var precisionScore in GetPrecisionScores())
{
var filteredResult = results.Where(result => result.Score >= precisionScore).Select(result => result).OrderByDescending(x => x.Score).ToList();
var filteredResult = results.Where(result => result.Score >= precisionScore)
.Select(result => result)
.OrderByDescending(x => x.Score)
.ToList();
Debug.WriteLine("");
Debug.WriteLine("###############################################");
@ -124,20 +127,22 @@ namespace Flow.Launcher.Test
}
[TestCase(Chrome, Chrome, 157)]
[TestCase(Chrome, LastIsChrome, 103)]
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 21)]
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 15)]
[TestCase(Chrome, LastIsChrome, 147)]
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 25)]
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 21)]
[TestCase(Chrome, CandyCrushSagaFromKing, 0)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, 56)]
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 99)]//double spacing intended
public void WhenGivenQueryStringThenShouldReturnCurrentScoring(string queryString, string compareString, int expectedScore)
[TestCase("sql", MicrosoftSqlServerManagementStudio, 110)]
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)]//double spacing intended
public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring(
string queryString, string compareString, int expectedScore)
{
// When, Given
var matcher = new StringMatcher();
var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore;
// Should
Assert.AreEqual(expectedScore, rawScore, $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
Assert.AreEqual(expectedScore, rawScore,
$"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
}
[TestCase("goo", "Google Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
@ -150,7 +155,7 @@ namespace Flow.Launcher.Test
[TestCase("ccs", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("cand", "Candy Crush Saga from King",StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("cand", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual(
public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual(
string queryString,
string compareString,
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
@ -185,8 +190,8 @@ namespace Flow.Launcher.Test
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql serv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sqlserv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql servman", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("servez", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql servz", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql serv man", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql studio", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("mic", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
@ -199,7 +204,7 @@ namespace Flow.Launcher.Test
[TestCase("cod", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("code", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("codes", "Visual Studio Codes", StringMatcher.SearchPrecisionScore.Regular, true)]
public void WhenGivenQueryShouldReturnResultsContainingAllQuerySubstrings(
public void WhenGivenQuery_ShouldReturnResults_ContainingAllQuerySubstrings(
string queryString,
string compareString,
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
@ -225,5 +230,60 @@ namespace Flow.Launcher.Test
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
$"Precision Score: {(int)expectedPrecisionScore}");
}
[TestCase("man", "Task Manager", "eManual")]
[TestCase("term", "Windows Terminal", "Character Map")]
[TestCase("winterm", "Windows Terminal", "Cygwin64 Terminal")]
public void WhenGivenAQuery_Scoring_ShouldGiveMoreWeightToStartOfNewWord(
string queryString, string compareString1, string compareString2)
{
// When
var matcher = new StringMatcher { UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular };
// Given
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
var compareString2Result = matcher.FuzzyMatch(queryString, compareString2);
Debug.WriteLine("");
Debug.WriteLine("###############################################");
Debug.WriteLine($"QueryString: \"{queryString}\"{Environment.NewLine}");
Debug.WriteLine($"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}");
Debug.WriteLine($"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}");
Debug.WriteLine("###############################################");
Debug.WriteLine("");
// Should
Assert.True(compareString1Result.Score > compareString2Result.Score,
$"Query: \"{queryString}\"{Environment.NewLine} " +
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" +
$"Should be greater than{ Environment.NewLine}" +
$"CompareString2: \"{compareString2}\", Score: {compareString1Result.Score}{Environment.NewLine}");
}
[TestCase("vim", "Vim", "ignoreDescription", "ignore.exe", "Vim Diff", "ignoreDescription", "ignore.exe")]
public void WhenMultipleResults_ExactMatchingResult_ShouldHaveGreatestScore(
string queryString, string firstName, string firstDescription, string firstExecutableName,
string secondName, string secondDescription, string secondExecutableName)
{
// Act
var matcher = new StringMatcher();
var firstNameMatch = matcher.FuzzyMatch(queryString, firstName).RawScore;
var firstDescriptionMatch = matcher.FuzzyMatch(queryString, firstDescription).RawScore;
var firstExecutableNameMatch = matcher.FuzzyMatch(queryString, firstExecutableName).RawScore;
var secondNameMatch = matcher.FuzzyMatch(queryString, secondName).RawScore;
var secondDescriptionMatch = matcher.FuzzyMatch(queryString, secondDescription).RawScore;
var secondExecutableNameMatch = matcher.FuzzyMatch(queryString, secondExecutableName).RawScore;
var firstScore = new[] { firstNameMatch, firstDescriptionMatch, firstExecutableNameMatch }.Max();
var secondScore = new[] { secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch }.Max();
// Assert
Assert.IsTrue(firstScore > secondScore,
$"Query: \"{queryString}\"{Environment.NewLine} " +
$"Name of first: \"{firstName}\", Final Score: {firstScore}{Environment.NewLine}" +
$"Should be greater than{ Environment.NewLine}" +
$"Name of second: \"{secondName}\", Final Score: {secondScore}{Environment.NewLine}");
}
}
}

View file

@ -62,18 +62,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
SolutionAssemblyInfo.cs = SolutionAssemblyInfo.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloWorldCSharp", "Plugins\HelloWorldCSharp\HelloWorldCSharp.csproj", "{03FFA443-5F50-48D5-8869-F3DF316803AA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Shell", "Plugins\Flow.Launcher.Plugin.Shell\Flow.Launcher.Plugin.Shell.csproj", "{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.BrowserBookmark", "Plugins\Flow.Launcher.Plugin.BrowserBookmark\Flow.Launcher.Plugin.BrowserBookmark.csproj", "{9B130CC5-14FB-41FF-B310-0A95B6894C37}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Calculator", "Plugins\Flow.Launcher.Plugin.Calculator\Flow.Launcher.Plugin.Calculator.csproj", "{59BD9891-3837-438A-958D-ADC7F91F6F7E}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "HelloWorldFSharp", "Plugins\HelloWorldFSharp\HelloWorldFSharp.fsproj", "{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Explorer", "Plugins\Flow.Launcher.Plugin.Explorer\Flow.Launcher.Plugin.Explorer.csproj", "{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.ProcessKiller", "Plugins\Flow.Launcher.Plugin.ProcessKiller\Flow.Launcher.Plugin.ProcessKiller.csproj", "{588088F4-3262-4F9F-9663-A05DE12534C3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -253,18 +251,6 @@ Global
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Release|x64.Build.0 = Release|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Release|x86.ActiveCfg = Release|Any CPU
{230AE83F-E92E-4E69-8355-426B305DA9C0}.Release|x86.Build.0 = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|x64.ActiveCfg = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|x64.Build.0 = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|x86.ActiveCfg = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Debug|x86.Build.0 = Debug|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|Any CPU.Build.0 = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|x64.ActiveCfg = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|x64.Build.0 = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|x86.ActiveCfg = Release|Any CPU
{03FFA443-5F50-48D5-8869-F3DF316803AA}.Release|x86.Build.0 = Release|Any CPU
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -301,18 +287,6 @@ Global
{59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x64.Build.0 = Release|Any CPU
{59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x86.ActiveCfg = Release|Any CPU
{59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x86.Build.0 = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|x64.ActiveCfg = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|x64.Build.0 = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|x86.ActiveCfg = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Debug|x86.Build.0 = Debug|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|Any CPU.Build.0 = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x64.ActiveCfg = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x64.Build.0 = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x86.ActiveCfg = Release|Any CPU
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E}.Release|x86.Build.0 = Release|Any CPU
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -325,6 +299,18 @@ Global
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x64.Build.0 = Release|Any CPU
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x86.ActiveCfg = Release|Any CPU
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}.Release|x86.Build.0 = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|x64.ActiveCfg = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|x64.Build.0 = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|x86.ActiveCfg = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Debug|x86.Build.0 = Debug|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|Any CPU.Build.0 = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x64.ActiveCfg = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x64.Build.0 = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x86.ActiveCfg = Release|Any CPU
{588088F4-3262-4F9F-9663-A05DE12534C3}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -339,12 +325,11 @@ Global
{A3DCCBCA-ACC1-421D-B16E-210896234C26} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{F35190AA-4758-4D9E-A193-E3BDF6AD3567} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{230AE83F-E92E-4E69-8355-426B305DA9C0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{03FFA443-5F50-48D5-8869-F3DF316803AA} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{9B130CC5-14FB-41FF-B310-0A95B6894C37} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{30DDA7D9-3712-44F4-BD18-DC1C05B2DD9E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
{588088F4-3262-4F9F-9663-A05DE12534C3} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F26ACB50-3F6C-4907-B0C9-1ADACC1D0DED}

View file

@ -62,7 +62,7 @@
</TextBlock>
</StackPanel>
<TextBlock Style="{DynamicResource ItemTitleStyle}" DockPanel.Dock="Left"
VerticalAlignment="Center" ToolTip="{Binding Result.Title}" x:Name="Title"
VerticalAlignment="Center" ToolTip="{Binding ShowTitleToolTip}" x:Name="Title"
Text="{Binding Result.Title}">
<vm:ResultsViewModel.FormattedText>
<MultiBinding Converter="{StaticResource HighlightTextConverter}">
@ -71,7 +71,7 @@
</MultiBinding>
</vm:ResultsViewModel.FormattedText>
</TextBlock>
<TextBlock Style="{DynamicResource ItemSubTitleStyle}" ToolTip="{Binding Result.SubTitle}"
<TextBlock Style="{DynamicResource ItemSubTitleStyle}" ToolTip="{Binding ShowSubTitleToolTip}"
Grid.Row="1" x:Name="SubTitle" Text="{Binding Result.SubTitle}" MinWidth="750">
<vm:ResultsViewModel.FormattedText>
<MultiBinding Converter="{StaticResource HighlightTextConverter}">

View file

@ -21,6 +21,7 @@
</Style>
<Style x:Key="WindowStyle" BasedOn="{StaticResource BaseWindowStyle}" TargetType="{x:Type Window}">
<Setter Property="Width" Value="750" /> <!-- This is used to set the blur width only -->
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="Black" Opacity="0.3"/>

View file

@ -21,6 +21,7 @@
</Style>
<Style x:Key="WindowStyle" BasedOn="{StaticResource BaseWindowStyle}" TargetType="{x:Type Window}">
<Setter Property="Width" Value="750" /> <!-- This is used to set the blur width only -->
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="White" Opacity="0.1"/>

View file

@ -29,6 +29,14 @@ namespace Flow.Launcher.ViewModel
public string OpenResultModifiers => Settings.OpenResultModifiers;
public string ShowTitleToolTip => string.IsNullOrEmpty(Result.TitleToolTip)
? Result.Title
: Result.TitleToolTip;
public string ShowSubTitleToolTip => string.IsNullOrEmpty(Result.SubTitleToolTip)
? Result.SubTitle
: Result.SubTitleToolTip;
public ImageSource Image
{
get

View file

@ -1,102 +0,0 @@
<!--
===================================================
DO NOT DELETE UNTIL YOU HAVE READ THE FIRST SECTION
===================================================
To be able to help you we need enough information to find
what is causing your issue. Please follow the instructions
and type the requested information where prompted.
===================================================
中文版说明,请仔细阅读并在填写后删除此说明段落,无效的反馈会被直接关闭
===================================================
请遵循以下说明,提供我们所需要的信息来,这样我们才能找出导致你
遇到的问题的原因,来更好的帮助你。
如果可以的话,请附上一份英文的翻译(哪怕是直接使用谷歌翻译)
请注意,无效的反馈(尤其是没有填写下面那些信息的反馈)会被直接关闭!
-->
### Are you submitting a bug report?
(write your answer here)
<!--
If your answer is "Yes", please follow the instructions below.
If your answer is "No", you may delete everything below and then proceed
to write your concern.
你是否是在提交一个bug软件缺陷
如果你的答案是『是的』,请继续回答下面的提问。
如果你的答案是『不是』,你可以删除下面的一切,然后说出你的想法。
-->
<!--
Some problems are known of and there are
already workarounds or existing issues.
Known problems for 1.3.183:
We are aware of the following issues and the following workarounds exist:
1. `System.NullReferenceException`: https://github.com/Wox-launcher/Wox/releases/tag/v1.3.475
2. `System.UriFormatException`: delete your old theme file
3. `System.Threading.Tasks.TaskCanceledException`: https://github.com/Wox-launcher/Wox/releases/tag/v1.3.475
4. `System.AggregateException`: see https://github.com/Wox-launcher/Wox/issues/1777
If none of them match your case, please continue.
已知问题及解决方案:(略,如果有任何问题请继续提问)
-->
### Steps to reproduce
<!--
Write what steps you took in order for this problem to happen.
If you do not provide any steps, we cannot replicate your problem
and will not be able to help you.
复现问题的步骤:
在此按顺序一步步说明在问题发生前你做了什么操作。
如果你没法说明你的步骤,那么我们无法复现你的问题,也就无法帮助你了。
-->
1.
2.
3.
### Flow Launcher Error Window text
<!--
Paste below the logs generated by the Flow.Launcher error reporter.
请在此处粘贴 Flow Launcher 错误报告程序提供的日志。
-->
(paste here)
### Detailed logs
<!--
Please also provide detailed logs. The latest log file
can be found here: %APPDATA%\Flow.Launcher\Logs\version\<date>.txt
Drag and drop that file below this comment.
In some cases you can skip uploading the the logs, but the chances
of us being able to solve the problem will be higher if you do.
详细日志:
请在此处提供详细日志。你可以在这个目录找到最新的日志:
%APPDATA%\Flow.Launcher\Logs\version\<date>.txt
直接拖放文件到这个评论就可以上传(国内大部分地区可能需要翻墙才能上传,作为备选方案可以考虑直接在此粘贴文件内容)
-->
(drop your log file here)
### Screenshots (optional)
<!--
If you think it will be helpful to provide a screenshot
to better describe your problem, upload it below
截图(可选)
如果你认为截图能够有助于说明你的问题,请在下面上传它。
-->
(drop your screenshot here)

View file

@ -156,7 +156,7 @@ namespace Flow.Launcher.Plugin.Explorer
{
try
{
Process.Start("explorer.exe", $" /select,\"{record.FullPath}\"");
FilesFolders.OpenContainingFolder(record.FullPath);
}
catch (Exception e)
{

View file

@ -16,7 +16,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal const string DifferentUserIconImagePath = "Images\\user.png";
internal const string IndexingOptionsIconImagePath = "Images\\windowsindexingoptions.png";
internal const string DefaultFolderSubtitleString = "Ctrl + Enter to open the directory";
internal const string ToolTipOpenDirectory = "Ctrl + Enter to open the directory";
internal const string ToolTipOpenContainingFolder = "Ctrl + Enter to open the containing folder";
internal const char AllFilesFolderSearchWildcard = '>';

View file

@ -65,7 +65,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
if (fileSystemInfo is System.IO.DirectoryInfo)
{
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, Constants.DefaultFolderSubtitleString, fileSystemInfo.FullName, query, true, false));
folderList.Add(resultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, fileSystemInfo.FullName, query, true, false));
}
else
{

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
@ -18,12 +18,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal static Dictionary<string, string> LoadEnvironmentStringPaths()
{
var envStringPaths = new Dictionary<string, string>();
var envStringPaths = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
{
if (Directory.Exists(special.Value.ToString()))
{
// Variables are returned with a mixture of all upper/lower case.
// Call ToLower() to make the results look consistent
envStringPaths.Add(special.Key.ToString().ToLower(), special.Value.ToString());
}
}
@ -82,11 +84,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
foreach (var p in environmentVariables)
{
if (p.Key.StartsWith(search))
if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase))
{
results.Add(new ResultManager(context).CreateFolderResult($"%{p.Key}%", p.Value, p.Value, query));
}
}
return results;
}
}

View file

@ -12,7 +12,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
return folderLinks
.Select(item =>
new ResultManager(context)
.CreateFolderResult(item.Nickname, Constants.DefaultFolderSubtitleString, item.Path, query))
.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
.ToList();
string search = query.Search.ToLower();
@ -21,7 +21,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
return queriedFolderLinks.Select(item =>
new ResultManager(context)
.CreateFolderResult(item.Nickname, Constants.DefaultFolderSubtitleString, item.Path, query))
.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
.ToList();
}
}

View file

@ -1,6 +1,7 @@
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
@ -38,13 +39,15 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return false;
}
}
string changeTo = path.EndsWith(Constants.DirectorySeperator) ? path : path + Constants.DirectorySeperator;
context.API.ChangeQuery(string.IsNullOrEmpty(query.ActionKeyword) ?
changeTo :
query.ActionKeyword + " " + changeTo);
return false;
},
TitleToolTip = Constants.ToolTipOpenDirectory,
SubTitleToolTip = Constants.ToolTipOpenDirectory,
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, ShowIndexState = showIndexState, WindowsIndexed = windowsIndexed }
};
}
@ -85,6 +88,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
FilesFolders.OpenPath(retrievedDirectoryPath);
return true;
},
TitleToolTip = retrievedDirectoryPath,
SubTitleToolTip = retrievedDirectoryPath,
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = retrievedDirectoryPath, ShowIndexState = true, WindowsIndexed = windowsIndexed }
};
}
@ -101,7 +106,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
try
{
FilesFolders.OpenPath(filePath);
if (c.SpecialKeyState.CtrlPressed)
{
FilesFolders.OpenContainingFolder(filePath);
}
else
{
FilesFolders.OpenPath(filePath);
}
}
catch (Exception ex)
{
@ -110,6 +122,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return true;
},
TitleToolTip = Constants.ToolTipOpenContainingFolder,
SubTitleToolTip = Constants.ToolTipOpenContainingFolder,
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, ShowIndexState = showIndexState, WindowsIndexed = windowsIndexed }
};
return result;

View file

@ -54,8 +54,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
if (dataReaderResults.GetString(2) == "Directory")
{
folderResults.Add(resultManager.CreateFolderResult(
dataReaderResults.GetString(0),
Constants.DefaultFolderSubtitleString,
dataReaderResults.GetString(0),
dataReaderResults.GetString(1),
dataReaderResults.GetString(1),
query, true, true));
}

View file

@ -4,7 +4,7 @@
"Name": "Explorer",
"Description": "Search and manage files and folders. Explorer utilises Windows Index Search",
"Author": "Jeremy Wu",
"Version": "1.0.0",
"Version": "1.1.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -17,7 +17,6 @@ namespace Flow.Launcher.Plugin.PluginManagement
public class Main : IPlugin, IPluginI18n
{
private static string APIBASE = "http://api.wox.one";
private static string PluginConfigName = "plugin.json";
private static string pluginSearchUrl = APIBASE + "/plugin/search/";
private const string ListCommand = "list";
private const string InstallCommand = "install";

View file

@ -1,55 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<ProjectGuid>{03FFA443-5F50-48D5-8869-F3DF316803AA}</ProjectGuid>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>HelloWorldCSharp</RootNamespace>
<AssemblyName>HelloWorldCSharp</AssemblyName>
<AssemblyName>Flow.Launcher.Plugin.ProcessKiller</AssemblyName>
<PackageId>Flow.Launcher.Plugin.ProcessKiller</PackageId>
<Authors>Flow-Launcher</Authors>
<PackageProjectUrl>https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller</PackageProjectUrl>
<RepositoryUrl>https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller</RepositoryUrl>
<PackageTags>flow-launcher flow-plugin</PackageTags>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<ApplicationIcon />
<OutputType>Library</OutputType>
<StartupObject />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Output\Debug\Plugins\HelloWorldCSharp\</OutputPath>
<OutputPath>..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.ProcessKiller\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Output\Release\Plugins\HelloWorldCSharp\</OutputPath>
<OutputPath>..\..\Output\Release\Plugins\Flow.Launcher.Plugin.ProcessKiller\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<None Include="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="Images\app.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Languages\en.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="plugin.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,11 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="flowlauncher_plugin_processkiller_plugin_name">Process Killer</system:String>
<system:String x:Key="flowlauncher_plugin_processkiller_plugin_description">Kill running processes from Flow Launcher</system:String>
<system:String x:Key="flowlauncher_plugin_processkiller_kill_all">kill all "{0}" processes</system:String>
<system:String x:Key="flowlauncher_plugin_processkiller_kill_instances">kill all instances</system:String>
</ResourceDictionary>

View file

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Dynamic;
using System.Runtime.InteropServices;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Plugin.ProcessKiller
{
public class Main : IPlugin, IPluginI18n, IContextMenu
{
private ProcessHelper processHelper = new ProcessHelper();
private static PluginInitContext _context;
public void Init(PluginInitContext context)
{
_context = context;
}
public List<Result> Query(Query query)
{
var termToSearch = query.Terms.Length <= 1
? null
: string.Join(Plugin.Query.TermSeperater, query.Terms.Skip(1)).ToLower();
var processlist = processHelper.GetMatchingProcesses(termToSearch);
return !processlist.Any()
? null
: CreateResultsFromProcesses(processlist, termToSearch);
}
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return _context.API.GetTranslation("flowlauncher_plugin_processkiller_plugin_description");
}
public List<Result> LoadContextMenus(Result result)
{
var menuOptions = new List<Result>();
var processPath = result.SubTitle;
// get all non-system processes whose file path matches that of the given result (processPath)
var similarProcesses = processHelper.GetSimilarProcesses(processPath);
menuOptions.Add(new Result
{
Title = _context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_instances"),
SubTitle = processPath,
Action = _ =>
{
foreach (var p in similarProcesses)
{
processHelper.TryKill(p);
}
return true;
},
IcoPath = processPath
});
return menuOptions;
}
private List<Result> CreateResultsFromProcesses(List<ProcessResult> processlist, string termToSearch)
{
var results = new List<Result>();
foreach (var pr in processlist)
{
var p = pr.Process;
var path = processHelper.TryGetProcessFilename(p);
results.Add(new Result()
{
IcoPath = path,
Title = p.ProcessName + " - " + p.Id,
SubTitle = path,
TitleHighlightData = StringMatcher.FuzzySearch(termToSearch, p.ProcessName).MatchData,
Score = pr.Score,
Action = (c) =>
{
processHelper.TryKill(p);
return true;
}
});
}
// When there are multiple results AND all of them are instances of the same executable
// add a quick option to kill them all at the top of the results.
var firstResult = results.FirstOrDefault()?.SubTitle;
if (processlist.Count > 1 && !string.IsNullOrEmpty(termToSearch) && results.All(r => r.SubTitle == firstResult))
{
results.Insert(0, new Result()
{
IcoPath = "Images/app.png",
Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), termToSearch),
SubTitle = "",
Score = 200,
Action = (c) =>
{
foreach (var p in processlist)
{
processHelper.TryKill(p.Process);
}
return true;
}
});
}
return results;
}
}
}

View file

@ -0,0 +1,125 @@
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Flow.Launcher.Plugin.ProcessKiller
{
internal class ProcessHelper
{
private readonly HashSet<string> _systemProcessList = new HashSet<string>()
{
"conhost",
"svchost",
"idle",
"system",
"rundll32",
"csrss",
"lsass",
"lsm",
"smss",
"wininit",
"winlogon",
"services",
"spoolsv",
"explorer"
};
private bool IsSystemProcess(Process p) => _systemProcessList.Contains(p.ProcessName.ToLower());
/// <summary>
/// Returns a ProcessResult for evey running non-system process whose name matches the given searchTerm
/// </summary>
public List<ProcessResult> GetMatchingProcesses(string searchTerm)
{
var processlist = new List<ProcessResult>();
foreach (var p in Process.GetProcesses())
{
if (IsSystemProcess(p)) continue;
if (string.IsNullOrWhiteSpace(searchTerm))
{
// show all non-system processes
processlist.Add(new ProcessResult(p, 0));
}
else
{
var score = StringMatcher.FuzzySearch(searchTerm, p.ProcessName + p.Id).Score;
if (score > 0)
{
processlist.Add(new ProcessResult(p, score));
}
}
}
return processlist;
}
/// <summary>
/// Returns all non-system processes whose file path matches the given processPath
/// </summary>
public IEnumerable<Process> GetSimilarProcesses(string processPath)
{
return Process.GetProcesses().Where(p => !IsSystemProcess(p) && TryGetProcessFilename(p) == processPath);
}
public void TryKill(Process p)
{
try
{
if (!p.HasExited)
{
p.Kill();
}
}
catch (Exception e)
{
Log.Exception($"|ProcessKiller.CreateResultsFromProcesses|Failed to kill process {p.ProcessName}", e);
}
}
public string TryGetProcessFilename(Process p)
{
try
{
int capacity = 2000;
StringBuilder builder = new StringBuilder(capacity);
IntPtr ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id);
if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity))
{
return String.Empty;
}
return builder.ToString();
}
catch
{
return "";
}
}
[Flags]
private enum ProcessAccessFlags : uint
{
QueryLimitedInformation = 0x00001000
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool QueryFullProcessImageName(
[In] IntPtr hProcess,
[In] int dwFlags,
[Out] StringBuilder lpExeName,
ref int lpdwSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(
ProcessAccessFlags processAccess,
bool bInheritHandle,
int processId);
}
}

View file

@ -0,0 +1,17 @@
using System.Diagnostics;
namespace Flow.Launcher.Plugin.ProcessKiller
{
internal class ProcessResult
{
public ProcessResult(Process process, int score)
{
Process = process;
Score = score;
}
public Process Process { get; }
public int Score { get; }
}
}

View file

@ -0,0 +1,8 @@
Process Killer Plugin
=====================
A Flow-Launcher plugin that allows you to kill running processes.
Based on Wox.Plugin.ProcessKiller, originally created by [cxfksword](https://github.com/cxfksword/Wox.Plugin.ProcessKiller) and improved by [theClueless](https://github.com/theClueless/Wox.Plugins/tree/master/Wox.Plugin.ProcessKiller).
![image](https://user-images.githubusercontent.com/697917/86478286-b4697700-bd52-11ea-875a-b3b90fa34bf2.png)

View file

@ -0,0 +1,12 @@
{
"ID":"b64d0a79-329a-48b0-b53f-d658318a1bf6",
"ActionKeyword":"kill",
"Name":"Process Killer",
"Description":"kill running processes from Flow",
"Author":"Flow-Launcher",
"Version":"1.0.0",
"Language":"csharp",
"Website":"https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller",
"IcoPath":"Images\\app.png",
"ExecuteFileName":"Flow.Launcher.Plugin.ProcessKiller.dll"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Flow.Launcher.Plugin;
namespace HelloWorldCSharp
{
class Main : IPlugin
{
public List<Result> Query(Query query)
{
var result = new Result
{
Title = "Hello World from CSharp",
SubTitle = $"Query: {query.Search}",
IcoPath = "app.png"
};
return new List<Result> {result};
}
public void Init(PluginInitContext context)
{
}
}
}

View file

@ -1,13 +0,0 @@
{
"ID": "CEA0FDFC6D3B4085823D60DC76F28844",
"ActionKeyword": "hc",
"Name": "Hello World CSharp",
"Description": "Hello World CSharp",
"Author": "happlebao",
"Version": "1.0.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "HelloWorldCSharp.dll",
"IcoPath": "app.png",
"Disabled": true
}

View file

@ -1,36 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>..\..\Output\Debug\Plugins\HelloWorldFSharp\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<OutputPath>..\..\Output\Release\Plugins\HelloWorldFSharp\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Content Include="Images\app.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="plugin.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Compile Include="Main.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="FSharp.Core" Version="4.7.1" />
</ItemGroup>
</Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -1,31 +0,0 @@
namespace HelloWorldFSharp
open Flow.Launcher.Plugin
open System.Collections.Generic
type HelloWorldFSharpPlugin() =
let mutable initContext = PluginInitContext()
interface IPlugin with
member this.Init (context: PluginInitContext) =
initContext <- context
member this.Query (query: Query) =
List<Result> [
Result (Title = "Hello World from F#",
SubTitle = sprintf "Query: %s" query.Search)
Result (Title = "Browse source code of this plugin",
SubTitle = "click to open in browser",
Action = (fun ctx ->
initContext.CurrentPluginMetadata.Website
|> System.Diagnostics.Process.Start
|> ignore
true))
Result (Title = "Trigger a tray message",
Action = (fun _ ->
initContext.API.ShowMsg ("Sample tray message", "from the F# plugin")
false))
]

View file

@ -1,12 +0,0 @@
{
"ID": "8FF5D5C1F8194958A12E8668FB7ECC04",
"ActionKeyword": "hf",
"Name": "Hello World FSharp",
"Description": "Hello World FSharp",
"Author": "Ioannis G.",
"Version": "1.0.0",
"Language": "fsharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "HelloWorldFSharp.dll",
"IcoPath": "Images\\app.png"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
from flow.launcher import Flow.Launcher
class HelloWorld(FlowLauncher):
def query(self, query):
results = []
results.append({
"Title": "Hello World",
"SubTitle": "Query: {}".format(query),
"IcoPath":"Images/app.ico",
"ContextData": "ctxData"
})
return results
def context_menu(self, data):
results = []
results.append({
"Title": "Context menu entry",
"SubTitle": "Data: {}".format(data),
"IcoPath":"Images/app.ico"
})
return results
if __name__ == "__main__":
HelloWorld()

View file

@ -1,12 +0,0 @@
{
"ID":"2f4e384e-76ce-45c3-aea2-b16f5e5c328f",
"ActionKeyword":"h",
"Name":"Hello World Python",
"Description":"Hello World",
"Author":"happlebao",
"Version":"1.0",
"Language":"python",
"Website":"https://github.com/Flow-Launcher/Flow.Launcher",
"IcoPath":"Images\\app.png",
"ExecuteFileName":"main.py"
}

View file

@ -36,7 +36,6 @@ function Copy-Resources ($path, $config) {
$output = "$path\Output"
$target = "$output\$config"
Copy-Item -Recurse -Force $project\Images\* $target\Images\
Copy-Item -Recurse -Force $path\Plugins\HelloWorldPython $target\Plugins\HelloWorldPython
Copy-Item -Recurse -Force $path\JsonRPC $target\JsonRPC
# making version static as multiple versions can exist in the nuget folder and in the case a breaking change is introduced.
Copy-Item -Force $env:USERPROFILE\.nuget\packages\squirrel.windows\1.5.2\tools\Squirrel.exe $output\Update.exe

View file

@ -16,6 +16,6 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: AssemblyVersion("1.0.0")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyVersion("1.1.0")]
[assembly: AssemblyFileVersion("1.1.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]

View file

@ -1,4 +1,4 @@
version: '0.9.0.{build}'
version: '1.1.0.{build}'
init:
- ps: |