mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into add-select-all-query
This commit is contained in:
commit
18ab03bd2c
137 changed files with 2539 additions and 954 deletions
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
|
|
@ -8,7 +8,8 @@ updates:
|
||||||
- package-ecosystem: "nuget" # See documentation for possible values
|
- package-ecosystem: "nuget" # See documentation for possible values
|
||||||
directory: "/" # Location of package manifests
|
directory: "/" # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "daily"
|
||||||
|
open-pull-requests-limit: 3
|
||||||
ignore:
|
ignore:
|
||||||
- dependency-name: "squirrel-windows"
|
- dependency-name: "squirrel-windows"
|
||||||
reviewers:
|
reviewers:
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,15 @@ using Flow.Launcher.Infrastructure.Logger;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.Configuration
|
namespace Flow.Launcher.Core.Configuration
|
||||||
{
|
{
|
||||||
public class Portable : IPortable
|
public class Portable : IPortable
|
||||||
{
|
{
|
||||||
|
private readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
|
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -40,7 +44,7 @@ namespace Flow.Launcher.Core.Configuration
|
||||||
#endif
|
#endif
|
||||||
IndicateDeletion(DataLocation.PortableDataPath);
|
IndicateDeletion(DataLocation.PortableDataPath);
|
||||||
|
|
||||||
MessageBoxEx.Show("Flow Launcher needs to restart to finish disabling portable mode, " +
|
API.ShowMsgBox("Flow Launcher needs to restart to finish disabling portable mode, " +
|
||||||
"after the restart your portable data profile will be deleted and roaming data profile kept");
|
"after the restart your portable data profile will be deleted and roaming data profile kept");
|
||||||
|
|
||||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||||
|
|
@ -64,7 +68,7 @@ namespace Flow.Launcher.Core.Configuration
|
||||||
#endif
|
#endif
|
||||||
IndicateDeletion(DataLocation.RoamingDataPath);
|
IndicateDeletion(DataLocation.RoamingDataPath);
|
||||||
|
|
||||||
MessageBoxEx.Show("Flow Launcher needs to restart to finish enabling portable mode, " +
|
API.ShowMsgBox("Flow Launcher needs to restart to finish enabling portable mode, " +
|
||||||
"after the restart your roaming data profile will be deleted and portable data profile kept");
|
"after the restart your roaming data profile will be deleted and portable data profile kept");
|
||||||
|
|
||||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||||
|
|
@ -95,13 +99,13 @@ namespace Flow.Launcher.Core.Configuration
|
||||||
|
|
||||||
public void MoveUserDataFolder(string fromLocation, string toLocation)
|
public void MoveUserDataFolder(string fromLocation, string toLocation)
|
||||||
{
|
{
|
||||||
FilesFolders.CopyAll(fromLocation, toLocation, MessageBoxEx.Show);
|
FilesFolders.CopyAll(fromLocation, toLocation, (s) => API.ShowMsgBox(s));
|
||||||
VerifyUserDataAfterMove(fromLocation, toLocation);
|
VerifyUserDataAfterMove(fromLocation, toLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void VerifyUserDataAfterMove(string fromLocation, string toLocation)
|
public void VerifyUserDataAfterMove(string fromLocation, string toLocation)
|
||||||
{
|
{
|
||||||
FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, MessageBoxEx.Show);
|
FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => API.ShowMsgBox(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateShortcuts()
|
public void CreateShortcuts()
|
||||||
|
|
@ -157,13 +161,13 @@ namespace Flow.Launcher.Core.Configuration
|
||||||
// delete it and prompt the user to pick the portable data location
|
// delete it and prompt the user to pick the portable data location
|
||||||
if (File.Exists(roamingDataDeleteFilePath))
|
if (File.Exists(roamingDataDeleteFilePath))
|
||||||
{
|
{
|
||||||
FilesFolders.RemoveFolderIfExists(roamingDataDir, MessageBoxEx.Show);
|
FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => API.ShowMsgBox(s));
|
||||||
|
|
||||||
if (MessageBoxEx.Show("Flow Launcher has detected you enabled portable mode, " +
|
if (API.ShowMsgBox("Flow Launcher has detected you enabled portable mode, " +
|
||||||
"would you like to move it to a different location?", string.Empty,
|
"would you like to move it to a different location?", string.Empty,
|
||||||
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||||
{
|
{
|
||||||
FilesFolders.OpenPath(Constant.RootDirectory, MessageBoxEx.Show);
|
FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s));
|
||||||
|
|
||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
|
|
@ -172,9 +176,9 @@ namespace Flow.Launcher.Core.Configuration
|
||||||
// delete it and notify the user about it.
|
// delete it and notify the user about it.
|
||||||
else if (File.Exists(portableDataDeleteFilePath))
|
else if (File.Exists(portableDataDeleteFilePath))
|
||||||
{
|
{
|
||||||
FilesFolders.RemoveFolderIfExists(portableDataDir, MessageBoxEx.Show);
|
FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => API.ShowMsgBox(s));
|
||||||
|
|
||||||
MessageBoxEx.Show("Flow Launcher has detected you disabled portable mode, " +
|
API.ShowMsgBox("Flow Launcher has detected you disabled portable mode, " +
|
||||||
"the relevant shortcuts and uninstaller entry have been created");
|
"the relevant shortcuts and uninstaller entry have been created");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +190,7 @@ namespace Flow.Launcher.Core.Configuration
|
||||||
|
|
||||||
if (roamingLocationExists && portableLocationExists)
|
if (roamingLocationExists && portableLocationExists)
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(string.Format("Flow Launcher detected your user data exists both in {0} and " +
|
API.ShowMsgBox(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 occurred.",
|
"{1}. {2}{2}Please delete {1} in order to proceed. No changes have occurred.",
|
||||||
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
|
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,14 @@ using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using Flow.Launcher.Core.Resource;
|
using Flow.Launcher.Core.Resource;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
{
|
{
|
||||||
public abstract class AbstractPluginEnvironment
|
public abstract class AbstractPluginEnvironment
|
||||||
{
|
{
|
||||||
|
protected readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
|
|
||||||
internal abstract string Language { get; }
|
internal abstract string Language { get; }
|
||||||
|
|
||||||
internal abstract string EnvName { get; }
|
internal abstract string EnvName { get; }
|
||||||
|
|
@ -25,7 +28,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
|
|
||||||
internal virtual string FileDialogFilter => string.Empty;
|
internal virtual string FileDialogFilter => string.Empty;
|
||||||
|
|
||||||
internal abstract string PluginsSettingsFilePath { get; set; }
|
internal abstract string PluginsSettingsFilePath { get; set; }
|
||||||
|
|
||||||
internal List<PluginMetadata> PluginMetadataList;
|
internal List<PluginMetadata> PluginMetadataList;
|
||||||
|
|
||||||
|
|
@ -57,7 +60,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
EnvName,
|
EnvName,
|
||||||
Environment.NewLine
|
Environment.NewLine
|
||||||
);
|
);
|
||||||
if (MessageBoxEx.Show(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||||
{
|
{
|
||||||
var msg = string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
|
var msg = string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
|
||||||
string selectedFile;
|
string selectedFile;
|
||||||
|
|
@ -82,7 +85,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
|
API.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
|
||||||
Log.Error("PluginsLoader",
|
Log.Error("PluginsLoader",
|
||||||
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
|
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
|
||||||
$"{Language}Environment");
|
$"{Language}Environment");
|
||||||
|
|
@ -98,7 +101,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
if (expectedPath == currentPath)
|
if (expectedPath == currentPath)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FilesFolders.RemoveFolderIfExists(installedDirPath, MessageBoxEx.Show);
|
FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s));
|
||||||
|
|
||||||
InstallEnvironment();
|
InstallEnvironment();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
|
|
||||||
internal override void InstallEnvironment()
|
internal override void InstallEnvironment()
|
||||||
{
|
{
|
||||||
FilesFolders.RemoveFolderIfExists(InstallPath, MessageBoxEx.Show);
|
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
|
||||||
|
|
||||||
// Python 3.11.4 is no longer Windows 7 compatible. If user is on Win 7 and
|
// Python 3.11.4 is no longer Windows 7 compatible. If user is on Win 7 and
|
||||||
// uses Python plugin they need to custom install and use v3.8.9
|
// uses Python plugin they need to custom install and use v3.8.9
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
|
|
||||||
internal override void InstallEnvironment()
|
internal override void InstallEnvironment()
|
||||||
{
|
{
|
||||||
FilesFolders.RemoveFolderIfExists(InstallPath, MessageBoxEx.Show);
|
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
|
||||||
|
|
||||||
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
|
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
|
||||||
|
|
||||||
internal override void InstallEnvironment()
|
internal override void InstallEnvironment()
|
||||||
{
|
{
|
||||||
FilesFolders.RemoveFolderIfExists(InstallPath, MessageBoxEx.Show);
|
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
|
||||||
|
|
||||||
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
|
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
||||||
|
|
||||||
public static List<UserPlugin> UserPlugins { get; private set; }
|
public static List<UserPlugin> UserPlugins { get; private set; }
|
||||||
|
|
||||||
public static async Task UpdateManifestAsync(CancellationToken token = default, bool usePrimaryUrlOnly = false)
|
public static async Task<bool> UpdateManifestAsync(CancellationToken token = default, bool usePrimaryUrlOnly = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -31,8 +31,14 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
||||||
{
|
{
|
||||||
var results = await mainPluginStore.FetchAsync(token, usePrimaryUrlOnly).ConfigureAwait(false);
|
var results = await mainPluginStore.FetchAsync(token, usePrimaryUrlOnly).ConfigureAwait(false);
|
||||||
|
|
||||||
UserPlugins = results;
|
// If the results are empty, we shouldn't update the manifest because the results are invalid.
|
||||||
lastFetchedAt = DateTime.Now;
|
if (results.Count != 0)
|
||||||
|
{
|
||||||
|
UserPlugins = results;
|
||||||
|
lastFetchedAt = DateTime.Now;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -43,6 +49,8 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
||||||
{
|
{
|
||||||
manifestUpdateLock.Release();
|
manifestUpdateLock.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,11 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Droplex" Version="1.7.0" />
|
<PackageReference Include="Droplex" Version="1.7.0" />
|
||||||
<PackageReference Include="FSharp.Core" Version="9.0.100" />
|
<PackageReference Include="FSharp.Core" Version="9.0.201" />
|
||||||
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.0" />
|
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.0" />
|
||||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||||
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
|
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
|
||||||
<PackageReference Include="StreamJsonRpc" Version="2.20.20" />
|
<PackageReference Include="StreamJsonRpc" Version="2.21.10" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
/// Represent the plugin that using JsonPRC
|
/// Represent the plugin that using JsonPRC
|
||||||
/// every JsonRPC plugin should has its own plugin instance
|
/// every JsonRPC plugin should has its own plugin instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal abstract class JsonRPCPluginBase : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
|
public abstract class JsonRPCPluginBase : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
|
||||||
{
|
{
|
||||||
protected PluginInitContext Context;
|
protected PluginInitContext Context;
|
||||||
public const string JsonRPC = "JsonRPC";
|
public const string JsonRPC = "JsonRPC";
|
||||||
|
|
@ -44,8 +44,10 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
private string SettingConfigurationPath =>
|
private string SettingConfigurationPath =>
|
||||||
Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
|
Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
|
||||||
|
|
||||||
private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory,
|
private string SettingDirectory => Path.Combine(DataLocation.PluginSettingsDirectory,
|
||||||
Context.CurrentPluginMetadata.Name, "Settings.json");
|
Context.CurrentPluginMetadata.Name);
|
||||||
|
|
||||||
|
private string SettingPath => Path.Combine(SettingDirectory, "Settings.json");
|
||||||
|
|
||||||
public abstract List<Result> LoadContextMenus(Result selectedResult);
|
public abstract List<Result> LoadContextMenus(Result selectedResult);
|
||||||
|
|
||||||
|
|
@ -155,9 +157,22 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
Settings?.Save();
|
Settings?.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool NeedCreateSettingPanel()
|
||||||
|
{
|
||||||
|
return Settings.NeedCreateSettingPanel();
|
||||||
|
}
|
||||||
|
|
||||||
public Control CreateSettingPanel()
|
public Control CreateSettingPanel()
|
||||||
{
|
{
|
||||||
return Settings.CreateSettingPanel();
|
return Settings.CreateSettingPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DeletePluginSettingsDirectory()
|
||||||
|
{
|
||||||
|
if (Directory.Exists(SettingDirectory))
|
||||||
|
{
|
||||||
|
Directory.Delete(SettingDirectory, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,15 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
_storage.Save();
|
_storage.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool NeedCreateSettingPanel()
|
||||||
|
{
|
||||||
|
// If there are no settings or the settings configuration is empty, return null
|
||||||
|
return Settings != null && Configuration != null && Configuration.Body.Count != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public Control CreateSettingPanel()
|
public Control CreateSettingPanel()
|
||||||
{
|
{
|
||||||
if (Settings == null || Settings.Count == 0)
|
// No need to check if NeedCreateSettingPanel is true because CreateSettingPanel will only be called if it's true
|
||||||
return new();
|
|
||||||
|
|
||||||
var settingWindow = new UserControl();
|
var settingWindow = new UserControl();
|
||||||
var mainPanel = new Grid { Margin = settingPanelMargin, VerticalAlignment = VerticalAlignment.Center };
|
var mainPanel = new Grid { Margin = settingPanelMargin, VerticalAlignment = VerticalAlignment.Center };
|
||||||
|
|
|
||||||
|
|
@ -26,54 +26,33 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
protected override async Task<bool> ExecuteResultAsync(JsonRPCResult result)
|
protected override async Task<bool> ExecuteResultAsync(JsonRPCResult result)
|
||||||
{
|
{
|
||||||
try
|
var res = await RPC.InvokeAsync<JsonRPCExecuteResponse>(result.JsonRPCAction.Method,
|
||||||
{
|
argument: result.JsonRPCAction.Parameters);
|
||||||
var res = await RPC.InvokeAsync<JsonRPCExecuteResponse>(result.JsonRPCAction.Method,
|
|
||||||
argument: result.JsonRPCAction.Parameters);
|
|
||||||
|
|
||||||
return res.Hide;
|
return res.Hide;
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
|
private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
|
||||||
|
|
||||||
public override List<Result> LoadContextMenus(Result selectedResult)
|
public override List<Result> LoadContextMenus(Result selectedResult)
|
||||||
{
|
{
|
||||||
try
|
var res = JTF.Run(() => RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("context_menu",
|
||||||
{
|
new object[] { selectedResult.ContextData }));
|
||||||
var res = JTF.Run(() => RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("context_menu",
|
|
||||||
new object[] { selectedResult.ContextData }));
|
|
||||||
|
|
||||||
var results = ParseResults(res);
|
var results = ParseResults(res);
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return new List<Result>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
public override async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||||
{
|
{
|
||||||
try
|
var res = await RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("query",
|
||||||
{
|
new object[] { query, Settings.Inner },
|
||||||
var res = await RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("query",
|
token);
|
||||||
new object[] { query, Settings.Inner },
|
|
||||||
token);
|
|
||||||
|
|
||||||
var results = ParseResults(res);
|
var results = ParseResults(res);
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return new List<Result>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -133,10 +112,15 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
RPC.StartListening();
|
RPC.StartListening();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Task ReloadDataAsync()
|
public virtual async Task ReloadDataAsync()
|
||||||
{
|
{
|
||||||
SetupJsonRPC();
|
try
|
||||||
return Task.CompletedTask;
|
{
|
||||||
|
await RPC.InvokeAsync("reload_data", Context);
|
||||||
|
}
|
||||||
|
catch (RemoteMethodNotFoundException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async ValueTask DisposeAsync()
|
public virtual async ValueTask DisposeAsync()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
@ -121,10 +120,10 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
|
||||||
return _api.HttpGetStreamAsync(url, token);
|
return _api.HttpGetStreamAsync(url, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath,
|
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null,
|
||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return _api.HttpDownloadAsync(url, filePath, token);
|
return _api.HttpDownloadAsync(url, filePath, reportProgress, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddActionKeyword(string pluginId, string newActionKeyword)
|
public void AddActionKeyword(string pluginId, string newActionKeyword)
|
||||||
|
|
@ -162,16 +161,29 @@ namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models
|
||||||
_api.OpenDirectory(DirectoryPath, FileNameOrFilePath);
|
_api.OpenDirectory(DirectoryPath, FileNameOrFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void OpenUrl(string url, bool? inPrivate = null)
|
public void OpenUrl(string url, bool? inPrivate = null)
|
||||||
{
|
{
|
||||||
_api.OpenUrl(url, inPrivate);
|
_api.OpenUrl(url, inPrivate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void OpenAppUri(string appUri)
|
public void OpenAppUri(string appUri)
|
||||||
{
|
{
|
||||||
_api.OpenAppUri(appUri);
|
_api.OpenAppUri(appUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void BackToQueryResults()
|
||||||
|
{
|
||||||
|
_api.BackToQueryResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartLoadingBar()
|
||||||
|
{
|
||||||
|
_api.StartLoadingBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopLoadingBar()
|
||||||
|
{
|
||||||
|
_api.StopLoadingBar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using ISavable = Flow.Launcher.Plugin.ISavable;
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Flow.Launcher.Core.Resource;
|
using Flow.Launcher.Core.Resource;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.Plugin
|
namespace Flow.Launcher.Core.Plugin
|
||||||
{
|
{
|
||||||
|
|
@ -28,7 +29,9 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
public static readonly HashSet<PluginPair> GlobalPlugins = new();
|
public static readonly HashSet<PluginPair> GlobalPlugins = new();
|
||||||
public static readonly Dictionary<string, PluginPair> NonGlobalPlugins = new();
|
public static readonly Dictionary<string, PluginPair> NonGlobalPlugins = new();
|
||||||
|
|
||||||
public static IPublicAPI API { private set; get; }
|
// We should not initialize API in static constructor because it will create another API instance
|
||||||
|
private static IPublicAPI api = null;
|
||||||
|
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
|
|
||||||
private static PluginsSettings Settings;
|
private static PluginsSettings Settings;
|
||||||
private static List<PluginMetadata> _metadatas;
|
private static List<PluginMetadata> _metadatas;
|
||||||
|
|
@ -158,9 +161,8 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
/// Call initialize for all plugins
|
/// Call initialize for all plugins
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>return the list of failed to init plugins or null for none</returns>
|
/// <returns>return the list of failed to init plugins or null for none</returns>
|
||||||
public static async Task InitializePluginsAsync(IPublicAPI api)
|
public static async Task InitializePluginsAsync()
|
||||||
{
|
{
|
||||||
API = api;
|
|
||||||
var failedPlugins = new ConcurrentQueue<PluginPair>();
|
var failedPlugins = new ConcurrentQueue<PluginPair>();
|
||||||
|
|
||||||
var InitTasks = AllPlugins.Select(pair => Task.Run(async delegate
|
var InitTasks = AllPlugins.Select(pair => Task.Run(async delegate
|
||||||
|
|
@ -204,15 +206,15 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
InternationalizationManager.Instance.AddPluginLanguageDirectories(GetPluginsForInterface<IPluginI18n>());
|
InternationalizationManager.Instance.AddPluginLanguageDirectories(GetPluginsForInterface<IPluginI18n>());
|
||||||
InternationalizationManager.Instance.ChangeLanguage(InternationalizationManager.Instance.Settings.Language);
|
InternationalizationManager.Instance.ChangeLanguage(Ioc.Default.GetRequiredService<Settings>().Language);
|
||||||
|
|
||||||
if (failedPlugins.Any())
|
if (failedPlugins.Any())
|
||||||
{
|
{
|
||||||
var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name));
|
var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name));
|
||||||
API.ShowMsg(
|
API.ShowMsg(
|
||||||
InternationalizationManager.Instance.GetTranslation("failedToInitializePluginsTitle"),
|
API.GetTranslation("failedToInitializePluginsTitle"),
|
||||||
string.Format(
|
string.Format(
|
||||||
InternationalizationManager.Instance.GetTranslation("failedToInitializePluginsMessage"),
|
API.GetTranslation("failedToInitializePluginsMessage"),
|
||||||
failed
|
failed
|
||||||
),
|
),
|
||||||
"",
|
"",
|
||||||
|
|
@ -281,7 +283,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UpdatePluginMetadata(List<Result> results, PluginMetadata metadata, Query query)
|
public static void UpdatePluginMetadata(IReadOnlyList<Result> results, PluginMetadata metadata, Query query)
|
||||||
{
|
{
|
||||||
foreach (var r in results)
|
foreach (var r in results)
|
||||||
{
|
{
|
||||||
|
|
@ -439,7 +441,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
|
public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
|
||||||
{
|
{
|
||||||
InstallPlugin(newVersion, zipFilePath, checkModified:false);
|
InstallPlugin(newVersion, zipFilePath, checkModified:false);
|
||||||
UninstallPlugin(existingVersion, removeSettings:false, checkModified:false);
|
UninstallPlugin(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
|
||||||
_modifiedPlugins.Add(existingVersion.ID);
|
_modifiedPlugins.Add(existingVersion.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,9 +456,9 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uninstall a plugin.
|
/// Uninstall a plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true)
|
public static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
|
||||||
{
|
{
|
||||||
UninstallPlugin(plugin, removeSettings, true);
|
UninstallPlugin(plugin, removePluginFromSettings, removePluginSettings, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -519,9 +521,17 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
var newPluginPath = Path.Combine(installDirectory, folderName);
|
var newPluginPath = Path.Combine(installDirectory, folderName);
|
||||||
|
|
||||||
FilesFolders.CopyAll(pluginFolderPath, newPluginPath, MessageBoxEx.Show);
|
FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => API.ShowMsgBox(s));
|
||||||
|
|
||||||
Directory.Delete(tempFolderPluginPath, true);
|
try
|
||||||
|
{
|
||||||
|
if (Directory.Exists(tempFolderPluginPath))
|
||||||
|
Directory.Delete(tempFolderPluginPath, true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Exception($"|PluginManager.InstallPlugin|Failed to delete temp folder {tempFolderPluginPath}", e);
|
||||||
|
}
|
||||||
|
|
||||||
if (checkModified)
|
if (checkModified)
|
||||||
{
|
{
|
||||||
|
|
@ -529,14 +539,62 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void UninstallPlugin(PluginMetadata plugin, bool removeSettings, bool checkModified)
|
internal static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified)
|
||||||
{
|
{
|
||||||
if (checkModified && PluginModified(plugin.ID))
|
if (checkModified && PluginModified(plugin.ID))
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Plugin {plugin.Name} has been modified");
|
throw new ArgumentException($"Plugin {plugin.Name} has been modified");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removeSettings)
|
if (removePluginSettings)
|
||||||
|
{
|
||||||
|
if (AllowedLanguage.IsDotNet(plugin.Language)) // for the plugin in .NET, we can use assembly loader
|
||||||
|
{
|
||||||
|
var assemblyLoader = new PluginAssemblyLoader(plugin.ExecuteFilePath);
|
||||||
|
var assembly = assemblyLoader.LoadAssemblyAndDependencies();
|
||||||
|
var assemblyName = assembly.GetName().Name;
|
||||||
|
|
||||||
|
// if user want to remove the plugin settings, we cannot call save method for the plugin json storage instance of this plugin
|
||||||
|
// so we need to remove it from the api instance
|
||||||
|
var method = API.GetType().GetMethod("RemovePluginSettings");
|
||||||
|
var pluginJsonStorage = method?.Invoke(API, new object[] { assemblyName });
|
||||||
|
|
||||||
|
// if there exists a json storage for current plugin, we need to delete the directory path
|
||||||
|
if (pluginJsonStorage != null)
|
||||||
|
{
|
||||||
|
var deleteMethod = pluginJsonStorage.GetType().GetMethod("DeleteDirectory");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
deleteMethod?.Invoke(pluginJsonStorage, null);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e);
|
||||||
|
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
|
||||||
|
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // the plugin with json prc interface
|
||||||
|
{
|
||||||
|
var pluginPair = AllPlugins.FirstOrDefault(p => p.Metadata.ID == plugin.ID);
|
||||||
|
if (pluginPair != null && pluginPair.Plugin is JsonRPCPlugin jsonRpcPlugin)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
jsonRpcPlugin.DeletePluginSettingsDirectory();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e);
|
||||||
|
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
|
||||||
|
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removePluginFromSettings)
|
||||||
{
|
{
|
||||||
Settings.Plugins.Remove(plugin.ID);
|
Settings.Plugins.Remove(plugin.ID);
|
||||||
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
|
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
using Flow.Launcher.Core.ExternalPlugins.Environments;
|
using Flow.Launcher.Core.ExternalPlugins.Environments;
|
||||||
#pragma warning disable IDE0005
|
#pragma warning disable IDE0005
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
|
|
@ -119,7 +120,7 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
_ = Task.Run(() =>
|
_ = Task.Run(() =>
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
|
Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsgBox($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
|
||||||
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
|
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
|
||||||
$"Please refer to the logs for more information", "",
|
$"Please refer to the logs for more information", "",
|
||||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Diagnostics;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -25,14 +26,13 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
var path = Path.Combine(Constant.ProgramDirectory, JsonRPC);
|
var path = Path.Combine(Constant.ProgramDirectory, JsonRPC);
|
||||||
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
|
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
|
||||||
|
// Prevent Python from writing .py[co] files.
|
||||||
|
// Because .pyc contains location infos which will prevent python portable.
|
||||||
|
_startInfo.EnvironmentVariables["PYTHONDONTWRITEBYTECODE"] = "1";
|
||||||
|
|
||||||
_startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
|
_startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
|
||||||
_startInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory;
|
_startInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory;
|
||||||
_startInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory;
|
_startInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory;
|
||||||
|
|
||||||
|
|
||||||
//Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable
|
|
||||||
_startInfo.ArgumentList.Add("-B");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<Stream> RequestAsync(JsonRPCRequestModel request, CancellationToken token = default)
|
protected override Task<Stream> RequestAsync(JsonRPCRequestModel request, CancellationToken token = default)
|
||||||
|
|
@ -50,10 +50,53 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
// TODO: Async Action
|
// TODO: Async Action
|
||||||
return Execute(_startInfo);
|
return Execute(_startInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task InitAsync(PluginInitContext context)
|
public override async Task InitAsync(PluginInitContext context)
|
||||||
{
|
{
|
||||||
_startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
|
// Run .py files via `-c <code>`
|
||||||
_startInfo.ArgumentList.Add("");
|
if (context.CurrentPluginMetadata.ExecuteFilePath.EndsWith(".py", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var rootDirectory = context.CurrentPluginMetadata.PluginDirectory;
|
||||||
|
var libDirectory = Path.Combine(rootDirectory, "lib");
|
||||||
|
var libPyWin32Directory = Path.Combine(libDirectory, "win32");
|
||||||
|
var libPyWin32LibDirectory = Path.Combine(libPyWin32Directory, "lib");
|
||||||
|
var pluginDirectory = Path.Combine(rootDirectory, "plugin");
|
||||||
|
|
||||||
|
// This makes it easier for plugin authors to import their own modules.
|
||||||
|
// They won't have to add `.`, `./lib`, or `./plugin` to their sys.path manually.
|
||||||
|
// Instead of running the .py file directly, we pass the code we want to run as a CLI argument.
|
||||||
|
// This code sets sys.path for the plugin author and then runs the .py file via runpy.
|
||||||
|
_startInfo.ArgumentList.Add("-c");
|
||||||
|
_startInfo.ArgumentList.Add(
|
||||||
|
$"""
|
||||||
|
import sys
|
||||||
|
sys.path.append(r'{rootDirectory}')
|
||||||
|
sys.path.append(r'{libDirectory}')
|
||||||
|
sys.path.append(r'{libPyWin32LibDirectory}')
|
||||||
|
sys.path.append(r'{libPyWin32Directory}')
|
||||||
|
sys.path.append(r'{pluginDirectory}')
|
||||||
|
|
||||||
|
import runpy
|
||||||
|
runpy.run_path(r'{context.CurrentPluginMetadata.ExecuteFilePath}', None, '__main__')
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
// Plugins always expect the JSON data to be in the third argument
|
||||||
|
// (we're always setting it as _startInfo.ArgumentList[2] = ...).
|
||||||
|
_startInfo.ArgumentList.Add("");
|
||||||
|
}
|
||||||
|
// Run .pyz files as is
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No need for -B flag because we're using PYTHONDONTWRITEBYTECODE env variable now,
|
||||||
|
// but the plugins still expect data to be sent as the third argument, so we're keeping
|
||||||
|
// the flag here, even though it's not necessary anymore.
|
||||||
|
_startInfo.ArgumentList.Add("-B");
|
||||||
|
_startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
|
||||||
|
// Plugins always expect the JSON data to be in the third argument
|
||||||
|
// (we're always setting it as _startInfo.ArgumentList[2] = ...).
|
||||||
|
_startInfo.ArgumentList.Add("");
|
||||||
|
}
|
||||||
|
|
||||||
await base.InitAsync(context);
|
await base.InitAsync(context);
|
||||||
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
|
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,45 @@ namespace Flow.Launcher.Core.Plugin
|
||||||
|
|
||||||
var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
|
var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
|
||||||
StartInfo.EnvironmentVariables["PYTHONPATH"] = path;
|
StartInfo.EnvironmentVariables["PYTHONPATH"] = path;
|
||||||
|
StartInfo.EnvironmentVariables["PYTHONDONTWRITEBYTECODE"] = "1";
|
||||||
//Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable
|
|
||||||
StartInfo.ArgumentList.Add("-B");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task InitAsync(PluginInitContext context)
|
public override async Task InitAsync(PluginInitContext context)
|
||||||
{
|
{
|
||||||
StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
|
// Run .py files via `-c <code>`
|
||||||
|
if (context.CurrentPluginMetadata.ExecuteFilePath.EndsWith(".py", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var rootDirectory = context.CurrentPluginMetadata.PluginDirectory;
|
||||||
|
var libDirectory = Path.Combine(rootDirectory, "lib");
|
||||||
|
var libPyWin32Directory = Path.Combine(libDirectory, "win32");
|
||||||
|
var libPyWin32LibDirectory = Path.Combine(libPyWin32Directory, "lib");
|
||||||
|
var pluginDirectory = Path.Combine(rootDirectory, "plugin");
|
||||||
|
var filePath = context.CurrentPluginMetadata.ExecuteFilePath;
|
||||||
|
|
||||||
|
// This makes it easier for plugin authors to import their own modules.
|
||||||
|
// They won't have to add `.`, `./lib`, or `./plugin` to their sys.path manually.
|
||||||
|
// Instead of running the .py file directly, we pass the code we want to run as a CLI argument.
|
||||||
|
// This code sets sys.path for the plugin author and then runs the .py file via runpy.
|
||||||
|
StartInfo.ArgumentList.Add("-c");
|
||||||
|
StartInfo.ArgumentList.Add(
|
||||||
|
$"""
|
||||||
|
import sys
|
||||||
|
sys.path.append(r'{rootDirectory}')
|
||||||
|
sys.path.append(r'{libDirectory}')
|
||||||
|
sys.path.append(r'{libPyWin32LibDirectory}')
|
||||||
|
sys.path.append(r'{libPyWin32Directory}')
|
||||||
|
sys.path.append(r'{pluginDirectory}')
|
||||||
|
|
||||||
|
import runpy
|
||||||
|
runpy.run_path(r'{filePath}', None, '__main__')
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Run .pyz files as is
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
|
||||||
|
}
|
||||||
await base.InitAsync(context);
|
await base.InitAsync(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ namespace Flow.Launcher.Core.Resource
|
||||||
public static Language Vietnamese = new Language("vi-vn", "Tiếng Việt");
|
public static Language Vietnamese = new Language("vi-vn", "Tiếng Việt");
|
||||||
public static Language Hebrew = new Language("he", "עברית");
|
public static Language Hebrew = new Language("he", "עברית");
|
||||||
|
|
||||||
|
|
||||||
public static List<Language> GetAvailableLanguages()
|
public static List<Language> GetAvailableLanguages()
|
||||||
{
|
{
|
||||||
List<Language> languages = new List<Language>
|
List<Language> languages = new List<Language>
|
||||||
|
|
@ -63,5 +62,38 @@ namespace Flow.Launcher.Core.Resource
|
||||||
};
|
};
|
||||||
return languages;
|
return languages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetSystemTranslation(string languageCode)
|
||||||
|
{
|
||||||
|
return languageCode switch
|
||||||
|
{
|
||||||
|
"en" => "System",
|
||||||
|
"zh-cn" => "系统",
|
||||||
|
"zh-tw" => "系統",
|
||||||
|
"uk-UA" => "Система",
|
||||||
|
"ru" => "Система",
|
||||||
|
"fr" => "Système",
|
||||||
|
"ja" => "システム",
|
||||||
|
"nl" => "Systeem",
|
||||||
|
"pl" => "System",
|
||||||
|
"da" => "System",
|
||||||
|
"de" => "System",
|
||||||
|
"ko" => "시스템",
|
||||||
|
"sr" => "Систем",
|
||||||
|
"pt-pt" => "Sistema",
|
||||||
|
"pt-br" => "Sistema",
|
||||||
|
"es" => "Sistema",
|
||||||
|
"es-419" => "Sistema",
|
||||||
|
"it" => "Sistema",
|
||||||
|
"nb-NO" => "System",
|
||||||
|
"sk" => "Systém",
|
||||||
|
"tr" => "Sistem",
|
||||||
|
"cs" => "Systém",
|
||||||
|
"ar" => "النظام",
|
||||||
|
"vi-vn" => "Hệ thống",
|
||||||
|
"he" => "מערכת",
|
||||||
|
_ => "System",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,30 +11,61 @@ using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.Resource
|
namespace Flow.Launcher.Core.Resource
|
||||||
{
|
{
|
||||||
public class Internationalization
|
public class Internationalization
|
||||||
{
|
{
|
||||||
public Settings Settings { get; set; }
|
|
||||||
private const string Folder = "Languages";
|
private const string Folder = "Languages";
|
||||||
|
private const string DefaultLanguageCode = "en";
|
||||||
private const string DefaultFile = "en.xaml";
|
private const string DefaultFile = "en.xaml";
|
||||||
private const string Extension = ".xaml";
|
private const string Extension = ".xaml";
|
||||||
|
private readonly Settings _settings;
|
||||||
private readonly List<string> _languageDirectories = new List<string>();
|
private readonly List<string> _languageDirectories = new List<string>();
|
||||||
private readonly List<ResourceDictionary> _oldResources = new List<ResourceDictionary>();
|
private readonly List<ResourceDictionary> _oldResources = new List<ResourceDictionary>();
|
||||||
|
private readonly string SystemLanguageCode;
|
||||||
|
|
||||||
public Internationalization()
|
public Internationalization(Settings settings)
|
||||||
{
|
{
|
||||||
|
_settings = settings;
|
||||||
AddFlowLauncherLanguageDirectory();
|
AddFlowLauncherLanguageDirectory();
|
||||||
|
SystemLanguageCode = GetSystemLanguageCodeAtStartup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void AddFlowLauncherLanguageDirectory()
|
private void AddFlowLauncherLanguageDirectory()
|
||||||
{
|
{
|
||||||
var directory = Path.Combine(Constant.ProgramDirectory, Folder);
|
var directory = Path.Combine(Constant.ProgramDirectory, Folder);
|
||||||
_languageDirectories.Add(directory);
|
_languageDirectories.Add(directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetSystemLanguageCodeAtStartup()
|
||||||
|
{
|
||||||
|
var availableLanguages = AvailableLanguages.GetAvailableLanguages();
|
||||||
|
|
||||||
|
// Retrieve the language identifiers for the current culture.
|
||||||
|
// ChangeLanguage method overrides the CultureInfo.CurrentCulture, so this needs to
|
||||||
|
// be called at startup in order to get the correct lang code of system.
|
||||||
|
var currentCulture = CultureInfo.CurrentCulture;
|
||||||
|
var twoLetterCode = currentCulture.TwoLetterISOLanguageName;
|
||||||
|
var threeLetterCode = currentCulture.ThreeLetterISOLanguageName;
|
||||||
|
var fullName = currentCulture.Name;
|
||||||
|
|
||||||
|
// Try to find a match in the available languages list
|
||||||
|
foreach (var language in availableLanguages)
|
||||||
|
{
|
||||||
|
var languageCode = language.LanguageCode;
|
||||||
|
|
||||||
|
if (string.Equals(languageCode, twoLetterCode, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(languageCode, threeLetterCode, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(languageCode, fullName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return languageCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultLanguageCode;
|
||||||
|
}
|
||||||
|
|
||||||
internal void AddPluginLanguageDirectories(IEnumerable<PluginPair> plugins)
|
internal void AddPluginLanguageDirectories(IEnumerable<PluginPair> plugins)
|
||||||
{
|
{
|
||||||
|
|
@ -68,8 +99,18 @@ namespace Flow.Launcher.Core.Resource
|
||||||
public void ChangeLanguage(string languageCode)
|
public void ChangeLanguage(string languageCode)
|
||||||
{
|
{
|
||||||
languageCode = languageCode.NonNull();
|
languageCode = languageCode.NonNull();
|
||||||
Language language = GetLanguageByLanguageCode(languageCode);
|
|
||||||
ChangeLanguage(language);
|
// Get actual language if language code is system
|
||||||
|
var isSystem = false;
|
||||||
|
if (languageCode == Constant.SystemLanguageCode)
|
||||||
|
{
|
||||||
|
languageCode = SystemLanguageCode;
|
||||||
|
isSystem = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get language by language code and change language
|
||||||
|
var language = GetLanguageByLanguageCode(languageCode);
|
||||||
|
ChangeLanguage(language, isSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Language GetLanguageByLanguageCode(string languageCode)
|
private Language GetLanguageByLanguageCode(string languageCode)
|
||||||
|
|
@ -87,11 +128,10 @@ namespace Flow.Launcher.Core.Resource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeLanguage(Language language)
|
private void ChangeLanguage(Language language, bool isSystem)
|
||||||
{
|
{
|
||||||
language = language.NonNull();
|
language = language.NonNull();
|
||||||
|
|
||||||
|
|
||||||
RemoveOldLanguageFiles();
|
RemoveOldLanguageFiles();
|
||||||
if (language != AvailableLanguages.English)
|
if (language != AvailableLanguages.English)
|
||||||
{
|
{
|
||||||
|
|
@ -103,7 +143,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture;
|
CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture;
|
||||||
|
|
||||||
// Raise event after culture is set
|
// Raise event after culture is set
|
||||||
Settings.Language = language.LanguageCode;
|
_settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode;
|
||||||
_ = Task.Run(() =>
|
_ = Task.Run(() =>
|
||||||
{
|
{
|
||||||
UpdatePluginMetadataTranslations();
|
UpdatePluginMetadataTranslations();
|
||||||
|
|
@ -114,7 +154,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
{
|
{
|
||||||
var languageToSet = GetLanguageByLanguageCode(languageCodeToSet);
|
var languageToSet = GetLanguageByLanguageCode(languageCodeToSet);
|
||||||
|
|
||||||
if (Settings.ShouldUsePinyin)
|
if (_settings.ShouldUsePinyin)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (languageToSet != AvailableLanguages.Chinese && languageToSet != AvailableLanguages.Chinese_TW)
|
if (languageToSet != AvailableLanguages.Chinese && languageToSet != AvailableLanguages.Chinese_TW)
|
||||||
|
|
@ -124,7 +164,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
// "Do you want to search with pinyin?"
|
// "Do you want to search with pinyin?"
|
||||||
string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?" ;
|
string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?" ;
|
||||||
|
|
||||||
if (MessageBoxEx.Show(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
if (Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -167,7 +207,9 @@ namespace Flow.Launcher.Core.Resource
|
||||||
|
|
||||||
public List<Language> LoadAvailableLanguages()
|
public List<Language> LoadAvailableLanguages()
|
||||||
{
|
{
|
||||||
return AvailableLanguages.GetAvailableLanguages();
|
var list = AvailableLanguages.GetAvailableLanguages();
|
||||||
|
list.Insert(0, new Language(Constant.SystemLanguageCode, AvailableLanguages.GetSystemTranslation(SystemLanguageCode)));
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetTranslation(string key)
|
public string GetTranslation(string key)
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,12 @@
|
||||||
namespace Flow.Launcher.Core.Resource
|
using System;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Flow.Launcher.Core.Resource
|
||||||
{
|
{
|
||||||
|
[Obsolete("InternationalizationManager.Instance is obsolete. Use Ioc.Default.GetRequiredService<Internationalization>() instead.")]
|
||||||
public static class InternationalizationManager
|
public static class InternationalizationManager
|
||||||
{
|
{
|
||||||
private static Internationalization instance;
|
|
||||||
private static object syncObject = new object();
|
|
||||||
|
|
||||||
public static Internationalization Instance
|
public static Internationalization Instance
|
||||||
{
|
=> Ioc.Default.GetRequiredService<Internationalization>();
|
||||||
get
|
|
||||||
{
|
|
||||||
if (instance == null)
|
|
||||||
{
|
|
||||||
lock (syncObject)
|
|
||||||
{
|
|
||||||
if (instance == null)
|
|
||||||
{
|
|
||||||
instance = new Internationalization();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,11 @@ using System.Windows.Controls;
|
||||||
using System.Windows.Markup;
|
using System.Windows.Markup;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Effects;
|
using System.Windows.Media.Effects;
|
||||||
|
using System.Windows.Shell;
|
||||||
using Flow.Launcher.Infrastructure;
|
using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.Resource
|
namespace Flow.Launcher.Core.Resource
|
||||||
{
|
{
|
||||||
|
|
@ -22,21 +24,27 @@ namespace Flow.Launcher.Core.Resource
|
||||||
|
|
||||||
private const int ShadowExtraMargin = 32;
|
private const int ShadowExtraMargin = 32;
|
||||||
|
|
||||||
private readonly List<string> _themeDirectories = new List<string>();
|
private readonly IPublicAPI _api;
|
||||||
|
private readonly Settings _settings;
|
||||||
|
private readonly List<string> _themeDirectories = new();
|
||||||
private ResourceDictionary _oldResource;
|
private ResourceDictionary _oldResource;
|
||||||
private string _oldTheme;
|
private string _oldTheme;
|
||||||
public Settings Settings { get; set; }
|
|
||||||
private const string Folder = Constant.Themes;
|
private const string Folder = Constant.Themes;
|
||||||
private const string Extension = ".xaml";
|
private const string Extension = ".xaml";
|
||||||
private string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder);
|
private string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder);
|
||||||
private string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder);
|
private string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder);
|
||||||
|
|
||||||
|
public string CurrentTheme => _settings.Theme;
|
||||||
|
|
||||||
public bool BlurEnabled { get; set; }
|
public bool BlurEnabled { get; set; }
|
||||||
|
|
||||||
private double mainWindowWidth;
|
private double mainWindowWidth;
|
||||||
|
|
||||||
public Theme()
|
public Theme(IPublicAPI publicAPI, Settings settings)
|
||||||
{
|
{
|
||||||
|
_api = publicAPI;
|
||||||
|
_settings = settings;
|
||||||
|
|
||||||
_themeDirectories.Add(DirectoryPath);
|
_themeDirectories.Add(DirectoryPath);
|
||||||
_themeDirectories.Add(UserDirectoryPath);
|
_themeDirectories.Add(UserDirectoryPath);
|
||||||
MakeSureThemeDirectoriesExist();
|
MakeSureThemeDirectoriesExist();
|
||||||
|
|
@ -87,7 +95,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
// to things like fonts
|
// to things like fonts
|
||||||
UpdateResourceDictionary(GetResourceDictionary(theme));
|
UpdateResourceDictionary(GetResourceDictionary(theme));
|
||||||
|
|
||||||
Settings.Theme = theme;
|
_settings.Theme = theme;
|
||||||
|
|
||||||
|
|
||||||
//always allow re-loading default theme, in case of failure of switching to a new theme from default theme
|
//always allow re-loading default theme, in case of failure of switching to a new theme from default theme
|
||||||
|
|
@ -98,7 +106,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
|
|
||||||
BlurEnabled = Win32Helper.IsBlurTheme();
|
BlurEnabled = Win32Helper.IsBlurTheme();
|
||||||
|
|
||||||
if (Settings.UseDropShadowEffect && !BlurEnabled)
|
if (_settings.UseDropShadowEffect && !BlurEnabled)
|
||||||
AddDropShadowEffectToCurrentTheme();
|
AddDropShadowEffectToCurrentTheme();
|
||||||
|
|
||||||
Win32Helper.SetBlurForWindow(Application.Current.MainWindow, BlurEnabled);
|
Win32Helper.SetBlurForWindow(Application.Current.MainWindow, BlurEnabled);
|
||||||
|
|
@ -108,7 +116,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found");
|
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found");
|
||||||
if (theme != defaultTheme)
|
if (theme != defaultTheme)
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_path_not_exists"), theme));
|
_api.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_path_not_exists"), theme));
|
||||||
ChangeTheme(defaultTheme);
|
ChangeTheme(defaultTheme);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -118,7 +126,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse");
|
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse");
|
||||||
if (theme != defaultTheme)
|
if (theme != defaultTheme)
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_parse_error"), theme));
|
_api.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_parse_error"), theme));
|
||||||
ChangeTheme(defaultTheme);
|
ChangeTheme(defaultTheme);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -146,7 +154,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceDictionary CurrentThemeResourceDictionary() => GetThemeResourceDictionary(Settings.Theme);
|
private ResourceDictionary CurrentThemeResourceDictionary() => GetThemeResourceDictionary(_settings.Theme);
|
||||||
|
|
||||||
public ResourceDictionary GetResourceDictionary(string theme)
|
public ResourceDictionary GetResourceDictionary(string theme)
|
||||||
{
|
{
|
||||||
|
|
@ -155,10 +163,10 @@ namespace Flow.Launcher.Core.Resource
|
||||||
if (dict["QueryBoxStyle"] is Style queryBoxStyle &&
|
if (dict["QueryBoxStyle"] is Style queryBoxStyle &&
|
||||||
dict["QuerySuggestionBoxStyle"] is Style querySuggestionBoxStyle)
|
dict["QuerySuggestionBoxStyle"] is Style querySuggestionBoxStyle)
|
||||||
{
|
{
|
||||||
var fontFamily = new FontFamily(Settings.QueryBoxFont);
|
var fontFamily = new FontFamily(_settings.QueryBoxFont);
|
||||||
var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(Settings.QueryBoxFontStyle);
|
var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.QueryBoxFontStyle);
|
||||||
var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(Settings.QueryBoxFontWeight);
|
var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.QueryBoxFontWeight);
|
||||||
var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(Settings.QueryBoxFontStretch);
|
var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.QueryBoxFontStretch);
|
||||||
|
|
||||||
queryBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, fontFamily));
|
queryBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, fontFamily));
|
||||||
queryBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, fontStyle));
|
queryBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, fontStyle));
|
||||||
|
|
@ -183,10 +191,10 @@ namespace Flow.Launcher.Core.Resource
|
||||||
dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle &&
|
dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle &&
|
||||||
dict["ItemHotkeySelectedStyle"] is Style resultHotkeyItemSelectedStyle)
|
dict["ItemHotkeySelectedStyle"] is Style resultHotkeyItemSelectedStyle)
|
||||||
{
|
{
|
||||||
Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(Settings.ResultFont));
|
Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(_settings.ResultFont));
|
||||||
Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(Settings.ResultFontStyle));
|
Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultFontStyle));
|
||||||
Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(Settings.ResultFontWeight));
|
Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultFontWeight));
|
||||||
Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(Settings.ResultFontStretch));
|
Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultFontStretch));
|
||||||
|
|
||||||
Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch };
|
Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch };
|
||||||
Array.ForEach(
|
Array.ForEach(
|
||||||
|
|
@ -198,10 +206,10 @@ namespace Flow.Launcher.Core.Resource
|
||||||
dict["ItemSubTitleStyle"] is Style resultSubItemStyle &&
|
dict["ItemSubTitleStyle"] is Style resultSubItemStyle &&
|
||||||
dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle)
|
dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle)
|
||||||
{
|
{
|
||||||
Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(Settings.ResultSubFont));
|
Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(_settings.ResultSubFont));
|
||||||
Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(Settings.ResultSubFontStyle));
|
Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultSubFontStyle));
|
||||||
Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(Settings.ResultSubFontWeight));
|
Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultSubFontWeight));
|
||||||
Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(Settings.ResultSubFontStretch));
|
Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultSubFontStretch));
|
||||||
|
|
||||||
Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch };
|
Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch };
|
||||||
Array.ForEach(
|
Array.ForEach(
|
||||||
|
|
@ -211,7 +219,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
|
|
||||||
/* Ignore Theme Window Width and use setting */
|
/* Ignore Theme Window Width and use setting */
|
||||||
var windowStyle = dict["WindowStyle"] as Style;
|
var windowStyle = dict["WindowStyle"] as Style;
|
||||||
var width = Settings.WindowSize;
|
var width = _settings.WindowSize;
|
||||||
windowStyle.Setters.Add(new Setter(Window.WidthProperty, width));
|
windowStyle.Setters.Add(new Setter(Window.WidthProperty, width));
|
||||||
mainWindowWidth = (double)width;
|
mainWindowWidth = (double)width;
|
||||||
return dict;
|
return dict;
|
||||||
|
|
@ -219,7 +227,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
|
|
||||||
private ResourceDictionary GetCurrentResourceDictionary( )
|
private ResourceDictionary GetCurrentResourceDictionary( )
|
||||||
{
|
{
|
||||||
return GetResourceDictionary(Settings.Theme);
|
return GetResourceDictionary(_settings.Theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ThemeData> LoadAvailableThemes()
|
public List<ThemeData> LoadAvailableThemes()
|
||||||
|
|
@ -306,12 +314,15 @@ namespace Flow.Launcher.Core.Resource
|
||||||
var marginSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) as Setter;
|
var marginSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) as Setter;
|
||||||
if (marginSetter == null)
|
if (marginSetter == null)
|
||||||
{
|
{
|
||||||
|
var margin = new Thickness(ShadowExtraMargin, 12, ShadowExtraMargin, ShadowExtraMargin);
|
||||||
marginSetter = new Setter()
|
marginSetter = new Setter()
|
||||||
{
|
{
|
||||||
Property = Border.MarginProperty,
|
Property = Border.MarginProperty,
|
||||||
Value = new Thickness(ShadowExtraMargin, 12, ShadowExtraMargin, ShadowExtraMargin),
|
Value = margin,
|
||||||
};
|
};
|
||||||
windowBorderStyle.Setters.Add(marginSetter);
|
windowBorderStyle.Setters.Add(marginSetter);
|
||||||
|
|
||||||
|
SetResizeBoarderThickness(margin);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -322,6 +333,8 @@ namespace Flow.Launcher.Core.Resource
|
||||||
baseMargin.Right + ShadowExtraMargin,
|
baseMargin.Right + ShadowExtraMargin,
|
||||||
baseMargin.Bottom + ShadowExtraMargin);
|
baseMargin.Bottom + ShadowExtraMargin);
|
||||||
marginSetter.Value = newMargin;
|
marginSetter.Value = newMargin;
|
||||||
|
|
||||||
|
SetResizeBoarderThickness(newMargin);
|
||||||
}
|
}
|
||||||
|
|
||||||
windowBorderStyle.Setters.Add(effectSetter);
|
windowBorderStyle.Setters.Add(effectSetter);
|
||||||
|
|
@ -352,9 +365,36 @@ namespace Flow.Launcher.Core.Resource
|
||||||
marginSetter.Value = newMargin;
|
marginSetter.Value = newMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetResizeBoarderThickness(null);
|
||||||
|
|
||||||
UpdateResourceDictionary(dict);
|
UpdateResourceDictionary(dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// because adding drop shadow effect will change the margin of the window,
|
||||||
|
// we need to update the window chrome thickness to correct set the resize border
|
||||||
|
private static void SetResizeBoarderThickness(Thickness? effectMargin)
|
||||||
|
{
|
||||||
|
var window = Application.Current.MainWindow;
|
||||||
|
if (WindowChrome.GetWindowChrome(window) is WindowChrome windowChrome)
|
||||||
|
{
|
||||||
|
Thickness thickness;
|
||||||
|
if (effectMargin == null)
|
||||||
|
{
|
||||||
|
thickness = SystemParameters.WindowResizeBorderThickness;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
thickness = new Thickness(
|
||||||
|
effectMargin.Value.Left + SystemParameters.WindowResizeBorderThickness.Left,
|
||||||
|
effectMargin.Value.Top + SystemParameters.WindowResizeBorderThickness.Top,
|
||||||
|
effectMargin.Value.Right + SystemParameters.WindowResizeBorderThickness.Right,
|
||||||
|
effectMargin.Value.Bottom + SystemParameters.WindowResizeBorderThickness.Bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
windowChrome.ResizeBorderThickness = thickness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null);
|
public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,12 @@
|
||||||
namespace Flow.Launcher.Core.Resource
|
using System;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Flow.Launcher.Core.Resource
|
||||||
{
|
{
|
||||||
|
[Obsolete("ThemeManager.Instance is obsolete. Use Ioc.Default.GetRequiredService<Theme>() instead.")]
|
||||||
public class ThemeManager
|
public class ThemeManager
|
||||||
{
|
{
|
||||||
private static Theme instance;
|
|
||||||
private static object syncObject = new object();
|
|
||||||
|
|
||||||
public static Theme Instance
|
public static Theme Instance
|
||||||
{
|
=> Ioc.Default.GetRequiredService<Theme>();
|
||||||
get
|
|
||||||
{
|
|
||||||
if (instance == null)
|
|
||||||
{
|
|
||||||
lock (syncObject)
|
|
||||||
{
|
|
||||||
if (instance == null)
|
|
||||||
{
|
|
||||||
instance = new Theme();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,23 +22,26 @@ namespace Flow.Launcher.Core
|
||||||
{
|
{
|
||||||
public class Updater
|
public class Updater
|
||||||
{
|
{
|
||||||
public string GitHubRepository { get; }
|
public string GitHubRepository { get; init; }
|
||||||
|
|
||||||
public Updater(string gitHubRepository)
|
private readonly IPublicAPI _api;
|
||||||
|
|
||||||
|
public Updater(IPublicAPI publicAPI, string gitHubRepository)
|
||||||
{
|
{
|
||||||
|
_api = publicAPI;
|
||||||
GitHubRepository = gitHubRepository;
|
GitHubRepository = gitHubRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SemaphoreSlim UpdateLock { get; } = new SemaphoreSlim(1);
|
private SemaphoreSlim UpdateLock { get; } = new SemaphoreSlim(1);
|
||||||
|
|
||||||
public async Task UpdateAppAsync(IPublicAPI api, bool silentUpdate = true)
|
public async Task UpdateAppAsync(bool silentUpdate = true)
|
||||||
{
|
{
|
||||||
await UpdateLock.WaitAsync().ConfigureAwait(false);
|
await UpdateLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!silentUpdate)
|
if (!silentUpdate)
|
||||||
api.ShowMsg(api.GetTranslation("pleaseWait"),
|
_api.ShowMsg(_api.GetTranslation("pleaseWait"),
|
||||||
api.GetTranslation("update_flowlauncher_update_check"));
|
_api.GetTranslation("update_flowlauncher_update_check"));
|
||||||
|
|
||||||
using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false);
|
using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
@ -53,13 +56,13 @@ namespace Flow.Launcher.Core
|
||||||
if (newReleaseVersion <= currentVersion)
|
if (newReleaseVersion <= currentVersion)
|
||||||
{
|
{
|
||||||
if (!silentUpdate)
|
if (!silentUpdate)
|
||||||
MessageBoxEx.Show(api.GetTranslation("update_flowlauncher_already_on_latest"));
|
_api.ShowMsgBox(_api.GetTranslation("update_flowlauncher_already_on_latest"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!silentUpdate)
|
if (!silentUpdate)
|
||||||
api.ShowMsg(api.GetTranslation("update_flowlauncher_update_found"),
|
_api.ShowMsg(_api.GetTranslation("update_flowlauncher_update_found"),
|
||||||
api.GetTranslation("update_flowlauncher_updating"));
|
_api.GetTranslation("update_flowlauncher_updating"));
|
||||||
|
|
||||||
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
|
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
@ -68,9 +71,9 @@ namespace Flow.Launcher.Core
|
||||||
if (DataLocation.PortableDataLocationInUse())
|
if (DataLocation.PortableDataLocationInUse())
|
||||||
{
|
{
|
||||||
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
|
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
|
||||||
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, MessageBoxEx.Show);
|
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
|
||||||
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, MessageBoxEx.Show))
|
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)))
|
||||||
MessageBoxEx.Show(string.Format(api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
|
_api.ShowMsgBox(string.Format(_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
|
||||||
DataLocation.PortableDataPath,
|
DataLocation.PortableDataPath,
|
||||||
targetDestination));
|
targetDestination));
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +86,7 @@ namespace Flow.Launcher.Core
|
||||||
|
|
||||||
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
|
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
|
||||||
|
|
||||||
if (MessageBoxEx.Show(newVersionTips, api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
if (_api.ShowMsgBox(newVersionTips, _api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||||
{
|
{
|
||||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||||
}
|
}
|
||||||
|
|
@ -96,8 +99,8 @@ namespace Flow.Launcher.Core
|
||||||
Log.Exception($"|Updater.UpdateApp|Error Occurred", e);
|
Log.Exception($"|Updater.UpdateApp|Error Occurred", e);
|
||||||
|
|
||||||
if (!silentUpdate)
|
if (!silentUpdate)
|
||||||
api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"),
|
_api.ShowMsg(_api.GetTranslation("update_flowlauncher_fail"),
|
||||||
api.GetTranslation("update_flowlauncher_check_connection"));
|
_api.GetTranslation("update_flowlauncher_check_connection"));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -52,5 +52,7 @@ namespace Flow.Launcher.Infrastructure
|
||||||
public const string SponsorPage = "https://github.com/sponsors/Flow-Launcher";
|
public const string SponsorPage = "https://github.com/sponsors/Flow-Launcher";
|
||||||
public const string GitHub = "https://github.com/Flow-Launcher/Flow.Launcher";
|
public const string GitHub = "https://github.com/Flow-Launcher/Flow.Launcher";
|
||||||
public const string Docs = "https://flowlauncher.com/docs";
|
public const string Docs = "https://flowlauncher.com/docs";
|
||||||
|
|
||||||
|
public const string SystemLanguageCode = "system";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,20 @@ namespace Flow.Launcher.Infrastructure
|
||||||
{
|
{
|
||||||
var explorerWindow = GetActiveExplorer();
|
var explorerWindow = GetActiveExplorer();
|
||||||
string locationUrl = explorerWindow?.LocationURL;
|
string locationUrl = explorerWindow?.LocationURL;
|
||||||
return !string.IsNullOrEmpty(locationUrl) ? new Uri(locationUrl).LocalPath + "\\" : null;
|
return !string.IsNullOrEmpty(locationUrl) ? GetDirectoryPath(new Uri(locationUrl).LocalPath) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get directory path from a file path
|
||||||
|
/// </summary>
|
||||||
|
private static string GetDirectoryPath(string path)
|
||||||
|
{
|
||||||
|
if (!path.EndsWith("\\"))
|
||||||
|
{
|
||||||
|
return path + "\\";
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,13 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||||
<PackageReference Include="BitFaster.Caching" Version="2.5.2" />
|
<PackageReference Include="BitFaster.Caching" Version="2.5.3" />
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
<PackageReference Include="Fody" Version="6.5.5">
|
<PackageReference Include="Fody" Version="6.5.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="MemoryPack" Version="1.21.3" />
|
<PackageReference Include="MemoryPack" Version="1.21.4" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.12.19" />
|
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.12.19" />
|
||||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure.Http
|
namespace Flow.Launcher.Infrastructure.Http
|
||||||
{
|
{
|
||||||
|
|
@ -17,8 +18,6 @@ namespace Flow.Launcher.Infrastructure.Http
|
||||||
|
|
||||||
private static HttpClient client = new HttpClient();
|
private static HttpClient client = new HttpClient();
|
||||||
|
|
||||||
public static IPublicAPI API { get; set; }
|
|
||||||
|
|
||||||
static Http()
|
static Http()
|
||||||
{
|
{
|
||||||
// need to be added so it would work on a win10 machine
|
// need to be added so it would work on a win10 machine
|
||||||
|
|
@ -78,20 +77,55 @@ namespace Flow.Launcher.Infrastructure.Http
|
||||||
}
|
}
|
||||||
catch (UriFormatException e)
|
catch (UriFormatException e)
|
||||||
{
|
{
|
||||||
API.ShowMsg("Please try again", "Unable to parse Http Proxy");
|
Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsg("Please try again", "Unable to parse Http Proxy");
|
||||||
Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e);
|
Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default)
|
public static async Task DownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
|
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
if (response.StatusCode == HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
|
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
|
||||||
await response.Content.CopyToAsync(fileStream, token);
|
var canReportProgress = totalBytes != -1;
|
||||||
|
|
||||||
|
if (canReportProgress && reportProgress != null)
|
||||||
|
{
|
||||||
|
await using var contentStream = await response.Content.ReadAsStreamAsync(token);
|
||||||
|
await using var fileStream = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 8192, true);
|
||||||
|
|
||||||
|
var buffer = new byte[8192];
|
||||||
|
long totalRead = 0;
|
||||||
|
int read;
|
||||||
|
double progressValue = 0;
|
||||||
|
|
||||||
|
reportProgress(0);
|
||||||
|
|
||||||
|
while ((read = await contentStream.ReadAsync(buffer, token)) > 0)
|
||||||
|
{
|
||||||
|
await fileStream.WriteAsync(buffer.AsMemory(0, read), token);
|
||||||
|
totalRead += read;
|
||||||
|
|
||||||
|
progressValue = totalRead * 100.0 / totalBytes;
|
||||||
|
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
reportProgress(progressValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressValue < 100)
|
||||||
|
reportProgress(100);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
|
||||||
|
await response.Content.CopyToAsync(fileStream, token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,11 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
// Fallback to IconOnly if ThumbnailOnly fails
|
// Fallback to IconOnly if ThumbnailOnly fails
|
||||||
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
|
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
|
||||||
}
|
}
|
||||||
|
catch (FileNotFoundException) when (options == ThumbnailOptions.ThumbnailOnly)
|
||||||
|
{
|
||||||
|
// Fallback to IconOnly if files cannot be found
|
||||||
|
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -48,17 +48,45 @@ namespace Flow.Launcher.Infrastructure.Logger
|
||||||
configuration.AddTarget("file", fileTargetASyncWrapper);
|
configuration.AddTarget("file", fileTargetASyncWrapper);
|
||||||
configuration.AddTarget("debug", debugTarget);
|
configuration.AddTarget("debug", debugTarget);
|
||||||
|
|
||||||
|
var fileRule = new LoggingRule("*", LogLevel.Debug, fileTargetASyncWrapper)
|
||||||
|
{
|
||||||
|
RuleName = "file"
|
||||||
|
};
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
var fileRule = new LoggingRule("*", LogLevel.Debug, fileTargetASyncWrapper);
|
var debugRule = new LoggingRule("*", LogLevel.Debug, debugTarget)
|
||||||
var debugRule = new LoggingRule("*", LogLevel.Debug, debugTarget);
|
{
|
||||||
|
RuleName = "debug"
|
||||||
|
};
|
||||||
configuration.LoggingRules.Add(debugRule);
|
configuration.LoggingRules.Add(debugRule);
|
||||||
#else
|
|
||||||
var fileRule = new LoggingRule("*", LogLevel.Info, fileTargetASyncWrapper);
|
|
||||||
#endif
|
#endif
|
||||||
configuration.LoggingRules.Add(fileRule);
|
configuration.LoggingRules.Add(fileRule);
|
||||||
LogManager.Configuration = configuration;
|
LogManager.Configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetLogLevel(LOGLEVEL level)
|
||||||
|
{
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case LOGLEVEL.DEBUG:
|
||||||
|
UseDebugLogLevel();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UseInfoLogLevel();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Info(nameof(Logger), $"Using log level: {level}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UseDebugLogLevel()
|
||||||
|
{
|
||||||
|
LogManager.Configuration.FindRuleByName("file").SetLoggingLevels(LogLevel.Debug, LogLevel.Fatal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UseInfoLogLevel()
|
||||||
|
{
|
||||||
|
LogManager.Configuration.FindRuleByName("file").SetLoggingLevels(LogLevel.Info, LogLevel.Fatal);
|
||||||
|
}
|
||||||
|
|
||||||
private static void LogFaultyFormat(string message)
|
private static void LogFaultyFormat(string message)
|
||||||
{
|
{
|
||||||
var logger = LogManager.GetLogger("FaultyLogger");
|
var logger = LogManager.GetLogger("FaultyLogger");
|
||||||
|
|
@ -206,4 +234,10 @@ namespace Flow.Launcher.Infrastructure.Logger
|
||||||
LogInternal(message, LogLevel.Warn);
|
LogInternal(message, LogLevel.Warn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum LOGLEVEL
|
||||||
|
{
|
||||||
|
DEBUG,
|
||||||
|
INFO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Text;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using ToolGood.Words.Pinyin;
|
using ToolGood.Words.Pinyin;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure
|
namespace Flow.Launcher.Infrastructure
|
||||||
{
|
{
|
||||||
|
|
@ -129,7 +130,12 @@ namespace Flow.Launcher.Infrastructure
|
||||||
|
|
||||||
private Settings _settings;
|
private Settings _settings;
|
||||||
|
|
||||||
public void Initialize([NotNull] Settings settings)
|
public PinyinAlphabet()
|
||||||
|
{
|
||||||
|
Initialize(Ioc.Default.GetRequiredService<Settings>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize([NotNull] Settings settings)
|
||||||
{
|
{
|
||||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,12 @@ namespace Flow.Launcher.Infrastructure.Storage
|
||||||
protected JsonStorage()
|
protected JsonStorage()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonStorage(string filePath)
|
public JsonStorage(string filePath)
|
||||||
{
|
{
|
||||||
FilePath = filePath;
|
FilePath = filePath;
|
||||||
DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path");
|
DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path");
|
||||||
|
|
||||||
Helper.ValidateDirectory(DirectoryPath);
|
Helper.ValidateDirectory(DirectoryPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,6 +98,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RestoreBackup()
|
private void RestoreBackup()
|
||||||
{
|
{
|
||||||
Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully");
|
Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully");
|
||||||
|
|
@ -179,25 +181,21 @@ namespace Flow.Launcher.Infrastructure.Storage
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
string serialized = JsonSerializer.Serialize(Data,
|
string serialized = JsonSerializer.Serialize(Data,
|
||||||
new JsonSerializerOptions
|
new JsonSerializerOptions { WriteIndented = true });
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
});
|
|
||||||
|
|
||||||
File.WriteAllText(TempFilePath, serialized);
|
File.WriteAllText(TempFilePath, serialized);
|
||||||
|
|
||||||
AtomicWriteSetting();
|
AtomicWriteSetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveAsync()
|
public async Task SaveAsync()
|
||||||
{
|
{
|
||||||
var tempOutput = File.OpenWrite(TempFilePath);
|
await using var tempOutput = File.OpenWrite(TempFilePath);
|
||||||
await JsonSerializer.SerializeAsync(tempOutput, Data,
|
await JsonSerializer.SerializeAsync(tempOutput, Data,
|
||||||
new JsonSerializerOptions
|
new JsonSerializerOptions { WriteIndented = true });
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
});
|
|
||||||
AtomicWriteSetting();
|
AtomicWriteSetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AtomicWriteSetting()
|
private void AtomicWriteSetting()
|
||||||
{
|
{
|
||||||
if (!File.Exists(FilePath))
|
if (!File.Exists(FilePath))
|
||||||
|
|
@ -206,9 +204,9 @@ namespace Flow.Launcher.Infrastructure.Storage
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
File.Replace(TempFilePath, FilePath, BackupFilePath);
|
var finalFilePath = new FileInfo(FilePath).LinkTarget ?? FilePath;
|
||||||
|
File.Replace(TempFilePath, finalFilePath, BackupFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,17 @@ using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure.Storage
|
namespace Flow.Launcher.Infrastructure.Storage
|
||||||
{
|
{
|
||||||
public class PluginJsonStorage<T> :JsonStorage<T> where T : new()
|
public class PluginJsonStorage<T> : JsonStorage<T> where T : new()
|
||||||
{
|
{
|
||||||
|
// Use assembly name to check which plugin is using this storage
|
||||||
|
public readonly string AssemblyName;
|
||||||
|
|
||||||
public PluginJsonStorage()
|
public PluginJsonStorage()
|
||||||
{
|
{
|
||||||
// C# related, add python related below
|
// C# related, add python related below
|
||||||
var dataType = typeof(T);
|
var dataType = typeof(T);
|
||||||
var assemblyName = dataType.Assembly.GetName().Name;
|
AssemblyName = dataType.Assembly.GetName().Name;
|
||||||
DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Plugins, assemblyName);
|
DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Plugins, AssemblyName);
|
||||||
Helper.ValidateDirectory(DirectoryPath);
|
Helper.ValidateDirectory(DirectoryPath);
|
||||||
|
|
||||||
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
|
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
|
||||||
|
|
@ -20,6 +23,13 @@ namespace Flow.Launcher.Infrastructure.Storage
|
||||||
{
|
{
|
||||||
Data = data;
|
Data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DeleteDirectory()
|
||||||
|
{
|
||||||
|
if (Directory.Exists(DirectoryPath))
|
||||||
|
{
|
||||||
|
Directory.Delete(DirectoryPath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,35 @@
|
||||||
using Flow.Launcher.Plugin.SharedModels;
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
using Flow.Launcher.Plugin.SharedModels;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure
|
namespace Flow.Launcher.Infrastructure
|
||||||
{
|
{
|
||||||
public class StringMatcher
|
public class StringMatcher
|
||||||
{
|
{
|
||||||
private readonly MatchOption _defaultMatchOption = new MatchOption();
|
private readonly MatchOption _defaultMatchOption = new();
|
||||||
|
|
||||||
public SearchPrecisionScore UserSettingSearchPrecision { get; set; }
|
public SearchPrecisionScore UserSettingSearchPrecision { get; set; }
|
||||||
|
|
||||||
private readonly IAlphabet _alphabet;
|
private readonly IAlphabet _alphabet;
|
||||||
|
|
||||||
public StringMatcher(IAlphabet alphabet = null)
|
public StringMatcher(IAlphabet alphabet, Settings settings)
|
||||||
|
{
|
||||||
|
_alphabet = alphabet;
|
||||||
|
UserSettingSearchPrecision = settings.QuerySearchPrecision;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a workaround to allow unit tests to set the instance
|
||||||
|
public StringMatcher(IAlphabet alphabet)
|
||||||
{
|
{
|
||||||
_alphabet = alphabet;
|
_alphabet = alphabet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StringMatcher Instance { get; internal set; }
|
|
||||||
|
|
||||||
public static MatchResult FuzzySearch(string query, string stringToCompare)
|
public static MatchResult FuzzySearch(string query, string stringToCompare)
|
||||||
{
|
{
|
||||||
return Instance.FuzzyMatch(query, stringToCompare);
|
return Ioc.Default.GetRequiredService<StringMatcher>().FuzzyMatch(query, stringToCompare);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MatchResult FuzzyMatch(string query, string stringToCompare)
|
public MatchResult FuzzyMatch(string query, string stringToCompare)
|
||||||
|
|
@ -241,16 +248,16 @@ namespace Flow.Launcher.Infrastructure
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAcronymChar(string stringToCompare, int compareStringIndex)
|
private static bool IsAcronymChar(string stringToCompare, int compareStringIndex)
|
||||||
=> char.IsUpper(stringToCompare[compareStringIndex]) ||
|
=> char.IsUpper(stringToCompare[compareStringIndex]) ||
|
||||||
compareStringIndex == 0 || // 0 index means char is the start of the compare string, which is an acronym
|
compareStringIndex == 0 || // 0 index means char is the start of the compare string, which is an acronym
|
||||||
char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]);
|
char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]);
|
||||||
|
|
||||||
private bool IsAcronymNumber(string stringToCompare, int compareStringIndex)
|
private static bool IsAcronymNumber(string stringToCompare, int compareStringIndex)
|
||||||
=> stringToCompare[compareStringIndex] >= 0 && stringToCompare[compareStringIndex] <= 9;
|
=> stringToCompare[compareStringIndex] >= 0 && stringToCompare[compareStringIndex] <= 9;
|
||||||
|
|
||||||
// To get the index of the closest space which preceeds the first matching index
|
// To get the index of the closest space which preceeds the first matching index
|
||||||
private int CalculateClosestSpaceIndex(List<int> spaceIndices, int firstMatchIndex)
|
private static int CalculateClosestSpaceIndex(List<int> spaceIndices, int firstMatchIndex)
|
||||||
{
|
{
|
||||||
var closestSpaceIndex = -1;
|
var closestSpaceIndex = -1;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
using Flow.Launcher.Infrastructure.Hotkey;
|
using Flow.Launcher.Infrastructure.Hotkey;
|
||||||
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
|
using Flow.Launcher.Infrastructure.Storage;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using Flow.Launcher.Plugin.SharedModels;
|
using Flow.Launcher.Plugin.SharedModels;
|
||||||
using Flow.Launcher.ViewModel;
|
using Flow.Launcher.ViewModel;
|
||||||
|
|
@ -13,7 +15,25 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
||||||
{
|
{
|
||||||
public class Settings : BaseModel, IHotkeySettings
|
public class Settings : BaseModel, IHotkeySettings
|
||||||
{
|
{
|
||||||
private string language = "en";
|
private FlowLauncherJsonStorage<Settings> _storage;
|
||||||
|
private StringMatcher _stringMatcher = null;
|
||||||
|
|
||||||
|
public void SetStorage(FlowLauncherJsonStorage<Settings> storage)
|
||||||
|
{
|
||||||
|
_storage = storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_stringMatcher = Ioc.Default.GetRequiredService<StringMatcher>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
_storage.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string language = Constant.SystemLanguageCode;
|
||||||
private string _theme = Constant.DefaultTheme;
|
private string _theme = Constant.DefaultTheme;
|
||||||
public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}";
|
public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}";
|
||||||
public string OpenResultModifiers { get; set; } = KeyConstant.Alt;
|
public string OpenResultModifiers { get; set; } = KeyConstant.Alt;
|
||||||
|
|
@ -180,6 +200,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||||
|
public LOGLEVEL LogLevel { get; set; } = LOGLEVEL.INFO;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// when false Alphabet static service will always return empty results
|
/// when false Alphabet static service will always return empty results
|
||||||
|
|
@ -198,8 +220,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_querySearchPrecision = value;
|
_querySearchPrecision = value;
|
||||||
if (StringMatcher.Instance != null)
|
if (_stringMatcher != null)
|
||||||
StringMatcher.Instance.UserSettingSearchPrecision = value;
|
_stringMatcher.UserSettingSearchPrecision = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,6 +260,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
||||||
public bool EnableUpdateLog { get; set; }
|
public bool EnableUpdateLog { get; set; }
|
||||||
|
|
||||||
public bool StartFlowLauncherOnSystemStartup { get; set; } = false;
|
public bool StartFlowLauncherOnSystemStartup { get; set; } = false;
|
||||||
|
public bool UseLogonTaskForStartup { get; set; } = false;
|
||||||
public bool HideOnStartup { get; set; } = true;
|
public bool HideOnStartup { get; set; } = true;
|
||||||
bool _hideNotifyIcon { get; set; }
|
bool _hideNotifyIcon { get; set; }
|
||||||
public bool HideNotifyIcon
|
public bool HideNotifyIcon
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Flow.Launcher.Plugin.SharedModels;
|
using Flow.Launcher.Plugin.SharedModels;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -17,7 +17,8 @@ namespace Flow.Launcher.Plugin
|
||||||
public interface IPublicAPI
|
public interface IPublicAPI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Change Flow.Launcher query
|
/// Change Flow.Launcher query.
|
||||||
|
/// When current results are from context menu or history, it will go back to query results before changing query.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="query">query text</param>
|
/// <param name="query">query text</param>
|
||||||
/// <param name="requery">
|
/// <param name="requery">
|
||||||
|
|
@ -181,9 +182,13 @@ namespace Flow.Launcher.Plugin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url">URL to download file</param>
|
/// <param name="url">URL to download file</param>
|
||||||
/// <param name="filePath">path to save downloaded file</param>
|
/// <param name="filePath">path to save downloaded file</param>
|
||||||
|
/// <param name="reportProgress">
|
||||||
|
/// Action to report progress. The input of the action is the progress value which is a double value between 0 and 100.
|
||||||
|
/// It will be called if url support range request and the reportProgress is not null.
|
||||||
|
/// </param>
|
||||||
/// <param name="token">place to store file</param>
|
/// <param name="token">place to store file</param>
|
||||||
/// <returns>Task showing the progress</returns>
|
/// <returns>Task showing the progress</returns>
|
||||||
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default);
|
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add ActionKeyword for specific plugin
|
/// Add ActionKeyword for specific plugin
|
||||||
|
|
@ -295,7 +300,7 @@ namespace Flow.Launcher.Plugin
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reloads the query.
|
/// Reloads the query.
|
||||||
/// This method should run when selected item is from query results.
|
/// When current results are from context menu or history, it will go back to query results before requerying.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reselect">Choose the first result after reload if true; keep the last selected result if false. Default is true.</param>
|
/// <param name="reselect">Choose the first result after reload if true; keep the last selected result if false. Default is true.</param>
|
||||||
public void ReQuery(bool reselect = true);
|
public void ReQuery(bool reselect = true);
|
||||||
|
|
@ -316,5 +321,28 @@ namespace Flow.Launcher.Plugin
|
||||||
/// <param name="defaultResult">Specifies the default result of the message box.</param>
|
/// <param name="defaultResult">Specifies the default result of the message box.</param>
|
||||||
/// <returns>Specifies which message box button is clicked by the user.</returns>
|
/// <returns>Specifies which message box button is clicked by the user.</returns>
|
||||||
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK);
|
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a standardised Flow progress box.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="caption">The caption of the progress box.</param>
|
||||||
|
/// <param name="reportProgressAsync">
|
||||||
|
/// Time-consuming task function, whose input is the action to report progress.
|
||||||
|
/// The input of the action is the progress value which is a double value between 0 and 100.
|
||||||
|
/// If there are any exceptions, this action will be null.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="cancelProgress">When user cancel the progress, this action will be called.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action cancelProgress = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start the loading bar in main window
|
||||||
|
/// </summary>
|
||||||
|
public void StartLoadingBar();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop the loading bar in main window
|
||||||
|
/// </summary>
|
||||||
|
public void StopLoadingBar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
using System.Text.Json.Serialization;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin
|
namespace Flow.Launcher.Plugin
|
||||||
{
|
{
|
||||||
|
|
@ -9,15 +6,6 @@ namespace Flow.Launcher.Plugin
|
||||||
{
|
{
|
||||||
public Query() { }
|
public Query() { }
|
||||||
|
|
||||||
[Obsolete("Use the default Query constructor.")]
|
|
||||||
public Query(string rawQuery, string search, string[] terms, string[] searchTerms, string actionKeyword = "")
|
|
||||||
{
|
|
||||||
Search = search;
|
|
||||||
RawQuery = rawQuery;
|
|
||||||
SearchTerms = searchTerms;
|
|
||||||
ActionKeyword = actionKeyword;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raw query, this includes action keyword if it has
|
/// Raw query, this includes action keyword if it has
|
||||||
/// We didn't recommend use this property directly. You should always use Search property.
|
/// We didn't recommend use this property directly. You should always use Search property.
|
||||||
|
|
|
||||||
|
|
@ -157,27 +157,6 @@ namespace Flow.Launcher.Plugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
var r = obj as Result;
|
|
||||||
|
|
||||||
var equality = string.Equals(r?.Title, Title) &&
|
|
||||||
string.Equals(r?.SubTitle, SubTitle) &&
|
|
||||||
string.Equals(r?.AutoCompleteText, AutoCompleteText) &&
|
|
||||||
string.Equals(r?.CopyText, CopyText) &&
|
|
||||||
string.Equals(r?.IcoPath, IcoPath) &&
|
|
||||||
TitleHighlightData == r.TitleHighlightData;
|
|
||||||
|
|
||||||
return equality;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(Title, SubTitle, AutoCompleteText, CopyText, IcoPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|
@ -206,6 +185,16 @@ namespace Flow.Launcher.Plugin
|
||||||
TitleHighlightData = TitleHighlightData,
|
TitleHighlightData = TitleHighlightData,
|
||||||
OriginQuery = OriginQuery,
|
OriginQuery = OriginQuery,
|
||||||
PluginDirectory = PluginDirectory,
|
PluginDirectory = PluginDirectory,
|
||||||
|
ContextData = ContextData,
|
||||||
|
PluginID = PluginID,
|
||||||
|
TitleToolTip = TitleToolTip,
|
||||||
|
SubTitleToolTip = SubTitleToolTip,
|
||||||
|
PreviewPanel = PreviewPanel,
|
||||||
|
ProgressBar = ProgressBar,
|
||||||
|
ProgressBarColor = ProgressBarColor,
|
||||||
|
Preview = Preview,
|
||||||
|
AddSelectedCount = AddSelectedCount,
|
||||||
|
RecordKey = RecordKey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,6 +262,14 @@ namespace Flow.Launcher.Plugin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int MaxScore = int.MaxValue;
|
public const int MaxScore = int.MaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The key to identify the record. This is used when FL checks whether the result is the topmost record. Or FL calculates the hashcode of the result for user selected records.
|
||||||
|
/// This can be useful when your plugin will change the Title or SubTitle of the result dynamically.
|
||||||
|
/// If the plugin does not specific this, FL just uses Title and SubTitle to identify this result.
|
||||||
|
/// Note: Because old data does not have this key, we should use null as the default value for consistency.
|
||||||
|
/// </summary>
|
||||||
|
public string RecordKey { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Info of the preview section of a <see cref="Result"/>
|
/// Info of the preview section of a <see cref="Result"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.Legacy;
|
||||||
|
|
||||||
namespace Flow.Launcher.Test
|
namespace Flow.Launcher.Test
|
||||||
{
|
{
|
||||||
|
|
@ -35,7 +36,7 @@ namespace Flow.Launcher.Test
|
||||||
[TestCase(@"c:\barr", @"c:\foo\..\bar\baz", false)]
|
[TestCase(@"c:\barr", @"c:\foo\..\bar\baz", false)]
|
||||||
public void GivenTwoPaths_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult)
|
public void GivenTwoPaths_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path));
|
ClassicAssert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equality
|
// Equality
|
||||||
|
|
@ -47,7 +48,7 @@ namespace Flow.Launcher.Test
|
||||||
[TestCase(@"c:\foo", @"c:\foo\", true)]
|
[TestCase(@"c:\foo", @"c:\foo\", true)]
|
||||||
public void GivenTwoPathsAreTheSame_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult)
|
public void GivenTwoPathsAreTheSame_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path, allowEqual: expectedResult));
|
ClassicAssert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path, allowEqual: expectedResult));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Moq" Version="4.18.4" />
|
<PackageReference Include="Moq" Version="4.18.4" />
|
||||||
<PackageReference Include="nunit" Version="3.14.0" />
|
<PackageReference Include="nunit" Version="4.3.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0">
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.Legacy;
|
||||||
using Flow.Launcher.Infrastructure;
|
using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using Flow.Launcher.Plugin.SharedModels;
|
using Flow.Launcher.Plugin.SharedModels;
|
||||||
|
|
@ -21,6 +22,8 @@ namespace Flow.Launcher.Test
|
||||||
private const string MicrosoftSqlServerManagementStudio = "Microsoft SQL Server Management Studio";
|
private const string MicrosoftSqlServerManagementStudio = "Microsoft SQL Server Management Studio";
|
||||||
private const string VisualStudioCode = "Visual Studio Code";
|
private const string VisualStudioCode = "Visual Studio Code";
|
||||||
|
|
||||||
|
private readonly IAlphabet alphabet = null;
|
||||||
|
|
||||||
public List<string> GetSearchStrings()
|
public List<string> GetSearchStrings()
|
||||||
=> new List<string>
|
=> new List<string>
|
||||||
{
|
{
|
||||||
|
|
@ -34,7 +37,7 @@ namespace Flow.Launcher.Test
|
||||||
OneOneOneOne
|
OneOneOneOne
|
||||||
};
|
};
|
||||||
|
|
||||||
public List<int> GetPrecisionScores()
|
public static List<int> GetPrecisionScores()
|
||||||
{
|
{
|
||||||
var listToReturn = new List<int>();
|
var listToReturn = new List<int>();
|
||||||
|
|
||||||
|
|
@ -59,7 +62,7 @@ namespace Flow.Launcher.Test
|
||||||
};
|
};
|
||||||
|
|
||||||
var results = new List<Result>();
|
var results = new List<Result>();
|
||||||
var matcher = new StringMatcher();
|
var matcher = new StringMatcher(alphabet);
|
||||||
foreach (var str in sources)
|
foreach (var str in sources)
|
||||||
{
|
{
|
||||||
results.Add(new Result
|
results.Add(new Result
|
||||||
|
|
@ -71,20 +74,20 @@ namespace Flow.Launcher.Test
|
||||||
|
|
||||||
results = results.Where(x => x.Score > 0).OrderByDescending(x => x.Score).ToList();
|
results = results.Where(x => x.Score > 0).OrderByDescending(x => x.Score).ToList();
|
||||||
|
|
||||||
Assert.IsTrue(results.Count == 3);
|
ClassicAssert.IsTrue(results.Count == 3);
|
||||||
Assert.IsTrue(results[0].Title == "Inste");
|
ClassicAssert.IsTrue(results[0].Title == "Inste");
|
||||||
Assert.IsTrue(results[1].Title == "Install Package");
|
ClassicAssert.IsTrue(results[1].Title == "Install Package");
|
||||||
Assert.IsTrue(results[2].Title == "file open in browser-test");
|
ClassicAssert.IsTrue(results[2].Title == "file open in browser-test");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("Chrome")]
|
[TestCase("Chrome")]
|
||||||
public void WhenNotAllCharactersFoundInSearchString_ThenShouldReturnZeroScore(string searchString)
|
public void WhenNotAllCharactersFoundInSearchString_ThenShouldReturnZeroScore(string searchString)
|
||||||
{
|
{
|
||||||
var compareString = "Can have rum only in my glass";
|
var compareString = "Can have rum only in my glass";
|
||||||
var matcher = new StringMatcher();
|
var matcher = new StringMatcher(alphabet);
|
||||||
var scoreResult = matcher.FuzzyMatch(searchString, compareString).RawScore;
|
var scoreResult = matcher.FuzzyMatch(searchString, compareString).RawScore;
|
||||||
|
|
||||||
Assert.True(scoreResult == 0);
|
ClassicAssert.True(scoreResult == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("chr")]
|
[TestCase("chr")]
|
||||||
|
|
@ -97,7 +100,7 @@ namespace Flow.Launcher.Test
|
||||||
string searchTerm)
|
string searchTerm)
|
||||||
{
|
{
|
||||||
var results = new List<Result>();
|
var results = new List<Result>();
|
||||||
var matcher = new StringMatcher();
|
var matcher = new StringMatcher(alphabet);
|
||||||
foreach (var str in GetSearchStrings())
|
foreach (var str in GetSearchStrings())
|
||||||
{
|
{
|
||||||
results.Add(new Result
|
results.Add(new Result
|
||||||
|
|
@ -125,7 +128,7 @@ namespace Flow.Launcher.Test
|
||||||
Debug.WriteLine("###############################################");
|
Debug.WriteLine("###############################################");
|
||||||
Debug.WriteLine("");
|
Debug.WriteLine("");
|
||||||
|
|
||||||
Assert.IsFalse(filteredResult.Any(x => x.Score < precisionScore));
|
ClassicAssert.IsFalse(filteredResult.Any(x => x.Score < precisionScore));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,11 +150,11 @@ namespace Flow.Launcher.Test
|
||||||
string queryString, string compareString, int expectedScore)
|
string queryString, string compareString, int expectedScore)
|
||||||
{
|
{
|
||||||
// When, Given
|
// When, Given
|
||||||
var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
|
var matcher = new StringMatcher(alphabet) {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
|
||||||
var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore;
|
var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore;
|
||||||
|
|
||||||
// Should
|
// Should
|
||||||
Assert.AreEqual(expectedScore, rawScore,
|
ClassicAssert.AreEqual(expectedScore, rawScore,
|
||||||
$"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
|
$"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,7 +184,7 @@ namespace Flow.Launcher.Test
|
||||||
bool expectedPrecisionResult)
|
bool expectedPrecisionResult)
|
||||||
{
|
{
|
||||||
// When
|
// When
|
||||||
var matcher = new StringMatcher {UserSettingSearchPrecision = expectedPrecisionScore};
|
var matcher = new StringMatcher(alphabet) {UserSettingSearchPrecision = expectedPrecisionScore};
|
||||||
|
|
||||||
// Given
|
// Given
|
||||||
var matchResult = matcher.FuzzyMatch(queryString, compareString);
|
var matchResult = matcher.FuzzyMatch(queryString, compareString);
|
||||||
|
|
@ -190,12 +193,12 @@ namespace Flow.Launcher.Test
|
||||||
Debug.WriteLine("###############################################");
|
Debug.WriteLine("###############################################");
|
||||||
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
||||||
Debug.WriteLine(
|
Debug.WriteLine(
|
||||||
$"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})");
|
$"RAW SCORE: {matchResult.RawScore}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})");
|
||||||
Debug.WriteLine("###############################################");
|
Debug.WriteLine("###############################################");
|
||||||
Debug.WriteLine("");
|
Debug.WriteLine("");
|
||||||
|
|
||||||
// Should
|
// Should
|
||||||
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
|
ClassicAssert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
|
||||||
$"Query: {queryString}{Environment.NewLine} " +
|
$"Query: {queryString}{Environment.NewLine} " +
|
||||||
$"Compare: {compareString}{Environment.NewLine}" +
|
$"Compare: {compareString}{Environment.NewLine}" +
|
||||||
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
|
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
|
||||||
|
|
@ -232,7 +235,7 @@ namespace Flow.Launcher.Test
|
||||||
bool expectedPrecisionResult)
|
bool expectedPrecisionResult)
|
||||||
{
|
{
|
||||||
// When
|
// When
|
||||||
var matcher = new StringMatcher {UserSettingSearchPrecision = expectedPrecisionScore};
|
var matcher = new StringMatcher(alphabet) {UserSettingSearchPrecision = expectedPrecisionScore};
|
||||||
|
|
||||||
// Given
|
// Given
|
||||||
var matchResult = matcher.FuzzyMatch(queryString, compareString);
|
var matchResult = matcher.FuzzyMatch(queryString, compareString);
|
||||||
|
|
@ -241,12 +244,12 @@ namespace Flow.Launcher.Test
|
||||||
Debug.WriteLine("###############################################");
|
Debug.WriteLine("###############################################");
|
||||||
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
||||||
Debug.WriteLine(
|
Debug.WriteLine(
|
||||||
$"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})");
|
$"RAW SCORE: {matchResult.RawScore}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})");
|
||||||
Debug.WriteLine("###############################################");
|
Debug.WriteLine("###############################################");
|
||||||
Debug.WriteLine("");
|
Debug.WriteLine("");
|
||||||
|
|
||||||
// Should
|
// Should
|
||||||
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
|
ClassicAssert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
|
||||||
$"Query:{queryString}{Environment.NewLine} " +
|
$"Query:{queryString}{Environment.NewLine} " +
|
||||||
$"Compare:{compareString}{Environment.NewLine}" +
|
$"Compare:{compareString}{Environment.NewLine}" +
|
||||||
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
|
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
|
||||||
|
|
@ -260,7 +263,7 @@ namespace Flow.Launcher.Test
|
||||||
string queryString, string compareString1, string compareString2)
|
string queryString, string compareString1, string compareString2)
|
||||||
{
|
{
|
||||||
// When
|
// When
|
||||||
var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
|
var matcher = new StringMatcher(alphabet) {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
|
||||||
|
|
||||||
// Given
|
// Given
|
||||||
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
|
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
|
||||||
|
|
@ -277,7 +280,7 @@ namespace Flow.Launcher.Test
|
||||||
Debug.WriteLine("");
|
Debug.WriteLine("");
|
||||||
|
|
||||||
// Should
|
// Should
|
||||||
Assert.True(compareString1Result.Score > compareString2Result.Score,
|
ClassicAssert.True(compareString1Result.Score > compareString2Result.Score,
|
||||||
$"Query: \"{queryString}\"{Environment.NewLine} " +
|
$"Query: \"{queryString}\"{Environment.NewLine} " +
|
||||||
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" +
|
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" +
|
||||||
$"Should be greater than{Environment.NewLine}" +
|
$"Should be greater than{Environment.NewLine}" +
|
||||||
|
|
@ -293,7 +296,7 @@ namespace Flow.Launcher.Test
|
||||||
string queryString, string compareString1, string compareString2)
|
string queryString, string compareString1, string compareString2)
|
||||||
{
|
{
|
||||||
// When
|
// When
|
||||||
var matcher = new StringMatcher { UserSettingSearchPrecision = SearchPrecisionScore.Regular };
|
var matcher = new StringMatcher(alphabet) { UserSettingSearchPrecision = SearchPrecisionScore.Regular };
|
||||||
|
|
||||||
// Given
|
// Given
|
||||||
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
|
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
|
||||||
|
|
@ -310,7 +313,7 @@ namespace Flow.Launcher.Test
|
||||||
Debug.WriteLine("");
|
Debug.WriteLine("");
|
||||||
|
|
||||||
// Should
|
// Should
|
||||||
Assert.True(compareString1Result.Score > compareString2Result.Score,
|
ClassicAssert.True(compareString1Result.Score > compareString2Result.Score,
|
||||||
$"Query: \"{queryString}\"{Environment.NewLine} " +
|
$"Query: \"{queryString}\"{Environment.NewLine} " +
|
||||||
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" +
|
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" +
|
||||||
$"Should be greater than{Environment.NewLine}" +
|
$"Should be greater than{Environment.NewLine}" +
|
||||||
|
|
@ -323,7 +326,7 @@ namespace Flow.Launcher.Test
|
||||||
string secondName, string secondDescription, string secondExecutableName)
|
string secondName, string secondDescription, string secondExecutableName)
|
||||||
{
|
{
|
||||||
// Act
|
// Act
|
||||||
var matcher = new StringMatcher();
|
var matcher = new StringMatcher(alphabet);
|
||||||
var firstNameMatch = matcher.FuzzyMatch(queryString, firstName).RawScore;
|
var firstNameMatch = matcher.FuzzyMatch(queryString, firstName).RawScore;
|
||||||
var firstDescriptionMatch = matcher.FuzzyMatch(queryString, firstDescription).RawScore;
|
var firstDescriptionMatch = matcher.FuzzyMatch(queryString, firstDescription).RawScore;
|
||||||
var firstExecutableNameMatch = matcher.FuzzyMatch(queryString, firstExecutableName).RawScore;
|
var firstExecutableNameMatch = matcher.FuzzyMatch(queryString, firstExecutableName).RawScore;
|
||||||
|
|
@ -336,7 +339,7 @@ namespace Flow.Launcher.Test
|
||||||
var secondScore = new[] {secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch}.Max();
|
var secondScore = new[] {secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch}.Max();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsTrue(firstScore > secondScore,
|
ClassicAssert.IsTrue(firstScore > secondScore,
|
||||||
$"Query: \"{queryString}\"{Environment.NewLine} " +
|
$"Query: \"{queryString}\"{Environment.NewLine} " +
|
||||||
$"Name of first: \"{firstName}\", Final Score: {firstScore}{Environment.NewLine}" +
|
$"Name of first: \"{firstName}\", Final Score: {firstScore}{Environment.NewLine}" +
|
||||||
$"Should be greater than{Environment.NewLine}" +
|
$"Should be greater than{Environment.NewLine}" +
|
||||||
|
|
@ -358,9 +361,9 @@ namespace Flow.Launcher.Test
|
||||||
public void WhenGivenAnAcronymQuery_ShouldReturnAcronymScore(string queryString, string compareString,
|
public void WhenGivenAnAcronymQuery_ShouldReturnAcronymScore(string queryString, string compareString,
|
||||||
int desiredScore)
|
int desiredScore)
|
||||||
{
|
{
|
||||||
var matcher = new StringMatcher();
|
var matcher = new StringMatcher(alphabet);
|
||||||
var score = matcher.FuzzyMatch(queryString, compareString).Score;
|
var score = matcher.FuzzyMatch(queryString, compareString).Score;
|
||||||
Assert.IsTrue(score == desiredScore,
|
ClassicAssert.IsTrue(score == desiredScore,
|
||||||
$@"Query: ""{queryString}""
|
$@"Query: ""{queryString}""
|
||||||
CompareString: ""{compareString}""
|
CompareString: ""{compareString}""
|
||||||
Score: {score}
|
Score: {score}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.Legacy;
|
||||||
using System;
|
using System;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Infrastructure.Http;
|
using Flow.Launcher.Infrastructure.Http;
|
||||||
|
|
@ -16,16 +17,16 @@ namespace Flow.Launcher.Test
|
||||||
|
|
||||||
proxy.Enabled = true;
|
proxy.Enabled = true;
|
||||||
proxy.Server = "127.0.0.1";
|
proxy.Server = "127.0.0.1";
|
||||||
Assert.AreEqual(Http.WebProxy.Address, new Uri($"http://{proxy.Server}:{proxy.Port}"));
|
ClassicAssert.AreEqual(Http.WebProxy.Address, new Uri($"http://{proxy.Server}:{proxy.Port}"));
|
||||||
Assert.IsNull(Http.WebProxy.Credentials);
|
ClassicAssert.IsNull(Http.WebProxy.Credentials);
|
||||||
|
|
||||||
proxy.UserName = "test";
|
proxy.UserName = "test";
|
||||||
Assert.NotNull(Http.WebProxy.Credentials);
|
ClassicAssert.NotNull(Http.WebProxy.Credentials);
|
||||||
Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").UserName, proxy.UserName);
|
ClassicAssert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").UserName, proxy.UserName);
|
||||||
Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, "");
|
ClassicAssert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, "");
|
||||||
|
|
||||||
proxy.Password = "test password";
|
proxy.Password = "test password";
|
||||||
Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, proxy.Password);
|
ClassicAssert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, proxy.Password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.Legacy;
|
||||||
using Flow.Launcher.Core.Plugin;
|
using Flow.Launcher.Core.Plugin;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -15,37 +16,37 @@ namespace Flow.Launcher.Test
|
||||||
// Given
|
// Given
|
||||||
var duplicateList = new List<PluginMetadata>
|
var duplicateList = new List<PluginMetadata>
|
||||||
{
|
{
|
||||||
new PluginMetadata
|
new()
|
||||||
{
|
{
|
||||||
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
||||||
Version = "1.0.0"
|
Version = "1.0.0"
|
||||||
},
|
},
|
||||||
new PluginMetadata
|
new()
|
||||||
{
|
{
|
||||||
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
||||||
Version = "1.0.1"
|
Version = "1.0.1"
|
||||||
},
|
},
|
||||||
new PluginMetadata
|
new()
|
||||||
{
|
{
|
||||||
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
||||||
Version = "1.0.2"
|
Version = "1.0.2"
|
||||||
},
|
},
|
||||||
new PluginMetadata
|
new()
|
||||||
{
|
{
|
||||||
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
||||||
Version = "1.0.0"
|
Version = "1.0.0"
|
||||||
},
|
},
|
||||||
new PluginMetadata
|
new()
|
||||||
{
|
{
|
||||||
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
ID = "CEA0TYUC6D3B4085823D60DC76F28855",
|
||||||
Version = "1.0.0"
|
Version = "1.0.0"
|
||||||
},
|
},
|
||||||
new PluginMetadata
|
new()
|
||||||
{
|
{
|
||||||
ID = "ABC0TYUC6D3B7855823D60DC76F28855",
|
ID = "ABC0TYUC6D3B7855823D60DC76F28855",
|
||||||
Version = "1.0.0"
|
Version = "1.0.0"
|
||||||
},
|
},
|
||||||
new PluginMetadata
|
new()
|
||||||
{
|
{
|
||||||
ID = "ABC0TYUC6D3B7855823D60DC76F28855",
|
ID = "ABC0TYUC6D3B7855823D60DC76F28855",
|
||||||
Version = "1.0.0"
|
Version = "1.0.0"
|
||||||
|
|
@ -56,11 +57,11 @@ namespace Flow.Launcher.Test
|
||||||
(var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList);
|
(var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.True(unique.FirstOrDefault().ID == "CEA0TYUC6D3B4085823D60DC76F28855" && unique.FirstOrDefault().Version == "1.0.2");
|
ClassicAssert.True(unique.FirstOrDefault().ID == "CEA0TYUC6D3B4085823D60DC76F28855" && unique.FirstOrDefault().Version == "1.0.2");
|
||||||
Assert.True(unique.Count() == 1);
|
ClassicAssert.True(unique.Count == 1);
|
||||||
|
|
||||||
Assert.False(duplicates.Any(x => x.Version == "1.0.2" && x.ID == "CEA0TYUC6D3B4085823D60DC76F28855"));
|
ClassicAssert.False(duplicates.Any(x => x.Version == "1.0.2" && x.ID == "CEA0TYUC6D3B4085823D60DC76F28855"));
|
||||||
Assert.True(duplicates.Count() == 6);
|
ClassicAssert.True(duplicates.Count == 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -69,12 +70,12 @@ namespace Flow.Launcher.Test
|
||||||
// Given
|
// Given
|
||||||
var duplicateList = new List<PluginMetadata>
|
var duplicateList = new List<PluginMetadata>
|
||||||
{
|
{
|
||||||
new PluginMetadata
|
new()
|
||||||
{
|
{
|
||||||
ID = "CEA0TYUC6D3B7855823D60DC76F28855",
|
ID = "CEA0TYUC6D3B7855823D60DC76F28855",
|
||||||
Version = "1.0.0"
|
Version = "1.0.0"
|
||||||
},
|
},
|
||||||
new PluginMetadata
|
new()
|
||||||
{
|
{
|
||||||
ID = "CEA0TYUC6D3B7855823D60DC76F28855",
|
ID = "CEA0TYUC6D3B7855823D60DC76F28855",
|
||||||
Version = "1.0.0"
|
Version = "1.0.0"
|
||||||
|
|
@ -85,8 +86,8 @@ namespace Flow.Launcher.Test
|
||||||
(var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList);
|
(var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.True(unique.Count() == 0);
|
ClassicAssert.True(unique.Count == 0);
|
||||||
Assert.True(duplicates.Count() == 2);
|
ClassicAssert.True(duplicates.Count == 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,10 @@ using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo;
|
||||||
using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
|
using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
|
||||||
using Flow.Launcher.Plugin.SharedCommands;
|
using Flow.Launcher.Plugin.SharedCommands;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.Legacy;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using static Flow.Launcher.Plugin.Explorer.Search.SearchManager;
|
using static Flow.Launcher.Plugin.Explorer.Search.SearchManager;
|
||||||
|
|
||||||
namespace Flow.Launcher.Test.Plugins
|
namespace Flow.Launcher.Test.Plugins
|
||||||
|
|
@ -22,28 +20,6 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class ExplorerTest
|
public class ExplorerTest
|
||||||
{
|
{
|
||||||
#pragma warning disable CS1998 // async method with no await (more readable to leave it async to match the tested signature)
|
|
||||||
private async Task<List<Result>> MethodWindowsIndexSearchReturnsZeroResultsAsync(Query dummyQuery, string dummyString, CancellationToken dummyToken)
|
|
||||||
{
|
|
||||||
return new List<Result>();
|
|
||||||
}
|
|
||||||
#pragma warning restore CS1998
|
|
||||||
|
|
||||||
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString, CancellationToken token)
|
|
||||||
{
|
|
||||||
return new List<Result>
|
|
||||||
{
|
|
||||||
new Result
|
|
||||||
{
|
|
||||||
Title = "Result 1"
|
|
||||||
},
|
|
||||||
new Result
|
|
||||||
{
|
|
||||||
Title = "Result 2"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool PreviousLocationExistsReturnsTrue(string dummyString) => true;
|
private bool PreviousLocationExistsReturnsTrue(string dummyString) => true;
|
||||||
|
|
||||||
private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false;
|
private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false;
|
||||||
|
|
@ -57,7 +33,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var result = QueryConstructor.TopLevelDirectoryConstraint(folderPath);
|
var result = QueryConstructor.TopLevelDirectoryConstraint(folderPath);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.IsTrue(result == expectedString,
|
ClassicAssert.IsTrue(result == expectedString,
|
||||||
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
|
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
|
||||||
$"Actual: {result}{Environment.NewLine}");
|
$"Actual: {result}{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +50,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var queryString = queryConstructor.Directory(folderPath);
|
var queryString = queryConstructor.Directory(folderPath);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.IsTrue(queryString.Replace(" ", " ") == expectedString.Replace(" ", " "),
|
ClassicAssert.IsTrue(queryString.Replace(" ", " ") == expectedString.Replace(" ", " "),
|
||||||
$"Expected string: {expectedString}{Environment.NewLine} " +
|
$"Expected string: {expectedString}{Environment.NewLine} " +
|
||||||
$"Actual string was: {queryString}{Environment.NewLine}");
|
$"Actual string was: {queryString}{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +70,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var queryString = queryConstructor.Directory(folderPath, userSearchString);
|
var queryString = queryConstructor.Directory(folderPath, userSearchString);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(expectedString, queryString);
|
ClassicAssert.AreEqual(expectedString, queryString);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("windows7.0")]
|
[SupportedOSPlatform("windows7.0")]
|
||||||
|
|
@ -105,7 +81,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
const string resultString = QueryConstructor.RestrictionsForAllFilesAndFoldersSearch;
|
const string resultString = QueryConstructor.RestrictionsForAllFilesAndFoldersSearch;
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(expectedString, resultString);
|
ClassicAssert.AreEqual(expectedString, resultString);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("windows7.0")]
|
[SupportedOSPlatform("windows7.0")]
|
||||||
|
|
@ -128,7 +104,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var resultString = queryConstructor.FilesAndFolders(userSearchString);
|
var resultString = queryConstructor.FilesAndFolders(userSearchString);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(expectedString, resultString);
|
ClassicAssert.AreEqual(expectedString, resultString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -138,13 +114,13 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
string querySearchString, string expectedString)
|
string querySearchString, string expectedString)
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var queryConstructor = new QueryConstructor(new Settings());
|
_ = new QueryConstructor(new Settings());
|
||||||
|
|
||||||
//When
|
//When
|
||||||
var resultString = QueryConstructor.RestrictionsForFileContentSearch(querySearchString);
|
var resultString = QueryConstructor.RestrictionsForFileContentSearch(querySearchString);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.IsTrue(resultString == expectedString,
|
ClassicAssert.IsTrue(resultString == expectedString,
|
||||||
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
|
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
|
||||||
$"Actual string was: {resultString}{Environment.NewLine}");
|
$"Actual string was: {resultString}{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
@ -162,12 +138,12 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var resultString = queryConstructor.FileContent(userSearchString);
|
var resultString = queryConstructor.FileContent(userSearchString);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.IsTrue(resultString == expectedString,
|
ClassicAssert.IsTrue(resultString == expectedString,
|
||||||
$"Expected query string: {expectedString}{Environment.NewLine} " +
|
$"Expected query string: {expectedString}{Environment.NewLine} " +
|
||||||
$"Actual string was: {resultString}{Environment.NewLine}");
|
$"Actual string was: {resultString}{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileContentSearchRequiredShouldReturnTrue()
|
public static void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileContentSearchRequiredShouldReturnTrue()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var query = new Query
|
var query = new Query
|
||||||
|
|
@ -181,7 +157,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var result = searchManager.IsFileContentSearch(query.ActionKeyword);
|
var result = searchManager.IsFileContentSearch(query.ActionKeyword);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.IsTrue(result,
|
ClassicAssert.IsTrue(result,
|
||||||
$"Expected True for file content search. {Environment.NewLine} " +
|
$"Expected True for file content search. {Environment.NewLine} " +
|
||||||
$"Actual result was: {result}{Environment.NewLine}");
|
$"Actual result was: {result}{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +182,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var result = FilesFolders.IsLocationPathString(querySearchString);
|
var result = FilesFolders.IsLocationPathString(querySearchString);
|
||||||
|
|
||||||
//Then
|
//Then
|
||||||
Assert.IsTrue(result == expectedResult,
|
ClassicAssert.IsTrue(result == expectedResult,
|
||||||
$"Expected query search string check result is: {expectedResult} {Environment.NewLine} " +
|
$"Expected query search string check result is: {expectedResult} {Environment.NewLine} " +
|
||||||
$"Actual check result is {result} {Environment.NewLine}");
|
$"Actual check result is {result} {Environment.NewLine}");
|
||||||
|
|
||||||
|
|
@ -233,7 +209,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var previousDirectoryPath = FilesFolders.GetPreviousExistingDirectory(previousLocationExists, path);
|
var previousDirectoryPath = FilesFolders.GetPreviousExistingDirectory(previousLocationExists, path);
|
||||||
|
|
||||||
//Then
|
//Then
|
||||||
Assert.IsTrue(previousDirectoryPath == expectedString,
|
ClassicAssert.IsTrue(previousDirectoryPath == expectedString,
|
||||||
$"Expected path string: {expectedString} {Environment.NewLine} " +
|
$"Expected path string: {expectedString} {Environment.NewLine} " +
|
||||||
$"Actual path string is {previousDirectoryPath} {Environment.NewLine}");
|
$"Actual path string is {previousDirectoryPath} {Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
@ -246,7 +222,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var returnedPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path);
|
var returnedPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path);
|
||||||
|
|
||||||
//Then
|
//Then
|
||||||
Assert.IsTrue(returnedPath == expectedString,
|
ClassicAssert.IsTrue(returnedPath == expectedString,
|
||||||
$"Expected path string: {expectedString} {Environment.NewLine} " +
|
$"Expected path string: {expectedString} {Environment.NewLine} " +
|
||||||
$"Actual path string is {returnedPath} {Environment.NewLine}");
|
$"Actual path string is {returnedPath} {Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +236,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var resultString = QueryConstructor.RecursiveDirectoryConstraint(path);
|
var resultString = QueryConstructor.RecursiveDirectoryConstraint(path);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(expectedString, resultString);
|
ClassicAssert.AreEqual(expectedString, resultString);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("windows7.0")]
|
[SupportedOSPlatform("windows7.0")]
|
||||||
|
|
@ -274,7 +250,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var resultString = DirectoryInfoSearch.ConstructSearchCriteria(path);
|
var resultString = DirectoryInfoSearch.ConstructSearchCriteria(path);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(expectedString, resultString);
|
ClassicAssert.AreEqual(expectedString, resultString);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("c:\\somefolder\\someotherfolder", ResultType.Folder, "irrelevant", false, true, "c:\\somefolder\\someotherfolder\\")]
|
[TestCase("c:\\somefolder\\someotherfolder", ResultType.Folder, "irrelevant", false, true, "c:\\somefolder\\someotherfolder\\")]
|
||||||
|
|
@ -305,7 +281,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var result = ResultManager.GetPathWithActionKeyword(path, type, actionKeyword);
|
var result = ResultManager.GetPathWithActionKeyword(path, type, actionKeyword);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(result, expectedResult);
|
ClassicAssert.AreEqual(result, expectedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("c:\\somefolder\\somefile", ResultType.File, "irrelevant", false, true, "e c:\\somefolder\\somefile")]
|
[TestCase("c:\\somefolder\\somefile", ResultType.File, "irrelevant", false, true, "e c:\\somefolder\\somefile")]
|
||||||
|
|
@ -334,7 +310,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var result = ResultManager.GetPathWithActionKeyword(path, type, actionKeyword);
|
var result = ResultManager.GetPathWithActionKeyword(path, type, actionKeyword);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(result, expectedResult);
|
ClassicAssert.AreEqual(result, expectedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("somefolder", "c:\\somefolder\\", ResultType.Folder, "q", false, false, "q somefolder")]
|
[TestCase("somefolder", "c:\\somefolder\\", ResultType.Folder, "q", false, false, "q somefolder")]
|
||||||
|
|
@ -366,7 +342,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var result = ResultManager.GetAutoCompleteText(title, query, path, resultType);
|
var result = ResultManager.GetAutoCompleteText(title, query, path, resultType);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(result, expectedResult);
|
ClassicAssert.AreEqual(result, expectedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("somefile", "c:\\somefolder\\somefile", ResultType.File, "q", false, false, "q somefile")]
|
[TestCase("somefile", "c:\\somefolder\\somefile", ResultType.File, "q", false, false, "q somefile")]
|
||||||
|
|
@ -398,7 +374,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var result = ResultManager.GetAutoCompleteText(title, query, path, resultType);
|
var result = ResultManager.GetAutoCompleteText(title, query, path, resultType);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(result, expectedResult);
|
ClassicAssert.AreEqual(result, expectedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(@"c:\foo", @"c:\foo", true)]
|
[TestCase(@"c:\foo", @"c:\foo", true)]
|
||||||
|
|
@ -420,7 +396,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
};
|
};
|
||||||
|
|
||||||
// When, Then
|
// When, Then
|
||||||
Assert.AreEqual(expectedResult, comparator.Equals(result1, result2));
|
ClassicAssert.AreEqual(expectedResult, comparator.Equals(result1, result2));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(@"c:\foo\", @"c:\foo\")]
|
[TestCase(@"c:\foo\", @"c:\foo\")]
|
||||||
|
|
@ -444,7 +420,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var hash2 = comparator.GetHashCode(result2);
|
var hash2 = comparator.GetHashCode(result2);
|
||||||
|
|
||||||
// When, Then
|
// When, Then
|
||||||
Assert.IsTrue(hash1 == hash2);
|
ClassicAssert.IsTrue(hash1 == hash2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(@"%appdata%", true)]
|
[TestCase(@"%appdata%", true)]
|
||||||
|
|
@ -461,7 +437,7 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
var result = EnvironmentVariables.HasEnvironmentVar(path);
|
var result = EnvironmentVariables.HasEnvironmentVar(path);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
Assert.AreEqual(result, expectedResult);
|
ClassicAssert.AreEqual(result, expectedResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.Legacy;
|
||||||
using Flow.Launcher.Core.Plugin;
|
using Flow.Launcher.Core.Plugin;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Flow.Launcher.Test.Plugins
|
namespace Flow.Launcher.Test.Plugins
|
||||||
|
|
@ -40,13 +39,13 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
Search = resultText
|
Search = resultText
|
||||||
}, default);
|
}, default);
|
||||||
|
|
||||||
Assert.IsNotNull(results);
|
ClassicAssert.IsNotNull(results);
|
||||||
|
|
||||||
foreach (var result in results)
|
foreach (var result in results)
|
||||||
{
|
{
|
||||||
Assert.IsNotNull(result);
|
ClassicAssert.IsNotNull(result);
|
||||||
Assert.IsNotNull(result.AsyncAction);
|
ClassicAssert.IsNotNull(result.AsyncAction);
|
||||||
Assert.IsNotNull(result.Title);
|
ClassicAssert.IsNotNull(result.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -56,35 +55,11 @@ namespace Flow.Launcher.Test.Plugins
|
||||||
new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>()),
|
new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>()),
|
||||||
new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>
|
new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>
|
||||||
{
|
{
|
||||||
new JsonRPCResult
|
new()
|
||||||
{
|
{
|
||||||
Title = "Test1", SubTitle = "Test2"
|
Title = "Test1", SubTitle = "Test2"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
[TestCaseSource(typeof(JsonRPCPluginTest), nameof(ResponseModelsSource))]
|
|
||||||
public async Task GivenModel_WhenSerializeWithDifferentNamingPolicy_ThenExpectSameResult_Async(JsonRPCQueryResponseModel reference)
|
|
||||||
{
|
|
||||||
var camelText = JsonSerializer.Serialize(reference, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
|
||||||
|
|
||||||
var pascalText = JsonSerializer.Serialize(reference);
|
|
||||||
|
|
||||||
var results1 = await QueryAsync(new Query { Search = camelText }, default);
|
|
||||||
var results2 = await QueryAsync(new Query { Search = pascalText }, default);
|
|
||||||
|
|
||||||
Assert.IsNotNull(results1);
|
|
||||||
Assert.IsNotNull(results2);
|
|
||||||
|
|
||||||
foreach (var ((result1, result2), referenceResult) in results1.Zip(results2).Zip(reference.Result))
|
|
||||||
{
|
|
||||||
Assert.AreEqual(result1, result2);
|
|
||||||
Assert.AreEqual(result1, referenceResult);
|
|
||||||
|
|
||||||
Assert.IsNotNull(result1);
|
|
||||||
Assert.IsNotNull(result1.AsyncAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.Legacy;
|
||||||
using Flow.Launcher.Plugin.Url;
|
using Flow.Launcher.Plugin.Url;
|
||||||
|
|
||||||
namespace Flow.Launcher.Test
|
namespace Flow.Launcher.Test.Plugins
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class UrlPluginTest
|
public class UrlPluginTest
|
||||||
|
|
@ -10,23 +11,23 @@ namespace Flow.Launcher.Test
|
||||||
public void URLMatchTest()
|
public void URLMatchTest()
|
||||||
{
|
{
|
||||||
var plugin = new Main();
|
var plugin = new Main();
|
||||||
Assert.IsTrue(plugin.IsURL("http://www.google.com"));
|
ClassicAssert.IsTrue(plugin.IsURL("http://www.google.com"));
|
||||||
Assert.IsTrue(plugin.IsURL("https://www.google.com"));
|
ClassicAssert.IsTrue(plugin.IsURL("https://www.google.com"));
|
||||||
Assert.IsTrue(plugin.IsURL("http://google.com"));
|
ClassicAssert.IsTrue(plugin.IsURL("http://google.com"));
|
||||||
Assert.IsTrue(plugin.IsURL("www.google.com"));
|
ClassicAssert.IsTrue(plugin.IsURL("www.google.com"));
|
||||||
Assert.IsTrue(plugin.IsURL("google.com"));
|
ClassicAssert.IsTrue(plugin.IsURL("google.com"));
|
||||||
Assert.IsTrue(plugin.IsURL("http://localhost"));
|
ClassicAssert.IsTrue(plugin.IsURL("http://localhost"));
|
||||||
Assert.IsTrue(plugin.IsURL("https://localhost"));
|
ClassicAssert.IsTrue(plugin.IsURL("https://localhost"));
|
||||||
Assert.IsTrue(plugin.IsURL("http://localhost:80"));
|
ClassicAssert.IsTrue(plugin.IsURL("http://localhost:80"));
|
||||||
Assert.IsTrue(plugin.IsURL("https://localhost:80"));
|
ClassicAssert.IsTrue(plugin.IsURL("https://localhost:80"));
|
||||||
Assert.IsTrue(plugin.IsURL("http://110.10.10.10"));
|
ClassicAssert.IsTrue(plugin.IsURL("http://110.10.10.10"));
|
||||||
Assert.IsTrue(plugin.IsURL("110.10.10.10"));
|
ClassicAssert.IsTrue(plugin.IsURL("110.10.10.10"));
|
||||||
Assert.IsTrue(plugin.IsURL("ftp://110.10.10.10"));
|
ClassicAssert.IsTrue(plugin.IsURL("ftp://110.10.10.10"));
|
||||||
|
|
||||||
|
|
||||||
Assert.IsFalse(plugin.IsURL("wwww"));
|
ClassicAssert.IsFalse(plugin.IsURL("wwww"));
|
||||||
Assert.IsFalse(plugin.IsURL("wwww.c"));
|
ClassicAssert.IsFalse(plugin.IsURL("wwww.c"));
|
||||||
Assert.IsFalse(plugin.IsURL("wwww.c"));
|
ClassicAssert.IsFalse(plugin.IsURL("wwww.c"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.Legacy;
|
||||||
using Flow.Launcher.Core.Plugin;
|
using Flow.Launcher.Core.Plugin;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
|
|
@ -17,17 +18,17 @@ namespace Flow.Launcher.Test
|
||||||
|
|
||||||
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
|
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
|
||||||
|
|
||||||
Assert.AreEqual("> ping google.com -n 20 -6", q.RawQuery);
|
ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.RawQuery);
|
||||||
Assert.AreEqual("ping google.com -n 20 -6", q.Search, "Search should not start with the ActionKeyword.");
|
ClassicAssert.AreEqual("ping google.com -n 20 -6", q.Search, "Search should not start with the ActionKeyword.");
|
||||||
Assert.AreEqual(">", q.ActionKeyword);
|
ClassicAssert.AreEqual(">", q.ActionKeyword);
|
||||||
|
|
||||||
Assert.AreEqual(5, q.SearchTerms.Length, "The length of SearchTerms should match.");
|
ClassicAssert.AreEqual(5, q.SearchTerms.Length, "The length of SearchTerms should match.");
|
||||||
|
|
||||||
Assert.AreEqual("ping", q.FirstSearch);
|
ClassicAssert.AreEqual("ping", q.FirstSearch);
|
||||||
Assert.AreEqual("google.com", q.SecondSearch);
|
ClassicAssert.AreEqual("google.com", q.SecondSearch);
|
||||||
Assert.AreEqual("-n", q.ThirdSearch);
|
ClassicAssert.AreEqual("-n", q.ThirdSearch);
|
||||||
|
|
||||||
Assert.AreEqual("google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters");
|
ClassicAssert.AreEqual("google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -40,11 +41,11 @@ namespace Flow.Launcher.Test
|
||||||
|
|
||||||
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
|
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
|
||||||
|
|
||||||
Assert.AreEqual("> ping google.com -n 20 -6", q.Search);
|
ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.Search);
|
||||||
Assert.AreEqual(q.Search, q.RawQuery, "RawQuery should be equal to Search.");
|
ClassicAssert.AreEqual(q.Search, q.RawQuery, "RawQuery should be equal to Search.");
|
||||||
Assert.AreEqual(6, q.SearchTerms.Length, "The length of SearchTerms should match.");
|
ClassicAssert.AreEqual(6, q.SearchTerms.Length, "The length of SearchTerms should match.");
|
||||||
Assert.AreNotEqual(">", q.ActionKeyword, "ActionKeyword should not match that of a disabled plugin.");
|
ClassicAssert.AreNotEqual(">", q.ActionKeyword, "ActionKeyword should not match that of a disabled plugin.");
|
||||||
Assert.AreEqual("ping google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters");
|
ClassicAssert.AreEqual("ping google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -52,13 +53,13 @@ namespace Flow.Launcher.Test
|
||||||
{
|
{
|
||||||
Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary<string, PluginPair>());
|
Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary<string, PluginPair>());
|
||||||
|
|
||||||
Assert.AreEqual("file.txt file2 file3", q.Search);
|
ClassicAssert.AreEqual("file.txt file2 file3", q.Search);
|
||||||
Assert.AreEqual("", q.ActionKeyword);
|
ClassicAssert.AreEqual("", q.ActionKeyword);
|
||||||
|
|
||||||
Assert.AreEqual("file.txt", q.FirstSearch);
|
ClassicAssert.AreEqual("file.txt", q.FirstSearch);
|
||||||
Assert.AreEqual("file2", q.SecondSearch);
|
ClassicAssert.AreEqual("file2", q.SecondSearch);
|
||||||
Assert.AreEqual("file3", q.ThirdSearch);
|
ClassicAssert.AreEqual("file3", q.ThirdSearch);
|
||||||
Assert.AreEqual("file2 file3", q.SecondToEndSearch);
|
ClassicAssert.AreEqual("file2 file3", q.SecondToEndSearch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ namespace Flow.Launcher
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string msg = translater.GetTranslation("newActionKeywordsHasBeenAssigned");
|
string msg = translater.GetTranslation("newActionKeywordsHasBeenAssigned");
|
||||||
MessageBoxEx.Show(msg);
|
App.API.ShowMsgBox(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
using Flow.Launcher.Core;
|
using Flow.Launcher.Core;
|
||||||
using Flow.Launcher.Core.Configuration;
|
using Flow.Launcher.Core.Configuration;
|
||||||
using Flow.Launcher.Core.ExternalPlugins.Environments;
|
using Flow.Launcher.Core.ExternalPlugins.Environments;
|
||||||
|
|
@ -14,24 +15,85 @@ using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.Infrastructure.Http;
|
using Flow.Launcher.Infrastructure.Http;
|
||||||
using Flow.Launcher.Infrastructure.Image;
|
using Flow.Launcher.Infrastructure.Image;
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
|
using Flow.Launcher.Infrastructure.Storage;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
using Flow.Launcher.ViewModel;
|
using Flow.Launcher.ViewModel;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
|
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
|
||||||
|
|
||||||
namespace Flow.Launcher
|
namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
public partial class App : IDisposable, ISingleInstanceApp
|
public partial class App : IDisposable, ISingleInstanceApp
|
||||||
{
|
{
|
||||||
public static PublicAPIInstance API { get; private set; }
|
public static IPublicAPI API { get; private set; }
|
||||||
private const string Unique = "Flow.Launcher_Unique_Application_Mutex";
|
private const string Unique = "Flow.Launcher_Unique_Application_Mutex";
|
||||||
private static bool _disposed;
|
private static bool _disposed;
|
||||||
private Settings _settings;
|
private readonly Settings _settings;
|
||||||
private MainViewModel _mainVM;
|
|
||||||
private SettingWindowViewModel _settingsVM;
|
public App()
|
||||||
private readonly Updater _updater = new Updater(Flow.Launcher.Properties.Settings.Default.GithubRepo);
|
{
|
||||||
private readonly Portable _portable = new Portable();
|
// Initialize settings
|
||||||
private readonly PinyinAlphabet _alphabet = new PinyinAlphabet();
|
try
|
||||||
private StringMatcher _stringMatcher;
|
{
|
||||||
|
var storage = new FlowLauncherJsonStorage<Settings>();
|
||||||
|
_settings = storage.Load();
|
||||||
|
_settings.SetStorage(storage);
|
||||||
|
_settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ShowErrorMsgBoxAndFailFast("Cannot load setting storage, please check local data directory", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the dependency injection container
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var host = Host.CreateDefaultBuilder()
|
||||||
|
.UseContentRoot(AppContext.BaseDirectory)
|
||||||
|
.ConfigureServices(services => services
|
||||||
|
.AddSingleton(_ => _settings)
|
||||||
|
.AddSingleton(sp => new Updater(sp.GetRequiredService<IPublicAPI>(), Launcher.Properties.Settings.Default.GithubRepo))
|
||||||
|
.AddSingleton<Portable>()
|
||||||
|
.AddSingleton<SettingWindowViewModel>()
|
||||||
|
.AddSingleton<IAlphabet, PinyinAlphabet>()
|
||||||
|
.AddSingleton<StringMatcher>()
|
||||||
|
.AddSingleton<Internationalization>()
|
||||||
|
.AddSingleton<IPublicAPI, PublicAPIInstance>()
|
||||||
|
.AddSingleton<MainViewModel>()
|
||||||
|
.AddSingleton<Theme>()
|
||||||
|
).Build();
|
||||||
|
Ioc.Default.ConfigureServices(host.Services);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ShowErrorMsgBoxAndFailFast("Cannot configure dependency injection container, please open new issue in Flow.Launcher", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the public API and Settings first
|
||||||
|
try
|
||||||
|
{
|
||||||
|
API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
|
_settings.Initialize();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
|
||||||
|
{
|
||||||
|
// Firstly show users the message
|
||||||
|
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
|
||||||
|
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
|
||||||
|
Environment.FailFast(message, e);
|
||||||
|
}
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main()
|
public static void Main()
|
||||||
|
|
@ -50,52 +112,42 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
|
await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
|
||||||
{
|
{
|
||||||
_portable.PreStartCleanUpAfterPortabilityUpdate();
|
Log.SetLogLevel(_settings.LogLevel);
|
||||||
|
|
||||||
Log.Info(
|
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
|
||||||
"|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
|
|
||||||
|
Log.Info("|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
|
||||||
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
|
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
|
||||||
|
|
||||||
RegisterAppDomainExceptions();
|
RegisterAppDomainExceptions();
|
||||||
RegisterDispatcherUnhandledException();
|
RegisterDispatcherUnhandledException();
|
||||||
|
|
||||||
var imageLoadertask = ImageLoader.InitializeAsync();
|
var imageLoadertask = ImageLoader.InitializeAsync();
|
||||||
|
|
||||||
_settingsVM = new SettingWindowViewModel(_updater, _portable);
|
|
||||||
_settings = _settingsVM.Settings;
|
|
||||||
_settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled();
|
|
||||||
|
|
||||||
AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings);
|
AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings);
|
||||||
|
|
||||||
_alphabet.Initialize(_settings);
|
// TODO: Clean InternationalizationManager.Instance and InternationalizationManager.Instance.GetTranslation in future
|
||||||
_stringMatcher = new StringMatcher(_alphabet);
|
|
||||||
StringMatcher.Instance = _stringMatcher;
|
|
||||||
_stringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision;
|
|
||||||
|
|
||||||
InternationalizationManager.Instance.Settings = _settings;
|
|
||||||
InternationalizationManager.Instance.ChangeLanguage(_settings.Language);
|
InternationalizationManager.Instance.ChangeLanguage(_settings.Language);
|
||||||
|
|
||||||
PluginManager.LoadPlugins(_settings.PluginSettings);
|
PluginManager.LoadPlugins(_settings.PluginSettings);
|
||||||
_mainVM = new MainViewModel(_settings);
|
|
||||||
|
|
||||||
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet);
|
|
||||||
|
|
||||||
Http.API = API;
|
|
||||||
Http.Proxy = _settings.Proxy;
|
Http.Proxy = _settings.Proxy;
|
||||||
|
|
||||||
await PluginManager.InitializePluginsAsync(API);
|
await PluginManager.InitializePluginsAsync();
|
||||||
await imageLoadertask;
|
await imageLoadertask;
|
||||||
|
|
||||||
var window = new MainWindow(_settings, _mainVM);
|
var mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
|
||||||
|
var window = new MainWindow(_settings, mainVM);
|
||||||
|
|
||||||
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
|
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
|
||||||
|
|
||||||
Current.MainWindow = window;
|
Current.MainWindow = window;
|
||||||
Current.MainWindow.Title = Constant.FlowLauncher;
|
Current.MainWindow.Title = Constant.FlowLauncher;
|
||||||
|
|
||||||
HotKeyMapper.Initialize(_mainVM);
|
HotKeyMapper.Initialize();
|
||||||
|
|
||||||
// main windows needs initialized before theme change because of blur settings
|
// main windows needs initialized before theme change because of blur settings
|
||||||
ThemeManager.Instance.Settings = _settings;
|
// TODO: Clean ThemeManager.Instance in future
|
||||||
ThemeManager.Instance.ChangeTheme(_settings.Theme);
|
ThemeManager.Instance.ChangeTheme(_settings.Theme);
|
||||||
|
|
||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
|
@ -119,7 +171,14 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Helper.AutoStartup.Enable();
|
if (_settings.UseLogonTaskForStartup)
|
||||||
|
{
|
||||||
|
Helper.AutoStartup.EnableViaLogonTask();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Helper.AutoStartup.EnableViaRegistry();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -141,11 +200,11 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
// check update every 5 hours
|
// check update every 5 hours
|
||||||
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
|
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
|
||||||
await _updater.UpdateAppAsync(API);
|
await Ioc.Default.GetRequiredService<Updater>().UpdateAppAsync();
|
||||||
|
|
||||||
while (await timer.WaitForNextTickAsync())
|
while (await timer.WaitForNextTickAsync())
|
||||||
// check updates on startup
|
// check updates on startup
|
||||||
await _updater.UpdateAppAsync(API);
|
await Ioc.Default.GetRequiredService<Updater>().UpdateAppAsync();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +247,7 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
public void OnSecondAppStarted()
|
public void OnSecondAppStarted()
|
||||||
{
|
{
|
||||||
_mainVM.Show();
|
Ioc.Default.GetRequiredService<MainViewModel>().Show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,14 +32,11 @@
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button
|
<Button
|
||||||
Grid.Column="4"
|
Grid.Column="1"
|
||||||
Click="BtnCancel_OnClick"
|
Click="BtnCancel_OnClick"
|
||||||
Style="{StaticResource TitleBarCloseButtonStyle}">
|
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||||
<Path
|
<Path
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,18 @@ using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using Flow.Launcher.Core;
|
|
||||||
|
|
||||||
namespace Flow.Launcher
|
namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
public partial class CustomQueryHotkeySetting : Window
|
public partial class CustomQueryHotkeySetting : Window
|
||||||
{
|
{
|
||||||
private SettingWindow _settingWidow;
|
private readonly Settings _settings;
|
||||||
private bool update;
|
private bool update;
|
||||||
private CustomPluginHotkey updateCustomHotkey;
|
private CustomPluginHotkey updateCustomHotkey;
|
||||||
public Settings Settings { get; }
|
|
||||||
|
|
||||||
public CustomQueryHotkeySetting(SettingWindow settingWidow, Settings settings)
|
public CustomQueryHotkeySetting(Settings settings)
|
||||||
{
|
{
|
||||||
_settingWidow = settingWidow;
|
_settings = settings;
|
||||||
Settings = settings;
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,13 +30,13 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
if (!update)
|
if (!update)
|
||||||
{
|
{
|
||||||
Settings.CustomPluginHotkeys ??= new ObservableCollection<CustomPluginHotkey>();
|
_settings.CustomPluginHotkeys ??= new ObservableCollection<CustomPluginHotkey>();
|
||||||
|
|
||||||
var pluginHotkey = new CustomPluginHotkey
|
var pluginHotkey = new CustomPluginHotkey
|
||||||
{
|
{
|
||||||
Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text
|
Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text
|
||||||
};
|
};
|
||||||
Settings.CustomPluginHotkeys.Add(pluginHotkey);
|
_settings.CustomPluginHotkeys.Add(pluginHotkey);
|
||||||
|
|
||||||
HotKeyMapper.SetCustomQueryHotkey(pluginHotkey);
|
HotKeyMapper.SetCustomQueryHotkey(pluginHotkey);
|
||||||
}
|
}
|
||||||
|
|
@ -59,11 +56,11 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
public void UpdateItem(CustomPluginHotkey item)
|
public void UpdateItem(CustomPluginHotkey item)
|
||||||
{
|
{
|
||||||
updateCustomHotkey = Settings.CustomPluginHotkeys.FirstOrDefault(o =>
|
updateCustomHotkey = _settings.CustomPluginHotkeys.FirstOrDefault(o =>
|
||||||
o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey);
|
o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey);
|
||||||
if (updateCustomHotkey == null)
|
if (updateCustomHotkey == null)
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("invalidPluginHotkey"));
|
App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("invalidPluginHotkey"));
|
||||||
Close();
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -77,8 +74,7 @@ namespace Flow.Launcher
|
||||||
private void BtnTestActionKeyword_OnClick(object sender, RoutedEventArgs e)
|
private void BtnTestActionKeyword_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
App.API.ChangeQuery(tbAction.Text);
|
App.API.ChangeQuery(tbAction.Text);
|
||||||
Application.Current.MainWindow.Show();
|
App.API.ShowMainWindow();
|
||||||
Application.Current.MainWindow.Opacity = 1;
|
|
||||||
Application.Current.MainWindow.Focus();
|
Application.Current.MainWindow.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,11 @@
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button
|
<Button
|
||||||
Grid.Column="4"
|
Grid.Column="1"
|
||||||
Click="BtnCancel_OnClick"
|
Click="BtnCancel_OnClick"
|
||||||
Style="{StaticResource TitleBarCloseButtonStyle}">
|
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||||
<Path
|
<Path
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,13 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
if (String.IsNullOrEmpty(Key) || String.IsNullOrEmpty(Value))
|
if (String.IsNullOrEmpty(Key) || String.IsNullOrEmpty(Value))
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("emptyShortcut"));
|
App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("emptyShortcut"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Check if key is modified or adding a new one
|
// Check if key is modified or adding a new one
|
||||||
if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key))
|
if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key))
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut"));
|
App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("duplicateShortcut"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DialogResult = !update || originalKey != Key || originalValue != Value;
|
DialogResult = !update || originalKey != Key || originalValue != Value;
|
||||||
|
|
@ -65,8 +65,7 @@ namespace Flow.Launcher
|
||||||
private void BtnTestShortcut_OnClick(object sender, RoutedEventArgs e)
|
private void BtnTestShortcut_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
App.API.ChangeQuery(tbExpand.Text);
|
App.API.ChangeQuery(tbExpand.Text);
|
||||||
Application.Current.MainWindow.Show();
|
App.API.ShowMainWindow();
|
||||||
Application.Current.MainWindow.Opacity = 1;
|
|
||||||
Application.Current.MainWindow.Focus();
|
Application.Current.MainWindow.Focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
|
@ -83,12 +83,16 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ChefKeys" Version="0.1.2" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
<PackageReference Include="Fody" Version="6.5.4">
|
<PackageReference Include="Fody" Version="6.5.4">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="InputSimulator" Version="1.0.4" />
|
<PackageReference Include="InputSimulator" Version="1.0.4" />
|
||||||
|
<!-- Do not upgrade Microsoft.Extensions.DependencyInjection and Microsoft.Extensions.Hosting since we are .Net7.0 -->
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|
@ -100,7 +104,8 @@
|
||||||
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
|
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
|
||||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
|
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
|
||||||
<PackageReference Include="SemanticVersioning" Version="3.0.0" />
|
<PackageReference Include="SemanticVersioning" Version="3.0.0" />
|
||||||
<PackageReference Include="VirtualizingWrapPanel" Version="2.1.0" />
|
<PackageReference Include="TaskScheduler" Version="2.12.1" />
|
||||||
|
<PackageReference Include="VirtualizingWrapPanel" Version="2.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,31 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
using Flow.Launcher.Infrastructure;
|
using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
using Microsoft.Win32.TaskScheduler;
|
||||||
|
|
||||||
namespace Flow.Launcher.Helper;
|
namespace Flow.Launcher.Helper;
|
||||||
|
|
||||||
public class AutoStartup
|
public class AutoStartup
|
||||||
{
|
{
|
||||||
private const string StartupPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
|
private const string StartupPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
|
||||||
|
private const string LogonTaskName = $"{Constant.FlowLauncher} Startup";
|
||||||
|
private const string LogonTaskDesc = $"{Constant.FlowLauncher} Auto Startup";
|
||||||
|
|
||||||
public static bool IsEnabled
|
public static bool IsEnabled
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
// Check if logon task is enabled
|
||||||
|
if (CheckLogonTask())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if registry is enabled
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
|
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
|
||||||
|
|
@ -28,12 +41,74 @@ public class AutoStartup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Disable()
|
private static bool CheckLogonTask()
|
||||||
|
{
|
||||||
|
using var taskService = new TaskService();
|
||||||
|
var task = taskService.RootFolder.AllTasks.FirstOrDefault(t => t.Name == LogonTaskName);
|
||||||
|
if (task != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if the action is the same as the current executable path
|
||||||
|
var action = task.Definition.Actions.FirstOrDefault()!.ToString().Trim();
|
||||||
|
if (!Constant.ExecutablePath.Equals(action, StringComparison.OrdinalIgnoreCase) && !File.Exists(action))
|
||||||
|
{
|
||||||
|
UnscheduleLogonTask();
|
||||||
|
ScheduleLogonTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error("AutoStartup", $"Failed to check logon task: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DisableViaLogonTaskAndRegistry()
|
||||||
|
{
|
||||||
|
Disable(true);
|
||||||
|
Disable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EnableViaLogonTask()
|
||||||
|
{
|
||||||
|
Enable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EnableViaRegistry()
|
||||||
|
{
|
||||||
|
Enable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ChangeToViaLogonTask()
|
||||||
|
{
|
||||||
|
Disable(false);
|
||||||
|
Enable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ChangeToViaRegistry()
|
||||||
|
{
|
||||||
|
Disable(true);
|
||||||
|
Enable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Disable(bool logonTask)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
|
if (logonTask)
|
||||||
key?.DeleteValue(Constant.FlowLauncher, false);
|
{
|
||||||
|
UnscheduleLogonTask();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
|
||||||
|
key?.DeleteValue(Constant.FlowLauncher, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -42,12 +117,19 @@ public class AutoStartup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void Enable()
|
private static void Enable(bool logonTask)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
|
if (logonTask)
|
||||||
key?.SetValue(Constant.FlowLauncher, $"\"{Constant.ExecutablePath}\"");
|
{
|
||||||
|
ScheduleLogonTask();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
|
||||||
|
key?.SetValue(Constant.FlowLauncher, $"\"{Constant.ExecutablePath}\"");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -55,4 +137,54 @@ public class AutoStartup
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ScheduleLogonTask()
|
||||||
|
{
|
||||||
|
using var td = TaskService.Instance.NewTask();
|
||||||
|
td.RegistrationInfo.Description = LogonTaskDesc;
|
||||||
|
td.Triggers.Add(new LogonTrigger { UserId = WindowsIdentity.GetCurrent().Name, Delay = TimeSpan.FromSeconds(2) });
|
||||||
|
td.Actions.Add(Constant.ExecutablePath);
|
||||||
|
|
||||||
|
if (IsCurrentUserIsAdmin())
|
||||||
|
{
|
||||||
|
td.Principal.RunLevel = TaskRunLevel.Highest;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.Settings.StopIfGoingOnBatteries = false;
|
||||||
|
td.Settings.DisallowStartIfOnBatteries = false;
|
||||||
|
td.Settings.ExecutionTimeLimit = TimeSpan.Zero;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TaskService.Instance.RootFolder.RegisterTaskDefinition(LogonTaskName, td);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error("AutoStartup", $"Failed to schedule logon task: {e}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool UnscheduleLogonTask()
|
||||||
|
{
|
||||||
|
using var taskService = new TaskService();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
taskService.RootFolder.DeleteTask(LogonTaskName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error("AutoStartup", $"Failed to unschedule logon task: {e}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsCurrentUserIsAdmin()
|
||||||
|
{
|
||||||
|
var identity = WindowsIdentity.GetCurrent();
|
||||||
|
var principal = new WindowsPrincipal(identity);
|
||||||
|
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ using NHotkey;
|
||||||
using NHotkey.Wpf;
|
using NHotkey.Wpf;
|
||||||
using Flow.Launcher.Core.Resource;
|
using Flow.Launcher.Core.Resource;
|
||||||
using Flow.Launcher.ViewModel;
|
using Flow.Launcher.ViewModel;
|
||||||
using Flow.Launcher.Core;
|
using ChefKeys;
|
||||||
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
|
||||||
namespace Flow.Launcher.Helper;
|
namespace Flow.Launcher.Helper;
|
||||||
|
|
||||||
|
|
@ -14,10 +16,10 @@ internal static class HotKeyMapper
|
||||||
private static Settings _settings;
|
private static Settings _settings;
|
||||||
private static MainViewModel _mainViewModel;
|
private static MainViewModel _mainViewModel;
|
||||||
|
|
||||||
internal static void Initialize(MainViewModel mainVM)
|
internal static void Initialize()
|
||||||
{
|
{
|
||||||
_mainViewModel = mainVM;
|
_mainViewModel = Ioc.Default.GetRequiredService<MainViewModel>();
|
||||||
_settings = _mainViewModel.Settings;
|
_settings = Ioc.Default.GetService<Settings>();
|
||||||
|
|
||||||
SetHotkey(_settings.Hotkey, OnToggleHotkey);
|
SetHotkey(_settings.Hotkey, OnToggleHotkey);
|
||||||
LoadCustomPluginHotkey();
|
LoadCustomPluginHotkey();
|
||||||
|
|
@ -29,33 +31,92 @@ internal static class HotKeyMapper
|
||||||
_mainViewModel.ToggleFlowLauncher();
|
_mainViewModel.ToggleFlowLauncher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void OnToggleHotkeyWithChefKeys()
|
||||||
|
{
|
||||||
|
if (!_mainViewModel.ShouldIgnoreHotkeys())
|
||||||
|
_mainViewModel.ToggleFlowLauncher();
|
||||||
|
}
|
||||||
|
|
||||||
private static void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action)
|
private static void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action)
|
||||||
{
|
{
|
||||||
var hotkey = new HotkeyModel(hotkeyStr);
|
var hotkey = new HotkeyModel(hotkeyStr);
|
||||||
SetHotkey(hotkey, action);
|
SetHotkey(hotkey, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SetWithChefKeys(string hotkeyStr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ChefKeysManager.RegisterHotkey(hotkeyStr, hotkeyStr, OnToggleHotkeyWithChefKeys);
|
||||||
|
ChefKeysManager.Start();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(
|
||||||
|
string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}",
|
||||||
|
e.Message,
|
||||||
|
e.StackTrace));
|
||||||
|
string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
|
||||||
|
string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle");
|
||||||
|
MessageBoxEx.Show(errorMsg, errorMsgTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal static void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> action)
|
internal static void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> action)
|
||||||
{
|
{
|
||||||
string hotkeyStr = hotkey.ToString();
|
string hotkeyStr = hotkey.ToString();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (hotkeyStr == "LWin" || hotkeyStr == "RWin")
|
||||||
|
{
|
||||||
|
SetWithChefKeys(hotkeyStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action);
|
HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(
|
||||||
|
string.Format("|HotkeyMapper.SetHotkey|Error registering hotkey {2}: {0} \nStackTrace:{1}",
|
||||||
|
e.Message,
|
||||||
|
e.StackTrace,
|
||||||
|
hotkeyStr));
|
||||||
string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
|
string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
|
||||||
string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle");
|
string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle");
|
||||||
MessageBoxEx.Show(errorMsg, errorMsgTitle);
|
App.API.ShowMsgBox(errorMsg, errorMsgTitle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void RemoveHotkey(string hotkeyStr)
|
internal static void RemoveHotkey(string hotkeyStr)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(hotkeyStr))
|
try
|
||||||
{
|
{
|
||||||
HotkeyManager.Current.Remove(hotkeyStr);
|
if (hotkeyStr == "LWin" || hotkeyStr == "RWin")
|
||||||
|
{
|
||||||
|
RemoveWithChefKeys(hotkeyStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(hotkeyStr))
|
||||||
|
HotkeyManager.Current.Remove(hotkeyStr);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(
|
||||||
|
string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}",
|
||||||
|
e.Message,
|
||||||
|
e.StackTrace));
|
||||||
|
string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("unregisterHotkeyFailed"), hotkeyStr);
|
||||||
|
string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle");
|
||||||
|
MessageBoxEx.Show(errorMsg, errorMsgTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveWithChefKeys(string hotkeyStr)
|
||||||
|
{
|
||||||
|
ChefKeysManager.UnregisterHotkey(hotkeyStr);
|
||||||
|
ChefKeysManager.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void LoadCustomPluginHotkey()
|
internal static void LoadCustomPluginHotkey()
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,29 @@ public static class SingletonWindowOpener
|
||||||
{
|
{
|
||||||
var window = Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.GetType() == typeof(T))
|
var window = Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.GetType() == typeof(T))
|
||||||
?? (T)Activator.CreateInstance(typeof(T), args);
|
?? (T)Activator.CreateInstance(typeof(T), args);
|
||||||
|
|
||||||
// Fix UI bug
|
// Fix UI bug
|
||||||
// Add `window.WindowState = WindowState.Normal`
|
// Add `window.WindowState = WindowState.Normal`
|
||||||
// If only use `window.Show()`, Settings-window doesn't show when minimized in taskbar
|
// If only use `window.Show()`, Settings-window doesn't show when minimized in taskbar
|
||||||
// Not sure why this works tho
|
// Not sure why this works tho
|
||||||
// Probably because, when `.Show()` fails, `window.WindowState == Minimized` (not `Normal`)
|
// Probably because, when `.Show()` fails, `window.WindowState == Minimized` (not `Normal`)
|
||||||
// https://stackoverflow.com/a/59719760/4230390
|
// https://stackoverflow.com/a/59719760/4230390
|
||||||
window.WindowState = WindowState.Normal;
|
// Ensure the window is not minimized before showing it
|
||||||
window.Show();
|
if (window.WindowState == WindowState.Minimized)
|
||||||
|
{
|
||||||
|
window.WindowState = WindowState.Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the window is visible
|
||||||
|
if (!window.IsVisible)
|
||||||
|
{
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.Activate(); // Bring the window to the foreground if already open
|
||||||
|
}
|
||||||
|
|
||||||
window.Focus();
|
window.Focus();
|
||||||
|
|
||||||
return (T)window;
|
return (T)window;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Windows;
|
||||||
using System.Windows.Documents;
|
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using Windows.Win32;
|
using Windows.Win32;
|
||||||
using Windows.Win32.UI.WindowsAndMessaging;
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
@ -13,8 +15,70 @@ namespace Flow.Launcher.Helper;
|
||||||
public static class WallpaperPathRetrieval
|
public static class WallpaperPathRetrieval
|
||||||
{
|
{
|
||||||
private static readonly int MAX_PATH = 260;
|
private static readonly int MAX_PATH = 260;
|
||||||
|
private static readonly int MAX_CACHE_SIZE = 3;
|
||||||
|
|
||||||
public static unsafe string GetWallpaperPath()
|
private static readonly Dictionary<(string, DateTime), ImageBrush> wallpaperCache = new();
|
||||||
|
|
||||||
|
public static Brush GetWallpaperBrush()
|
||||||
|
{
|
||||||
|
// Invoke the method on the UI thread
|
||||||
|
if (!Application.Current.Dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
return Application.Current.Dispatcher.Invoke(GetWallpaperBrush);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var wallpaperPath = GetWallpaperPath();
|
||||||
|
if (wallpaperPath is not null && File.Exists(wallpaperPath))
|
||||||
|
{
|
||||||
|
// Since the wallpaper file name can be the same (TranscodedWallpaper),
|
||||||
|
// we need to add the last modified date to differentiate them
|
||||||
|
var dateModified = File.GetLastWriteTime(wallpaperPath);
|
||||||
|
wallpaperCache.TryGetValue((wallpaperPath, dateModified), out var cachedWallpaper);
|
||||||
|
if (cachedWallpaper != null)
|
||||||
|
{
|
||||||
|
return cachedWallpaper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should not dispose the memory stream since the bitmap is still in use
|
||||||
|
var memStream = new MemoryStream(File.ReadAllBytes(wallpaperPath));
|
||||||
|
var bitmap = new BitmapImage();
|
||||||
|
bitmap.BeginInit();
|
||||||
|
bitmap.StreamSource = memStream;
|
||||||
|
bitmap.DecodePixelWidth = 800;
|
||||||
|
bitmap.DecodePixelHeight = 600;
|
||||||
|
bitmap.EndInit();
|
||||||
|
bitmap.Freeze(); // Make the bitmap thread-safe
|
||||||
|
var wallpaperBrush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
|
||||||
|
wallpaperBrush.Freeze(); // Make the brush thread-safe
|
||||||
|
|
||||||
|
// Manage cache size
|
||||||
|
if (wallpaperCache.Count >= MAX_CACHE_SIZE)
|
||||||
|
{
|
||||||
|
// Remove the oldest wallpaper from the cache
|
||||||
|
var oldestCache = wallpaperCache.Keys.OrderBy(k => k.Item2).FirstOrDefault();
|
||||||
|
if (oldestCache != default)
|
||||||
|
{
|
||||||
|
wallpaperCache.Remove(oldestCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wallpaperCache.Add((wallpaperPath, dateModified), wallpaperBrush);
|
||||||
|
return wallpaperBrush;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wallpaperColor = GetWallpaperColor();
|
||||||
|
return new SolidColorBrush(wallpaperColor);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.API.LogException(nameof(WallpaperPathRetrieval), "Error retrieving wallpaper", ex);
|
||||||
|
return new SolidColorBrush(Colors.Transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe string GetWallpaperPath()
|
||||||
{
|
{
|
||||||
var wallpaperPtr = stackalloc char[MAX_PATH];
|
var wallpaperPtr = stackalloc char[MAX_PATH];
|
||||||
PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, (uint)MAX_PATH,
|
PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, (uint)MAX_PATH,
|
||||||
|
|
@ -25,7 +89,7 @@ public static class WallpaperPathRetrieval
|
||||||
return wallpaper.ToString();
|
return wallpaper.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color GetWallpaperColor()
|
private static Color GetWallpaperColor()
|
||||||
{
|
{
|
||||||
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", true);
|
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", true);
|
||||||
var result = key?.GetValue("Background", null);
|
var result = key?.GetValue("Background", null);
|
||||||
|
|
|
||||||
|
|
@ -148,4 +148,64 @@ public class WindowsInteropHelper
|
||||||
|
|
||||||
return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY));
|
return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Alt Tab
|
||||||
|
|
||||||
|
private static int SetWindowLong(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong)
|
||||||
|
{
|
||||||
|
PInvoke.SetLastError(WIN32_ERROR.NO_ERROR); // Clear any existing error
|
||||||
|
|
||||||
|
var result = PInvoke.SetWindowLong(hWnd, nIndex, dwNewLong);
|
||||||
|
if (result == 0 && Marshal.GetLastPInvokeError() != 0)
|
||||||
|
{
|
||||||
|
throw new Win32Exception(Marshal.GetLastPInvokeError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hide windows in the Alt+Tab window list
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">To hide a window</param>
|
||||||
|
public static void HideFromAltTab(Window window)
|
||||||
|
{
|
||||||
|
var exStyle = GetCurrentWindowStyle(window);
|
||||||
|
|
||||||
|
// Add TOOLWINDOW style, remove APPWINDOW style
|
||||||
|
var newExStyle = ((uint)exStyle | (uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) & ~(uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
|
||||||
|
|
||||||
|
SetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restore window display in the Alt+Tab window list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">To restore the displayed window</param>
|
||||||
|
public static void ShowInAltTab(Window window)
|
||||||
|
{
|
||||||
|
var exStyle = GetCurrentWindowStyle(window);
|
||||||
|
|
||||||
|
// Remove the TOOLWINDOW style and add the APPWINDOW style.
|
||||||
|
var newExStyle = ((uint)exStyle & ~(uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) | (uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
|
||||||
|
|
||||||
|
SetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// To obtain the current overridden style of a window.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">To obtain the style dialog window</param>
|
||||||
|
/// <returns>current extension style value</returns>
|
||||||
|
private static int GetCurrentWindowStyle(Window window)
|
||||||
|
{
|
||||||
|
var style = PInvoke.GetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
|
||||||
|
if (style == 0 && Marshal.GetLastPInvokeError() != 0)
|
||||||
|
{
|
||||||
|
throw new Win32Exception(Marshal.GetLastPInvokeError());
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,16 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
if (triggerValidate)
|
if (triggerValidate)
|
||||||
{
|
{
|
||||||
bool hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture);
|
bool hotkeyAvailable = false;
|
||||||
|
// TODO: This is a temporary way to enforce changing only the open flow hotkey to Win, and will be removed by PR #3157
|
||||||
|
if (keyModel.ToString() == "LWin" || keyModel.ToString() == "RWin")
|
||||||
|
{
|
||||||
|
hotkeyAvailable = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture);
|
||||||
|
}
|
||||||
|
|
||||||
if (!hotkeyAvailable)
|
if (!hotkeyAvailable)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using ChefKeys;
|
||||||
using Flow.Launcher.Core.Resource;
|
using Flow.Launcher.Core.Resource;
|
||||||
using Flow.Launcher.Helper;
|
using Flow.Launcher.Helper;
|
||||||
using Flow.Launcher.Infrastructure.Hotkey;
|
using Flow.Launcher.Infrastructure.Hotkey;
|
||||||
|
|
@ -33,6 +34,8 @@ public partial class HotkeyControlDialog : ContentDialog
|
||||||
public string ResultValue { get; private set; } = string.Empty;
|
public string ResultValue { get; private set; } = string.Empty;
|
||||||
public static string EmptyHotkey => InternationalizationManager.Instance.GetTranslation("none");
|
public static string EmptyHotkey => InternationalizationManager.Instance.GetTranslation("none");
|
||||||
|
|
||||||
|
private static bool isOpenFlowHotkey;
|
||||||
|
|
||||||
public HotkeyControlDialog(string hotkey, string defaultHotkey, IHotkeySettings hotkeySettings, string windowTitle = "")
|
public HotkeyControlDialog(string hotkey, string defaultHotkey, IHotkeySettings hotkeySettings, string windowTitle = "")
|
||||||
{
|
{
|
||||||
WindowTitle = windowTitle switch
|
WindowTitle = windowTitle switch
|
||||||
|
|
@ -46,6 +49,14 @@ public partial class HotkeyControlDialog : ContentDialog
|
||||||
SetKeysToDisplay(CurrentHotkey);
|
SetKeysToDisplay(CurrentHotkey);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
// TODO: This is a temporary way to enforce changing only the open flow hotkey to Win, and will be removed by PR #3157
|
||||||
|
isOpenFlowHotkey = _hotkeySettings.RegisteredHotkeys
|
||||||
|
.Any(x => x.DescriptionResourceKey == "flowlauncherHotkey"
|
||||||
|
&& x.Hotkey.ToString() == hotkey);
|
||||||
|
|
||||||
|
ChefKeysManager.StartMenuEnableBlocking = true;
|
||||||
|
ChefKeysManager.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Reset(object sender, RoutedEventArgs routedEventArgs)
|
private void Reset(object sender, RoutedEventArgs routedEventArgs)
|
||||||
|
|
@ -61,12 +72,18 @@ public partial class HotkeyControlDialog : ContentDialog
|
||||||
|
|
||||||
private void Cancel(object sender, RoutedEventArgs routedEventArgs)
|
private void Cancel(object sender, RoutedEventArgs routedEventArgs)
|
||||||
{
|
{
|
||||||
|
ChefKeysManager.StartMenuEnableBlocking = false;
|
||||||
|
ChefKeysManager.Stop();
|
||||||
|
|
||||||
ResultType = EResultType.Cancel;
|
ResultType = EResultType.Cancel;
|
||||||
Hide();
|
Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Save(object sender, RoutedEventArgs routedEventArgs)
|
private void Save(object sender, RoutedEventArgs routedEventArgs)
|
||||||
{
|
{
|
||||||
|
ChefKeysManager.StartMenuEnableBlocking = false;
|
||||||
|
ChefKeysManager.Stop();
|
||||||
|
|
||||||
if (KeysToDisplay.Count == 1 && KeysToDisplay[0] == EmptyHotkey)
|
if (KeysToDisplay.Count == 1 && KeysToDisplay[0] == EmptyHotkey)
|
||||||
{
|
{
|
||||||
ResultType = EResultType.Delete;
|
ResultType = EResultType.Delete;
|
||||||
|
|
@ -85,6 +102,9 @@ public partial class HotkeyControlDialog : ContentDialog
|
||||||
//when alt is pressed, the real key should be e.SystemKey
|
//when alt is pressed, the real key should be e.SystemKey
|
||||||
Key key = e.Key == Key.System ? e.SystemKey : e.Key;
|
Key key = e.Key == Key.System ? e.SystemKey : e.Key;
|
||||||
|
|
||||||
|
if (ChefKeysManager.StartMenuBlocked && key.ToString() == ChefKeysManager.StartMenuSimulatedKey)
|
||||||
|
return;
|
||||||
|
|
||||||
SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers();
|
SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers();
|
||||||
|
|
||||||
var hotkeyModel = new HotkeyModel(
|
var hotkeyModel = new HotkeyModel(
|
||||||
|
|
@ -168,8 +188,13 @@ public partial class HotkeyControlDialog : ContentDialog
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) =>
|
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture)
|
||||||
hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
|
{
|
||||||
|
if (isOpenFlowHotkey && (hotkey.ToString() == "LWin" || hotkey.ToString() == "RWin"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
|
||||||
|
}
|
||||||
|
|
||||||
private void Overwrite(object sender, RoutedEventArgs e)
|
private void Overwrite(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
<!-- MainWindow -->
|
<!-- MainWindow -->
|
||||||
<system:String x:Key="registerHotkeyFailed">Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program.</system:String>
|
<system:String x:Key="registerHotkeyFailed">Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program.</system:String>
|
||||||
|
<system:String x:Key="unregisterHotkeyFailed">Failed to unregister hotkey "{0}". Please try again or see log for details</system:String>
|
||||||
<system:String x:Key="MessageBoxTitle">Flow Launcher</system:String>
|
<system:String x:Key="MessageBoxTitle">Flow Launcher</system:String>
|
||||||
<system:String x:Key="couldnotStartCmd">Could not start {0}</system:String>
|
<system:String x:Key="couldnotStartCmd">Could not start {0}</system:String>
|
||||||
<system:String x:Key="invalidFlowLauncherPluginFileFormat">Invalid Flow Launcher plugin file format</system:String>
|
<system:String x:Key="invalidFlowLauncherPluginFileFormat">Invalid Flow Launcher plugin file format</system:String>
|
||||||
|
|
@ -46,6 +47,8 @@
|
||||||
<system:String x:Key="portableMode">Portable Mode</system:String>
|
<system:String x:Key="portableMode">Portable Mode</system:String>
|
||||||
<system:String x:Key="portableModeToolTIp">Store all settings and user data in one folder (Useful when used with removable drives or cloud services).</system:String>
|
<system:String x:Key="portableModeToolTIp">Store all settings and user data in one folder (Useful when used with removable drives or cloud services).</system:String>
|
||||||
<system:String x:Key="startFlowLauncherOnSystemStartup">Start Flow Launcher on system startup</system:String>
|
<system:String x:Key="startFlowLauncherOnSystemStartup">Start Flow Launcher on system startup</system:String>
|
||||||
|
<system:String x:Key="useLogonTaskForStartup">Use logon task instead of startup entry for faster startup experience</system:String>
|
||||||
|
<system:String x:Key="useLogonTaskForStartupTooltip">After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler</system:String>
|
||||||
<system:String x:Key="setAutoStartFailed">Error setting launch on startup</system:String>
|
<system:String x:Key="setAutoStartFailed">Error setting launch on startup</system:String>
|
||||||
<system:String x:Key="hideFlowLauncherWhenLoseFocus">Hide Flow Launcher when focus is lost</system:String>
|
<system:String x:Key="hideFlowLauncherWhenLoseFocus">Hide Flow Launcher when focus is lost</system:String>
|
||||||
<system:String x:Key="dontPromptUpdateMsg">Do not show new version notifications</system:String>
|
<system:String x:Key="dontPromptUpdateMsg">Do not show new version notifications</system:String>
|
||||||
|
|
@ -128,7 +131,8 @@
|
||||||
<system:String x:Key="plugin_query_version">Version</system:String>
|
<system:String x:Key="plugin_query_version">Version</system:String>
|
||||||
<system:String x:Key="plugin_query_web">Website</system:String>
|
<system:String x:Key="plugin_query_web">Website</system:String>
|
||||||
<system:String x:Key="plugin_uninstall">Uninstall</system:String>
|
<system:String x:Key="plugin_uninstall">Uninstall</system:String>
|
||||||
|
<system:String x:Key="failedToRemovePluginSettingsTitle">Fail to remove plugin settings</system:String>
|
||||||
|
<system:String x:Key="failedToRemovePluginSettingsMessage">Plugins: {0} - Fail to remove plugin settings files, please remove them manually</system:String>
|
||||||
|
|
||||||
<!-- Setting Plugin Store -->
|
<!-- Setting Plugin Store -->
|
||||||
<system:String x:Key="pluginStore">Plugin Store</system:String>
|
<system:String x:Key="pluginStore">Plugin Store</system:String>
|
||||||
|
|
@ -145,8 +149,6 @@
|
||||||
<system:String x:Key="LabelNewToolTip">This plugin has been updated within the last 7 days</system:String>
|
<system:String x:Key="LabelNewToolTip">This plugin has been updated within the last 7 days</system:String>
|
||||||
<system:String x:Key="LabelUpdateToolTip">New Update is Available</system:String>
|
<system:String x:Key="LabelUpdateToolTip">New Update is Available</system:String>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Setting Theme -->
|
<!-- Setting Theme -->
|
||||||
<system:String x:Key="theme">Theme</system:String>
|
<system:String x:Key="theme">Theme</system:String>
|
||||||
<system:String x:Key="appearance">Appearance</system:String>
|
<system:String x:Key="appearance">Appearance</system:String>
|
||||||
|
|
@ -196,7 +198,6 @@
|
||||||
<system:String x:Key="TypeIsDarkToolTip">This theme supports two(light/dark) modes.</system:String>
|
<system:String x:Key="TypeIsDarkToolTip">This theme supports two(light/dark) modes.</system:String>
|
||||||
<system:String x:Key="TypeHasBlurToolTip">This theme supports Blur Transparent Background.</system:String>
|
<system:String x:Key="TypeHasBlurToolTip">This theme supports Blur Transparent Background.</system:String>
|
||||||
|
|
||||||
|
|
||||||
<!-- Setting Hotkey -->
|
<!-- Setting Hotkey -->
|
||||||
<system:String x:Key="hotkey">Hotkey</system:String>
|
<system:String x:Key="hotkey">Hotkey</system:String>
|
||||||
<system:String x:Key="hotkeys">Hotkeys</system:String>
|
<system:String x:Key="hotkeys">Hotkeys</system:String>
|
||||||
|
|
@ -299,6 +300,9 @@
|
||||||
<system:String x:Key="userdatapath">User Data Location</system:String>
|
<system:String x:Key="userdatapath">User Data Location</system:String>
|
||||||
<system:String x:Key="userdatapathToolTip">User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not.</system:String>
|
<system:String x:Key="userdatapathToolTip">User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not.</system:String>
|
||||||
<system:String x:Key="userdatapathButton">Open Folder</system:String>
|
<system:String x:Key="userdatapathButton">Open Folder</system:String>
|
||||||
|
<system:String x:Key="logLevel">Log Level</system:String>
|
||||||
|
<system:String x:Key="LogLevelDEBUG">Debug</system:String>
|
||||||
|
<system:String x:Key="LogLevelINFO">Info</system:String>
|
||||||
|
|
||||||
<!-- FileManager Setting Dialog -->
|
<!-- FileManager Setting Dialog -->
|
||||||
<system:String x:Key="fileManagerWindow">Select File Manager</system:String>
|
<system:String x:Key="fileManagerWindow">Select File Manager</system:String>
|
||||||
|
|
@ -367,6 +371,7 @@
|
||||||
<system:String x:Key="commonOK">OK</system:String>
|
<system:String x:Key="commonOK">OK</system:String>
|
||||||
<system:String x:Key="commonYes">Yes</system:String>
|
<system:String x:Key="commonYes">Yes</system:String>
|
||||||
<system:String x:Key="commonNo">No</system:String>
|
<system:String x:Key="commonNo">No</system:String>
|
||||||
|
<system:String x:Key="commonBackground">Background</system:String>
|
||||||
|
|
||||||
<!-- Crash Reporter -->
|
<!-- Crash Reporter -->
|
||||||
<system:String x:Key="reportWindow_version">Version</system:String>
|
<system:String x:Key="reportWindow_version">Version</system:String>
|
||||||
|
|
@ -383,6 +388,9 @@
|
||||||
<system:String x:Key="reportWindow_report_succeed">Report sent successfully</system:String>
|
<system:String x:Key="reportWindow_report_succeed">Report sent successfully</system:String>
|
||||||
<system:String x:Key="reportWindow_report_failed">Failed to send report</system:String>
|
<system:String x:Key="reportWindow_report_failed">Failed to send report</system:String>
|
||||||
<system:String x:Key="reportWindow_flowlauncher_got_an_error">Flow Launcher got an error</system:String>
|
<system:String x:Key="reportWindow_flowlauncher_got_an_error">Flow Launcher got an error</system:String>
|
||||||
|
<system:String x:Key="reportWindow_please_open_issue">Please open new issue in</system:String>
|
||||||
|
<system:String x:Key="reportWindow_upload_log">1. Upload log file: {0}</system:String>
|
||||||
|
<system:String x:Key="reportWindow_copy_below">2. Copy below exception message</system:String>
|
||||||
|
|
||||||
<!-- General Notice -->
|
<!-- General Notice -->
|
||||||
<system:String x:Key="pleaseWait">Please wait...</system:String>
|
<system:String x:Key="pleaseWait">Please wait...</system:String>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
Closing="OnClosing"
|
Closing="OnClosing"
|
||||||
Deactivated="OnDeactivated"
|
Deactivated="OnDeactivated"
|
||||||
Icon="Images/app.png"
|
Icon="Images/app.png"
|
||||||
|
SourceInitialized="OnSourceInitialized"
|
||||||
Initialized="OnInitialized"
|
Initialized="OnInitialized"
|
||||||
Left="{Binding Settings.WindowLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
Left="{Binding Settings.WindowLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
Loaded="OnLoaded"
|
Loaded="OnLoaded"
|
||||||
|
|
@ -37,7 +38,7 @@
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<!-- WindowChrome -->
|
<!-- WindowChrome -->
|
||||||
<WindowChrome.WindowChrome>
|
<WindowChrome.WindowChrome>
|
||||||
<WindowChrome CaptionHeight="9" ResizeBorderThickness="32 4 32 32" />
|
<WindowChrome CaptionHeight="9" />
|
||||||
</WindowChrome.WindowChrome>
|
</WindowChrome.WindowChrome>
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<converters:QuerySuggestionBoxConverter x:Key="QuerySuggestionBoxConverter" />
|
<converters:QuerySuggestionBoxConverter x:Key="QuerySuggestionBoxConverter" />
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,11 @@ namespace Flow.Launcher
|
||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnSourceInitialized(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
WindowsInteropHelper.HideFromAltTab(this);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnInitialized(object sender, EventArgs e)
|
private void OnInitialized(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -438,7 +443,7 @@ namespace Flow.Launcher
|
||||||
if (_settings.FirstLaunch)
|
if (_settings.FirstLaunch)
|
||||||
{
|
{
|
||||||
_settings.FirstLaunch = false;
|
_settings.FirstLaunch = false;
|
||||||
PluginManager.API.SaveAppAllSettings();
|
App.API.SaveAppAllSettings();
|
||||||
OpenWelcomeWindow();
|
OpenWelcomeWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<Window
|
<Window
|
||||||
x:Class="Flow.Launcher.Core.MessageBoxEx"
|
x:Class="Flow.Launcher.MessageBoxEx"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:Flow.Launcher.Core"
|
xmlns:local="clr-namespace:Flow.Launcher"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
x:Name="MessageBoxWindow"
|
x:Name="MessageBoxWindow"
|
||||||
Width="420"
|
Width="420"
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
Foreground="{DynamicResource PopupTextColor}"
|
Foreground="{DynamicResource PopupTextColor}"
|
||||||
ResizeMode="NoResize"
|
ResizeMode="NoResize"
|
||||||
SizeToContent="Height"
|
SizeToContent="Height"
|
||||||
|
Topmost="True"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<WindowChrome.WindowChrome>
|
<WindowChrome.WindowChrome>
|
||||||
|
|
@ -7,7 +7,7 @@ using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.Infrastructure.Image;
|
using Flow.Launcher.Infrastructure.Image;
|
||||||
using Flow.Launcher.Infrastructure.Logger;
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core
|
namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
public partial class MessageBoxEx : Window
|
public partial class MessageBoxEx : Window
|
||||||
{
|
{
|
||||||
|
|
@ -14,4 +14,7 @@ FindWindowEx
|
||||||
WINDOW_STYLE
|
WINDOW_STYLE
|
||||||
|
|
||||||
WM_ENTERSIZEMOVE
|
WM_ENTERSIZEMOVE
|
||||||
WM_EXITSIZEMOVE
|
WM_EXITSIZEMOVE
|
||||||
|
|
||||||
|
SetLastError
|
||||||
|
WINDOW_EX_STYLE
|
||||||
|
|
@ -29,14 +29,11 @@
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button
|
<Button
|
||||||
Grid.Column="4"
|
Grid.Column="1"
|
||||||
Click="BtnCancel_OnClick"
|
Click="BtnCancel_OnClick"
|
||||||
Style="{StaticResource TitleBarCloseButtonStyle}">
|
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||||
<Path
|
<Path
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Flow.Launcher
|
||||||
this.pluginViewModel = pluginViewModel;
|
this.pluginViewModel = pluginViewModel;
|
||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(translater.GetTranslation("cannotFindSpecifiedPlugin"));
|
App.API.ShowMsgBox(translater.GetTranslation("cannotFindSpecifiedPlugin"));
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +44,7 @@ namespace Flow.Launcher
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string msg = translater.GetTranslation("invalidPriority");
|
string msg = translater.GetTranslation("invalidPriority");
|
||||||
MessageBoxEx.Show(msg);
|
App.API.ShowMsgBox(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
136
Flow.Launcher/ProgressBoxEx.xaml
Normal file
136
Flow.Launcher/ProgressBoxEx.xaml
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
<Window
|
||||||
|
x:Class="Flow.Launcher.ProgressBoxEx"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Flow.Launcher"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
x:Name="MessageBoxWindow"
|
||||||
|
Width="420"
|
||||||
|
Height="Auto"
|
||||||
|
Background="{DynamicResource PopuBGColor}"
|
||||||
|
Foreground="{DynamicResource PopupTextColor}"
|
||||||
|
ResizeMode="NoResize"
|
||||||
|
SizeToContent="Height"
|
||||||
|
Topmost="True"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<WindowChrome.WindowChrome>
|
||||||
|
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
|
||||||
|
</WindowChrome.WindowChrome>
|
||||||
|
<Window.InputBindings>
|
||||||
|
<KeyBinding Key="Escape" Command="Close" />
|
||||||
|
</Window.InputBindings>
|
||||||
|
<Window.CommandBindings>
|
||||||
|
<CommandBinding Command="Close" Executed="KeyEsc_OnPress" />
|
||||||
|
</Window.CommandBindings>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition MinHeight="68" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Grid.Row="0">
|
||||||
|
<StackPanel>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button
|
||||||
|
Grid.Column="1"
|
||||||
|
Click="Button_Minimize"
|
||||||
|
RenderOptions.EdgeMode="Aliased"
|
||||||
|
Style="{DynamicResource TitleBarButtonStyle}">
|
||||||
|
<Path
|
||||||
|
Width="46"
|
||||||
|
Height="32"
|
||||||
|
Data="M 18,15 H 28"
|
||||||
|
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
|
||||||
|
StrokeThickness="1">
|
||||||
|
<Path.Style>
|
||||||
|
<Style TargetType="Path">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
|
||||||
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Path.Style>
|
||||||
|
</Path>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
Grid.Column="2"
|
||||||
|
Click="Button_Cancel"
|
||||||
|
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||||
|
<Path
|
||||||
|
Width="46"
|
||||||
|
Height="32"
|
||||||
|
Data="M 18,11 27,20 M 18,20 27,11"
|
||||||
|
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
|
||||||
|
StrokeThickness="1">
|
||||||
|
<Path.Style>
|
||||||
|
<Style TargetType="Path">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
|
||||||
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Path.Style>
|
||||||
|
</Path>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<Grid Grid.Row="1" Margin="30 0 30 24">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
x:Name="TitleTextBlock"
|
||||||
|
Grid.Row="0"
|
||||||
|
MaxWidth="400"
|
||||||
|
Margin="0 0 26 12"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontFamily="Segoe UI"
|
||||||
|
FontSize="20"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
TextAlignment="Left"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<ProgressBar
|
||||||
|
x:Name="ProgressBar"
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0 0 26 0"
|
||||||
|
Maximum="100"
|
||||||
|
Minimum="0"
|
||||||
|
Value="0" />
|
||||||
|
</Grid>
|
||||||
|
<Border
|
||||||
|
Grid.Row="2"
|
||||||
|
Margin="0 0 0 0"
|
||||||
|
Background="{DynamicResource PopupButtonAreaBGColor}"
|
||||||
|
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
|
||||||
|
BorderThickness="0 1 0 0">
|
||||||
|
<WrapPanel
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
x:Name="btnBackground"
|
||||||
|
MinWidth="120"
|
||||||
|
Margin="5 0 5 0"
|
||||||
|
Click="Button_Background"
|
||||||
|
Content="{DynamicResource commonBackground}" />
|
||||||
|
<Button
|
||||||
|
x:Name="btnCancel"
|
||||||
|
MinWidth="120"
|
||||||
|
Margin="5 0 5 0"
|
||||||
|
Click="Button_Cancel"
|
||||||
|
Content="{DynamicResource commonCancel}" />
|
||||||
|
</WrapPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
119
Flow.Launcher/ProgressBoxEx.xaml.cs
Normal file
119
Flow.Launcher/ProgressBoxEx.xaml.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
|
|
||||||
|
namespace Flow.Launcher
|
||||||
|
{
|
||||||
|
public partial class ProgressBoxEx : Window
|
||||||
|
{
|
||||||
|
private readonly Action _cancelProgress;
|
||||||
|
|
||||||
|
private ProgressBoxEx(Action cancelProgress)
|
||||||
|
{
|
||||||
|
_cancelProgress = cancelProgress;
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task ShowAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action cancelProgress = null)
|
||||||
|
{
|
||||||
|
ProgressBoxEx prgBox = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Application.Current.Dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
prgBox = new ProgressBoxEx(cancelProgress)
|
||||||
|
{
|
||||||
|
Title = caption
|
||||||
|
};
|
||||||
|
prgBox.TitleTextBlock.Text = caption;
|
||||||
|
prgBox.Show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prgBox = new ProgressBoxEx(cancelProgress)
|
||||||
|
{
|
||||||
|
Title = caption
|
||||||
|
};
|
||||||
|
prgBox.TitleTextBlock.Text = caption;
|
||||||
|
prgBox.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
await reportProgressAsync(prgBox.ReportProgress).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error($"|ProgressBoxEx.Show|An error occurred: {e.Message}");
|
||||||
|
|
||||||
|
await reportProgressAsync(null).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!Application.Current.Dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
prgBox?.Close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prgBox?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReportProgress(double progress)
|
||||||
|
{
|
||||||
|
if (!Application.Current.Dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() => ReportProgress(progress));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress < 0)
|
||||||
|
{
|
||||||
|
ProgressBar.Value = 0;
|
||||||
|
}
|
||||||
|
else if (progress >= 100)
|
||||||
|
{
|
||||||
|
ProgressBar.Value = 100;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ProgressBar.Value = progress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void KeyEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ForceClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Button_Cancel(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ForceClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Button_Minimize(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
WindowState = WindowState.Minimized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Button_Background(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ForceClose()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
_cancelProgress?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,23 +25,23 @@ using Flow.Launcher.Infrastructure.Storage;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
using Flow.Launcher.Core;
|
using Flow.Launcher.Core;
|
||||||
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
|
||||||
namespace Flow.Launcher
|
namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
public class PublicAPIInstance : IPublicAPI
|
public class PublicAPIInstance : IPublicAPI
|
||||||
{
|
{
|
||||||
private readonly SettingWindowViewModel _settingsVM;
|
private readonly Settings _settings;
|
||||||
private readonly MainViewModel _mainVM;
|
private readonly MainViewModel _mainVM;
|
||||||
private readonly PinyinAlphabet _alphabet;
|
|
||||||
|
|
||||||
#region Constructor
|
#region Constructor
|
||||||
|
|
||||||
public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM, PinyinAlphabet alphabet)
|
public PublicAPIInstance(Settings settings, MainViewModel mainVM)
|
||||||
{
|
{
|
||||||
_settingsVM = settingsVM;
|
_settings = settings;
|
||||||
_mainVM = mainVM;
|
_mainVM = mainVM;
|
||||||
_alphabet = alphabet;
|
|
||||||
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
|
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
|
||||||
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
|
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
|
||||||
}
|
}
|
||||||
|
|
@ -78,14 +78,15 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
public event VisibilityChangedEventHandler VisibilityChanged { add => _mainVM.VisibilityChanged += value; remove => _mainVM.VisibilityChanged -= value; }
|
public event VisibilityChangedEventHandler VisibilityChanged { add => _mainVM.VisibilityChanged += value; remove => _mainVM.VisibilityChanged -= value; }
|
||||||
|
|
||||||
public void CheckForNewUpdate() => _settingsVM.UpdateApp();
|
// Must use Ioc.Default.GetRequiredService<Updater>() to avoid circular dependency
|
||||||
|
public void CheckForNewUpdate() => _ = Ioc.Default.GetRequiredService<Updater>().UpdateAppAsync(false);
|
||||||
|
|
||||||
public void SaveAppAllSettings()
|
public void SaveAppAllSettings()
|
||||||
{
|
{
|
||||||
PluginManager.Save();
|
PluginManager.Save();
|
||||||
_mainVM.Save();
|
_mainVM.Save();
|
||||||
_settingsVM.Save();
|
_settings.Save();
|
||||||
ImageLoader.Save();
|
_ = ImageLoader.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();
|
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();
|
||||||
|
|
@ -105,7 +106,7 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
SettingWindow sw = SingletonWindowOpener.Open<SettingWindow>(this, _settingsVM);
|
SettingWindow sw = SingletonWindowOpener.Open<SettingWindow>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,8 +165,8 @@ namespace Flow.Launcher
|
||||||
public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default) =>
|
public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default) =>
|
||||||
Http.GetStreamAsync(url);
|
Http.GetStreamAsync(url);
|
||||||
|
|
||||||
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath,
|
public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action<double> reportProgress = null,
|
||||||
CancellationToken token = default) => Http.DownloadAsync(url, filePath, token);
|
CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token);
|
||||||
|
|
||||||
public void AddActionKeyword(string pluginId, string newActionKeyword) =>
|
public void AddActionKeyword(string pluginId, string newActionKeyword) =>
|
||||||
PluginManager.AddActionKeyword(pluginId, newActionKeyword);
|
PluginManager.AddActionKeyword(pluginId, newActionKeyword);
|
||||||
|
|
@ -189,6 +190,23 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<Type, object> _pluginJsonStorages = new();
|
private readonly ConcurrentDictionary<Type, object> _pluginJsonStorages = new();
|
||||||
|
|
||||||
|
public object RemovePluginSettings(string assemblyName)
|
||||||
|
{
|
||||||
|
foreach (var keyValuePair in _pluginJsonStorages)
|
||||||
|
{
|
||||||
|
var key = keyValuePair.Key;
|
||||||
|
var value = keyValuePair.Value;
|
||||||
|
var name = value.GetType().GetField("AssemblyName")?.GetValue(value)?.ToString();
|
||||||
|
if (name == assemblyName)
|
||||||
|
{
|
||||||
|
_pluginJsonStorages.Remove(key, out var pluginJsonStorage);
|
||||||
|
return pluginJsonStorage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save plugin settings.
|
/// Save plugin settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -230,7 +248,7 @@ namespace Flow.Launcher
|
||||||
public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
|
public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
|
||||||
{
|
{
|
||||||
using var explorer = new Process();
|
using var explorer = new Process();
|
||||||
var explorerInfo = _settingsVM.Settings.CustomExplorer;
|
var explorerInfo = _settings.CustomExplorer;
|
||||||
|
|
||||||
explorer.StartInfo = new ProcessStartInfo
|
explorer.StartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
|
|
@ -251,7 +269,7 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
|
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
|
||||||
{
|
{
|
||||||
var browserInfo = _settingsVM.Settings.CustomBrowser;
|
var browserInfo = _settings.CustomBrowser;
|
||||||
|
|
||||||
var path = browserInfo.Path == "*" ? "" : browserInfo.Path;
|
var path = browserInfo.Path == "*" ? "" : browserInfo.Path;
|
||||||
|
|
||||||
|
|
@ -324,6 +342,8 @@ namespace Flow.Launcher
|
||||||
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK) =>
|
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK) =>
|
||||||
MessageBoxEx.Show(messageBoxText, caption, button, icon, defaultResult);
|
MessageBoxEx.Show(messageBoxText, caption, button, icon, defaultResult);
|
||||||
|
|
||||||
|
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Private Methods
|
#region Private Methods
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,82 @@
|
||||||
<Window x:Class="Flow.Launcher.ReportWindow"
|
<Window
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
x:Class="Flow.Launcher.ReportWindow"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
mc:Ignorable="d"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
WindowStartupLocation="CenterScreen"
|
Title="{DynamicResource reportWindow_flowlauncher_got_an_error}"
|
||||||
Icon="Images/app_error.png"
|
Width="600"
|
||||||
Topmost="True"
|
Height="455"
|
||||||
ResizeMode="NoResize"
|
d:DesignHeight="300"
|
||||||
Width="600"
|
d:DesignWidth="600"
|
||||||
Height="455"
|
x:ClassModifier="internal"
|
||||||
Title="{DynamicResource reportWindow_flowlauncher_got_an_error}"
|
Background="{DynamicResource PopuBGColor}"
|
||||||
d:DesignHeight="300" d:DesignWidth="600" x:ClassModifier="internal">
|
Foreground="{DynamicResource PopupTextColor}"
|
||||||
<RichTextBox x:Name="ErrorTextbox"
|
Icon="/Images/app_error.png"
|
||||||
IsDocumentEnabled="True"
|
ResizeMode="NoResize"
|
||||||
VerticalScrollBarVisibility="Auto"
|
Topmost="True"
|
||||||
HorizontalScrollBarVisibility="Auto"
|
WindowStartupLocation="CenterScreen"
|
||||||
FontSize="14"
|
mc:Ignorable="d">
|
||||||
Margin="10"
|
<WindowChrome.WindowChrome>
|
||||||
BorderThickness="0"/>
|
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
|
||||||
|
</WindowChrome.WindowChrome>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Image
|
||||||
|
Grid.Column="0"
|
||||||
|
Width="16"
|
||||||
|
Height="16"
|
||||||
|
Margin="10 4 4 4"
|
||||||
|
RenderOptions.BitmapScalingMode="HighQuality"
|
||||||
|
Source="/Images/app_error.png" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="4 0 0 0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="{DynamicResource Color05B}"
|
||||||
|
Text="{DynamicResource reportWindow_flowlauncher_got_an_error}" />
|
||||||
|
<Button
|
||||||
|
Grid.Column="2"
|
||||||
|
Click="BtnCancel_OnClick"
|
||||||
|
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||||
|
<Path
|
||||||
|
Width="46"
|
||||||
|
Height="32"
|
||||||
|
Data="M 18,11 27,20 M 18,20 27,11"
|
||||||
|
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
|
||||||
|
StrokeThickness="1">
|
||||||
|
<Path.Style>
|
||||||
|
<Style TargetType="Path">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
|
||||||
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Path.Style>
|
||||||
|
</Path>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<RichTextBox
|
||||||
|
x:Name="ErrorTextbox"
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="10"
|
||||||
|
BorderThickness="0"
|
||||||
|
FontSize="14"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
IsDocumentEnabled="True"
|
||||||
|
VerticalScrollBarVisibility="Auto" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
</Window>
|
</Window>
|
||||||
|
|
|
||||||
|
|
@ -43,20 +43,21 @@ namespace Flow.Launcher
|
||||||
var log = directory.GetFiles().OrderByDescending(f => f.LastWriteTime).First();
|
var log = directory.GetFiles().OrderByDescending(f => f.LastWriteTime).First();
|
||||||
|
|
||||||
var websiteUrl = exception switch
|
var websiteUrl = exception switch
|
||||||
{
|
{
|
||||||
FlowPluginException pluginException =>GetIssuesUrl(pluginException.Metadata.Website),
|
FlowPluginException pluginException =>GetIssuesUrl(pluginException.Metadata.Website),
|
||||||
_ => Constant.IssuesUrl
|
_ => Constant.IssuesUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var paragraph = Hyperlink("Please open new issue in: ", websiteUrl);
|
var paragraph = Hyperlink(App.API.GetTranslation("reportWindow_please_open_issue"), websiteUrl);
|
||||||
paragraph.Inlines.Add($"1. upload log file: {log.FullName}\n");
|
paragraph.Inlines.Add(string.Format(App.API.GetTranslation("reportWindow_upload_log"), log.FullName));
|
||||||
paragraph.Inlines.Add($"2. copy below exception message");
|
paragraph.Inlines.Add("\n");
|
||||||
|
paragraph.Inlines.Add(App.API.GetTranslation("reportWindow_copy_below"));
|
||||||
ErrorTextbox.Document.Blocks.Add(paragraph);
|
ErrorTextbox.Document.Blocks.Add(paragraph);
|
||||||
|
|
||||||
StringBuilder content = new StringBuilder();
|
StringBuilder content = new StringBuilder();
|
||||||
content.AppendLine(ErrorReporting.RuntimeInfo());
|
content.AppendLine(ErrorReporting.RuntimeInfo());
|
||||||
content.AppendLine(ErrorReporting.DependenciesInfo());
|
content.AppendLine(ErrorReporting.DependenciesInfo());
|
||||||
|
content.AppendLine();
|
||||||
content.AppendLine($"Date: {DateTime.Now.ToString(CultureInfo.InvariantCulture)}");
|
content.AppendLine($"Date: {DateTime.Now.ToString(CultureInfo.InvariantCulture)}");
|
||||||
content.AppendLine("Exception:");
|
content.AppendLine("Exception:");
|
||||||
content.AppendLine(exception.ToString());
|
content.AppendLine(exception.ToString());
|
||||||
|
|
@ -65,10 +66,12 @@ namespace Flow.Launcher
|
||||||
ErrorTextbox.Document.Blocks.Add(paragraph);
|
ErrorTextbox.Document.Blocks.Add(paragraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Paragraph Hyperlink(string textBeforeUrl, string url)
|
private static Paragraph Hyperlink(string textBeforeUrl, string url)
|
||||||
{
|
{
|
||||||
var paragraph = new Paragraph();
|
var paragraph = new Paragraph
|
||||||
paragraph.Margin = new Thickness(0);
|
{
|
||||||
|
Margin = new Thickness(0)
|
||||||
|
};
|
||||||
|
|
||||||
var link = new Hyperlink
|
var link = new Hyperlink
|
||||||
{
|
{
|
||||||
|
|
@ -79,10 +82,16 @@ namespace Flow.Launcher
|
||||||
link.Click += (s, e) => SearchWeb.OpenInBrowserTab(url);
|
link.Click += (s, e) => SearchWeb.OpenInBrowserTab(url);
|
||||||
|
|
||||||
paragraph.Inlines.Add(textBeforeUrl);
|
paragraph.Inlines.Add(textBeforeUrl);
|
||||||
|
paragraph.Inlines.Add(" ");
|
||||||
paragraph.Inlines.Add(link);
|
paragraph.Inlines.Add(link);
|
||||||
paragraph.Inlines.Add("\n");
|
paragraph.Inlines.Add("\n");
|
||||||
|
|
||||||
return paragraph;
|
return paragraph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,10 @@
|
||||||
using Flow.Launcher.Infrastructure.Hotkey;
|
using Flow.Launcher.Infrastructure.Hotkey;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using System;
|
using System;
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Flow.Launcher.ViewModel;
|
using Flow.Launcher.ViewModel;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
namespace Flow.Launcher.Resources.Pages
|
namespace Flow.Launcher.Resources.Pages
|
||||||
{
|
{
|
||||||
|
|
@ -29,5 +28,10 @@ namespace Flow.Launcher.Resources.Pages
|
||||||
{
|
{
|
||||||
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
|
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Brush PreviewBackground
|
||||||
|
{
|
||||||
|
get => WallpaperPathRetrieval.GetWallpaperBrush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,11 @@
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button
|
<Button
|
||||||
Grid.Column="4"
|
Grid.Column="1"
|
||||||
Click="btnCancel_Click"
|
Click="btnCancel_Click"
|
||||||
Style="{StaticResource TitleBarCloseButtonStyle}">
|
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||||
<Path
|
<Path
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,11 @@
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Grid Grid.Row="0">
|
<Grid Grid.Row="0">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button
|
<Button
|
||||||
Grid.Column="4"
|
Grid.Column="1"
|
||||||
Click="btnCancel_Click"
|
Click="btnCancel_Click"
|
||||||
Style="{StaticResource TitleBarCloseButtonStyle}">
|
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||||
<Path
|
<Path
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Flow.Launcher.Core;
|
using Flow.Launcher.Core;
|
||||||
using Flow.Launcher.Core.Plugin;
|
|
||||||
using Flow.Launcher.Core.Resource;
|
using Flow.Launcher.Core.Resource;
|
||||||
using Flow.Launcher.Infrastructure;
|
using Flow.Launcher.Infrastructure;
|
||||||
|
using Flow.Launcher.Infrastructure.Logger;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
|
|
@ -46,10 +46,35 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
_settings.ActivateTimes
|
_settings.ActivateTimes
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public class LogLevelData : DropdownDataGeneric<LOGLEVEL> { }
|
||||||
|
|
||||||
|
public List<LogLevelData> LogLevels { get; } =
|
||||||
|
DropdownDataGeneric<LOGLEVEL>.GetValues<LogLevelData>("LogLevel");
|
||||||
|
|
||||||
|
public LOGLEVEL LogLevel
|
||||||
|
{
|
||||||
|
get => _settings.LogLevel;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_settings.LogLevel != value)
|
||||||
|
{
|
||||||
|
_settings.LogLevel = value;
|
||||||
|
|
||||||
|
Log.SetLogLevel(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public SettingsPaneAboutViewModel(Settings settings, Updater updater)
|
public SettingsPaneAboutViewModel(Settings settings, Updater updater)
|
||||||
{
|
{
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
_updater = updater;
|
_updater = updater;
|
||||||
|
UpdateEnumDropdownLocalizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateEnumDropdownLocalizations()
|
||||||
|
{
|
||||||
|
DropdownDataGeneric<LOGLEVEL>.UpdateLabels(LogLevels);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
@ -62,7 +87,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void AskClearLogFolderConfirmation()
|
private void AskClearLogFolderConfirmation()
|
||||||
{
|
{
|
||||||
var confirmResult = MessageBoxEx.Show(
|
var confirmResult = App.API.ShowMsgBox(
|
||||||
InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"),
|
InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"),
|
||||||
InternationalizationManager.Instance.GetTranslation("clearlogfolder"),
|
InternationalizationManager.Instance.GetTranslation("clearlogfolder"),
|
||||||
MessageBoxButton.YesNo
|
MessageBoxButton.YesNo
|
||||||
|
|
@ -77,7 +102,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void OpenSettingsFolder()
|
private void OpenSettingsFolder()
|
||||||
{
|
{
|
||||||
PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings));
|
App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
@ -85,7 +110,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
{
|
{
|
||||||
string settingsFolderPath = Path.Combine(DataLocation.DataDirectory(), Constant.Settings);
|
string settingsFolderPath = Path.Combine(DataLocation.DataDirectory(), Constant.Settings);
|
||||||
string parentFolderPath = Path.GetDirectoryName(settingsFolderPath);
|
string parentFolderPath = Path.GetDirectoryName(settingsFolderPath);
|
||||||
PluginManager.API.OpenDirectory(parentFolderPath);
|
App.API.OpenDirectory(parentFolderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -96,7 +121,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private Task UpdateApp() => _updater.UpdateAppAsync(App.API, false);
|
private Task UpdateApp() => _updater.UpdateAppAsync(false);
|
||||||
|
|
||||||
private void ClearLogFolder()
|
private void ClearLogFolder()
|
||||||
{
|
{
|
||||||
|
|
@ -139,5 +164,4 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
|
|
||||||
return "0 B";
|
return "0 B";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Flow.Launcher.Core;
|
using Flow.Launcher.Core;
|
||||||
|
|
@ -43,9 +42,20 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (value)
|
if (value)
|
||||||
AutoStartup.Enable();
|
{
|
||||||
|
if (UseLogonTaskForStartup)
|
||||||
|
{
|
||||||
|
AutoStartup.EnableViaLogonTask();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AutoStartup.EnableViaRegistry();
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
AutoStartup.Disable();
|
{
|
||||||
|
AutoStartup.DisableViaLogonTaskAndRegistry();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -54,12 +64,41 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SelectAllQueryOnReopen // Pbff7
|
public bool SelectAllQueryOnReopen
|
||||||
{
|
{
|
||||||
get => Settings.SelectAllQueryOnReopen;
|
get => Settings.SelectAllQueryOnReopen;
|
||||||
set => Settings.SelectAllQueryOnReopen = value;
|
set => Settings.SelectAllQueryOnReopen = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool UseLogonTaskForStartup
|
||||||
|
{
|
||||||
|
get => Settings.UseLogonTaskForStartup;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Settings.UseLogonTaskForStartup = value;
|
||||||
|
|
||||||
|
if (StartFlowLauncherOnSystemStartup)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (UseLogonTaskForStartup)
|
||||||
|
{
|
||||||
|
AutoStartup.ChangeToViaLogonTask();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AutoStartup.ChangeToViaRegistry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"),
|
||||||
|
e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<SearchWindowScreenData> SearchWindowScreens { get; } =
|
public List<SearchWindowScreenData> SearchWindowScreens { get; } =
|
||||||
DropdownDataGeneric<SearchWindowScreens>.GetValues<SearchWindowScreenData>("SearchWindowScreen");
|
DropdownDataGeneric<SearchWindowScreens>.GetValues<SearchWindowScreenData>("SearchWindowScreen");
|
||||||
|
|
@ -166,7 +205,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
||||||
|
|
||||||
private void UpdateApp()
|
private void UpdateApp()
|
||||||
{
|
{
|
||||||
_ = _updater.UpdateAppAsync(App.API, false);
|
_ = _updater.UpdateAppAsync(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AutoUpdates
|
public bool AutoUpdates
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.Infrastructure.Hotkey;
|
using Flow.Launcher.Infrastructure.Hotkey;
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using Flow.Launcher.Core;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.SettingPages.ViewModels;
|
namespace Flow.Launcher.SettingPages.ViewModels;
|
||||||
|
|
||||||
|
|
@ -42,11 +41,11 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
|
||||||
var item = SelectedCustomPluginHotkey;
|
var item = SelectedCustomPluginHotkey;
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = MessageBoxEx.Show(
|
var result = App.API.ShowMsgBox(
|
||||||
string.Format(
|
string.Format(
|
||||||
InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey
|
InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey
|
||||||
),
|
),
|
||||||
|
|
@ -67,11 +66,11 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
|
||||||
var item = SelectedCustomPluginHotkey;
|
var item = SelectedCustomPluginHotkey;
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var window = new CustomQueryHotkeySetting(null, Settings);
|
var window = new CustomQueryHotkeySetting(Settings);
|
||||||
window.UpdateItem(item);
|
window.UpdateItem(item);
|
||||||
window.ShowDialog();
|
window.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +78,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void CustomHotkeyAdd()
|
private void CustomHotkeyAdd()
|
||||||
{
|
{
|
||||||
new CustomQueryHotkeySetting(null, Settings).ShowDialog();
|
new CustomQueryHotkeySetting(Settings).ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
@ -88,11 +87,11 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
|
||||||
var item = SelectedCustomShortcut;
|
var item = SelectedCustomShortcut;
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = MessageBoxEx.Show(
|
var result = App.API.ShowMsgBox(
|
||||||
string.Format(
|
string.Format(
|
||||||
InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value
|
InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value
|
||||||
),
|
),
|
||||||
|
|
@ -112,7 +111,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
|
||||||
var item = SelectedCustomShortcut;
|
var item = SelectedCustomShortcut;
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
|
||||||
{
|
{
|
||||||
public string FilterText { get; set; } = string.Empty;
|
public string FilterText { get; set; } = string.Empty;
|
||||||
|
|
||||||
public IList<PluginStoreItemViewModel> ExternalPlugins => PluginsManifest.UserPlugins
|
public IList<PluginStoreItemViewModel> ExternalPlugins =>
|
||||||
.Select(p => new PluginStoreItemViewModel(p))
|
PluginsManifest.UserPlugins?.Select(p => new PluginStoreItemViewModel(p))
|
||||||
.OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
|
.OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
|
||||||
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
|
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
|
||||||
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
|
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
|
||||||
|
|
@ -24,8 +24,10 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task RefreshExternalPluginsAsync()
|
private async Task RefreshExternalPluginsAsync()
|
||||||
{
|
{
|
||||||
await PluginsManifest.UpdateManifestAsync();
|
if (await PluginsManifest.UpdateManifestAsync())
|
||||||
OnPropertyChanged(nameof(ExternalPlugins));
|
{
|
||||||
|
OnPropertyChanged(nameof(ExternalPlugins));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SatisfiesFilter(PluginStoreItemViewModel plugin)
|
public bool SatisfiesFilter(PluginStoreItemViewModel plugin)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ public partial class SettingsPaneProxyViewModel : BaseModel
|
||||||
private void OnTestProxyClicked()
|
private void OnTestProxyClicked()
|
||||||
{
|
{
|
||||||
var message = TestProxy();
|
var message = TestProxy();
|
||||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation(message));
|
App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string TestProxy()
|
private string TestProxy()
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Flow.Launcher.Core.Resource;
|
using Flow.Launcher.Core.Resource;
|
||||||
using Flow.Launcher.Helper;
|
using Flow.Launcher.Helper;
|
||||||
|
|
@ -14,7 +13,6 @@ using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using Flow.Launcher.ViewModel;
|
using Flow.Launcher.ViewModel;
|
||||||
using ModernWpf;
|
using ModernWpf;
|
||||||
using Flow.Launcher.Core;
|
|
||||||
using ThemeManager = Flow.Launcher.Core.Resource.ThemeManager;
|
using ThemeManager = Flow.Launcher.Core.Resource.ThemeManager;
|
||||||
using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager;
|
using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager;
|
||||||
|
|
||||||
|
|
@ -49,7 +47,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
|
||||||
{
|
{
|
||||||
if (ThemeManager.Instance.BlurEnabled && value)
|
if (ThemeManager.Instance.BlurEnabled && value)
|
||||||
{
|
{
|
||||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed"));
|
App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,24 +210,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
|
||||||
|
|
||||||
public Brush PreviewBackground
|
public Brush PreviewBackground
|
||||||
{
|
{
|
||||||
get
|
get => WallpaperPathRetrieval.GetWallpaperBrush();
|
||||||
{
|
|
||||||
var wallpaper = WallpaperPathRetrieval.GetWallpaperPath();
|
|
||||||
if (wallpaper is not null && File.Exists(wallpaper))
|
|
||||||
{
|
|
||||||
var memStream = new MemoryStream(File.ReadAllBytes(wallpaper));
|
|
||||||
var bitmap = new BitmapImage();
|
|
||||||
bitmap.BeginInit();
|
|
||||||
bitmap.StreamSource = memStream;
|
|
||||||
bitmap.DecodePixelWidth = 800;
|
|
||||||
bitmap.DecodePixelHeight = 600;
|
|
||||||
bitmap.EndInit();
|
|
||||||
return new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
|
|
||||||
}
|
|
||||||
|
|
||||||
var wallpaperColor = WallpaperPathRetrieval.GetWallpaperColor();
|
|
||||||
return new SolidColorBrush(wallpaperColor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultsViewModel PreviewResults
|
public ResultsViewModel PreviewResults
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,14 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</cc:Card>
|
</cc:Card>
|
||||||
|
|
||||||
|
<cc:Card Title="{DynamicResource logLevel}" Icon="">
|
||||||
|
<ComboBox
|
||||||
|
DisplayMemberPath="Display"
|
||||||
|
ItemsSource="{Binding LogLevels}"
|
||||||
|
SelectedValue="{Binding LogLevel}"
|
||||||
|
SelectedValuePath="Value" />
|
||||||
|
</cc:Card>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="14 20 0 0"
|
Margin="14 20 0 0"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,13 @@
|
||||||
OnContent="{DynamicResource enable}" />
|
OnContent="{DynamicResource enable}" />
|
||||||
</cc:Card>
|
</cc:Card>
|
||||||
|
|
||||||
|
<cc:Card Title="{DynamicResource useLogonTaskForStartup}" Sub="{DynamicResource useLogonTaskForStartupTooltip}">
|
||||||
|
<ui:ToggleSwitch
|
||||||
|
IsOn="{Binding UseLogonTaskForStartup}"
|
||||||
|
OffContent="{DynamicResource disable}"
|
||||||
|
OnContent="{DynamicResource enable}" />
|
||||||
|
</cc:Card>
|
||||||
|
|
||||||
<cc:Card
|
<cc:Card
|
||||||
Title="{DynamicResource hideOnStartup}"
|
Title="{DynamicResource hideOnStartup}"
|
||||||
Icon=""
|
Icon=""
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using System.ComponentModel;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using Flow.Launcher.Core.Plugin;
|
|
||||||
using Flow.Launcher.SettingPages.ViewModels;
|
using Flow.Launcher.SettingPages.ViewModels;
|
||||||
using Flow.Launcher.ViewModel;
|
using Flow.Launcher.ViewModel;
|
||||||
|
|
||||||
|
|
@ -49,7 +48,7 @@ public partial class SettingsPanePluginStore
|
||||||
|
|
||||||
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
|
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||||
{
|
{
|
||||||
PluginManager.API.OpenUrl(e.Uri.AbsoluteUri);
|
App.API.OpenUrl(e.Uri.AbsoluteUri);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Windows;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
using Flow.Launcher.Core;
|
using Flow.Launcher.Core;
|
||||||
using Flow.Launcher.Core.Configuration;
|
using Flow.Launcher.Core.Configuration;
|
||||||
using Flow.Launcher.Helper;
|
using Flow.Launcher.Helper;
|
||||||
|
|
@ -17,16 +18,21 @@ namespace Flow.Launcher;
|
||||||
|
|
||||||
public partial class SettingWindow
|
public partial class SettingWindow
|
||||||
{
|
{
|
||||||
|
private readonly Updater _updater;
|
||||||
|
private readonly IPortable _portable;
|
||||||
private readonly IPublicAPI _api;
|
private readonly IPublicAPI _api;
|
||||||
private readonly Settings _settings;
|
private readonly Settings _settings;
|
||||||
private readonly SettingWindowViewModel _viewModel;
|
private readonly SettingWindowViewModel _viewModel;
|
||||||
|
|
||||||
public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel)
|
public SettingWindow()
|
||||||
{
|
{
|
||||||
_settings = viewModel.Settings;
|
var viewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
|
||||||
|
_settings = Ioc.Default.GetRequiredService<Settings>();
|
||||||
DataContext = viewModel;
|
DataContext = viewModel;
|
||||||
_viewModel = viewModel;
|
_viewModel = viewModel;
|
||||||
_api = api;
|
_updater = Ioc.Default.GetRequiredService<Updater>();
|
||||||
|
_portable = Ioc.Default.GetRequiredService<Portable>();
|
||||||
|
_api = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
InitializePosition();
|
InitializePosition();
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
@ -34,11 +40,11 @@ public partial class SettingWindow
|
||||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
RefreshMaximizeRestoreButton();
|
RefreshMaximizeRestoreButton();
|
||||||
// Fix (workaround) for the window freezes after lock screen (Win+L)
|
// Fix (workaround) for the window freezes after lock screen (Win+L) or sleep
|
||||||
// https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf
|
// https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf
|
||||||
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
|
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
|
||||||
HwndTarget hwndTarget = hwndSource.CompositionTarget;
|
HwndTarget hwndTarget = hwndSource.CompositionTarget;
|
||||||
hwndTarget.RenderMode = RenderMode.Default;
|
hwndTarget.RenderMode = RenderMode.SoftwareOnly; // Must use software only render mode here
|
||||||
|
|
||||||
InitializePosition();
|
InitializePosition();
|
||||||
|
|
||||||
|
|
@ -132,7 +138,7 @@ public partial class SettingWindow
|
||||||
WindowState = _settings.SettingWindowState;
|
WindowState = _settings.SettingWindowState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsPositionValid(double top, double left)
|
private static bool IsPositionValid(double top, double left)
|
||||||
{
|
{
|
||||||
foreach (var screen in Screen.AllScreens)
|
foreach (var screen in Screen.AllScreens)
|
||||||
{
|
{
|
||||||
|
|
@ -152,7 +158,7 @@ public partial class SettingWindow
|
||||||
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
|
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
|
||||||
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
|
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
|
||||||
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
|
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
|
||||||
var left = (dip2.X - this.ActualWidth) / 2 + dip1.X;
|
var left = (dip2.X - ActualWidth) / 2 + dip1.X;
|
||||||
return left;
|
return left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,13 +167,13 @@ public partial class SettingWindow
|
||||||
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
|
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
|
||||||
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
|
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
|
||||||
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
|
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
|
||||||
var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20;
|
var top = (dip2.Y - ActualHeight) / 2 + dip1.Y - 20;
|
||||||
return top;
|
return top;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
|
private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
|
||||||
{
|
{
|
||||||
var paneData = new PaneData(_settings, _viewModel.Updater, _viewModel.Portable);
|
var paneData = new PaneData(_settings, _updater, _portable);
|
||||||
if (args.IsSettingsSelected)
|
if (args.IsSettingsSelected)
|
||||||
{
|
{
|
||||||
ContentFrame.Navigate(typeof(SettingsPaneGeneral), paneData);
|
ContentFrame.Navigate(typeof(SettingsPaneGeneral), paneData);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ namespace Flow.Launcher.Storage
|
||||||
|
|
||||||
internal bool IsTopMost(Result result)
|
internal bool IsTopMost(Result result)
|
||||||
{
|
{
|
||||||
|
// origin query is null when user select the context menu item directly of one item from query list
|
||||||
|
// in this case, we do not need to check if the result is top most
|
||||||
if (records.IsEmpty || result.OriginQuery == null ||
|
if (records.IsEmpty || result.OriginQuery == null ||
|
||||||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
|
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
|
||||||
{
|
{
|
||||||
|
|
@ -24,24 +26,34 @@ namespace Flow.Launcher.Storage
|
||||||
|
|
||||||
internal void Remove(Result result)
|
internal void Remove(Result result)
|
||||||
{
|
{
|
||||||
|
// origin query is null when user select the context menu item directly of one item from query list
|
||||||
|
// in this case, we do not need to remove the record
|
||||||
|
if (result.OriginQuery == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
records.Remove(result.OriginQuery.RawQuery, out _);
|
records.Remove(result.OriginQuery.RawQuery, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddOrUpdate(Result result)
|
internal void AddOrUpdate(Result result)
|
||||||
{
|
{
|
||||||
|
// origin query is null when user select the context menu item directly of one item from query list
|
||||||
|
// in this case, we do not need to add or update the record
|
||||||
|
if (result.OriginQuery == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var record = new Record
|
var record = new Record
|
||||||
{
|
{
|
||||||
PluginID = result.PluginID,
|
PluginID = result.PluginID,
|
||||||
Title = result.Title,
|
Title = result.Title,
|
||||||
SubTitle = result.SubTitle
|
SubTitle = result.SubTitle,
|
||||||
|
RecordKey = result.RecordKey
|
||||||
};
|
};
|
||||||
records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record);
|
records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Load(Dictionary<string, Record> dictionary)
|
|
||||||
{
|
|
||||||
records = new ConcurrentDictionary<string, Record>(dictionary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Record
|
public class Record
|
||||||
|
|
@ -49,12 +61,21 @@ namespace Flow.Launcher.Storage
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string SubTitle { get; set; }
|
public string SubTitle { get; set; }
|
||||||
public string PluginID { get; set; }
|
public string PluginID { get; set; }
|
||||||
|
public string RecordKey { get; set; }
|
||||||
|
|
||||||
public bool Equals(Result r)
|
public bool Equals(Result r)
|
||||||
{
|
{
|
||||||
return Title == r.Title
|
if (string.IsNullOrEmpty(RecordKey) || string.IsNullOrEmpty(r.RecordKey))
|
||||||
&& SubTitle == r.SubTitle
|
{
|
||||||
&& PluginID == r.PluginID;
|
return Title == r.Title
|
||||||
|
&& SubTitle == r.SubTitle
|
||||||
|
&& PluginID == r.PluginID;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return RecordKey == r.RecordKey
|
||||||
|
&& PluginID == r.PluginID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ namespace Flow.Launcher.Storage
|
||||||
[JsonInclude, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
[JsonInclude, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
public Dictionary<string, int> records { get; private set; }
|
public Dictionary<string, int> records { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
public UserSelectedRecord()
|
public UserSelectedRecord()
|
||||||
{
|
{
|
||||||
recordsWithQuery = new Dictionary<int, int>();
|
recordsWithQuery = new Dictionary<int, int>();
|
||||||
|
|
@ -45,12 +44,21 @@ namespace Flow.Launcher.Storage
|
||||||
|
|
||||||
private static int GenerateResultHashCode(Result result)
|
private static int GenerateResultHashCode(Result result)
|
||||||
{
|
{
|
||||||
int hashcode = GenerateStaticHashCode(result.Title);
|
if (string.IsNullOrEmpty(result.RecordKey))
|
||||||
return GenerateStaticHashCode(result.SubTitle, hashcode);
|
{
|
||||||
|
int hashcode = GenerateStaticHashCode(result.Title);
|
||||||
|
return GenerateStaticHashCode(result.SubTitle, hashcode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return GenerateStaticHashCode(result.RecordKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GenerateQueryAndResultHashCode(Query query, Result result)
|
private static int GenerateQueryAndResultHashCode(Query query, Result result)
|
||||||
{
|
{
|
||||||
|
// query is null when user select the context menu item directly of one item from query list
|
||||||
|
// so we only need to consider the result
|
||||||
if (query == null)
|
if (query == null)
|
||||||
{
|
{
|
||||||
return GenerateResultHashCode(result);
|
return GenerateResultHashCode(result);
|
||||||
|
|
@ -58,8 +66,16 @@ namespace Flow.Launcher.Storage
|
||||||
|
|
||||||
int hashcode = GenerateStaticHashCode(query.ActionKeyword);
|
int hashcode = GenerateStaticHashCode(query.ActionKeyword);
|
||||||
hashcode = GenerateStaticHashCode(query.Search, hashcode);
|
hashcode = GenerateStaticHashCode(query.Search, hashcode);
|
||||||
hashcode = GenerateStaticHashCode(result.Title, hashcode);
|
|
||||||
hashcode = GenerateStaticHashCode(result.SubTitle, hashcode);
|
if (string.IsNullOrEmpty(result.RecordKey))
|
||||||
|
{
|
||||||
|
hashcode = GenerateStaticHashCode(result.Title, hashcode);
|
||||||
|
hashcode = GenerateStaticHashCode(result.SubTitle, hashcode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hashcode = GenerateStaticHashCode(result.RecordKey, hashcode);
|
||||||
|
}
|
||||||
|
|
||||||
return hashcode;
|
return hashcode;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ using System.Windows.Input;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using Flow.Launcher.Infrastructure.Image;
|
using Flow.Launcher.Infrastructure.Image;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
|
||||||
namespace Flow.Launcher.ViewModel
|
namespace Flow.Launcher.ViewModel
|
||||||
{
|
{
|
||||||
|
|
@ -34,8 +35,6 @@ namespace Flow.Launcher.ViewModel
|
||||||
|
|
||||||
private bool _isQueryRunning;
|
private bool _isQueryRunning;
|
||||||
private Query _lastQuery;
|
private Query _lastQuery;
|
||||||
private Result lastContextMenuResult = new Result();
|
|
||||||
private List<Result> lastContextMenuResults = new List<Result>();
|
|
||||||
private string _queryTextBeforeLeaveResults;
|
private string _queryTextBeforeLeaveResults;
|
||||||
|
|
||||||
private readonly FlowLauncherJsonStorage<History> _historyItemsStorage;
|
private readonly FlowLauncherJsonStorage<History> _historyItemsStorage;
|
||||||
|
|
@ -58,13 +57,13 @@ namespace Flow.Launcher.ViewModel
|
||||||
|
|
||||||
#region Constructor
|
#region Constructor
|
||||||
|
|
||||||
public MainViewModel(Settings settings)
|
public MainViewModel()
|
||||||
{
|
{
|
||||||
_queryTextBeforeLeaveResults = "";
|
_queryTextBeforeLeaveResults = "";
|
||||||
_queryText = "";
|
_queryText = "";
|
||||||
_lastQuery = new Query();
|
_lastQuery = new Query();
|
||||||
|
|
||||||
Settings = settings;
|
Settings = Ioc.Default.GetRequiredService<Settings>();
|
||||||
Settings.PropertyChanged += (_, args) =>
|
Settings.PropertyChanged += (_, args) =>
|
||||||
{
|
{
|
||||||
switch (args.PropertyName)
|
switch (args.PropertyName)
|
||||||
|
|
@ -233,8 +232,8 @@ namespace Flow.Launcher.ViewModel
|
||||||
|
|
||||||
var token = e.Token == default ? _updateToken : e.Token;
|
var token = e.Token == default ? _updateToken : e.Token;
|
||||||
|
|
||||||
// make a copy of results to avoid plugin change the result when updating view model
|
// make a clone to avoid possible issue that plugin will also change the list and items when updating view model
|
||||||
var resultsCopy = e.Results.ToList();
|
var resultsCopy = DeepCloneResults(e.Results, token);
|
||||||
|
|
||||||
PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query);
|
PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query);
|
||||||
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
|
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
|
||||||
|
|
@ -281,10 +280,8 @@ namespace Flow.Launcher.ViewModel
|
||||||
|
|
||||||
public void ReQuery(bool reselect)
|
public void ReQuery(bool reselect)
|
||||||
{
|
{
|
||||||
if (SelectedIsFromQueryResults())
|
BackToQueryResults();
|
||||||
{
|
QueryResults(isReQuery: true, reSelect: reselect);
|
||||||
QueryResults(isReQuery: true, reSelect: reselect);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
@ -398,11 +395,15 @@ namespace Flow.Launcher.ViewModel
|
||||||
})
|
})
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
||||||
if (SelectedIsFromQueryResults())
|
if (SelectedIsFromQueryResults())
|
||||||
{
|
{
|
||||||
_userSelectedRecord.Add(result);
|
_userSelectedRecord.Add(result);
|
||||||
_history.Add(result.OriginQuery.RawQuery);
|
// origin query is null when user select the context menu item directly of one item from query list
|
||||||
|
// so we don't want to add it to history
|
||||||
|
if (result.OriginQuery != null)
|
||||||
|
{
|
||||||
|
_history.Add(result.OriginQuery.RawQuery);
|
||||||
|
}
|
||||||
lastHistoryIndex = 1;
|
lastHistoryIndex = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -412,6 +413,22 @@ namespace Flow.Launcher.ViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<Result> DeepCloneResults(IReadOnlyList<Result> results, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var resultsCopy = new List<Result>();
|
||||||
|
foreach (var result in results.ToList())
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultCopy = result.Clone();
|
||||||
|
resultsCopy.Add(resultCopy);
|
||||||
|
}
|
||||||
|
return resultsCopy;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region BasicCommands
|
#region BasicCommands
|
||||||
|
|
@ -425,7 +442,7 @@ namespace Flow.Launcher.ViewModel
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void SelectHelp()
|
private void SelectHelp()
|
||||||
{
|
{
|
||||||
PluginManager.API.OpenUrl("https://www.flowlauncher.com/docs/#/usage-tips");
|
App.API.OpenUrl("https://www.flowlauncher.com/docs/#/usage-tips");
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
@ -608,6 +625,8 @@ namespace Flow.Launcher.ViewModel
|
||||||
{
|
{
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
|
BackToQueryResults();
|
||||||
|
|
||||||
if (QueryText != queryText)
|
if (QueryText != queryText)
|
||||||
{
|
{
|
||||||
// re-query is done in QueryText's setter method
|
// re-query is done in QueryText's setter method
|
||||||
|
|
@ -986,19 +1005,10 @@ namespace Flow.Launcher.ViewModel
|
||||||
if (selected != null) // SelectedItem returns null if selection is empty.
|
if (selected != null) // SelectedItem returns null if selection is empty.
|
||||||
{
|
{
|
||||||
List<Result> results;
|
List<Result> results;
|
||||||
if (selected == lastContextMenuResult)
|
|
||||||
{
|
|
||||||
results = lastContextMenuResults;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
results = PluginManager.GetContextMenusForPlugin(selected);
|
|
||||||
lastContextMenuResults = results;
|
|
||||||
lastContextMenuResult = selected;
|
|
||||||
results.Add(ContextMenuTopMost(selected));
|
|
||||||
results.Add(ContextMenuPluginInfo(selected.PluginID));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
results = PluginManager.GetContextMenusForPlugin(selected);
|
||||||
|
results.Add(ContextMenuTopMost(selected));
|
||||||
|
results.Add(ContextMenuPluginInfo(selected.PluginID));
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(query))
|
if (!string.IsNullOrEmpty(query))
|
||||||
{
|
{
|
||||||
|
|
@ -1187,9 +1197,18 @@ namespace Flow.Launcher.ViewModel
|
||||||
|
|
||||||
currentCancellationToken.ThrowIfCancellationRequested();
|
currentCancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
results ??= _emptyResult;
|
IReadOnlyList<Result> resultsCopy;
|
||||||
|
if (results == null)
|
||||||
|
{
|
||||||
|
resultsCopy = _emptyResult;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// make a copy of results to avoid possible issue that FL changes some properties of the records, like score, etc.
|
||||||
|
resultsCopy = DeepCloneResults(results);
|
||||||
|
}
|
||||||
|
|
||||||
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query,
|
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, plugin.Metadata, query,
|
||||||
currentCancellationToken, reSelect)))
|
currentCancellationToken, reSelect)))
|
||||||
{
|
{
|
||||||
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
|
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
|
||||||
|
|
@ -1273,6 +1292,7 @@ namespace Flow.Launcher.ViewModel
|
||||||
{
|
{
|
||||||
_topMostRecord.Remove(result);
|
_topMostRecord.Remove(result);
|
||||||
App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success"));
|
App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success"));
|
||||||
|
App.API.ReQuery();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1289,6 +1309,7 @@ namespace Flow.Launcher.ViewModel
|
||||||
{
|
{
|
||||||
_topMostRecord.AddOrUpdate(result);
|
_topMostRecord.AddOrUpdate(result);
|
||||||
App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success"));
|
App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success"));
|
||||||
|
App.API.ReQuery();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1377,8 +1398,6 @@ namespace Flow.Launcher.ViewModel
|
||||||
lastHistoryIndex = 1;
|
lastHistoryIndex = 1;
|
||||||
// Trick for no delay
|
// Trick for no delay
|
||||||
MainWindowOpacity = 0;
|
MainWindowOpacity = 0;
|
||||||
lastContextMenuResult = new Result();
|
|
||||||
lastContextMenuResults = new List<Result>();
|
|
||||||
|
|
||||||
if (ExternalPreviewVisible)
|
if (ExternalPreviewVisible)
|
||||||
CloseExternalPreview();
|
CloseExternalPreview();
|
||||||
|
|
@ -1476,12 +1495,31 @@ namespace Flow.Launcher.ViewModel
|
||||||
{
|
{
|
||||||
result.Score = Result.MaxScore;
|
result.Score = Result.MaxScore;
|
||||||
}
|
}
|
||||||
else if (result.Score != Result.MaxScore)
|
else
|
||||||
{
|
{
|
||||||
var priorityScore = metaResults.Metadata.Priority * 150;
|
var priorityScore = metaResults.Metadata.Priority * 150;
|
||||||
result.Score += result.AddSelectedCount ?
|
if (result.AddSelectedCount)
|
||||||
_userSelectedRecord.GetSelectedCount(result) + priorityScore :
|
{
|
||||||
priorityScore;
|
if ((long)result.Score + _userSelectedRecord.GetSelectedCount(result) + priorityScore > Result.MaxScore)
|
||||||
|
{
|
||||||
|
result.Score = Result.MaxScore;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Score += _userSelectedRecord.GetSelectedCount(result) + priorityScore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((long)result.Score + priorityScore > Result.MaxScore)
|
||||||
|
{
|
||||||
|
result.Score = Result.MaxScore;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Score += priorityScore;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,13 +90,13 @@ namespace Flow.Launcher.ViewModel
|
||||||
private Control _bottomPart2;
|
private Control _bottomPart2;
|
||||||
public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null;
|
public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null;
|
||||||
|
|
||||||
public bool HasSettingControl => PluginPair.Plugin is ISettingProvider;
|
public bool HasSettingControl => PluginPair.Plugin is ISettingProvider && (PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || jsonRPCPluginBase.NeedCreateSettingPanel());
|
||||||
public Control SettingControl
|
public Control SettingControl
|
||||||
=> IsExpanded
|
=> IsExpanded
|
||||||
? _settingControl
|
? _settingControl
|
||||||
??= PluginPair.Plugin is not ISettingProvider settingProvider
|
??= HasSettingControl
|
||||||
? null
|
? ((ISettingProvider)PluginPair.Plugin).CreateSettingPanel()
|
||||||
: settingProvider.CreateSettingPanel()
|
: null
|
||||||
: null;
|
: null;
|
||||||
private ImageSource _image = ImageLoader.MissingImage;
|
private ImageSource _image = ImageLoader.MissingImage;
|
||||||
|
|
||||||
|
|
@ -134,20 +134,20 @@ namespace Flow.Launcher.ViewModel
|
||||||
{
|
{
|
||||||
var directory = PluginPair.Metadata.PluginDirectory;
|
var directory = PluginPair.Metadata.PluginDirectory;
|
||||||
if (!string.IsNullOrEmpty(directory))
|
if (!string.IsNullOrEmpty(directory))
|
||||||
PluginManager.API.OpenDirectory(directory);
|
App.API.OpenDirectory(directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void OpenSourceCodeLink()
|
private void OpenSourceCodeLink()
|
||||||
{
|
{
|
||||||
PluginManager.API.OpenUrl(PluginPair.Metadata.Website);
|
App.API.OpenUrl(PluginPair.Metadata.Website);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void OpenDeletePluginWindow()
|
private void OpenDeletePluginWindow()
|
||||||
{
|
{
|
||||||
PluginManager.API.ChangeQuery($"{PluginManagerActionKeyword} uninstall {PluginPair.Metadata.Name}".Trim(), true);
|
App.API.ChangeQuery($"{PluginManagerActionKeyword} uninstall {PluginPair.Metadata.Name}".Trim(), true);
|
||||||
PluginManager.API.ShowMainWindow();
|
App.API.ShowMainWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
|
public static bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
|
||||||
|
|
@ -159,5 +159,4 @@ namespace Flow.Launcher.ViewModel
|
||||||
changeKeywordsWindow.ShowDialog();
|
changeKeywordsWindow.ShowDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ namespace Flow.Launcher.ViewModel
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// To avoid deadlock, this method should not called from main thread
|
/// To avoid deadlock, this method should not called from main thread
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, CancellationToken token, bool reselect = true)
|
public void AddResults(ICollection<ResultsForUpdate> resultsForUpdates, CancellationToken token, bool reselect = true)
|
||||||
{
|
{
|
||||||
var newResults = NewResults(resultsForUpdates);
|
var newResults = NewResults(resultsForUpdates);
|
||||||
|
|
||||||
|
|
@ -228,12 +228,12 @@ namespace Flow.Launcher.ViewModel
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ResultViewModel> NewResults(IEnumerable<ResultsForUpdate> resultsForUpdates)
|
private List<ResultViewModel> NewResults(ICollection<ResultsForUpdate> resultsForUpdates)
|
||||||
{
|
{
|
||||||
if (!resultsForUpdates.Any())
|
if (!resultsForUpdates.Any())
|
||||||
return Results;
|
return Results;
|
||||||
|
|
||||||
return Results.Where(r => r != null && !resultsForUpdates.Any(u => u.ID == r.Result.PluginID))
|
return Results.Where(r => r?.Result != null && resultsForUpdates.All(u => u.ID != r.Result.PluginID))
|
||||||
.Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
|
.Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
|
||||||
.OrderByDescending(rv => rv.Result.Score)
|
.OrderByDescending(rv => rv.Result.Score)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,46 @@
|
||||||
using Flow.Launcher.Core;
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
using Flow.Launcher.Core.Configuration;
|
|
||||||
using Flow.Launcher.Infrastructure.Storage;
|
|
||||||
using Flow.Launcher.Infrastructure.UserSettings;
|
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
namespace Flow.Launcher.ViewModel;
|
namespace Flow.Launcher.ViewModel;
|
||||||
|
|
||||||
public class SettingWindowViewModel : BaseModel
|
public partial class SettingWindowViewModel : BaseModel
|
||||||
{
|
{
|
||||||
private readonly FlowLauncherJsonStorage<Settings> _storage;
|
private readonly Settings _settings;
|
||||||
|
|
||||||
public Updater Updater { get; }
|
public SettingWindowViewModel(Settings settings)
|
||||||
|
|
||||||
public IPortable Portable { get; }
|
|
||||||
|
|
||||||
public Settings Settings { get; }
|
|
||||||
|
|
||||||
public SettingWindowViewModel(Updater updater, IPortable portable)
|
|
||||||
{
|
{
|
||||||
_storage = new FlowLauncherJsonStorage<Settings>();
|
_settings = settings;
|
||||||
|
|
||||||
Updater = updater;
|
|
||||||
Portable = portable;
|
|
||||||
Settings = _storage.Load();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void UpdateApp()
|
|
||||||
{
|
|
||||||
await Updater.UpdateAppAsync(App.API, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save Flow settings. Plugins settings are not included.
|
/// Save Flow settings. Plugins settings are not included.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
_storage.Save();
|
_settings.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double SettingWindowWidth
|
public double SettingWindowWidth
|
||||||
{
|
{
|
||||||
get => Settings.SettingWindowWidth;
|
get => _settings.SettingWindowWidth;
|
||||||
set => Settings.SettingWindowWidth = value;
|
set => _settings.SettingWindowWidth = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double SettingWindowHeight
|
public double SettingWindowHeight
|
||||||
{
|
{
|
||||||
get => Settings.SettingWindowHeight;
|
get => _settings.SettingWindowHeight;
|
||||||
set => Settings.SettingWindowHeight = value;
|
set => _settings.SettingWindowHeight = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double? SettingWindowTop
|
public double? SettingWindowTop
|
||||||
{
|
{
|
||||||
get => Settings.SettingWindowTop;
|
get => _settings.SettingWindowTop;
|
||||||
set => Settings.SettingWindowTop = value;
|
set => _settings.SettingWindowTop = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double? SettingWindowLeft
|
public double? SettingWindowLeft
|
||||||
{
|
{
|
||||||
get => Settings.SettingWindowLeft;
|
get => _settings.SettingWindowLeft;
|
||||||
set => Settings.SettingWindowLeft = value;
|
set => _settings.SettingWindowLeft = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,6 @@
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Image
|
<Image
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
|
|
@ -53,9 +51,8 @@
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Foreground="{DynamicResource Color05B}"
|
Foreground="{DynamicResource Color05B}"
|
||||||
Text="{DynamicResource Welcome_Page1_Title}" />
|
Text="{DynamicResource Welcome_Page1_Title}" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
Grid.Column="4"
|
Grid.Column="2"
|
||||||
Click="BtnCancel_OnClick"
|
Click="BtnCancel_OnClick"
|
||||||
Style="{StaticResource TitleBarCloseButtonStyle}">
|
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||||
<Path
|
<Path
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"Name": "Browser Bookmarks",
|
"Name": "Browser Bookmarks",
|
||||||
"Description": "Search your browser bookmarks",
|
"Description": "Search your browser bookmarks",
|
||||||
"Author": "qianlifeng, Ioannis G.",
|
"Author": "qianlifeng, Ioannis G.",
|
||||||
"Version": "3.3.3",
|
"Version": "3.3.4",
|
||||||
"Language": "csharp",
|
"Language": "csharp",
|
||||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||||
"ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll",
|
"ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"Name": "Calculator",
|
"Name": "Calculator",
|
||||||
"Description": "Provide mathematical calculations.(Try 5*3-2 in Flow Launcher)",
|
"Description": "Provide mathematical calculations.(Try 5*3-2 in Flow Launcher)",
|
||||||
"Author": "cxfksword",
|
"Author": "cxfksword",
|
||||||
"Version": "3.1.4",
|
"Version": "3.1.5",
|
||||||
"Language": "csharp",
|
"Language": "csharp",
|
||||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||||
"ExecuteFileName": "Flow.Launcher.Plugin.Calculator.dll",
|
"ExecuteFileName": "Flow.Launcher.Plugin.Calculator.dll",
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
var name = "Plugin: Folder";
|
var name = "Plugin: Folder";
|
||||||
var message = $"File not found: {e.Message}";
|
var message = $"File not found: {e.Message}";
|
||||||
Context.API.ShowMsgError(name, message);
|
Context.API.ShowMsgError(name, message);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<!-- Do not upgrade System.Data.OleDb since we are .Net7.0 -->
|
||||||
<PackageReference Include="System.Data.OleDb" Version="8.0.1" />
|
<PackageReference Include="System.Data.OleDb" Version="8.0.1" />
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||||
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0" />
|
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0" />
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
internal const string WindowsIndexErrorImagePath = "Images\\index_error2.png";
|
internal const string WindowsIndexErrorImagePath = "Images\\index_error2.png";
|
||||||
internal const string GeneralSearchErrorImagePath = "Images\\robot_error.png";
|
internal const string GeneralSearchErrorImagePath = "Images\\robot_error.png";
|
||||||
|
|
||||||
|
|
||||||
internal const string ToolTipOpenDirectory = "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 string ToolTipOpenContainingFolder = "Ctrl + Enter to open the containing folder";
|
||||||
|
|
@ -31,6 +30,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
|
|
||||||
internal const string DefaultContentSearchActionKeyword = "doc:";
|
internal const string DefaultContentSearchActionKeyword = "doc:";
|
||||||
|
|
||||||
|
internal const char UnixDirectorySeparator = '/';
|
||||||
|
|
||||||
internal const char DirectorySeparator = '\\';
|
internal const char DirectorySeparator = '\\';
|
||||||
|
|
||||||
internal const string WindowsIndexingOptions = "srchadmin.dll";
|
internal const string WindowsIndexingOptions = "srchadmin.dll";
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
var needToExpand = EnvironmentVariables.HasEnvironmentVar(querySearch);
|
var needToExpand = EnvironmentVariables.HasEnvironmentVar(querySearch);
|
||||||
var path = needToExpand ? Environment.ExpandEnvironmentVariables(querySearch) : querySearch;
|
var path = needToExpand ? Environment.ExpandEnvironmentVariables(querySearch) : querySearch;
|
||||||
|
|
||||||
|
// if user uses the unix directory separator, we need to convert it to windows directory separator
|
||||||
|
path = path.Replace(Constants.UnixDirectorySeparator, Constants.DirectorySeparator);
|
||||||
|
|
||||||
// Check that actual location exists, otherwise directory search will throw directory not found exception
|
// Check that actual location exists, otherwise directory search will throw directory not found exception
|
||||||
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path).LocationExists())
|
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path).LocationExists())
|
||||||
return results.ToList();
|
return results.ToList();
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,13 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
||||||
}
|
}
|
||||||
|
|
||||||
private string actionKeyword;
|
private string actionKeyword;
|
||||||
private readonly IPublicAPI api;
|
private readonly IPublicAPI _api;
|
||||||
private bool _keywordEnabled;
|
private bool _keywordEnabled;
|
||||||
|
|
||||||
public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api)
|
public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api)
|
||||||
{
|
{
|
||||||
CurrentActionKeyword = selectedActionKeyword;
|
CurrentActionKeyword = selectedActionKeyword;
|
||||||
this.api = api;
|
_api = api;
|
||||||
ActionKeyword = selectedActionKeyword.Keyword;
|
ActionKeyword = selectedActionKeyword.Keyword;
|
||||||
KeywordEnabled = selectedActionKeyword.Enabled;
|
KeywordEnabled = selectedActionKeyword.Enabled;
|
||||||
|
|
||||||
|
|
@ -62,14 +62,14 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
||||||
switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled)
|
switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled)
|
||||||
{
|
{
|
||||||
case (Settings.ActionKeyword.FileContentSearchActionKeyword, true):
|
case (Settings.ActionKeyword.FileContentSearchActionKeyword, true):
|
||||||
api.ShowMsgBox(api.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
|
_api.ShowMsgBox(_api.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
|
||||||
return;
|
return;
|
||||||
case (Settings.ActionKeyword.QuickAccessActionKeyword, true):
|
case (Settings.ActionKeyword.QuickAccessActionKeyword, true):
|
||||||
api.ShowMsgBox(api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid"));
|
_api.ShowMsgBox(_api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!KeywordEnabled || !api.ActionKeywordAssigned(ActionKeyword))
|
if (!KeywordEnabled || !_api.ActionKeywordAssigned(ActionKeyword))
|
||||||
{
|
{
|
||||||
DialogResult = true;
|
DialogResult = true;
|
||||||
Close();
|
Close();
|
||||||
|
|
@ -77,7 +77,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
||||||
}
|
}
|
||||||
|
|
||||||
// The keyword is not valid, so show message
|
// The keyword is not valid, so show message
|
||||||
api.ShowMsgBox(api.GetTranslation("newActionKeywordsHasBeenAssigned"));
|
_api.ShowMsgBox(_api.GetTranslation("newActionKeywordsHasBeenAssigned"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue