diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
index 189a6669e..b0cc07e1a 100644
--- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj
+++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
@@ -53,6 +53,7 @@
+
diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
index fcf178445..2c75f9633 100644
--- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs
+++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
@@ -3,13 +3,14 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Runtime.Loader;
using System.Threading.Tasks;
using System.Windows.Forms;
+using Droplex;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
+using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Core.Plugin
{
@@ -22,7 +23,7 @@ namespace Flow.Launcher.Core.Plugin
public static List Plugins(List metadatas, PluginsSettings settings)
{
var dotnetPlugins = DotNetPlugins(metadatas);
- var pythonPlugins = PythonPlugins(metadatas, settings.PythonDirectory);
+ var pythonPlugins = PythonPlugins(metadatas, settings);
var executablePlugins = ExecutablePlugins(metadatas);
var plugins = dotnetPlugins.Concat(pythonPlugins).Concat(executablePlugins).ToList();
return plugins;
@@ -113,11 +114,14 @@ namespace Flow.Launcher.Core.Plugin
return plugins;
}
- public static IEnumerable PythonPlugins(List source, string pythonDirectory)
+ public static IEnumerable PythonPlugins(List source, PluginsSettings settings)
{
- // try to set Constant.PythonPath, either from
+ if (!source.Any(o => o.Language.ToUpper() == AllowedLanguage.Python))
+ return new List();
+
+ // Try setting Constant.PythonPath first, either from
// PATH or from the given pythonDirectory
- if (string.IsNullOrEmpty(pythonDirectory))
+ if (string.IsNullOrEmpty(settings.PythonDirectory))
{
var paths = Environment.GetEnvironmentVariable(PATH);
if (paths != null)
@@ -129,47 +133,103 @@ namespace Flow.Launcher.Core.Plugin
if (pythonInPath)
{
- Constant.PythonPath = PythonExecutable;
+ Constant.PythonPath =
+ Path.Combine(paths.Split(';').Where(p => p.ToLower().Contains(Python)).FirstOrDefault(), PythonExecutable);
+ settings.PythonDirectory = FilesFolders.GetPreviousExistingDirectory(FilesFolders.LocationExists, Constant.PythonPath);
}
else
{
- Log.Error("|PluginsLoader.PythonPlugins|Python can't be found in PATH.");
+ Log.Error("PluginsLoader","Failed to set Python path despite the environment variable PATH is found", "PythonPlugins");
}
}
- else
- {
- Log.Error("|PluginsLoader.PythonPlugins|PATH environment variable is not set.");
- }
}
else
{
- var path = Path.Combine(pythonDirectory, PythonExecutable);
+ var path = Path.Combine(settings.PythonDirectory, PythonExecutable);
if (File.Exists(path))
{
Constant.PythonPath = path;
}
else
{
- Log.Error($"|PluginsLoader.PythonPlugins|Can't find python executable in {path}");
+ Log.Error("PluginsLoader",$"Tried to automatically set from Settings.PythonDirectory " +
+ $"but can't find python executable in {path}", "PythonPlugins");
}
}
- // if we have a path to the python executable,
- // load every python plugin pair.
- if (String.IsNullOrEmpty(Constant.PythonPath))
+ if (string.IsNullOrEmpty(settings.PythonDirectory))
{
+ if (MessageBox.Show("Flow detected you have installed Python plugins, " +
+ "would you like to install Python to run them? " +
+ Environment.NewLine + Environment.NewLine +
+ "Click no if it's already installed, " +
+ "and you will be prompted to select the folder that contains the Python executable",
+ string.Empty, MessageBoxButtons.YesNo) == DialogResult.No
+ && string.IsNullOrEmpty(settings.PythonDirectory))
+ {
+ var dlg = new FolderBrowserDialog
+ {
+ SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
+ };
+
+ var result = dlg.ShowDialog();
+ if (result == DialogResult.OK)
+ {
+ string pythonDirectory = dlg.SelectedPath;
+ if (!string.IsNullOrEmpty(pythonDirectory))
+ {
+ var pythonPath = Path.Combine(pythonDirectory, PythonExecutable);
+ if (File.Exists(pythonPath))
+ {
+ settings.PythonDirectory = pythonDirectory;
+ Constant.PythonPath = pythonPath;
+ }
+ else
+ {
+ MessageBox.Show("Can't find python in given directory");
+ }
+ }
+ }
+ }
+ else
+ {
+ DroplexPackage.Drop(App.python3_9_1).Wait();
+
+ var installedPythonDirectory =
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python39");
+ var pythonPath = Path.Combine(installedPythonDirectory, PythonExecutable);
+ if (FilesFolders.FileExists(pythonPath))
+ {
+ settings.PythonDirectory = installedPythonDirectory;
+ Constant.PythonPath = pythonPath;
+ }
+ else
+ {
+ Log.Error("PluginsLoader",
+ $"Failed to set Python path after Droplex install, {pythonPath} does not exist",
+ "PythonPlugins");
+ }
+ }
+ }
+
+ if (string.IsNullOrEmpty(settings.PythonDirectory))
+ {
+ MessageBox.Show("Unable to set Python executable path, please try from Flow's settings (scroll down to the bottom).");
+ Log.Error("PluginsLoader",
+ $"Not able to successfully set Python path, the PythonDirectory variable is still an empty string.",
+ "PythonPlugins");
+
return new List();
}
- else
- {
- return source
- .Where(o => o.Language.ToUpper() == AllowedLanguage.Python)
- .Select(metadata => new PluginPair
- {
- Plugin = new PythonPlugin(Constant.PythonPath),
- Metadata = metadata
- });
- }
+
+ return source
+ .Where(o => o.Language.ToUpper() == AllowedLanguage.Python)
+ .Select(metadata => new PluginPair
+ {
+ Plugin = new PythonPlugin(Constant.PythonPath),
+ Metadata = metadata
+ })
+ .ToList();
}
public static IEnumerable ExecutablePlugins(IEnumerable source)
diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs
index de2e82359..3a3e770a5 100644
--- a/Flow.Launcher.Infrastructure/Http/Http.cs
+++ b/Flow.Launcher.Infrastructure/Http/Http.cs
@@ -75,7 +75,7 @@ namespace Flow.Launcher.Infrastructure.Http
{
try
{
- using var response = await client.GetAsync(url, token);
+ using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
if (response.StatusCode == HttpStatusCode.OK)
{
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
@@ -135,7 +135,7 @@ namespace Flow.Launcher.Infrastructure.Http
public static async Task GetStreamAsync([NotNull] string url, CancellationToken token = default)
{
Log.Debug($"|Http.Get|Url <{url}>");
- var response = await client.GetAsync(url, token);
+ var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
return await response.Content.ReadAsStreamAsync();
}
}
diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
index f0e4a79fc..37cfec252 100644
--- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
@@ -12,7 +12,7 @@ namespace Flow.Launcher.Infrastructure.Storage
public class JsonStrorage where T : new()
{
private readonly JsonSerializerOptions _serializerSettings;
- private T _data;
+ protected T _data;
// need a new directory name
public const string DirectoryName = "Settings";
public const string FileSuffix = ".json";
diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs
index 5418e1837..ca7c454c4 100644
--- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs
@@ -15,5 +15,10 @@ namespace Flow.Launcher.Infrastructure.Storage
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
}
+
+ public PluginJsonStorage(T data) : this()
+ {
+ _data = data;
+ }
}
}
diff --git a/Flow.Launcher.Plugin/IPublicAPI.cs b/Flow.Launcher.Plugin/IPublicAPI.cs
index dd73eb0e5..07e2b2700 100644
--- a/Flow.Launcher.Plugin/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/IPublicAPI.cs
@@ -3,6 +3,7 @@ using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.IO;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@@ -88,17 +89,86 @@ namespace Flow.Launcher.Plugin
///
event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
+ ///
+ /// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses
+ ///
+ /// Query string
+ /// The string that will be compared against the query
+ /// Match results
MatchResult FuzzySearch(string query, string stringToCompare);
+ ///
+ /// Http download the spefic url and return as string
+ ///
+ /// URL to call Http Get
+ /// Cancellation Token
+ /// Task to get string result
Task HttpGetStringAsync(string url, CancellationToken token = default);
+ ///
+ /// Http download the spefic url and return as stream
+ ///
+ /// URL to call Http Get
+ /// Cancellation Token
+ /// Task to get stream result
Task HttpGetStreamAsync(string url, CancellationToken token = default);
- Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath);
+ ///
+ /// Download the specific url to a cretain file path
+ ///
+ /// URL to download file
+ /// place to store file
+ /// Task showing the progress
+ Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default);
+ ///
+ /// Add ActionKeyword for specific plugin
+ ///
+ /// ID for plugin that needs to add action keyword
+ /// The actionkeyword that is supposed to be added
void AddActionKeyword(string pluginId, string newActionKeyword);
+ ///
+ /// Remove ActionKeyword for specific plugin
+ ///
+ /// ID for plugin that needs to remove action keyword
+ /// The actionkeyword that is supposed to be removed
void RemoveActionKeyword(string pluginId, string oldActionKeyword);
+ ///
+ /// Log debug message
+ /// Message will only be logged in Debug mode
+ ///
+ void LogDebug(string className, string message, [CallerMemberName] string methodName = "");
+
+ ///
+ /// Log info message
+ ///
+ void LogInfo(string className, string message, [CallerMemberName] string methodName = "");
+
+ ///
+ /// Log warning message
+ ///
+ void LogWarn(string className, string message, [CallerMemberName] string methodName = "");
+
+ ///
+ /// Log an Exception. Will throw if in debug mode so developer will be aware,
+ /// otherwise logs the eror message. This is the primary logging method used for Flow
+ ///
+ void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "");
+
+ ///
+ /// Load JsonStorage for current plugin. This is the method used to load settings from json in Flow
+ ///
+ /// Type for deserialization
+ ///
+ T LoadJsonStorage() where T : new();
+
+ ///
+ /// Save JsonStorage for current plugin. This is the method used to save settings to json in Flow
+ ///
+ /// Type for Serialization
+ ///
+ void SaveJsonStorage(T setting) where T : new();
}
}
diff --git a/Flow.Launcher.Infrastructure/Storage/ISavable.cs b/Flow.Launcher.Plugin/Interfaces/ISavable.cs
similarity index 81%
rename from Flow.Launcher.Infrastructure/Storage/ISavable.cs
rename to Flow.Launcher.Plugin/Interfaces/ISavable.cs
index 8294a3df8..7c1110e0e 100644
--- a/Flow.Launcher.Infrastructure/Storage/ISavable.cs
+++ b/Flow.Launcher.Plugin/Interfaces/ISavable.cs
@@ -1,4 +1,4 @@
-namespace Flow.Launcher.Infrastructure.Storage
+namespace Flow.Launcher.Plugin
{
///
/// Save plugin settings/cache,
diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj
index 289a502d0..20e847f82 100644
--- a/Flow.Launcher/Flow.Launcher.csproj
+++ b/Flow.Launcher/Flow.Launcher.csproj
@@ -66,6 +66,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/Flow.Launcher/Images/app.ico b/Flow.Launcher/Images/app.ico
new file mode 100644
index 000000000..36b1d22d0
Binary files /dev/null and b/Flow.Launcher/Images/app.ico differ
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index b6bf76b7f..a036949fc 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -121,6 +121,7 @@
New Action Keyword can't be empty
This new Action Keyword is already assigned to another plugin, please choose a different one
Success
+ Completed successfully
Use * if you don't want to specify an action keyword
diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml
index 4cc0b4428..aa0240dd4 100644
--- a/Flow.Launcher/MainWindow.xaml
+++ b/Flow.Launcher/MainWindow.xaml
@@ -34,6 +34,7 @@
+
diff --git a/Flow.Launcher/Msg.xaml.cs b/Flow.Launcher/Msg.xaml.cs
index 4129ce28b..6bb2fc2dc 100644
--- a/Flow.Launcher/Msg.xaml.cs
+++ b/Flow.Launcher/Msg.xaml.cs
@@ -66,7 +66,7 @@ namespace Flow.Launcher
}
if (!File.Exists(iconPath))
{
- imgIco.Source = ImageLoader.Load(Path.Combine(Infrastructure.Constant.ProgramDirectory, "Images\\app.png"));
+ imgIco.Source = ImageLoader.Load(Path.Combine(Constant.ProgramDirectory, "Images\\app.png"));
}
else {
imgIco.Source = ImageLoader.Load(iconPath);
diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs
index 427fd9fc6..9b41c2c05 100644
--- a/Flow.Launcher/PublicAPIInstance.cs
+++ b/Flow.Launcher/PublicAPIInstance.cs
@@ -18,6 +18,9 @@ using System.Threading;
using System.IO;
using Flow.Launcher.Infrastructure.Http;
using JetBrains.Annotations;
+using System.Runtime.CompilerServices;
+using Flow.Launcher.Infrastructure.Logger;
+using Flow.Launcher.Infrastructure.Storage;
namespace Flow.Launcher
{
@@ -61,18 +64,15 @@ namespace Flow.Launcher
// which will cause ungraceful exit
SaveAppAllSettings();
+ // Restart requires Squirrel's Update.exe to be present in the parent folder,
+ // it is only published from the project's release pipeline. When debugging without it,
+ // the project may not restart or just terminates. This is expected.
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
- public void RestarApp()
- {
- RestartApp();
- }
+ public void RestarApp() => RestartApp();
- public void CheckForNewUpdate()
- {
- _settingsVM.UpdateApp();
- }
+ public void CheckForNewUpdate() => _settingsVM.UpdateApp();
public void SaveAppAllSettings()
{
@@ -82,15 +82,9 @@ namespace Flow.Launcher
ImageLoader.Save();
}
- public Task ReloadAllPluginData()
- {
- return PluginManager.ReloadData();
- }
+ public Task ReloadAllPluginData() => PluginManager.ReloadData();
- public void ShowMsg(string title, string subTitle = "", string iconPath = "")
- {
- ShowMsg(title, subTitle, iconPath, true);
- }
+ public void ShowMsg(string title, string subTitle = "", string iconPath = "") => ShowMsg(title, subTitle, iconPath, true);
public void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true)
{
@@ -109,54 +103,40 @@ namespace Flow.Launcher
});
}
- public void StartLoadingBar()
- {
- _mainVM.ProgressBarVisibility = Visibility.Visible;
- }
+ public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible;
- public void StopLoadingBar()
- {
- _mainVM.ProgressBarVisibility = Visibility.Collapsed;
- }
+ public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed;
- public string GetTranslation(string key)
- {
- return InternationalizationManager.Instance.GetTranslation(key);
- }
+ public string GetTranslation(string key) => InternationalizationManager.Instance.GetTranslation(key);
- public List GetAllPlugins()
- {
- return PluginManager.AllPlugins.ToList();
- }
-
- public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
+ public List GetAllPlugins() => PluginManager.AllPlugins.ToList();
public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare);
- public Task HttpGetStringAsync(string url, CancellationToken token = default)
- {
- return Http.GetAsync(url);
- }
+ public Task HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url);
- public Task HttpGetStreamAsync(string url, CancellationToken token = default)
- {
- return Http.GetStreamAsync(url);
- }
+ public Task HttpGetStreamAsync(string url, CancellationToken token = default) => Http.GetStreamAsync(url);
- public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath)
- {
- return Http.DownloadAsync(url, filePath);
- }
+ public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default) => Http.DownloadAsync(url, filePath, token);
- public void AddActionKeyword(string pluginId, string newActionKeyword)
- {
- PluginManager.AddActionKeyword(pluginId, newActionKeyword);
- }
+ public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword);
+
+ public void RemoveActionKeyword(string pluginId, string oldActionKeyword) => PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword);
+
+ public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") => Log.Debug(className, message, methodName);
+
+ public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") => Log.Info(className, message, methodName);
+
+ public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") => Log.Warn(className, message, methodName);
+
+ public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") => Log.Exception(className, message, e, methodName);
+
+ public T LoadJsonStorage() where T : new() => new PluginJsonStorage().Load();
+
+ public void SaveJsonStorage(T setting) where T : new() => new PluginJsonStorage(setting).Save();
+
+ public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
- public void RemoveActionKeyword(string pluginId, string oldActionKeyword)
- {
- PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword);
- }
#endregion
#region Private Methods
diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml
index 4c7eac114..cb0ef2def 100644
--- a/Flow.Launcher/SettingWindow.xaml
+++ b/Flow.Launcher/SettingWindow.xaml
@@ -11,7 +11,7 @@
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
x:Class="Flow.Launcher.SettingWindow"
mc:Ignorable="d"
- Icon="Images\app.png"
+ Icon="Images\app.ico"
Title="{DynamicResource flowlauncher_settings}"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs
index afbe6e197..a12912b67 100644
--- a/Flow.Launcher/ViewModel/MainViewModel.cs
+++ b/Flow.Launcher/ViewModel/MainViewModel.cs
@@ -89,7 +89,6 @@ namespace Flow.Launcher.ViewModel
_resultsViewUpdateTask =
Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted);
-
async Task updateAction()
{
var queue = new Dictionary();
@@ -105,9 +104,7 @@ namespace Flow.Launcher.ViewModel
UpdateResultView(queue.Values);
}
- }
-
- ;
+ };
void continueAction(Task t)
{
@@ -115,8 +112,8 @@ namespace Flow.Launcher.ViewModel
throw t.Exception;
#else
Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}");
- _resultsViewUpdateTask =
- Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted);
+ _resultsViewUpdateTask =
+ Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted);
#endif
}
}
@@ -225,6 +222,25 @@ namespace Flow.Launcher.ViewModel
SelectedResults = Results;
}
});
+
+ ReloadPluginDataCommand = new RelayCommand(_ =>
+ {
+ var msg = new Msg { Owner = Application.Current.MainWindow };
+
+ MainWindowVisibility = Visibility.Collapsed;
+
+ PluginManager
+ .ReloadData()
+ .ContinueWith(_ =>
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ msg.Show(
+ InternationalizationManager.Instance.GetTranslation("success"),
+ InternationalizationManager.Instance.GetTranslation("completedSuccessfully"),
+ "");
+ }))
+ .ConfigureAwait(false);
+ });
}
#endregion
@@ -313,6 +329,7 @@ namespace Flow.Launcher.ViewModel
public ICommand LoadContextMenuCommand { get; set; }
public ICommand LoadHistoryCommand { get; set; }
public ICommand OpenResultCommand { get; set; }
+ public ICommand ReloadPluginDataCommand { get; set; }
public string OpenResultCommandModifiers { get; private set; }
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png
new file mode 100644
index 000000000..0dee870b6
Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png differ
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml
index 3beccb5e7..f456e7495 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml
@@ -12,4 +12,6 @@
New tab
Set browser from path:
Choose
+ Copy url
+ Copy the bookmark's url to clipboard
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs
index 47493654f..b889bb0d0 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs
@@ -1,6 +1,9 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
+using System.Windows;
using System.Windows.Controls;
+using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin.BrowserBookmark.Commands;
using Flow.Launcher.Plugin.BrowserBookmark.Models;
@@ -9,7 +12,7 @@ using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Plugin.BrowserBookmark
{
- public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable
+ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable, IContextMenu
{
private PluginInitContext context;
@@ -60,7 +63,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
}
return true;
- }
+ },
+ ContextData = new BookmarkAttributes { Url = c.Url }
}).Where(r => r.Score > 0);
return returnList.ToList();
}
@@ -84,7 +88,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
}
return true;
- }
+ },
+ ContextData = new BookmarkAttributes { Url = c.Url }
}).ToList();
}
}
@@ -115,5 +120,39 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
{
_storage.Save();
}
+
+ public List LoadContextMenus(Result selectedResult)
+ {
+ return new List() {
+ new Result
+ {
+ Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"),
+ SubTitle = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"),
+ Action = _ =>
+ {
+ try
+ {
+ Clipboard.SetDataObject(((BookmarkAttributes)selectedResult.ContextData).Url);
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ var message = "Failed to set url in clipboard";
+ Log.Exception("Main",message, e, "LoadContextMenus");
+
+ context.API.ShowMsg(message);
+
+ return false;
+ }
+ },
+ IcoPath = "Images\\copylink.png"
+ }};
+ }
+
+ internal class BookmarkAttributes
+ {
+ internal string Url { get; set; }
+ }
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json
index b0c3d2e29..62311f514 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json
@@ -4,7 +4,7 @@
"Name": "Browser Bookmarks",
"Description": "Search your browser bookmarks",
"Author": "qianlifeng, Ioannis G.",
- "Version": "1.3.2",
+ "Version": "1.4.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll",
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
index 7bc357be4..85f918a03 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs
@@ -25,7 +25,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_openwebsite_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_openwebsite_subtitle"),
- IcoPath = "Images\\website.png",
+ IcoPath = selectedResult.IcoPath,
Action = _ =>
{
SharedCommands.SearchWeb.NewTabInBrowser(pluginManifestInfo.Website);
@@ -63,7 +63,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
{
Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title"),
SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle"),
- IcoPath = selectedResult.IcoPath,
+ IcoPath = "Images\\manifestsite.png",
Action = _ =>
{
SharedCommands.SearchWeb.NewTabInBrowser("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest");
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
index cb2507a2b..f94027bcc 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj
@@ -18,6 +18,7 @@
+
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/website.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/manifestsite.png
similarity index 100%
rename from Plugins/Flow.Launcher.Plugin.PluginsManager/Images/website.png
rename to Plugins/Flow.Launcher.Plugin.PluginsManager/Images/manifestsite.png
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index 881872fc1..90d103bc7 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -1,3 +1,4 @@
+using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Logger;
@@ -400,6 +401,9 @@ namespace Flow.Launcher.Plugin.PluginsManager
private void Uninstall(PluginMetadata plugin)
{
+ PluginManager.Settings.Plugins.Remove(plugin.ID);
+ PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
+
// Marked for deletion. Will be deleted on next start up
using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt"));
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
index b37a6b1af..4cba7ec83 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
@@ -6,7 +6,7 @@
"Name": "Plugins Manager",
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
"Author": "Jeremy Wu",
- "Version": "1.6.2",
+ "Version": "1.6.4",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",
diff --git a/README.md b/README.md
index 9af47a5ee..8512142b0 100644
--- a/README.md
+++ b/README.md
@@ -38,10 +38,11 @@ Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at be
Windows may complain about security due to code not being signed, this will be completed at a later stage. If you downloaded from this repo, you are good to continue the set up.
**Integrations**
- - If you use Python plugins:
- - Install [Python3](https://www.python.org/downloads/), download `.exe` installer.
+ - Python plugins:
+ - Once a Python plugin has been detected at start up, you will be prompted to either select the location or allow Python 3.9.1 to automatic download and install.
- Add Python to `%PATH%` or set it in flow's settings.
- - Use `pip` to install `flowlauncher`, cmd in `pip install flowlauncher`.
+ - Use `pip` to install `flowlauncher`, open cmd and type `pip install flowlauncher`.
+ - The Python plugin may require additional modules to be installed, please ensure you check by visiting the plugin's website via `pm install` + plugin name, go to context menu and select `Open website`.
- Start to launch your Python plugins.
- Flow searches files and contents via Windows Index Search, to use Everything, download the plugin [here](https://github.com/Flow-Launcher/Flow.Launcher.Plugin.Everything/releases/latest).
@@ -51,6 +52,7 @@ Windows may complain about security due to code not being signed, this will be c
- Open context menu: on the selected result, press Ctrl+O/Shift+Enter.
- Cancel/Return to previous screen: Esc.
- Install/Uninstall/Update plugins: in the search window, type `pm install`/`pm uninstall`/`pm update` + the plugin name.
+- Press `F5` while in the query window to reload all plugin data.
- Saved user settings are located:
- If using roaming: `%APPDATA%\FlowLauncher`
- If using portable, by default: `%localappdata%\FlowLauncher\app-\UserData`
diff --git a/SolutionAssemblyInfo.cs b/SolutionAssemblyInfo.cs
index afd76b5d7..7f13f4130 100644
--- a/SolutionAssemblyInfo.cs
+++ b/SolutionAssemblyInfo.cs
@@ -16,6 +16,6 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
-[assembly: AssemblyVersion("1.7.0")]
-[assembly: AssemblyFileVersion("1.7.0")]
-[assembly: AssemblyInformationalVersion("1.7.0")]
\ No newline at end of file
+[assembly: AssemblyVersion("1.7.1")]
+[assembly: AssemblyFileVersion("1.7.1")]
+[assembly: AssemblyInformationalVersion("1.7.1")]
\ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
index 2c2f43b66..6340318d4 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: '1.7.0.{build}'
+version: '1.7.1.{build}'
init:
- ps: |