mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'Flow-Launcher:dev' into Fix-'100%-CPU'-BrowserBookmark-issue
This commit is contained in:
commit
d57eca279f
94 changed files with 5241 additions and 460 deletions
3
.github/actions/spelling/allow.txt
vendored
3
.github/actions/spelling/allow.txt
vendored
|
|
@ -4,5 +4,4 @@ ssh
|
|||
ubuntu
|
||||
runcount
|
||||
Firefox
|
||||
Português
|
||||
Português (Brasil)
|
||||
workaround
|
||||
|
|
|
|||
1
.github/actions/spelling/expect.txt
vendored
1
.github/actions/spelling/expect.txt
vendored
|
|
@ -103,3 +103,4 @@ Reloadable
|
|||
metadatas
|
||||
WMP
|
||||
VSTHRD
|
||||
CJK
|
||||
|
|
|
|||
9
.github/actions/spelling/patterns.txt
vendored
9
.github/actions/spelling/patterns.txt
vendored
|
|
@ -134,3 +134,12 @@
|
|||
\bčeština\b
|
||||
\bPortuguês\b
|
||||
\bIoc\b
|
||||
\bXiao\s*He\b
|
||||
\bZi\s*Ran\s*Ma\b
|
||||
\bWei\s*Ruan\b
|
||||
\bZhi\s*Neng\s*ABC\b
|
||||
\bZi\s*Guang\s*Pin\s*Yin\b
|
||||
\bPin\s*Yin\s*Jia\s*Jia\b
|
||||
\bXing\s*Kong\s*Jian\s*Dao\b
|
||||
\bDa\s*Niu\b
|
||||
\bXiao\s*Lang\b
|
||||
|
|
|
|||
26
.github/workflows/default_plugins.yml
vendored
26
.github/workflows/default_plugins.yml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Update Plugins To Production Version
|
||||
run: |
|
||||
|
|
@ -42,7 +42,7 @@ jobs:
|
|||
|
||||
- name: Build BrowserBookmark
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.BrowserBookmark"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.BrowserBookmark"
|
||||
7z a -tzip "Flow.Launcher.Plugin.BrowserBookmark.zip" "./Flow.Launcher.Plugin.BrowserBookmark/*"
|
||||
rm -r "Flow.Launcher.Plugin.BrowserBookmark"
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ jobs:
|
|||
|
||||
- name: Build Calculator
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.Calculator"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.Calculator"
|
||||
7z a -tzip "Flow.Launcher.Plugin.Calculator.zip" "./Flow.Launcher.Plugin.Calculator/*"
|
||||
rm -r "Flow.Launcher.Plugin.Calculator"
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ jobs:
|
|||
|
||||
- name: Build Explorer
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.Explorer"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.Explorer"
|
||||
7z a -tzip "Flow.Launcher.Plugin.Explorer.zip" "./Flow.Launcher.Plugin.Explorer/*"
|
||||
rm -r "Flow.Launcher.Plugin.Explorer"
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ jobs:
|
|||
|
||||
- name: Build PluginIndicator
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.PluginIndicator"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.PluginIndicator"
|
||||
7z a -tzip "Flow.Launcher.Plugin.PluginIndicator.zip" "./Flow.Launcher.Plugin.PluginIndicator/*"
|
||||
rm -r "Flow.Launcher.Plugin.PluginIndicator"
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ jobs:
|
|||
|
||||
- name: Build PluginsManager
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.PluginsManager"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.PluginsManager"
|
||||
7z a -tzip "Flow.Launcher.Plugin.PluginsManager.zip" "./Flow.Launcher.Plugin.PluginsManager/*"
|
||||
rm -r "Flow.Launcher.Plugin.PluginsManager"
|
||||
|
||||
|
|
@ -162,7 +162,7 @@ jobs:
|
|||
|
||||
- name: Build ProcessKiller
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.ProcessKiller"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.ProcessKiller"
|
||||
7z a -tzip "Flow.Launcher.Plugin.ProcessKiller.zip" "./Flow.Launcher.Plugin.ProcessKiller/*"
|
||||
rm -r "Flow.Launcher.Plugin.ProcessKiller"
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ jobs:
|
|||
|
||||
- name: Build Program
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj' --framework net7.0-windows10.0.19041.0 -c Release -o "Flow.Launcher.Plugin.Program"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj' --framework net9.0-windows10.0.19041.0 -c Release -o "Flow.Launcher.Plugin.Program"
|
||||
7z a -tzip "Flow.Launcher.Plugin.Program.zip" "./Flow.Launcher.Plugin.Program/*"
|
||||
rm -r "Flow.Launcher.Plugin.Program"
|
||||
|
||||
|
|
@ -210,7 +210,7 @@ jobs:
|
|||
|
||||
- name: Build Shell
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.Shell"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.Shell"
|
||||
7z a -tzip "Flow.Launcher.Plugin.Shell.zip" "./Flow.Launcher.Plugin.Shell/*"
|
||||
rm -r "Flow.Launcher.Plugin.Shell"
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ jobs:
|
|||
|
||||
- name: Build Sys
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.Sys"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.Sys"
|
||||
7z a -tzip "Flow.Launcher.Plugin.Sys.zip" "./Flow.Launcher.Plugin.Sys/*"
|
||||
rm -r "Flow.Launcher.Plugin.Sys"
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ jobs:
|
|||
|
||||
- name: Build Url
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.Url"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.Url"
|
||||
7z a -tzip "Flow.Launcher.Plugin.Url.zip" "./Flow.Launcher.Plugin.Url/*"
|
||||
rm -r "Flow.Launcher.Plugin.Url"
|
||||
|
||||
|
|
@ -282,7 +282,7 @@ jobs:
|
|||
|
||||
- name: Build WebSearch
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.WebSearch"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.WebSearch"
|
||||
7z a -tzip "Flow.Launcher.Plugin.WebSearch.zip" "./Flow.Launcher.Plugin.WebSearch/*"
|
||||
rm -r "Flow.Launcher.Plugin.WebSearch"
|
||||
|
||||
|
|
@ -306,7 +306,7 @@ jobs:
|
|||
|
||||
- name: Build WindowsSettings
|
||||
run: |
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.WindowsSettings"
|
||||
dotnet publish 'Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj' --framework net9.0-windows -c Release -o "Flow.Launcher.Plugin.WindowsSettings"
|
||||
7z a -tzip "Flow.Launcher.Plugin.WindowsSettings.zip" "./Flow.Launcher.Plugin.WindowsSettings/*"
|
||||
rm -r "Flow.Launcher.Plugin.WindowsSettings"
|
||||
|
||||
|
|
|
|||
4
.github/workflows/dotnet.yml
vendored
4
.github/workflows/dotnet.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
FlowVersion: 1.19.5
|
||||
FlowVersion: 1.20.2
|
||||
NUGET_CERT_REVOCATION_MODE: offline
|
||||
BUILD_NUMBER: ${{ github.run_number }}
|
||||
steps:
|
||||
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 9.0.x
|
||||
# cache: true
|
||||
# cache-dependency-path: |
|
||||
# Flow.Launcher/packages.lock.json
|
||||
|
|
|
|||
4
.github/workflows/spelling.yml
vendored
4
.github/workflows/spelling.yml
vendored
|
|
@ -72,7 +72,7 @@ jobs:
|
|||
steps:
|
||||
- name: check-spelling
|
||||
id: spelling
|
||||
uses: check-spelling/check-spelling@prerelease
|
||||
uses: check-spelling/check-spelling@v0.0.25
|
||||
with:
|
||||
suppress_push_for_open_pull_request: 1
|
||||
checkout: true
|
||||
|
|
@ -128,7 +128,7 @@ jobs:
|
|||
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
|
||||
steps:
|
||||
- name: comment
|
||||
uses: check-spelling/check-spelling@prerelease
|
||||
uses: check-spelling/check-spelling@v0.0.25
|
||||
with:
|
||||
checkout: true
|
||||
spell_check_this: check-spelling/spell-check-this@main
|
||||
|
|
|
|||
|
|
@ -45,8 +45,7 @@ namespace Flow.Launcher.Core.Configuration
|
|||
#endif
|
||||
IndicateDeletion(DataLocation.PortableDataPath);
|
||||
|
||||
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");
|
||||
API.ShowMsgBox(API.GetTranslation("restartToDisablePortableMode"));
|
||||
|
||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||
}
|
||||
|
|
@ -69,8 +68,7 @@ namespace Flow.Launcher.Core.Configuration
|
|||
#endif
|
||||
IndicateDeletion(DataLocation.RoamingDataPath);
|
||||
|
||||
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");
|
||||
API.ShowMsgBox(API.GetTranslation("restartToEnablePortableMode"));
|
||||
|
||||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||
}
|
||||
|
|
@ -154,9 +152,8 @@ namespace Flow.Launcher.Core.Configuration
|
|||
{
|
||||
FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => API.ShowMsgBox(s));
|
||||
|
||||
if (API.ShowMsgBox("Flow Launcher has detected you enabled portable mode, " +
|
||||
"would you like to move it to a different location?", string.Empty,
|
||||
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
if (API.ShowMsgBox(API.GetTranslation("moveToDifferentLocation"),
|
||||
string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s));
|
||||
|
||||
|
|
@ -169,8 +166,7 @@ namespace Flow.Launcher.Core.Configuration
|
|||
{
|
||||
FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => API.ShowMsgBox(s));
|
||||
|
||||
API.ShowMsgBox("Flow Launcher has detected you disabled portable mode, " +
|
||||
"the relevant shortcuts and uninstaller entry have been created");
|
||||
API.ShowMsgBox(API.GetTranslation("shortcutsUninstallerCreated"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,9 +177,8 @@ namespace Flow.Launcher.Core.Configuration
|
|||
|
||||
if (roamingLocationExists && portableLocationExists)
|
||||
{
|
||||
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.",
|
||||
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
|
||||
API.ShowMsgBox(string.Format(API.GetTranslation("userDataDuplicated"),
|
||||
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
|||
private static DateTime lastFetchedAt = DateTime.MinValue;
|
||||
private static readonly TimeSpan fetchTimeout = TimeSpan.FromMinutes(2);
|
||||
|
||||
// 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>();
|
||||
|
||||
public static List<UserPlugin> UserPlugins { get; private set; }
|
||||
|
||||
public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default)
|
||||
|
|
@ -46,7 +50,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IPublicAPI>().LogException(ClassName, "Http request failed", e);
|
||||
API.LogException(ClassName, "Http request failed", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<UseWpf>true</UseWpf>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<OutputType>Library</OutputType>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
|
|
@ -57,6 +58,7 @@
|
|||
<PackageReference Include="FSharp.Core" Version="9.0.201" />
|
||||
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.0" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageReference Include="SemanticVersioning" Version="3.0.0" />
|
||||
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
|
||||
<PackageReference Include="StreamJsonRpc" Version="2.21.10" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
|
|
@ -277,6 +278,122 @@ public static class PluginInstaller
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the plugin to the latest version available from its source.
|
||||
/// </summary>
|
||||
/// <param name="updateAllPlugins">Action to execute when the user chooses to update all plugins.</param>
|
||||
/// <param name="silentUpdate">If true, do not show any messages when there is no update available.</param>
|
||||
/// <param name="usePrimaryUrlOnly">If true, only use the primary URL for updates.</param>
|
||||
/// <param name="token">Cancellation token to cancel the update operation.</param>
|
||||
/// <returns></returns>
|
||||
public static async Task CheckForPluginUpdatesAsync(Action<List<PluginUpdateInfo>> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default)
|
||||
{
|
||||
// Update the plugin manifest
|
||||
await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
|
||||
|
||||
// Get all plugins that can be updated
|
||||
var resultsForUpdate = (
|
||||
from existingPlugin in API.GetAllPlugins()
|
||||
join pluginUpdateSource in API.GetPluginManifest()
|
||||
on existingPlugin.Metadata.ID equals pluginUpdateSource.ID
|
||||
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
|
||||
StringComparison.InvariantCulture) <
|
||||
0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
|
||||
&& !API.PluginModified(existingPlugin.Metadata.ID)
|
||||
select
|
||||
new PluginUpdateInfo()
|
||||
{
|
||||
ID = existingPlugin.Metadata.ID,
|
||||
Name = existingPlugin.Metadata.Name,
|
||||
Author = existingPlugin.Metadata.Author,
|
||||
CurrentVersion = existingPlugin.Metadata.Version,
|
||||
NewVersion = pluginUpdateSource.Version,
|
||||
IcoPath = existingPlugin.Metadata.IcoPath,
|
||||
PluginExistingMetadata = existingPlugin.Metadata,
|
||||
PluginNewUserPlugin = pluginUpdateSource
|
||||
}).ToList();
|
||||
|
||||
// No updates
|
||||
if (!resultsForUpdate.Any())
|
||||
{
|
||||
if (!silentUpdate)
|
||||
{
|
||||
API.ShowMsg(API.GetTranslation("updateNoResultTitle"), API.GetTranslation("updateNoResultSubtitle"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If all plugins are modified, just return
|
||||
if (resultsForUpdate.All(x => API.PluginModified(x.ID)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Show message box with button to update all plugins
|
||||
API.ShowMsgWithButton(
|
||||
API.GetTranslation("updateAllPluginsTitle"),
|
||||
API.GetTranslation("updateAllPluginsButtonContent"),
|
||||
() =>
|
||||
{
|
||||
updateAllPlugins(resultsForUpdate);
|
||||
},
|
||||
string.Join(", ", resultsForUpdate.Select(x => x.PluginExistingMetadata.Name)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates all plugins that have available updates.
|
||||
/// </summary>
|
||||
/// <param name="resultsForUpdate"></param>
|
||||
/// <param name="restart"></param>
|
||||
public static async Task UpdateAllPluginsAsync(IEnumerable<PluginUpdateInfo> resultsForUpdate, bool restart)
|
||||
{
|
||||
var anyPluginSuccess = false;
|
||||
await Task.WhenAll(resultsForUpdate.Select(async plugin =>
|
||||
{
|
||||
var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
|
||||
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
await DownloadFileAsync(
|
||||
$"{API.GetTranslation("DownloadingPlugin")} {plugin.PluginNewUserPlugin.Name}",
|
||||
plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts);
|
||||
|
||||
// check if user cancelled download before installing plugin
|
||||
if (cts.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await API.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
anyPluginSuccess = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
API.LogException(ClassName, "Failed to update plugin", e);
|
||||
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin"));
|
||||
}
|
||||
}));
|
||||
|
||||
if (!anyPluginSuccess) return;
|
||||
|
||||
if (restart)
|
||||
{
|
||||
API.RestartApp();
|
||||
}
|
||||
else
|
||||
{
|
||||
API.ShowMsg(
|
||||
API.GetTranslation("updatebtn"),
|
||||
API.GetTranslation("PluginsUpdateSuccessNoRestart"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a file from a URL to a local path, optionally showing a progress box and handling cancellation.
|
||||
/// </summary>
|
||||
|
|
@ -351,3 +468,15 @@ public static class PluginInstaller
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
public record PluginUpdateInfo
|
||||
{
|
||||
public string ID { get; init; }
|
||||
public string Name { get; init; }
|
||||
public string Author { get; init; }
|
||||
public string CurrentVersion { get; init; }
|
||||
public string NewVersion { get; init; }
|
||||
public string IcoPath { get; init; }
|
||||
public PluginMetadata PluginExistingMetadata { get; init; }
|
||||
public UserPlugin PluginNewUserPlugin { get; init; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Core.ExternalPlugins;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.DialogJump;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
|
@ -40,10 +41,13 @@ namespace Flow.Launcher.Core.Plugin
|
|||
private static IEnumerable<PluginPair> _resultUpdatePlugin;
|
||||
private static IEnumerable<PluginPair> _translationPlugins;
|
||||
|
||||
private static readonly List<DialogJumpExplorerPair> _dialogJumpExplorerPlugins = new();
|
||||
private static readonly List<DialogJumpDialogPair> _dialogJumpDialogPlugins = new();
|
||||
|
||||
/// <summary>
|
||||
/// Directories that will hold Flow Launcher plugin directory
|
||||
/// </summary>
|
||||
private static readonly string[] Directories =
|
||||
public static readonly string[] Directories =
|
||||
{
|
||||
Constant.PreinstalledDirectory, DataLocation.PluginsDirectory
|
||||
};
|
||||
|
|
@ -186,6 +190,24 @@ namespace Flow.Launcher.Core.Plugin
|
|||
_homePlugins = GetPluginsForInterface<IAsyncHomeQuery>();
|
||||
_resultUpdatePlugin = GetPluginsForInterface<IResultUpdated>();
|
||||
_translationPlugins = GetPluginsForInterface<IPluginI18n>();
|
||||
|
||||
// Initialize Dialog Jump plugin pairs
|
||||
foreach (var pair in GetPluginsForInterface<IDialogJumpExplorer>())
|
||||
{
|
||||
_dialogJumpExplorerPlugins.Add(new DialogJumpExplorerPair
|
||||
{
|
||||
Plugin = (IDialogJumpExplorer)pair.Plugin,
|
||||
Metadata = pair.Metadata
|
||||
});
|
||||
}
|
||||
foreach (var pair in GetPluginsForInterface<IDialogJumpDialog>())
|
||||
{
|
||||
_dialogJumpDialogPlugins.Add(new DialogJumpDialogPair
|
||||
{
|
||||
Plugin = (IDialogJumpDialog)pair.Plugin,
|
||||
Metadata = pair.Metadata
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdatePluginDirectory(List<PluginMetadata> metadatas)
|
||||
|
|
@ -288,20 +310,24 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
}
|
||||
|
||||
public static ICollection<PluginPair> ValidPluginsForQuery(Query query)
|
||||
public static ICollection<PluginPair> ValidPluginsForQuery(Query query, bool dialogJump)
|
||||
{
|
||||
if (query is null)
|
||||
return Array.Empty<PluginPair>();
|
||||
|
||||
if (!NonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin))
|
||||
{
|
||||
return GlobalPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList();
|
||||
if (dialogJump)
|
||||
return GlobalPlugins.Where(p => p.Plugin is IAsyncDialogJump && !PluginModified(p.Metadata.ID)).ToList();
|
||||
else
|
||||
return GlobalPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList();
|
||||
}
|
||||
|
||||
if (API.PluginModified(plugin.Metadata.ID))
|
||||
{
|
||||
if (dialogJump && plugin.Plugin is not IAsyncDialogJump)
|
||||
return Array.Empty<PluginPair>();
|
||||
|
||||
if (API.PluginModified(plugin.Metadata.ID))
|
||||
return Array.Empty<PluginPair>();
|
||||
}
|
||||
|
||||
return new List<PluginPair>
|
||||
{
|
||||
|
|
@ -388,6 +414,36 @@ namespace Flow.Launcher.Core.Plugin
|
|||
return results;
|
||||
}
|
||||
|
||||
public static async Task<List<DialogJumpResult>> QueryDialogJumpForPluginAsync(PluginPair pair, Query query, CancellationToken token)
|
||||
{
|
||||
var results = new List<DialogJumpResult>();
|
||||
var metadata = pair.Metadata;
|
||||
|
||||
try
|
||||
{
|
||||
var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}",
|
||||
async () => results = await ((IAsyncDialogJump)pair.Plugin).QueryDialogJumpAsync(query, token).ConfigureAwait(false));
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (results == null)
|
||||
return null;
|
||||
UpdatePluginMetadata(results, metadata, query);
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// null will be fine since the results will only be added into queue if the token hasn't been cancelled
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to query Dialog Jump for plugin: {metadata.Name}", e);
|
||||
return null;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public static void UpdatePluginMetadata(IReadOnlyList<Result> results, PluginMetadata metadata, Query query)
|
||||
{
|
||||
foreach (var r in results)
|
||||
|
|
@ -463,6 +519,16 @@ namespace Flow.Launcher.Core.Plugin
|
|||
return _homePlugins.Where(p => !PluginModified(p.Metadata.ID)).Any(p => p.Metadata.ID == id);
|
||||
}
|
||||
|
||||
public static IList<DialogJumpExplorerPair> GetDialogJumpExplorers()
|
||||
{
|
||||
return _dialogJumpExplorerPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList();
|
||||
}
|
||||
|
||||
public static IList<DialogJumpDialogPair> GetDialogJumpDialogs()
|
||||
{
|
||||
return _dialogJumpDialogPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList();
|
||||
}
|
||||
|
||||
public static bool ActionKeywordRegistered(string actionKeyword)
|
||||
{
|
||||
// this method is only checking for action keywords (defined as not '*') registration
|
||||
|
|
@ -719,7 +785,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
catch (Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e);
|
||||
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
|
||||
API.ShowMsgError(API.GetTranslation("failedToRemovePluginSettingsTitle"),
|
||||
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
|
||||
}
|
||||
}
|
||||
|
|
@ -735,7 +801,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
catch (Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e);
|
||||
API.ShowMsg(API.GetTranslation("failedToRemovePluginCacheTitle"),
|
||||
API.ShowMsgError(API.GetTranslation("failedToRemovePluginCacheTitle"),
|
||||
string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name));
|
||||
}
|
||||
Settings.RemovePluginSettings(plugin.ID);
|
||||
|
|
|
|||
|
|
@ -120,15 +120,15 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
var errorPluginString = string.Join(Environment.NewLine, erroredPlugins);
|
||||
|
||||
var errorMessage = "The following "
|
||||
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
|
||||
+ "errored and cannot be loaded:";
|
||||
var errorMessage = erroredPlugins.Count > 1 ?
|
||||
API.GetTranslation("pluginsHaveErrored") :
|
||||
API.GetTranslation("pluginHasErrored");
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsgBox($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
|
||||
API.ShowMsgBox($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
|
||||
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
|
||||
$"Please refer to the logs for more information", "",
|
||||
API.GetTranslation("referToLogs"), string.Empty,
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
|
@ -28,22 +27,20 @@ namespace Flow.Launcher.Core.Resource
|
|||
private const string DefaultFile = "en.xaml";
|
||||
private const string Extension = ".xaml";
|
||||
private readonly Settings _settings;
|
||||
private readonly List<string> _languageDirectories = new();
|
||||
private readonly List<ResourceDictionary> _oldResources = new();
|
||||
private readonly List<string> _languageDirectories = [];
|
||||
private readonly List<ResourceDictionary> _oldResources = [];
|
||||
private static string SystemLanguageCode;
|
||||
|
||||
public Internationalization(Settings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
AddFlowLauncherLanguageDirectory();
|
||||
}
|
||||
|
||||
private void AddFlowLauncherLanguageDirectory()
|
||||
{
|
||||
var directory = Path.Combine(Constant.ProgramDirectory, Folder);
|
||||
_languageDirectories.Add(directory);
|
||||
}
|
||||
#region Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the system language code based on the current culture.
|
||||
/// </summary>
|
||||
public static void InitSystemLanguageCode()
|
||||
{
|
||||
var availableLanguages = AvailableLanguages.GetAvailableLanguages();
|
||||
|
|
@ -72,35 +69,6 @@ namespace Flow.Launcher.Core.Resource
|
|||
SystemLanguageCode = DefaultLanguageCode;
|
||||
}
|
||||
|
||||
private void AddPluginLanguageDirectories()
|
||||
{
|
||||
foreach (var plugin in PluginManager.GetTranslationPlugins())
|
||||
{
|
||||
var location = Assembly.GetAssembly(plugin.Plugin.GetType()).Location;
|
||||
var dir = Path.GetDirectoryName(location);
|
||||
if (dir != null)
|
||||
{
|
||||
var pluginThemeDirectory = Path.Combine(dir, Folder);
|
||||
_languageDirectories.Add(pluginThemeDirectory);
|
||||
}
|
||||
else
|
||||
{
|
||||
API.LogError(ClassName, $"Can't find plugin path <{location}> for <{plugin.Metadata.Name}>");
|
||||
}
|
||||
}
|
||||
|
||||
LoadDefaultLanguage();
|
||||
}
|
||||
|
||||
private void LoadDefaultLanguage()
|
||||
{
|
||||
// Removes language files loaded before any plugins were loaded.
|
||||
// Prevents the language Flow started in from overwriting English if the user switches back to English
|
||||
RemoveOldLanguageFiles();
|
||||
LoadLanguage(AvailableLanguages.English);
|
||||
_oldResources.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize language. Will change app language and plugin language based on settings.
|
||||
/// </summary>
|
||||
|
|
@ -116,13 +84,64 @@ namespace Flow.Launcher.Core.Resource
|
|||
// Get language by language code and change language
|
||||
var language = GetLanguageByLanguageCode(languageCode);
|
||||
|
||||
// Add Flow Launcher language directory
|
||||
AddFlowLauncherLanguageDirectory();
|
||||
|
||||
// Add plugin language directories first so that we can load language files from plugins
|
||||
AddPluginLanguageDirectories();
|
||||
|
||||
// Load default language resources
|
||||
LoadDefaultLanguage();
|
||||
|
||||
// Change language
|
||||
await ChangeLanguageAsync(language);
|
||||
await ChangeLanguageAsync(language, false);
|
||||
}
|
||||
|
||||
private void AddFlowLauncherLanguageDirectory()
|
||||
{
|
||||
// Check if Flow Launcher language directory exists
|
||||
var directory = Path.Combine(Constant.ProgramDirectory, Folder);
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
API.LogError(ClassName, $"Flow Launcher language directory can't be found <{directory}>");
|
||||
return;
|
||||
}
|
||||
|
||||
_languageDirectories.Add(directory);
|
||||
}
|
||||
|
||||
private void AddPluginLanguageDirectories()
|
||||
{
|
||||
foreach (var pluginsDir in PluginManager.Directories)
|
||||
{
|
||||
if (!Directory.Exists(pluginsDir)) continue;
|
||||
|
||||
// Enumerate all top directories in the plugin directory
|
||||
foreach (var dir in Directory.GetDirectories(pluginsDir))
|
||||
{
|
||||
// Check if the directory contains a language folder
|
||||
var pluginLanguageDir = Path.Combine(dir, Folder);
|
||||
if (!Directory.Exists(pluginLanguageDir)) continue;
|
||||
|
||||
// Check if the language directory contains default language file since it will be checked later
|
||||
_languageDirectories.Add(pluginLanguageDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDefaultLanguage()
|
||||
{
|
||||
// Removes language files loaded before any plugins were loaded.
|
||||
// Prevents the language Flow started in from overwriting English if the user switches back to English
|
||||
RemoveOldLanguageFiles();
|
||||
LoadLanguage(AvailableLanguages.English);
|
||||
_oldResources.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Change Language
|
||||
|
||||
/// <summary>
|
||||
/// Change language during runtime. Will change app language and plugin language & save settings.
|
||||
/// </summary>
|
||||
|
|
@ -151,8 +170,8 @@ namespace Flow.Launcher.Core.Resource
|
|||
|
||||
private static Language GetLanguageByLanguageCode(string languageCode)
|
||||
{
|
||||
var lowercase = languageCode.ToLower();
|
||||
var language = AvailableLanguages.GetAvailableLanguages().FirstOrDefault(o => o.LanguageCode.ToLower() == lowercase);
|
||||
var language = AvailableLanguages.GetAvailableLanguages().
|
||||
FirstOrDefault(o => o.LanguageCode.Equals(languageCode, StringComparison.OrdinalIgnoreCase));
|
||||
if (language == null)
|
||||
{
|
||||
API.LogError(ClassName, $"Language code can't be found <{languageCode}>");
|
||||
|
|
@ -164,7 +183,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
}
|
||||
|
||||
private async Task ChangeLanguageAsync(Language language)
|
||||
private async Task ChangeLanguageAsync(Language language, bool updateMetadata = true)
|
||||
{
|
||||
// Remove old language files and load language
|
||||
RemoveOldLanguageFiles();
|
||||
|
|
@ -176,8 +195,11 @@ namespace Flow.Launcher.Core.Resource
|
|||
// Change culture info
|
||||
ChangeCultureInfo(language.LanguageCode);
|
||||
|
||||
// Raise event for plugins after culture is set
|
||||
await Task.Run(UpdatePluginMetadataTranslations);
|
||||
if (updateMetadata)
|
||||
{
|
||||
// Raise event for plugins after culture is set
|
||||
await Task.Run(UpdatePluginMetadataTranslations);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ChangeCultureInfo(string languageCode)
|
||||
|
|
@ -200,6 +222,10 @@ namespace Flow.Launcher.Core.Resource
|
|||
thread.CurrentUICulture = currentCulture;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Prompt Pinyin
|
||||
|
||||
public bool PromptShouldUsePinyin(string languageCodeToSet)
|
||||
{
|
||||
var languageToSet = GetLanguageByLanguageCode(languageCodeToSet);
|
||||
|
|
@ -212,14 +238,18 @@ namespace Flow.Launcher.Core.Resource
|
|||
|
||||
// No other languages should show the following text so just make it hard-coded
|
||||
// "Do you want to search with pinyin?"
|
||||
string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?" ;
|
||||
string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?";
|
||||
|
||||
if (Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
if (API.ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Language Resources Management
|
||||
|
||||
private void RemoveOldLanguageFiles()
|
||||
{
|
||||
var dicts = Application.Current.Resources.MergedDictionaries;
|
||||
|
|
@ -255,46 +285,6 @@ namespace Flow.Launcher.Core.Resource
|
|||
}
|
||||
}
|
||||
|
||||
public List<Language> LoadAvailableLanguages()
|
||||
{
|
||||
var list = AvailableLanguages.GetAvailableLanguages();
|
||||
list.Insert(0, new Language(Constant.SystemLanguageCode, AvailableLanguages.GetSystemTranslation(SystemLanguageCode)));
|
||||
return list;
|
||||
}
|
||||
|
||||
public static string GetTranslation(string key)
|
||||
{
|
||||
var translation = Application.Current.TryFindResource(key);
|
||||
if (translation is string)
|
||||
{
|
||||
return translation.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
API.LogError(ClassName, $"No Translation for key {key}");
|
||||
return $"No Translation for key {key}";
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePluginMetadataTranslations()
|
||||
{
|
||||
// Update plugin metadata name & description
|
||||
foreach (var p in PluginManager.GetTranslationPlugins())
|
||||
{
|
||||
if (p.Plugin is not IPluginI18n pluginI18N) return;
|
||||
try
|
||||
{
|
||||
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
|
||||
p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription();
|
||||
pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string LanguageFile(string folder, string language)
|
||||
{
|
||||
if (Directory.Exists(folder))
|
||||
|
|
@ -324,5 +314,59 @@ namespace Flow.Launcher.Core.Resource
|
|||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Available Languages
|
||||
|
||||
public List<Language> LoadAvailableLanguages()
|
||||
{
|
||||
var list = AvailableLanguages.GetAvailableLanguages();
|
||||
list.Insert(0, new Language(Constant.SystemLanguageCode, AvailableLanguages.GetSystemTranslation(SystemLanguageCode)));
|
||||
return list;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Get Translations
|
||||
|
||||
public static string GetTranslation(string key)
|
||||
{
|
||||
var translation = Application.Current.TryFindResource(key);
|
||||
if (translation is string)
|
||||
{
|
||||
return translation.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
API.LogError(ClassName, $"No Translation for key {key}");
|
||||
return $"No Translation for key {key}";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update Metadata
|
||||
|
||||
public static void UpdatePluginMetadataTranslations()
|
||||
{
|
||||
// Update plugin metadata name & description
|
||||
foreach (var p in PluginManager.GetTranslationPlugins())
|
||||
{
|
||||
if (p.Plugin is not IPluginI18n pluginI18N) return;
|
||||
try
|
||||
{
|
||||
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
|
||||
p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription();
|
||||
pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,9 @@ namespace Flow.Launcher.Core
|
|||
// UpdateApp CheckForUpdate will return value only if the app is squirrel installed
|
||||
var newUpdateInfo = await updateManager.CheckForUpdate().NonNull().ConfigureAwait(false);
|
||||
|
||||
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
|
||||
var currentVersion = Version.Parse(Constant.Version);
|
||||
var newReleaseVersion =
|
||||
SemanticVersioning.Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
|
||||
var currentVersion = SemanticVersioning.Version.Parse(Constant.Version);
|
||||
|
||||
_api.LogInfo(ClassName, $"Future Release <{Formatted(newUpdateInfo.FutureReleaseEntry)}>");
|
||||
|
||||
|
|
@ -71,10 +72,13 @@ namespace Flow.Launcher.Core
|
|||
|
||||
if (DataLocation.PortableDataLocationInUse())
|
||||
{
|
||||
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion}\\{DataLocation.PortableFolderName}";
|
||||
var targetDestination = updateManager.RootAppDirectory +
|
||||
$"\\app-{newReleaseVersion}\\{DataLocation.PortableFolderName}";
|
||||
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
|
||||
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)))
|
||||
_api.ShowMsgBox(string.Format(_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
|
||||
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination,
|
||||
(s) => _api.ShowMsgBox(s)))
|
||||
_api.ShowMsgBox(string.Format(
|
||||
_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
|
||||
DataLocation.PortableDataPath,
|
||||
targetDestination));
|
||||
}
|
||||
|
|
@ -87,24 +91,27 @@ namespace Flow.Launcher.Core
|
|||
|
||||
_api.LogInfo(ClassName, $"Update success:{newVersionTips}");
|
||||
|
||||
if (_api.ShowMsgBox(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);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
|
||||
if (e is HttpRequestException or WebException or SocketException ||
|
||||
e.InnerException is TimeoutException)
|
||||
{
|
||||
_api.LogException(ClassName, $"Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
|
||||
_api.LogException(ClassName,
|
||||
$"Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
|
||||
}
|
||||
else
|
||||
{
|
||||
_api.LogException(ClassName, $"Error Occurred", e);
|
||||
}
|
||||
|
||||
|
||||
if (!silentUpdate)
|
||||
_api.ShowMsg(_api.GetTranslation("update_flowlauncher_fail"),
|
||||
_api.ShowMsgError(_api.GetTranslation("update_flowlauncher_fail"),
|
||||
_api.GetTranslation("update_flowlauncher_check_connection"));
|
||||
}
|
||||
finally
|
||||
|
|
@ -116,14 +123,11 @@ namespace Flow.Launcher.Core
|
|||
[UsedImplicitly]
|
||||
private class GithubRelease
|
||||
{
|
||||
[JsonPropertyName("prerelease")]
|
||||
public bool Prerelease { get; [UsedImplicitly] set; }
|
||||
[JsonPropertyName("prerelease")] public bool Prerelease { get; [UsedImplicitly] set; }
|
||||
|
||||
[JsonPropertyName("published_at")]
|
||||
public DateTime PublishedAt { get; [UsedImplicitly] set; }
|
||||
[JsonPropertyName("published_at")] public DateTime PublishedAt { get; [UsedImplicitly] set; }
|
||||
|
||||
[JsonPropertyName("html_url")]
|
||||
public string HtmlUrl { get; [UsedImplicitly] set; }
|
||||
[JsonPropertyName("html_url")] public string HtmlUrl { get; [UsedImplicitly] set; }
|
||||
}
|
||||
|
||||
// https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs
|
||||
|
|
@ -138,10 +142,7 @@ namespace Flow.Launcher.Core
|
|||
var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First();
|
||||
var latestUrl = latest.HtmlUrl.Replace("/tag/", "/download/");
|
||||
|
||||
var client = new WebClient
|
||||
{
|
||||
Proxy = Http.WebProxy
|
||||
};
|
||||
var client = new WebClient { Proxy = Http.WebProxy };
|
||||
var downloader = new FileDownloader(client);
|
||||
|
||||
var manager = new UpdateManager(latestUrl, urlDownloader: downloader);
|
||||
|
|
@ -158,10 +159,7 @@ namespace Flow.Launcher.Core
|
|||
|
||||
private static string Formatted<T>(T t)
|
||||
{
|
||||
var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions { WriteIndented = true });
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
|
|
|||
238
Flow.Launcher.Core/packages.lock.json
Normal file
238
Flow.Launcher.Core/packages.lock.json
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net9.0-windows7.0": {
|
||||
"Droplex": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.7.0, )",
|
||||
"resolved": "1.7.0",
|
||||
"contentHash": "wutfIus/Ufw/9TDsp86R1ycnIH+wWrj4UhcmrzAHWjsdyC2iM07WEQ9+APTB7pQynsDnYH1r2i58XgAJ3lxUXA==",
|
||||
"dependencies": {
|
||||
"YamlDotNet": "9.1.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Core": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.101, )",
|
||||
"resolved": "9.0.101",
|
||||
"contentHash": "3/YR1SDWFA+Ojx9HiBwND+0UR8ZWoeZfkhD0DWAPCDdr/YI+CyFkArmMGzGSyPXeYtjG0sy0emzfyNwjt7zhig=="
|
||||
},
|
||||
"Meziantou.Framework.Win32.Jobs": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.4.0, )",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "5GGLckfpwoC1jznInEYfK2INrHyD7K1RtwZJ98kNPKBU6jeu24i4zfgDGHHfb+eK3J+eFPAxo0aYcbUxNXIbNw=="
|
||||
},
|
||||
"Microsoft.IO.RecyclableMemoryStream": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.1, )",
|
||||
"resolved": "3.0.1",
|
||||
"contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g=="
|
||||
},
|
||||
"squirrel.windows": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.5.2, )",
|
||||
"resolved": "1.5.2",
|
||||
"contentHash": "89Y/CFxWm7SEOjvuV2stVa8p+SNM9GOLk4tUNm2nUF792nfkimAgwRA/umVsdyd/OXBH8byXSh4V1qck88ZAyQ==",
|
||||
"dependencies": {
|
||||
"DeltaCompressionDotNet": "[1.0.0, 2.0.0)",
|
||||
"Mono.Cecil": "0.9.6.1",
|
||||
"Splat": "1.6.2"
|
||||
}
|
||||
},
|
||||
"StreamJsonRpc": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.20.20, )",
|
||||
"resolved": "2.20.20",
|
||||
"contentHash": "gwG7KViLbSWS7EI0kYevinVmIga9wZNrpSY/FnWyC6DbdjKJ1xlv/FV1L9b0rLkVP8cGxfIMexdvo/+2W5eq6Q==",
|
||||
"dependencies": {
|
||||
"MessagePack": "2.5.187",
|
||||
"Microsoft.VisualStudio.Threading": "17.10.48",
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": "17.10.48",
|
||||
"Microsoft.VisualStudio.Validation": "17.8.8",
|
||||
"Nerdbank.Streams": "2.11.74",
|
||||
"Newtonsoft.Json": "13.0.1",
|
||||
"System.IO.Pipelines": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Ben.Demystifier": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.4.1",
|
||||
"contentHash": "axFeEMfmEORy3ipAzOXG/lE+KcNptRbei3F0C4kQCdeiQtW+qJW90K5iIovITGrdLt8AjhNCwk5qLSX9/rFpoA==",
|
||||
"dependencies": {
|
||||
"System.Reflection.Metadata": "5.0.0"
|
||||
}
|
||||
},
|
||||
"BitFaster.Caching": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.5.3",
|
||||
"contentHash": "Vo/39qcam5Xe+DbyfH0JZyqPswdOoa7jv4PGtRJ6Wj8AU+aZ+TuJRlJcIe+MQjRTJwliI8k8VSQpN8sEoBIv2g=="
|
||||
},
|
||||
"CommunityToolkit.Mvvm": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.4.0",
|
||||
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
|
||||
},
|
||||
"DeltaCompressionDotNet": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "nwbZAYd+DblXAIzlnwDSnl0CiCm8jWLfHSYnoN4wYhtIav6AegB3+T/vKzLbU2IZlPB8Bvl8U3NXpx3eaz+N5w=="
|
||||
},
|
||||
"JetBrains.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2024.3.0",
|
||||
"contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug=="
|
||||
},
|
||||
"MemoryPack": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.21.3",
|
||||
"contentHash": "cwCtED8y400vMWx/Vp0QCSeEpVFjDU4JwF52VX9WTaqVERUvNqjG9n6osFlmFuytegyXnHvYEu1qRJ8rv/rkbg==",
|
||||
"dependencies": {
|
||||
"MemoryPack.Core": "1.21.3",
|
||||
"MemoryPack.Generator": "1.21.3"
|
||||
}
|
||||
},
|
||||
"MemoryPack.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.21.3",
|
||||
"contentHash": "ajrYoBWT2aKeH4tlY8q/1C9qK1R/NK+7FkuVOX58ebOSxkABoFTqCR7W+Zk2rakUHZiEgNdRqO67hiRZPq6fLA=="
|
||||
},
|
||||
"MemoryPack.Generator": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.21.3",
|
||||
"contentHash": "hYU0TAIarDKnbkNIWvb7P4zBUL+CTahkuNkczsKvycSMR5kiwQ4IfLexywNKX3s05Izp4gzDSPbueepNWZRpWA=="
|
||||
},
|
||||
"MessagePack": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.5.187",
|
||||
"contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==",
|
||||
"dependencies": {
|
||||
"MessagePack.Annotations": "2.5.187",
|
||||
"Microsoft.NET.StringTools": "17.6.3"
|
||||
}
|
||||
},
|
||||
"MessagePack.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.5.187",
|
||||
"contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA=="
|
||||
},
|
||||
"Microsoft.NET.StringTools": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.6.3",
|
||||
"contentHash": "N0ZIanl1QCgvUumEL1laasU0a7sOE5ZwLZVTn0pAePnfhq8P7SvTjF8Axq+CnavuQkmdQpGNXQ1efZtu5kDFbA=="
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.12.19",
|
||||
"contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": "17.12.19",
|
||||
"Microsoft.VisualStudio.Validation": "17.8.8"
|
||||
}
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.12.19",
|
||||
"contentHash": "v3IYeedjoktvZ+GqYmLudxZJngmf/YWIxNT2Uy6QMMN19cvw+nkWoip1Gr1RtnFkUo1MPUVMis4C8Kj8d8DpSQ=="
|
||||
},
|
||||
"Microsoft.VisualStudio.Validation": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.8.8",
|
||||
"contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g=="
|
||||
},
|
||||
"Microsoft.Win32.SystemEvents": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "5BkGZ6mHp2dHydR29sb0fDfAuqkv30AHtTih8wMzvPZysOmBFvHfnkR2w3tsc0pSiIg8ZoKyefJXWy9r3pBh0w=="
|
||||
},
|
||||
"Mono.Cecil": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.9.6.1",
|
||||
"contentHash": "yMsurNaOxxKIjyW9pEB+tRrR1S3DFnN1+iBgKvYvXG8kW0Y6yknJeMAe/tl3+P78/2C6304TgF7aVqpqXgEQ9Q=="
|
||||
},
|
||||
"Nerdbank.Streams": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.11.74",
|
||||
"contentHash": "r4G7uHHfoo8LCilPOdtf2C+Q5ymHOAXtciT4ZtB2xRlAvv4gPkWBYNAijFblStv3+uidp81j5DP11jMZl4BfJw==",
|
||||
"dependencies": {
|
||||
"Microsoft.VisualStudio.Threading": "17.10.48",
|
||||
"Microsoft.VisualStudio.Validation": "17.8.8",
|
||||
"System.IO.Pipelines": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "13.0.1",
|
||||
"contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
|
||||
},
|
||||
"NLog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.7.10",
|
||||
"contentHash": "rcegW7kYOCjl7wX0SzsqpPBqnJ51JKi1WkYb6QBVX0Wc5IgH19Pv4t/co+T0s06OS0Ne44xgkY/mHg0PdrmJow=="
|
||||
},
|
||||
"PropertyChanged.Fody": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "IAZyq0uolKo2WYm4mjx+q7A8fSGFT0x2e1s3y+ODn4JI0kqTDoo9GF2tdaypUzRFJZfdMxfC5HZW9QzdJLtOnA==",
|
||||
"dependencies": {
|
||||
"Fody": "6.5.1"
|
||||
}
|
||||
},
|
||||
"Splat": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.6.2",
|
||||
"contentHash": "DeH0MxPU+D4JchkIDPYG4vUT+hsWs9S41cFle0/4K5EJMXWurx5DzAkj2366DfK14/XKNhsu6tCl4dZXJ3CD4w=="
|
||||
},
|
||||
"System.Drawing.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "JU947wzf8JbBS16Y5EIZzAlyQU+k68D7LRx6y03s2wlhlvLqkt/8uPBrjv2hJnnaJKbdb0GhQ3JZsfYXhrRjyg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.SystemEvents": "9.0.2"
|
||||
}
|
||||
},
|
||||
"System.IO.Pipelines": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "FHNOatmUq0sqJOkTx+UF/9YK1f180cnW5FVqnQMvYUN0elp6wFzbtPSiqbo1/ru8ICp43JM1i7kKkk6GsNGHlA=="
|
||||
},
|
||||
"System.Reflection.Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ=="
|
||||
},
|
||||
"ToolGood.Words.Pinyin": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.1.4",
|
||||
"contentHash": "uQo97618y9yzLDxrnehPN+/tuiOlk5BqieEdwctHZOAS9miMXnHKgMFYVw8CSGXRglyTYXlrW7qtUlU7Fje5Ew=="
|
||||
},
|
||||
"YamlDotNet": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.1.0",
|
||||
"contentHash": "fuvGXU4Ec5HrsmEc+BiFTNPCRf1cGBI2kh/3RzMWgddM2M4ALhbSPoI3X3mhXZUD1qqQd9oSkFAtWjpz8z9eRg=="
|
||||
},
|
||||
"flow.launcher.infrastructure": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Ben.Demystifier": "[0.4.1, )",
|
||||
"BitFaster.Caching": "[2.5.3, )",
|
||||
"CommunityToolkit.Mvvm": "[8.4.0, )",
|
||||
"Flow.Launcher.Plugin": "[4.4.0, )",
|
||||
"MemoryPack": "[1.21.3, )",
|
||||
"Microsoft.VisualStudio.Threading": "[17.12.19, )",
|
||||
"NLog": "[4.7.10, )",
|
||||
"PropertyChanged.Fody": "[3.4.0, )",
|
||||
"System.Drawing.Common": "[9.0.2, )",
|
||||
"ToolGood.Words.Pinyin": "[3.0.1.4, )"
|
||||
}
|
||||
},
|
||||
"flow.launcher.plugin": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"JetBrains.Annotations": "[2024.3.0, )",
|
||||
"PropertyChanged.Fody": "[3.4.0, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1079
Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs
Normal file
1079
Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs
Normal file
File diff suppressed because it is too large
Load diff
63
Flow.Launcher.Infrastructure/DialogJump/DialogJumpPair.cs
Normal file
63
Flow.Launcher.Infrastructure/DialogJump/DialogJumpPair.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.DialogJump;
|
||||
|
||||
public class DialogJumpExplorerPair
|
||||
{
|
||||
public IDialogJumpExplorer Plugin { get; init; }
|
||||
|
||||
public PluginMetadata Metadata { get; init; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Metadata.Name;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is DialogJumpExplorerPair r)
|
||||
{
|
||||
return string.Equals(r.Metadata.ID, Metadata.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashcode = Metadata.ID?.GetHashCode() ?? 0;
|
||||
return hashcode;
|
||||
}
|
||||
}
|
||||
|
||||
public class DialogJumpDialogPair
|
||||
{
|
||||
public IDialogJumpDialog Plugin { get; init; }
|
||||
|
||||
public PluginMetadata Metadata { get; init; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Metadata.Name;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is DialogJumpDialogPair r)
|
||||
{
|
||||
return string.Equals(r.Metadata.ID, Metadata.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashcode = Metadata.ID?.GetHashCode() ?? 0;
|
||||
return hashcode;
|
||||
}
|
||||
}
|
||||
345
Flow.Launcher.Infrastructure/DialogJump/Models/WindowsDialog.cs
Normal file
345
Flow.Launcher.Infrastructure/DialogJump/Models/WindowsDialog.cs
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using WindowsInput;
|
||||
using WindowsInput.Native;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.DialogJump.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for handling Windows File Dialog instances in DialogJump.
|
||||
/// </summary>
|
||||
public class WindowsDialog : IDialogJumpDialog
|
||||
{
|
||||
private const string WindowsDialogClassName = "#32770";
|
||||
|
||||
public IDialogJumpDialogWindow CheckDialogWindow(IntPtr hwnd)
|
||||
{
|
||||
// Is it a Win32 dialog box?
|
||||
if (GetClassName(new(hwnd)) == WindowsDialogClassName)
|
||||
{
|
||||
// Is it a windows file dialog?
|
||||
var dialogType = GetFileDialogType(new(hwnd));
|
||||
if (dialogType != DialogType.Others)
|
||||
{
|
||||
return new WindowsDialogWindow(hwnd, dialogType);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#region Help Methods
|
||||
|
||||
private static unsafe string GetClassName(HWND handle)
|
||||
{
|
||||
fixed (char* buf = new char[256])
|
||||
{
|
||||
return PInvoke.GetClassName(handle, buf, 256) switch
|
||||
{
|
||||
0 => string.Empty,
|
||||
_ => new string(buf),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static DialogType GetFileDialogType(HWND handle)
|
||||
{
|
||||
// Is it a Windows Open file dialog?
|
||||
var fileEditor = PInvoke.GetDlgItem(handle, 0x047C);
|
||||
if (fileEditor != HWND.Null && GetClassName(fileEditor) == "ComboBoxEx32") return DialogType.Open;
|
||||
|
||||
// Is it a Windows Save or Save As file dialog?
|
||||
fileEditor = PInvoke.GetDlgItem(handle, 0x0000);
|
||||
if (fileEditor != HWND.Null && GetClassName(fileEditor) == "DUIViewWndClassName") return DialogType.SaveOrSaveAs;
|
||||
|
||||
return DialogType.Others;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class WindowsDialogWindow : IDialogJumpDialogWindow
|
||||
{
|
||||
public IntPtr Handle { get; private set; } = IntPtr.Zero;
|
||||
|
||||
// After jumping folder, file editor handle of Save / SaveAs file dialogs cannot be found anymore
|
||||
// So we need to cache the current tab and use the original handle
|
||||
private IDialogJumpDialogWindowTab _currentTab { get; set; } = null;
|
||||
|
||||
private readonly DialogType _dialogType;
|
||||
|
||||
internal WindowsDialogWindow(IntPtr handle, DialogType dialogType)
|
||||
{
|
||||
Handle = handle;
|
||||
_dialogType = dialogType;
|
||||
}
|
||||
|
||||
public IDialogJumpDialogWindowTab GetCurrentTab()
|
||||
{
|
||||
return _currentTab ??= new WindowsDialogTab(Handle, _dialogType);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class WindowsDialogTab : IDialogJumpDialogWindowTab
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public IntPtr Handle { get; private set; } = IntPtr.Zero;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private static readonly string ClassName = nameof(WindowsDialogTab);
|
||||
|
||||
private static readonly InputSimulator _inputSimulator = new();
|
||||
|
||||
private readonly DialogType _dialogType;
|
||||
|
||||
private bool _legacy { get; set; } = false;
|
||||
private HWND _pathControl { get; set; } = HWND.Null;
|
||||
private HWND _pathEditor { get; set; } = HWND.Null;
|
||||
private HWND _fileEditor { get; set; } = HWND.Null;
|
||||
private HWND _openButton { get; set; } = HWND.Null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
internal WindowsDialogTab(IntPtr handle, DialogType dialogType)
|
||||
{
|
||||
Handle = handle;
|
||||
_dialogType = dialogType;
|
||||
Log.Debug(ClassName, $"File dialog type: {dialogType}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public string GetCurrentFolder()
|
||||
{
|
||||
if (_pathEditor.IsNull && !GetPathControlEditor()) return string.Empty;
|
||||
return GetWindowText(_pathEditor);
|
||||
}
|
||||
|
||||
public string GetCurrentFile()
|
||||
{
|
||||
if (_fileEditor.IsNull && !GetFileEditor()) return string.Empty;
|
||||
return GetWindowText(_fileEditor);
|
||||
}
|
||||
|
||||
public bool JumpFolder(string path, bool auto)
|
||||
{
|
||||
if (auto)
|
||||
{
|
||||
// Use legacy jump folder method for auto Dialog Jump because file editor is default value.
|
||||
// After setting path using file editor, we do not need to revert its value.
|
||||
return JumpFolderWithFileEditor(path, false);
|
||||
}
|
||||
|
||||
// Alt-D or Ctrl-L to focus on the path input box
|
||||
// "ComboBoxEx32" is not visible when the path editor is not with the keyboard focus
|
||||
_inputSimulator.Keyboard.ModifiedKeyStroke(VirtualKeyCode.LMENU, VirtualKeyCode.VK_D);
|
||||
// _inputSimulator.Keyboard.ModifiedKeyStroke(VirtualKeyCode.LCONTROL, VirtualKeyCode.VK_L);
|
||||
|
||||
if (_pathControl.IsNull && !GetPathControlEditor())
|
||||
{
|
||||
// https://github.com/idkidknow/Flow.Launcher.Plugin.DirQuickJump/issues/1
|
||||
// The dialog is a legacy one, so we can only edit file editor directly.
|
||||
Log.Debug(ClassName, "Legacy dialog, using legacy jump folder method");
|
||||
return JumpFolderWithFileEditor(path, true);
|
||||
}
|
||||
|
||||
var timeOut = !SpinWait.SpinUntil(() =>
|
||||
{
|
||||
var style = PInvoke.GetWindowLongPtr(_pathControl, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
|
||||
return (style & (int)WINDOW_STYLE.WS_VISIBLE) != 0;
|
||||
}, 1000);
|
||||
if (timeOut)
|
||||
{
|
||||
// Path control is not visible, so we can only edit file editor directly.
|
||||
Log.Debug(ClassName, "Path control is not visible, using legacy jump folder method");
|
||||
return JumpFolderWithFileEditor(path, true);
|
||||
}
|
||||
|
||||
if (_pathEditor.IsNull)
|
||||
{
|
||||
// Path editor cannot be found, so we can only edit file editor directly.
|
||||
Log.Debug(ClassName, "Path editor cannot be found, using legacy jump folder method");
|
||||
return JumpFolderWithFileEditor(path, true);
|
||||
}
|
||||
SetWindowText(_pathEditor, path);
|
||||
|
||||
_inputSimulator.Keyboard.KeyPress(VirtualKeyCode.RETURN);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool JumpFile(string path)
|
||||
{
|
||||
if (_fileEditor.IsNull && !GetFileEditor()) return false;
|
||||
SetWindowText(_fileEditor, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Open()
|
||||
{
|
||||
if (_openButton.IsNull && !GetOpenButton()) return false;
|
||||
PInvoke.PostMessage(_openButton, PInvoke.BM_CLICK, 0, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
#region Get Handles
|
||||
|
||||
private bool GetPathControlEditor()
|
||||
{
|
||||
// Get the handle of the path editor
|
||||
// Must use PInvoke.FindWindowEx because PInvoke.GetDlgItem(Handle, 0x0000) will get another control
|
||||
_pathControl = PInvoke.FindWindowEx(new(Handle), HWND.Null, "WorkerW", null); // 0x0000
|
||||
_pathControl = PInvoke.FindWindowEx(_pathControl, HWND.Null, "ReBarWindow32", null); // 0xA005
|
||||
_pathControl = PInvoke.FindWindowEx(_pathControl, HWND.Null, "Address Band Root", null); // 0xA205
|
||||
_pathControl = PInvoke.FindWindowEx(_pathControl, HWND.Null, "msctls_progress32", null); // 0x0000
|
||||
_pathControl = PInvoke.FindWindowEx(_pathControl, HWND.Null, "ComboBoxEx32", null); // 0xA205
|
||||
if (_pathControl == HWND.Null)
|
||||
{
|
||||
_pathEditor = HWND.Null;
|
||||
_legacy = true;
|
||||
Log.Info(ClassName, "Legacy dialog");
|
||||
}
|
||||
else
|
||||
{
|
||||
_pathEditor = PInvoke.GetDlgItem(_pathControl, 0xA205); // ComboBox
|
||||
_pathEditor = PInvoke.GetDlgItem(_pathEditor, 0xA205); // Edit
|
||||
if (_pathEditor == HWND.Null)
|
||||
{
|
||||
_legacy = true;
|
||||
Log.Error(ClassName, "Failed to find path editor handle");
|
||||
}
|
||||
}
|
||||
|
||||
return !_legacy;
|
||||
}
|
||||
|
||||
private bool GetFileEditor()
|
||||
{
|
||||
if (_dialogType == DialogType.Open)
|
||||
{
|
||||
// Get the handle of the file name editor of Open file dialog
|
||||
_fileEditor = PInvoke.GetDlgItem(new(Handle), 0x047C); // ComboBoxEx32
|
||||
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x047C); // ComboBox
|
||||
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x047C); // Edit
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the handle of the file name editor of Save / SaveAs file dialog
|
||||
_fileEditor = PInvoke.GetDlgItem(new(Handle), 0x0000); // DUIViewWndClassName
|
||||
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x0000); // DirectUIHWND
|
||||
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x0000); // FloatNotifySink
|
||||
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x0000); // ComboBox
|
||||
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x03E9); // Edit
|
||||
}
|
||||
|
||||
if (_fileEditor == HWND.Null)
|
||||
{
|
||||
Log.Error(ClassName, "Failed to find file name editor handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool GetOpenButton()
|
||||
{
|
||||
// Get the handle of the open button
|
||||
_openButton = PInvoke.GetDlgItem(new(Handle), 0x0001); // Open/Save/SaveAs Button
|
||||
if (_openButton == HWND.Null)
|
||||
{
|
||||
Log.Error(ClassName, "Failed to find open button handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Windows Text
|
||||
|
||||
private static unsafe string GetWindowText(HWND handle)
|
||||
{
|
||||
int length;
|
||||
Span<char> buffer = stackalloc char[1000];
|
||||
fixed (char* pBuffer = buffer)
|
||||
{
|
||||
// If the control has no title bar or text, or if the control handle is invalid, the return value is zero.
|
||||
length = (int)PInvoke.SendMessage(handle, PInvoke.WM_GETTEXT, 1000, (nint)pBuffer);
|
||||
}
|
||||
|
||||
return buffer[..length].ToString();
|
||||
}
|
||||
|
||||
private static unsafe nint SetWindowText(HWND handle, string text)
|
||||
{
|
||||
fixed (char* textPtr = text + '\0')
|
||||
{
|
||||
return PInvoke.SendMessage(handle, PInvoke.WM_SETTEXT, 0, (nint)textPtr).Value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Legacy Jump Folder
|
||||
|
||||
private bool JumpFolderWithFileEditor(string path, bool resetFocus)
|
||||
{
|
||||
// For Save / Save As dialog, the default value in file editor is not null and it can cause strange behaviors.
|
||||
if (resetFocus && _dialogType == DialogType.SaveOrSaveAs) return false;
|
||||
|
||||
if (_fileEditor.IsNull && !GetFileEditor()) return false;
|
||||
SetWindowText(_fileEditor, path);
|
||||
|
||||
if (_openButton.IsNull && !GetOpenButton()) return false;
|
||||
PInvoke.SendMessage(_openButton, PInvoke.BM_CLICK, 0, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal enum DialogType
|
||||
{
|
||||
Others,
|
||||
Open,
|
||||
SaveOrSaveAs
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,260 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Com;
|
||||
using Windows.Win32.UI.Shell;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.DialogJump.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for handling Windows Explorer instances in DialogJump.
|
||||
/// </summary>
|
||||
public class WindowsExplorer : IDialogJumpExplorer
|
||||
{
|
||||
public IDialogJumpExplorerWindow CheckExplorerWindow(IntPtr hwnd)
|
||||
{
|
||||
IDialogJumpExplorerWindow explorerWindow = null;
|
||||
|
||||
// Is it from Explorer?
|
||||
var processName = Win32Helper.GetProcessNameFromHwnd(new(hwnd));
|
||||
if (processName.Equals("explorer.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
EnumerateShellWindows((shellWindow) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (shellWindow is not IWebBrowser2 explorer) return true;
|
||||
|
||||
if (explorer.HWND != hwnd) return true;
|
||||
|
||||
explorerWindow = new WindowsExplorerWindow(hwnd);
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return explorerWindow;
|
||||
}
|
||||
|
||||
internal static unsafe void EnumerateShellWindows(Func<object, bool> action)
|
||||
{
|
||||
// Create an instance of ShellWindows
|
||||
var clsidShellWindows = new Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39"); // ShellWindowsClass
|
||||
var iidIShellWindows = typeof(IShellWindows).GUID; // IShellWindows
|
||||
|
||||
var result = PInvoke.CoCreateInstance(
|
||||
&clsidShellWindows,
|
||||
null,
|
||||
CLSCTX.CLSCTX_ALL,
|
||||
&iidIShellWindows,
|
||||
out var shellWindowsObj);
|
||||
|
||||
if (result.Failed) return;
|
||||
|
||||
var shellWindows = (IShellWindows)shellWindowsObj;
|
||||
|
||||
// Enumerate the shell windows
|
||||
var count = shellWindows.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (!action(shellWindows.Item(i)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class WindowsExplorerWindow : IDialogJumpExplorerWindow
|
||||
{
|
||||
public IntPtr Handle { get; }
|
||||
|
||||
private static Guid _shellBrowserGuid = typeof(IShellBrowser).GUID;
|
||||
|
||||
internal WindowsExplorerWindow(IntPtr handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
public string GetExplorerPath()
|
||||
{
|
||||
if (Handle == IntPtr.Zero) return null;
|
||||
|
||||
var activeTabHandle = GetActiveTabHandle(new(Handle));
|
||||
if (activeTabHandle.IsNull) return null;
|
||||
|
||||
var window = GetExplorerByTabHandle(activeTabHandle);
|
||||
if (window == null) return null;
|
||||
|
||||
var path = GetLocation(window);
|
||||
return path;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
// Inspired by: https://github.com/w4po/ExplorerTabUtility
|
||||
|
||||
private static HWND GetActiveTabHandle(HWND windowHandle)
|
||||
{
|
||||
// Active tab always at the top of the z-index, so it is the first child of the ShellTabWindowClass.
|
||||
var activeTab = PInvoke.FindWindowEx(windowHandle, HWND.Null, "ShellTabWindowClass", null);
|
||||
return activeTab;
|
||||
}
|
||||
|
||||
private static IWebBrowser2 GetExplorerByTabHandle(HWND tabHandle)
|
||||
{
|
||||
if (tabHandle.IsNull) return null;
|
||||
|
||||
IWebBrowser2 window = null;
|
||||
WindowsExplorer.EnumerateShellWindows((shellWindow) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return StartSTAThread(() =>
|
||||
{
|
||||
if (shellWindow is not IWebBrowser2 explorer) return true;
|
||||
|
||||
if (explorer is not IServiceProvider sp) return true;
|
||||
|
||||
sp.QueryService(ref _shellBrowserGuid, ref _shellBrowserGuid, out var shellBrowser);
|
||||
if (shellBrowser == null) return true;
|
||||
|
||||
try
|
||||
{
|
||||
shellBrowser.GetWindow(out var hWnd); // Must execute in STA thread to get this hWnd
|
||||
|
||||
if (hWnd == tabHandle)
|
||||
{
|
||||
window = explorer;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.ReleaseComObject(shellBrowser);
|
||||
}
|
||||
|
||||
return true;
|
||||
}) ?? true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
private static bool? StartSTAThread(Func<bool> action)
|
||||
{
|
||||
bool? result = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
result = action();
|
||||
})
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetLocation(IWebBrowser2 window)
|
||||
{
|
||||
var path = window.LocationURL.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(path)) return NormalizeLocation(path);
|
||||
|
||||
// Recycle Bin, This PC, etc
|
||||
if (window.Document is not IShellFolderViewDual folderView) return null;
|
||||
|
||||
// Attempt to get the path from the folder view
|
||||
try
|
||||
{
|
||||
// CSWin32 Folder does not have Self, so we need to use dynamic type here
|
||||
// Use dynamic to bypass static typing
|
||||
dynamic folder = folderView.Folder;
|
||||
|
||||
// Access the Self property via dynamic binding
|
||||
dynamic folderItem = folder.Self;
|
||||
|
||||
// Get path from the folder item
|
||||
path = folderItem.Path;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return NormalizeLocation(path);
|
||||
}
|
||||
|
||||
private static string NormalizeLocation(string location)
|
||||
{
|
||||
if (location.IndexOf('%') > -1)
|
||||
location = Environment.ExpandEnvironmentVariables(location);
|
||||
|
||||
if (location.StartsWith("::", StringComparison.Ordinal))
|
||||
location = $"shell:{location}";
|
||||
|
||||
else if (location.StartsWith("{", StringComparison.Ordinal))
|
||||
location = $"shell:::{location}";
|
||||
|
||||
location = location.Trim(' ', '/', '\\', '\n', '\'', '"');
|
||||
|
||||
return location.Replace('/', '\\');
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region COM Interfaces
|
||||
|
||||
// Inspired by: https://github.com/w4po/ExplorerTabUtility
|
||||
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
|
||||
[ComImport]
|
||||
public interface IServiceProvider
|
||||
{
|
||||
[PreserveSig]
|
||||
int QueryService(ref Guid guidService, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellBrowser ppvObject);
|
||||
}
|
||||
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("000214E2-0000-0000-C000-000000000046")]
|
||||
[ComImport]
|
||||
public interface IShellBrowser
|
||||
{
|
||||
[PreserveSig]
|
||||
int GetWindow(out nint handle);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ProjectGuid>{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<UseWpf>true</UseWpf>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
|
|
@ -59,12 +60,14 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="InputSimulator" Version="1.0.4" />
|
||||
<PackageReference Include="MemoryPack" Version="1.21.4" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.12.19" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
|
||||
<PackageReference Include="NLog" Version="4.7.10" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
@ -76,5 +79,4 @@
|
|||
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.0.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
@ -20,6 +20,10 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
|
||||
private static readonly HttpClient client = new();
|
||||
|
||||
// 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>();
|
||||
|
||||
static Http()
|
||||
{
|
||||
// need to be added so it would work on a win10 machine
|
||||
|
|
@ -78,7 +82,7 @@ namespace Flow.Launcher.Infrastructure.Http
|
|||
}
|
||||
catch (UriFormatException e)
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsg("Please try again", "Unable to parse Http Proxy");
|
||||
API.ShowMsgError(API.GetTranslation("pleaseTryAgain"), API.GetTranslation("parseProxyFailed"));
|
||||
Log.Exception(ClassName, "Unable to parse Uri", e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,3 +66,27 @@ LOCALE_TRANSIENT_KEYBOARD4
|
|||
SHParseDisplayName
|
||||
SHOpenFolderAndSelectItems
|
||||
CoTaskMemFree
|
||||
|
||||
SetWinEventHook
|
||||
UnhookWinEvent
|
||||
SendMessage
|
||||
EVENT_SYSTEM_FOREGROUND
|
||||
WINEVENT_OUTOFCONTEXT
|
||||
WM_SETTEXT
|
||||
IShellFolderViewDual2
|
||||
CoCreateInstance
|
||||
CLSCTX
|
||||
IShellWindows
|
||||
IWebBrowser2
|
||||
EVENT_OBJECT_DESTROY
|
||||
EVENT_OBJECT_LOCATIONCHANGE
|
||||
EVENT_SYSTEM_MOVESIZESTART
|
||||
EVENT_SYSTEM_MOVESIZEEND
|
||||
GetDlgItem
|
||||
PostMessage
|
||||
BM_CLICK
|
||||
WM_GETTEXT
|
||||
OpenProcess
|
||||
QueryFullProcessImageName
|
||||
EVENT_OBJECT_HIDE
|
||||
EVENT_SYSTEM_DIALOGEND
|
||||
|
|
|
|||
|
|
@ -14,11 +14,8 @@ namespace Flow.Launcher.Infrastructure
|
|||
{
|
||||
public class PinyinAlphabet : IAlphabet
|
||||
{
|
||||
private ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache =
|
||||
new();
|
||||
|
||||
private readonly ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache = new();
|
||||
private readonly Settings _settings;
|
||||
|
||||
private ReadOnlyDictionary<string, string> currentDoublePinyinTable;
|
||||
|
||||
public PinyinAlphabet()
|
||||
|
|
@ -28,10 +25,21 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
_settings.PropertyChanged += (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(Settings.UseDoublePinyin) ||
|
||||
e.PropertyName == nameof(Settings.DoublePinyinSchema))
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
Reload();
|
||||
case nameof (Settings.ShouldUsePinyin):
|
||||
if (_settings.ShouldUsePinyin)
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
break;
|
||||
case nameof(Settings.UseDoublePinyin):
|
||||
case nameof(Settings.DoublePinyinSchema):
|
||||
if (_settings.UseDoublePinyin)
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -44,105 +52,142 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
private void CreateDoublePinyinTableFromStream(Stream jsonStream)
|
||||
{
|
||||
Dictionary<string, Dictionary<string, string>> table = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(jsonStream);
|
||||
string schemaKey = _settings.DoublePinyinSchema.ToString(); // Convert enum to string
|
||||
if (!table.TryGetValue(schemaKey, out var value))
|
||||
var table = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(jsonStream) ??
|
||||
throw new InvalidOperationException("Failed to deserialize double pinyin table: result is null");
|
||||
|
||||
var schemaKey = _settings.DoublePinyinSchema.ToString();
|
||||
if (!table.TryGetValue(schemaKey, out var schemaDict))
|
||||
{
|
||||
throw new ArgumentException("DoublePinyinSchema is invalid or double pinyin table is broken.");
|
||||
throw new ArgumentException($"DoublePinyinSchema '{schemaKey}' is invalid or double pinyin table is broken.");
|
||||
}
|
||||
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(value);
|
||||
|
||||
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(schemaDict);
|
||||
}
|
||||
|
||||
private void LoadDoublePinyinTable()
|
||||
{
|
||||
if (_settings.UseDoublePinyin)
|
||||
if (!_settings.UseDoublePinyin)
|
||||
{
|
||||
var tablePath = Path.Join(AppContext.BaseDirectory, "Resources", "double_pinyin.json");
|
||||
try
|
||||
{
|
||||
using var fs = File.OpenRead(tablePath);
|
||||
CreateDoublePinyinTableFromStream(fs);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Exception(nameof(PinyinAlphabet), "Failed to load double pinyin table from file: " + tablePath, e);
|
||||
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
|
||||
}
|
||||
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
var tablePath = Path.Combine(AppContext.BaseDirectory, "Resources", "double_pinyin.json");
|
||||
try
|
||||
{
|
||||
using var fs = File.OpenRead(tablePath);
|
||||
CreateDoublePinyinTableFromStream(fs);
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
Log.Exception(nameof(PinyinAlphabet), $"Double pinyin table file not found: {tablePath}", e);
|
||||
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
|
||||
}
|
||||
catch (DirectoryNotFoundException e)
|
||||
{
|
||||
Log.Exception(nameof(PinyinAlphabet), $"Directory not found for double pinyin table: {tablePath}", e);
|
||||
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
|
||||
}
|
||||
catch (UnauthorizedAccessException e)
|
||||
{
|
||||
Log.Exception(nameof(PinyinAlphabet), $"Access denied to double pinyin table: {tablePath}", e);
|
||||
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Exception(nameof(PinyinAlphabet), $"Failed to load double pinyin table from file: {tablePath}", e);
|
||||
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShouldTranslate(string stringToTranslate)
|
||||
{
|
||||
// If a string has Chinese characters, we don't need to translate it to pinyin.
|
||||
return _settings.ShouldUsePinyin && !WordsHelper.HasChinese(stringToTranslate);
|
||||
// If the query (stringToTranslate) does NOT contain Chinese characters,
|
||||
// we should translate the target string to pinyin for matching
|
||||
return _settings.ShouldUsePinyin && !ContainsChinese(stringToTranslate);
|
||||
}
|
||||
|
||||
public (string translation, TranslationMapping map) Translate(string content)
|
||||
{
|
||||
if (!_settings.ShouldUsePinyin || !WordsHelper.HasChinese(content))
|
||||
if (!_settings.ShouldUsePinyin || !ContainsChinese(content))
|
||||
return (content, null);
|
||||
|
||||
return _pinyinCache.TryGetValue(content, out var value)
|
||||
? value
|
||||
: BuildCacheFromContent(content);
|
||||
return _pinyinCache.TryGetValue(content, out var cached) ? cached : BuildCacheFromContent(content);
|
||||
}
|
||||
|
||||
private (string translation, TranslationMapping map) BuildCacheFromContent(string content)
|
||||
{
|
||||
var resultList = WordsHelper.GetPinyinList(content);
|
||||
|
||||
var resultBuilder = new StringBuilder();
|
||||
var resultBuilder = new StringBuilder(_settings.UseDoublePinyin ? 3 : 4); // Pre-allocate with estimated capacity
|
||||
var map = new TranslationMapping();
|
||||
|
||||
var previousIsChinese = false;
|
||||
|
||||
for (var i = 0; i < resultList.Length; i++)
|
||||
{
|
||||
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
|
||||
if (IsChineseCharacter(content[i]))
|
||||
{
|
||||
string translated = _settings.UseDoublePinyin ? ToDoublePin(resultList[i]) : resultList[i];
|
||||
var translated = _settings.UseDoublePinyin ? ToDoublePinyin(resultList[i]) : resultList[i];
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
resultBuilder.Append(' ');
|
||||
}
|
||||
|
||||
map.AddNewIndex(resultBuilder.Length, translated.Length);
|
||||
resultBuilder.Append(translated);
|
||||
previousIsChinese = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add space after Chinese characters before non-Chinese characters
|
||||
if (previousIsChinese)
|
||||
{
|
||||
previousIsChinese = false;
|
||||
resultBuilder.Append(' ');
|
||||
}
|
||||
|
||||
map.AddNewIndex(resultBuilder.Length, resultList[i].Length);
|
||||
resultBuilder.Append(resultList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
map.endConstruct();
|
||||
map.EndConstruct();
|
||||
|
||||
var key = resultBuilder.ToString();
|
||||
|
||||
return _pinyinCache[content] = (key, map);
|
||||
var translation = resultBuilder.ToString();
|
||||
var result = (translation, map);
|
||||
|
||||
return _pinyinCache[content] = result;
|
||||
}
|
||||
|
||||
#region Double Pinyin
|
||||
|
||||
private string ToDoublePin(string fullPinyin)
|
||||
/// <summary>
|
||||
/// Optimized Chinese character detection using the comprehensive CJK Unicode ranges
|
||||
/// </summary>
|
||||
private static bool ContainsChinese(ReadOnlySpan<char> text)
|
||||
{
|
||||
if (currentDoublePinyinTable.TryGetValue(fullPinyin, out var doublePinyinValue))
|
||||
foreach (var c in text)
|
||||
{
|
||||
return doublePinyinValue;
|
||||
if (IsChineseCharacter(c))
|
||||
return true;
|
||||
}
|
||||
return fullPinyin;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Check if a character is a Chinese character using comprehensive Unicode ranges
|
||||
/// Covers CJK Unified Ideographs, Extension A
|
||||
/// </summary>
|
||||
private static bool IsChineseCharacter(char c)
|
||||
{
|
||||
return (c >= 0x4E00 && c <= 0x9FFF) || // CJK Unified Ideographs
|
||||
(c >= 0x3400 && c <= 0x4DBF); // CJK Extension A
|
||||
}
|
||||
|
||||
private string ToDoublePinyin(string fullPinyin)
|
||||
{
|
||||
return currentDoublePinyinTable.TryGetValue(fullPinyin, out var doublePinyinValue)
|
||||
? doublePinyinValue
|
||||
: fullPinyin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,31 +5,30 @@ namespace Flow.Launcher.Infrastructure
|
|||
{
|
||||
public class TranslationMapping
|
||||
{
|
||||
private bool constructed;
|
||||
private bool _isConstructed;
|
||||
|
||||
// Assuming one original item maps to multi translated items
|
||||
// list[i] is the last translated index + 1 of original index i
|
||||
private readonly List<int> originalToTranslated = new();
|
||||
// Assuming one original item maps to multi translated items
|
||||
// list[i] is the last translated index + 1 of original index i
|
||||
private readonly List<int> _originalToTranslated = new();
|
||||
|
||||
public void AddNewIndex(int translatedIndex, int length)
|
||||
{
|
||||
if (constructed)
|
||||
throw new InvalidOperationException("Mapping shouldn't be changed after constructed");
|
||||
|
||||
originalToTranslated.Add(translatedIndex + length);
|
||||
if (_isConstructed)
|
||||
throw new InvalidOperationException("Mapping shouldn't be changed after construction");
|
||||
_originalToTranslated.Add(translatedIndex + length);
|
||||
}
|
||||
|
||||
public int MapToOriginalIndex(int translatedIndex)
|
||||
{
|
||||
int loc = originalToTranslated.BinarySearch(translatedIndex);
|
||||
return loc >= 0 ? loc : ~loc;
|
||||
var searchResult = _originalToTranslated.BinarySearch(translatedIndex);
|
||||
return searchResult >= 0 ? searchResult : ~searchResult;
|
||||
}
|
||||
|
||||
public void endConstruct()
|
||||
public void EndConstruct()
|
||||
{
|
||||
if (constructed)
|
||||
if (_isConstructed)
|
||||
throw new InvalidOperationException("Mapping has already been constructed");
|
||||
constructed = true;
|
||||
_isConstructed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public string OpenHistoryHotkey { get; set; } = $"Ctrl+H";
|
||||
public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up";
|
||||
public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down";
|
||||
public string DialogJumpHotkey { get; set; } = $"{KeyConstant.Alt} + G";
|
||||
|
||||
private string _language = Constant.SystemLanguageCode;
|
||||
public string Language
|
||||
|
|
@ -233,6 +234,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
|
||||
public bool AutoRestartAfterChanging { get; set; } = false;
|
||||
public bool ShowUnknownSourceWarning { get; set; } = true;
|
||||
public bool AutoUpdatePlugins { get; set; } = true;
|
||||
|
||||
public int CustomExplorerIndex { get; set; } = 0;
|
||||
|
||||
|
|
@ -322,13 +324,40 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
}
|
||||
};
|
||||
|
||||
public bool EnableDialogJump { get; set; } = true;
|
||||
|
||||
public bool AutoDialogJump { get; set; } = false;
|
||||
|
||||
public bool ShowDialogJumpWindow { get; set; } = false;
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public DialogJumpWindowPositions DialogJumpWindowPosition { get; set; } = DialogJumpWindowPositions.UnderDialog;
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public DialogJumpResultBehaviours DialogJumpResultBehaviour { get; set; } = DialogJumpResultBehaviours.LeftClick;
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public DialogJumpFileResultBehaviours DialogJumpFileResultBehaviour { get; set; } = DialogJumpFileResultBehaviours.FullPath;
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public LOGLEVEL LogLevel { get; set; } = LOGLEVEL.INFO;
|
||||
|
||||
/// <summary>
|
||||
/// when false Alphabet static service will always return empty results
|
||||
/// </summary>
|
||||
public bool ShouldUsePinyin { get; set; } = false;
|
||||
private bool _useAlphabet = true;
|
||||
public bool ShouldUsePinyin
|
||||
{
|
||||
get => _useAlphabet;
|
||||
set
|
||||
{
|
||||
if (_useAlphabet != value)
|
||||
{
|
||||
_useAlphabet = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _useDoublePinyin = false;
|
||||
public bool UseDoublePinyin
|
||||
|
|
@ -502,7 +531,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
{
|
||||
var list = FixedHotkeys();
|
||||
|
||||
// Customizeable hotkeys
|
||||
// Customizable hotkeys
|
||||
if (!string.IsNullOrEmpty(Hotkey))
|
||||
list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = ""));
|
||||
if (!string.IsNullOrEmpty(PreviewHotkey))
|
||||
|
|
@ -533,6 +562,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = ""));
|
||||
if (!string.IsNullOrEmpty(CycleHistoryDownHotkey))
|
||||
list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = ""));
|
||||
if (!string.IsNullOrEmpty(DialogJumpHotkey))
|
||||
list.Add(new(DialogJumpHotkey, "dialogJumpHotkey", () => DialogJumpHotkey = ""));
|
||||
|
||||
// Custom Query Hotkeys
|
||||
foreach (var customPluginHotkey in CustomPluginHotkeys)
|
||||
|
|
@ -646,4 +677,23 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
DaNiu,
|
||||
XiaoLang
|
||||
}
|
||||
|
||||
public enum DialogJumpWindowPositions
|
||||
{
|
||||
UnderDialog,
|
||||
FollowDefault
|
||||
}
|
||||
|
||||
public enum DialogJumpResultBehaviours
|
||||
{
|
||||
LeftClick,
|
||||
RightClick
|
||||
}
|
||||
|
||||
public enum DialogJumpFileResultBehaviours
|
||||
{
|
||||
FullPath,
|
||||
FullPathOpen,
|
||||
Directory
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ using System.Windows.Markup;
|
|||
using System.Windows.Media;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Microsoft.Win32;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Graphics.Dwm;
|
||||
using Windows.Win32.System.Threading;
|
||||
using Windows.Win32.UI.Input.KeyboardAndMouse;
|
||||
using Windows.Win32.UI.Shell.Common;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
|
@ -138,6 +140,11 @@ namespace Flow.Launcher.Infrastructure
|
|||
return IsForegroundWindow(GetWindowHandle(window));
|
||||
}
|
||||
|
||||
public static bool IsForegroundWindow(nint handle)
|
||||
{
|
||||
return IsForegroundWindow(new HWND(handle));
|
||||
}
|
||||
|
||||
internal static bool IsForegroundWindow(HWND handle)
|
||||
{
|
||||
return handle.Equals(PInvoke.GetForegroundWindow());
|
||||
|
|
@ -344,6 +351,16 @@ namespace Flow.Launcher.Infrastructure
|
|||
return new(windowHelper.Handle);
|
||||
}
|
||||
|
||||
internal static HWND GetMainWindowHandle()
|
||||
{
|
||||
// When application is exiting, the Application.Current will be null
|
||||
if (Application.Current == null) return HWND.Null;
|
||||
|
||||
// Get the FL main window
|
||||
var hwnd = GetWindowHandle(Application.Current.MainWindow, true);
|
||||
return hwnd;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region STA Thread
|
||||
|
|
@ -761,6 +778,65 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
#endregion
|
||||
|
||||
#region Window Rect
|
||||
|
||||
public static unsafe bool GetWindowRect(nint handle, out Rect outRect)
|
||||
{
|
||||
var rect = new RECT();
|
||||
var result = PInvoke.GetWindowRect(new(handle), &rect);
|
||||
if (!result)
|
||||
{
|
||||
outRect = new Rect();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert RECT to Rect
|
||||
outRect = new Rect(
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Window Process
|
||||
|
||||
internal static unsafe string GetProcessNameFromHwnd(HWND hWnd)
|
||||
{
|
||||
return Path.GetFileName(GetProcessPathFromHwnd(hWnd));
|
||||
}
|
||||
|
||||
internal static unsafe string GetProcessPathFromHwnd(HWND hWnd)
|
||||
{
|
||||
uint pid;
|
||||
var threadId = PInvoke.GetWindowThreadProcessId(hWnd, &pid);
|
||||
if (threadId == 0) return string.Empty;
|
||||
|
||||
var process = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
||||
if (process.Value != IntPtr.Zero)
|
||||
{
|
||||
using var safeHandle = new SafeProcessHandle(process.Value, true);
|
||||
uint capacity = 2000;
|
||||
Span<char> buffer = new char[capacity];
|
||||
fixed (char* pBuffer = buffer)
|
||||
{
|
||||
if (!PInvoke.QueryFullProcessImageName(safeHandle, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, (PWSTR)pBuffer, ref capacity))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return buffer[..(int)capacity].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Explorer
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shopenfolderandselectitems
|
||||
|
|
|
|||
155
Flow.Launcher.Infrastructure/packages.lock.json
Normal file
155
Flow.Launcher.Infrastructure/packages.lock.json
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net9.0-windows7.0": {
|
||||
"Ben.Demystifier": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.4.1, )",
|
||||
"resolved": "0.4.1",
|
||||
"contentHash": "axFeEMfmEORy3ipAzOXG/lE+KcNptRbei3F0C4kQCdeiQtW+qJW90K5iIovITGrdLt8AjhNCwk5qLSX9/rFpoA==",
|
||||
"dependencies": {
|
||||
"System.Reflection.Metadata": "5.0.0"
|
||||
}
|
||||
},
|
||||
"BitFaster.Caching": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.5.3, )",
|
||||
"resolved": "2.5.3",
|
||||
"contentHash": "Vo/39qcam5Xe+DbyfH0JZyqPswdOoa7jv4PGtRJ6Wj8AU+aZ+TuJRlJcIe+MQjRTJwliI8k8VSQpN8sEoBIv2g=="
|
||||
},
|
||||
"CommunityToolkit.Mvvm": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.4.0, )",
|
||||
"resolved": "8.4.0",
|
||||
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
|
||||
},
|
||||
"Fody": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.5.5, )",
|
||||
"resolved": "6.5.5",
|
||||
"contentHash": "Krca41L/PDva1VsmDec5n52cQZxQAQp/bsHdzsNi8iLLI0lqKL94fNIkNaC8tVolUkCyWsbzvxfxJCeD2789fA=="
|
||||
},
|
||||
"MemoryPack": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.21.3, )",
|
||||
"resolved": "1.21.3",
|
||||
"contentHash": "cwCtED8y400vMWx/Vp0QCSeEpVFjDU4JwF52VX9WTaqVERUvNqjG9n6osFlmFuytegyXnHvYEu1qRJ8rv/rkbg==",
|
||||
"dependencies": {
|
||||
"MemoryPack.Core": "1.21.3",
|
||||
"MemoryPack.Generator": "1.21.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading": {
|
||||
"type": "Direct",
|
||||
"requested": "[17.12.19, )",
|
||||
"resolved": "17.12.19",
|
||||
"contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": "17.12.19",
|
||||
"Microsoft.VisualStudio.Validation": "17.8.8"
|
||||
}
|
||||
},
|
||||
"Microsoft.Windows.CsWin32": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.3.106, )",
|
||||
"resolved": "0.3.106",
|
||||
"contentHash": "Mx5fK7uN6fwLR4wUghs6//HonAnwPBNmC2oonyJVhCUlHS/r6SUS3NkBc3+gaQiv+0/9bqdj1oSCKQFkNI+21Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha",
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "60.0.34-preview",
|
||||
"Microsoft.Windows.WDK.Win32Metadata": "0.11.4-experimental"
|
||||
}
|
||||
},
|
||||
"NLog": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.7.10, )",
|
||||
"resolved": "4.7.10",
|
||||
"contentHash": "rcegW7kYOCjl7wX0SzsqpPBqnJ51JKi1WkYb6QBVX0Wc5IgH19Pv4t/co+T0s06OS0Ne44xgkY/mHg0PdrmJow=="
|
||||
},
|
||||
"PropertyChanged.Fody": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.4.0, )",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "IAZyq0uolKo2WYm4mjx+q7A8fSGFT0x2e1s3y+ODn4JI0kqTDoo9GF2tdaypUzRFJZfdMxfC5HZW9QzdJLtOnA==",
|
||||
"dependencies": {
|
||||
"Fody": "6.5.1"
|
||||
}
|
||||
},
|
||||
"System.Drawing.Common": {
|
||||
"type": "Direct",
|
||||
"requested": "[9.0.2, )",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "JU947wzf8JbBS16Y5EIZzAlyQU+k68D7LRx6y03s2wlhlvLqkt/8uPBrjv2hJnnaJKbdb0GhQ3JZsfYXhrRjyg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.SystemEvents": "9.0.2"
|
||||
}
|
||||
},
|
||||
"ToolGood.Words.Pinyin": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.1.4, )",
|
||||
"resolved": "3.0.1.4",
|
||||
"contentHash": "uQo97618y9yzLDxrnehPN+/tuiOlk5BqieEdwctHZOAS9miMXnHKgMFYVw8CSGXRglyTYXlrW7qtUlU7Fje5Ew=="
|
||||
},
|
||||
"JetBrains.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2024.3.0",
|
||||
"contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug=="
|
||||
},
|
||||
"MemoryPack.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.21.3",
|
||||
"contentHash": "ajrYoBWT2aKeH4tlY8q/1C9qK1R/NK+7FkuVOX58ebOSxkABoFTqCR7W+Zk2rakUHZiEgNdRqO67hiRZPq6fLA=="
|
||||
},
|
||||
"MemoryPack.Generator": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.21.3",
|
||||
"contentHash": "hYU0TAIarDKnbkNIWvb7P4zBUL+CTahkuNkczsKvycSMR5kiwQ4IfLexywNKX3s05Izp4gzDSPbueepNWZRpWA=="
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.12.19",
|
||||
"contentHash": "v3IYeedjoktvZ+GqYmLudxZJngmf/YWIxNT2Uy6QMMN19cvw+nkWoip1Gr1RtnFkUo1MPUVMis4C8Kj8d8DpSQ=="
|
||||
},
|
||||
"Microsoft.VisualStudio.Validation": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.8.8",
|
||||
"contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g=="
|
||||
},
|
||||
"Microsoft.Win32.SystemEvents": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "5BkGZ6mHp2dHydR29sb0fDfAuqkv30AHtTih8wMzvPZysOmBFvHfnkR2w3tsc0pSiIg8ZoKyefJXWy9r3pBh0w=="
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Docs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.1.42-alpha",
|
||||
"contentHash": "Z/9po23gUA9aoukirh2ItMU2ZS9++Js9Gdds9fu5yuMojDrmArvY2y+tq9985tR3cxFxpZO1O35Wjfo0khj5HA=="
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "60.0.34-preview",
|
||||
"contentHash": "TA3DUNi4CTeo+ItTXBnGZFt2159XOGSl0UOlG5vjDj4WHqZjhwYyyUnzOtrbCERiSaP2Hzg7otJNWwOSZgutyA=="
|
||||
},
|
||||
"Microsoft.Windows.WDK.Win32Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.11.4-experimental",
|
||||
"contentHash": "bf5MCmUyZf0gBlYQjx9UpRAZWBkRndyt9XicR+UNLvAUAFTZQbu6YaX/sNKZlR98Grn0gydfh/yT4I3vc0AIQA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "60.0.34-preview"
|
||||
}
|
||||
},
|
||||
"System.Reflection.Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ=="
|
||||
},
|
||||
"flow.launcher.plugin": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"JetBrains.Annotations": "[2024.3.0, )",
|
||||
"PropertyChanged.Fody": "[3.4.0, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Flow.Launcher.Plugin/DialogJumpResult.cs
Normal file
92
Flow.Launcher.Plugin/DialogJumpResult.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a result of a <see cref="Query"/> executed by a plugin in Dialog Jump window
|
||||
/// </summary>
|
||||
public class DialogJumpResult : Result
|
||||
{
|
||||
/// <summary>
|
||||
/// This holds the path which can be provided by plugin to be navigated to the
|
||||
/// file dialog when records in Dialog Jump window is right clicked on a result.
|
||||
/// </summary>
|
||||
public required string DialogJumpPath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Clones the current Dialog Jump result
|
||||
/// </summary>
|
||||
public new DialogJumpResult Clone()
|
||||
{
|
||||
return new DialogJumpResult
|
||||
{
|
||||
Title = Title,
|
||||
SubTitle = SubTitle,
|
||||
ActionKeywordAssigned = ActionKeywordAssigned,
|
||||
CopyText = CopyText,
|
||||
AutoCompleteText = AutoCompleteText,
|
||||
IcoPath = IcoPath,
|
||||
BadgeIcoPath = BadgeIcoPath,
|
||||
RoundedIcon = RoundedIcon,
|
||||
Icon = Icon,
|
||||
BadgeIcon = BadgeIcon,
|
||||
Glyph = Glyph,
|
||||
Action = Action,
|
||||
AsyncAction = AsyncAction,
|
||||
Score = Score,
|
||||
TitleHighlightData = TitleHighlightData,
|
||||
OriginQuery = OriginQuery,
|
||||
PluginDirectory = PluginDirectory,
|
||||
ContextData = ContextData,
|
||||
PluginID = PluginID,
|
||||
TitleToolTip = TitleToolTip,
|
||||
SubTitleToolTip = SubTitleToolTip,
|
||||
PreviewPanel = PreviewPanel,
|
||||
ProgressBar = ProgressBar,
|
||||
ProgressBarColor = ProgressBarColor,
|
||||
Preview = Preview,
|
||||
AddSelectedCount = AddSelectedCount,
|
||||
RecordKey = RecordKey,
|
||||
ShowBadge = ShowBadge,
|
||||
DialogJumpPath = DialogJumpPath
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert <see cref="Result"/> to <see cref="DialogJumpResult"/>.
|
||||
/// </summary>
|
||||
public static DialogJumpResult From(Result result, string dialogJumpPath)
|
||||
{
|
||||
return new DialogJumpResult
|
||||
{
|
||||
Title = result.Title,
|
||||
SubTitle = result.SubTitle,
|
||||
ActionKeywordAssigned = result.ActionKeywordAssigned,
|
||||
CopyText = result.CopyText,
|
||||
AutoCompleteText = result.AutoCompleteText,
|
||||
IcoPath = result.IcoPath,
|
||||
BadgeIcoPath = result.BadgeIcoPath,
|
||||
RoundedIcon = result.RoundedIcon,
|
||||
Icon = result.Icon,
|
||||
BadgeIcon = result.BadgeIcon,
|
||||
Glyph = result.Glyph,
|
||||
Action = result.Action,
|
||||
AsyncAction = result.AsyncAction,
|
||||
Score = result.Score,
|
||||
TitleHighlightData = result.TitleHighlightData,
|
||||
OriginQuery = result.OriginQuery,
|
||||
PluginDirectory = result.PluginDirectory,
|
||||
ContextData = result.ContextData,
|
||||
PluginID = result.PluginID,
|
||||
TitleToolTip = result.TitleToolTip,
|
||||
SubTitleToolTip = result.SubTitleToolTip,
|
||||
PreviewPanel = result.PreviewPanel,
|
||||
ProgressBar = result.ProgressBar,
|
||||
ProgressBarColor = result.ProgressBarColor,
|
||||
Preview = result.Preview,
|
||||
AddSelectedCount = result.AddSelectedCount,
|
||||
RecordKey = result.RecordKey,
|
||||
ShowBadge = result.ShowBadge,
|
||||
DialogJumpPath = dialogJumpPath
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ProjectGuid>{8451ECDD-2EA4-4966-BB0A-7BBC40138E80}</ProjectGuid>
|
||||
<UseWPF>true</UseWPF>
|
||||
<OutputType>Library</OutputType>
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
|
|||
24
Flow.Launcher.Plugin/Interfaces/IAsyncDialogJump.cs
Normal file
24
Flow.Launcher.Plugin/Interfaces/IAsyncDialogJump.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronous Dialog Jump Model
|
||||
/// </summary>
|
||||
public interface IAsyncDialogJump : IFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronous querying for Dialog Jump window
|
||||
/// </summary>
|
||||
/// <para>
|
||||
/// If the Querying method requires high IO transmission
|
||||
/// or performing CPU intense jobs (performing better with cancellation), please use this IAsyncDialogJump interface
|
||||
/// </para>
|
||||
/// <param name="query">Query to search</param>
|
||||
/// <param name="token">Cancel when querying job is obsolete</param>
|
||||
/// <returns></returns>
|
||||
Task<List<DialogJumpResult>> QueryDialogJumpAsync(Query query, CancellationToken token);
|
||||
}
|
||||
}
|
||||
29
Flow.Launcher.Plugin/Interfaces/IDialogJump.cs
Normal file
29
Flow.Launcher.Plugin/Interfaces/IDialogJump.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronous Dialog Jump Model
|
||||
/// <para>
|
||||
/// If the Querying method requires high IO transmission
|
||||
/// or performing CPU intense jobs (performing better with cancellation), please try the IAsyncDialogJump interface
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IDialogJump : IAsyncDialogJump
|
||||
{
|
||||
/// <summary>
|
||||
/// Querying for Dialog Jump window
|
||||
/// <para>
|
||||
/// This method will be called within a Task.Run,
|
||||
/// so please avoid synchrously wait for long.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="query">Query to search</param>
|
||||
/// <returns></returns>
|
||||
List<DialogJumpResult> QueryDialogJump(Query query);
|
||||
|
||||
Task<List<DialogJumpResult>> IAsyncDialogJump.QueryDialogJumpAsync(Query query, CancellationToken token) => Task.Run(() => QueryDialogJump(query), token);
|
||||
}
|
||||
}
|
||||
96
Flow.Launcher.Plugin/Interfaces/IDialogJumpDialog.cs
Normal file
96
Flow.Launcher.Plugin/Interfaces/IDialogJumpDialog.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for handling file dialog instances in DialogJump.
|
||||
/// </summary>
|
||||
public interface IDialogJumpDialog : IFeatures, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if the foreground window is a file dialog instance.
|
||||
/// </summary>
|
||||
/// <param name="hwnd">
|
||||
/// The handle of the foreground window to check.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The window if the foreground window is a file dialog instance. Null if it is not.
|
||||
/// </returns>
|
||||
IDialogJumpDialogWindow? CheckDialogWindow(IntPtr hwnd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for handling a specific file dialog window in DialogJump.
|
||||
/// </summary>
|
||||
public interface IDialogJumpDialogWindow : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The handle of the dialog window.
|
||||
/// </summary>
|
||||
IntPtr Handle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the current tab of the dialog window.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IDialogJumpDialogWindowTab GetCurrentTab();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for handling a specific tab in a file dialog window in DialogJump.
|
||||
/// </summary>
|
||||
public interface IDialogJumpDialogWindowTab : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The handle of the dialog tab.
|
||||
/// </summary>
|
||||
IntPtr Handle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the current folder path of the dialog tab.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetCurrentFolder();
|
||||
|
||||
/// <summary>
|
||||
/// Get the current file of the dialog tab.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetCurrentFile();
|
||||
|
||||
/// <summary>
|
||||
/// Jump to a folder in the dialog tab.
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// The path to the folder to jump to.
|
||||
/// </param>
|
||||
/// <param name="auto">
|
||||
/// Whether folder jump is under automatical mode.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the jump was successful, false otherwise.
|
||||
/// </returns>
|
||||
bool JumpFolder(string path, bool auto);
|
||||
|
||||
/// <summary>
|
||||
/// Jump to a file in the dialog tab.
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// The path to the file to jump to.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the jump was successful, false otherwise.
|
||||
/// </returns>
|
||||
bool JumpFile(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Open the file in the dialog tab.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if the file was opened successfully, false otherwise.
|
||||
/// </returns>
|
||||
bool Open();
|
||||
}
|
||||
}
|
||||
40
Flow.Launcher.Plugin/Interfaces/IDialogJumpExplorer.cs
Normal file
40
Flow.Launcher.Plugin/Interfaces/IDialogJumpExplorer.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for handling file explorer instances in DialogJump.
|
||||
/// </summary>
|
||||
public interface IDialogJumpExplorer : IFeatures, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if the foreground window is a Windows Explorer instance.
|
||||
/// </summary>
|
||||
/// <param name="hwnd">
|
||||
/// The handle of the foreground window to check.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The window if the foreground window is a file explorer instance. Null if it is not.
|
||||
/// </returns>
|
||||
IDialogJumpExplorerWindow? CheckExplorerWindow(IntPtr hwnd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for handling a specific file explorer window in DialogJump.
|
||||
/// </summary>
|
||||
public interface IDialogJumpExplorerWindow : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The handle of the explorer window.
|
||||
/// </summary>
|
||||
IntPtr Handle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the current folder path of the explorer window.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string? GetExplorerPath();
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,6 @@ namespace Flow.Launcher.Plugin
|
|||
|
||||
Task IAsyncPlugin.InitAsync(PluginInitContext context) => Task.Run(() => Init(context));
|
||||
|
||||
Task<List<Result>> IAsyncPlugin.QueryAsync(Query query, CancellationToken token) => Task.Run(() => Query(query));
|
||||
Task<List<Result>> IAsyncPlugin.QueryAsync(Query query, CancellationToken token) => Task.Run(() => Query(query), token);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -613,5 +613,17 @@ namespace Flow.Launcher.Plugin
|
|||
/// Invoked when the actual theme of the application has changed. Currently, the plugin will continue to be subscribed even if it is turned off.
|
||||
/// </summary>
|
||||
event ActualApplicationThemeChangedEventHandler ActualApplicationThemeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Get the user data directory of Flow Launcher.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetDataDirectory();
|
||||
|
||||
/// <summary>
|
||||
/// Get the log directory of Flow Launcher.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetLogDirectory();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ namespace Flow.Launcher.Plugin
|
|||
Preview = Preview,
|
||||
AddSelectedCount = AddSelectedCount,
|
||||
RecordKey = RecordKey,
|
||||
ShowBadge = ShowBadge,
|
||||
ShowBadge = ShowBadge
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
77
Flow.Launcher.Plugin/packages.lock.json
Normal file
77
Flow.Launcher.Plugin/packages.lock.json
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net9.0-windows7.0": {
|
||||
"Fody": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.5.4, )",
|
||||
"resolved": "6.5.4",
|
||||
"contentHash": "GXZuti428IZctfby10xkMbWLCibcb6s29I/psLbBoO2vHJI5eTNVybnlV/Wi1tlIu9GG0bgW/PQwMH+MCldHxw=="
|
||||
},
|
||||
"JetBrains.Annotations": {
|
||||
"type": "Direct",
|
||||
"requested": "[2024.3.0, )",
|
||||
"resolved": "2024.3.0",
|
||||
"contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug=="
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.1.1, )",
|
||||
"resolved": "1.1.1",
|
||||
"contentHash": "IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "1.1.1",
|
||||
"Microsoft.SourceLink.Common": "1.1.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.Windows.CsWin32": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.3.106, )",
|
||||
"resolved": "0.3.106",
|
||||
"contentHash": "Mx5fK7uN6fwLR4wUghs6//HonAnwPBNmC2oonyJVhCUlHS/r6SUS3NkBc3+gaQiv+0/9bqdj1oSCKQFkNI+21Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha",
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "60.0.34-preview",
|
||||
"Microsoft.Windows.WDK.Win32Metadata": "0.11.4-experimental"
|
||||
}
|
||||
},
|
||||
"PropertyChanged.Fody": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.4.0, )",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "IAZyq0uolKo2WYm4mjx+q7A8fSGFT0x2e1s3y+ODn4JI0kqTDoo9GF2tdaypUzRFJZfdMxfC5HZW9QzdJLtOnA==",
|
||||
"dependencies": {
|
||||
"Fody": "6.5.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.1",
|
||||
"contentHash": "AT3HlgTjsqHnWpBHSNeR0KxbLZD7bztlZVj7I8vgeYG9SYqbeFGh0TM/KVtC6fg53nrWHl3VfZFvb5BiQFcY6Q=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.1",
|
||||
"contentHash": "WMcGpWKrmJmzrNeuaEb23bEMnbtR/vLmvZtkAP5qWu7vQsY59GqfRJd65sFpBszbd2k/bQ8cs8eWawQKAabkVg=="
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Docs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.1.42-alpha",
|
||||
"contentHash": "Z/9po23gUA9aoukirh2ItMU2ZS9++Js9Gdds9fu5yuMojDrmArvY2y+tq9985tR3cxFxpZO1O35Wjfo0khj5HA=="
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "60.0.34-preview",
|
||||
"contentHash": "TA3DUNi4CTeo+ItTXBnGZFt2159XOGSl0UOlG5vjDj4WHqZjhwYyyUnzOtrbCERiSaP2Hzg7otJNWwOSZgutyA=="
|
||||
},
|
||||
"Microsoft.Windows.WDK.Win32Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.11.4-experimental",
|
||||
"contentHash": "bf5MCmUyZf0gBlYQjx9UpRAZWBkRndyt9XicR+UNLvAUAFTZQbu6YaX/sNKZlR98Grn0gydfh/yT4I3vc0AIQA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "60.0.34-preview"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
265
Flow.Launcher.Test/ChineseDetectionPerformanceTest.cs
Normal file
265
Flow.Launcher.Test/ChineseDetectionPerformanceTest.cs
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using ToolGood.Words.Pinyin;
|
||||
|
||||
namespace Flow.Launcher.Test
|
||||
{
|
||||
/// <summary>
|
||||
/// Performance test comparing ContainsChinese() vs WordsHelper.HasChinese()
|
||||
///
|
||||
/// This test verifies:
|
||||
/// 1. Both methods produce identical results (correctness)
|
||||
/// 2. Performance characteristics of both implementations
|
||||
/// 3. Memory allocation patterns
|
||||
///
|
||||
/// The ContainsChinese() method uses optimized Unicode range checking with ReadOnlySpan
|
||||
/// while WordsHelper.HasChinese() uses the ToolGood.Words library implementation.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class ChineseDetectionPerformanceTest
|
||||
{
|
||||
private readonly List<string> _testStrings = new()
|
||||
{
|
||||
// Pure English - should return false
|
||||
"Hello World",
|
||||
"Visual Studio Code",
|
||||
"Microsoft Office 2023",
|
||||
"Adobe Photoshop Creative Suite",
|
||||
"Google Chrome Browser Application",
|
||||
|
||||
// Pure Chinese - should return true
|
||||
"你好世界",
|
||||
"微软办公软件",
|
||||
"谷歌浏览器",
|
||||
"北京大学计算机科学与技术学院",
|
||||
"中华人民共和国国家发展和改革委员会",
|
||||
|
||||
// Mixed content - should return true
|
||||
"Hello 世界",
|
||||
"Visual Studio 代码编辑器",
|
||||
"QQ音乐 Music Player",
|
||||
"Windows 10 操作系统",
|
||||
"GitHub 代码仓库管理平台",
|
||||
|
||||
// Edge cases
|
||||
"",
|
||||
" ",
|
||||
"123456",
|
||||
"!@#$%^&*()",
|
||||
"café résumé naïve", // Accented characters (not Chinese)
|
||||
|
||||
// Long strings for performance testing
|
||||
"This is a very long English string that contains no Chinese characters but is designed to test performance with longer text content that might appear in file names or application descriptions",
|
||||
"这是一个非常长的中文字符串,包含了很多汉字,用来测试在处理较长中文文本时的性能表现,比如可能出现在文件名或应用程序描述中的文本内容",
|
||||
"This is a mixed 混合内容的字符串 that contains both English and Chinese characters 中英文混合 to test performance with 复杂的文本内容 in real-world scenarios 真实场景中的应用"
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void ContainsChinese_CorrectnessTest()
|
||||
{
|
||||
// Verify ContainsChinese works correctly for known cases
|
||||
ClassicAssert.IsFalse(ContainsChinese("Hello World"), "Pure English should return false");
|
||||
ClassicAssert.IsTrue(ContainsChinese("你好世界"), "Pure Chinese should return true");
|
||||
ClassicAssert.IsTrue(ContainsChinese("Hello 世界"), "Mixed content should return true");
|
||||
ClassicAssert.IsFalse(ContainsChinese(""), "Empty string should return false");
|
||||
ClassicAssert.IsFalse(ContainsChinese("123456"), "Numbers should return false");
|
||||
ClassicAssert.IsFalse(ContainsChinese("café résumé"), "Accented characters should return false");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WordsHelper_CorrectnessTest()
|
||||
{
|
||||
// Verify WordsHelper.HasChinese works correctly for known cases
|
||||
ClassicAssert.IsFalse(WordsHelper.HasChinese("Hello World"), "Pure English should return false");
|
||||
ClassicAssert.IsTrue(WordsHelper.HasChinese("你好世界"), "Pure Chinese should return true");
|
||||
ClassicAssert.IsTrue(WordsHelper.HasChinese("Hello 世界"), "Mixed content should return true");
|
||||
ClassicAssert.IsFalse(WordsHelper.HasChinese(""), "Empty string should return false");
|
||||
ClassicAssert.IsFalse(WordsHelper.HasChinese("123456"), "Numbers should return false");
|
||||
ClassicAssert.IsFalse(WordsHelper.HasChinese("café résumé"), "Accented characters should return false");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BothMethods_ShouldProduceSameResults()
|
||||
{
|
||||
// Critical test: verify both methods produce identical results for all test cases
|
||||
foreach (var testString in _testStrings)
|
||||
{
|
||||
var wordsHelperResult = WordsHelper.HasChinese(testString);
|
||||
var containsChineseResult = ContainsChinese(testString);
|
||||
|
||||
ClassicAssert.AreEqual(wordsHelperResult, containsChineseResult,
|
||||
$"Results differ for string: '{testString}'. WordsHelper: {wordsHelperResult}, ContainsChinese: {containsChineseResult}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"✓ Both methods produce identical results for all {_testStrings.Count} test cases");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PerformanceComparison_BasicBenchmark()
|
||||
{
|
||||
const int iterations = 1000000;
|
||||
|
||||
Console.WriteLine("=== CHINESE CHARACTER DETECTION PERFORMANCE TEST ===");
|
||||
Console.WriteLine($"Test iterations: {iterations:N0}");
|
||||
Console.WriteLine($"Test strings: {_testStrings.Count}");
|
||||
Console.WriteLine($"Total operations: {iterations * _testStrings.Count:N0}");
|
||||
Console.WriteLine();
|
||||
|
||||
// Warmup to ensure JIT compilation
|
||||
Console.WriteLine("Warming up...");
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
foreach (var testString in _testStrings)
|
||||
{
|
||||
_ = ContainsChinese(testString);
|
||||
_ = WordsHelper.HasChinese(testString);
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark ContainsChinese method
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
var sw1 = System.Diagnostics.Stopwatch.StartNew();
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
foreach (var testString in _testStrings)
|
||||
{
|
||||
_ = ContainsChinese(testString);
|
||||
}
|
||||
}
|
||||
sw1.Stop();
|
||||
|
||||
// Benchmark WordsHelper.HasChinese method
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
var sw2 = System.Diagnostics.Stopwatch.StartNew();
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
foreach (var testString in _testStrings)
|
||||
{
|
||||
_ = WordsHelper.HasChinese(testString);
|
||||
}
|
||||
}
|
||||
sw2.Stop();
|
||||
|
||||
// Calculate and display results
|
||||
var containsChineseMs = sw1.Elapsed.TotalMilliseconds;
|
||||
var wordsHelperMs = sw2.Elapsed.TotalMilliseconds;
|
||||
var speedRatio = wordsHelperMs / containsChineseMs;
|
||||
var timeDifference = wordsHelperMs - containsChineseMs;
|
||||
|
||||
Console.WriteLine("RESULTS:");
|
||||
Console.WriteLine($"ContainsChinese(): {containsChineseMs:F3} ms");
|
||||
Console.WriteLine($"WordsHelper.HasChinese(): {wordsHelperMs:F3} ms");
|
||||
Console.WriteLine($"Time difference: {timeDifference:F3} ms");
|
||||
Console.WriteLine($"Speed improvement: {speedRatio:F2}x");
|
||||
Console.WriteLine($"Performance gain: {((speedRatio - 1) * 100):F1}%");
|
||||
Console.WriteLine();
|
||||
|
||||
if (speedRatio > 1.0)
|
||||
{
|
||||
Console.WriteLine($"✓ ContainsChinese() is {speedRatio:F2}x faster than WordsHelper.HasChinese()");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"⚠ WordsHelper.HasChinese() is {(1/speedRatio):F2}x faster than ContainsChinese()");
|
||||
}
|
||||
|
||||
// Test always passes - this is a measurement test
|
||||
ClassicAssert.IsTrue(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PerformanceComparison_ByStringType()
|
||||
{
|
||||
Console.WriteLine("=== PERFORMANCE BY STRING TYPE ===");
|
||||
|
||||
var categories = new Dictionary<string, List<string>>
|
||||
{
|
||||
["Pure English"] = _testStrings.Where(s => !ContainsChinese(s) && s.All(c => c <= 127)).ToList(),
|
||||
["Pure Chinese"] = _testStrings.Where(s => ContainsChinese(s) && s.All(c => IsChineseCharacter(c) || char.IsWhiteSpace(c))).ToList(),
|
||||
["Mixed Content"] = _testStrings.Where(s => ContainsChinese(s) && s.Any(c => c <= 127 && char.IsLetter(c))).ToList(),
|
||||
["Edge Cases"] = _testStrings.Where(s => string.IsNullOrWhiteSpace(s) || s.All(c => !char.IsLetter(c))).ToList()
|
||||
};
|
||||
|
||||
foreach (var category in categories)
|
||||
{
|
||||
if (category.Value.Count == 0) continue;
|
||||
|
||||
Console.WriteLine($"\n{category.Key} ({category.Value.Count} strings):");
|
||||
|
||||
var sample = category.Value.First();
|
||||
var displayText = sample.Length > 40 ? sample.Substring(0, 40) + "..." : sample;
|
||||
Console.WriteLine($" Sample: '{displayText}'");
|
||||
|
||||
const int categoryIterations = 5000;
|
||||
|
||||
// Test each method
|
||||
var sw1 = System.Diagnostics.Stopwatch.StartNew();
|
||||
for (int i = 0; i < categoryIterations; i++)
|
||||
{
|
||||
foreach (var str in category.Value)
|
||||
{
|
||||
_ = ContainsChinese(str);
|
||||
}
|
||||
}
|
||||
sw1.Stop();
|
||||
|
||||
var sw2 = System.Diagnostics.Stopwatch.StartNew();
|
||||
for (int i = 0; i < categoryIterations; i++)
|
||||
{
|
||||
foreach (var str in category.Value)
|
||||
{
|
||||
_ = WordsHelper.HasChinese(str);
|
||||
}
|
||||
}
|
||||
sw2.Stop();
|
||||
|
||||
var ratio = (double)sw2.ElapsedTicks / sw1.ElapsedTicks;
|
||||
Console.WriteLine($" Performance: ContainsChinese is {ratio:F2}x faster");
|
||||
}
|
||||
|
||||
ClassicAssert.IsTrue(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimized Chinese character detection using comprehensive CJK Unicode ranges
|
||||
/// This method uses ReadOnlySpan for better performance and covers all CJK character ranges
|
||||
/// </summary>
|
||||
private static bool ContainsChinese(ReadOnlySpan<char> text)
|
||||
{
|
||||
foreach (var c in text)
|
||||
{
|
||||
if (IsChineseCharacter(c))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a character is a Chinese character using comprehensive Unicode ranges
|
||||
/// Covers CJK Unified Ideographs and all extension blocks
|
||||
/// </summary>
|
||||
private static bool IsChineseCharacter(char c)
|
||||
{
|
||||
return (c >= 0x4E00 && c <= 0x9FFF) || // CJK Unified Ideographs (most common Chinese characters)
|
||||
(c >= 0x3400 && c <= 0x4DBF) || // CJK Extension A
|
||||
(c >= 0x20000 && c <= 0x2A6DF) || // CJK Extension B
|
||||
(c >= 0x2A700 && c <= 0x2B73F) || // CJK Extension C
|
||||
(c >= 0x2B740 && c <= 0x2B81F) || // CJK Extension D
|
||||
(c >= 0x2B820 && c <= 0x2CEAF) || // CJK Extension E
|
||||
(c >= 0x2CEB0 && c <= 0x2EBEF) || // CJK Extension F
|
||||
(c >= 0xF900 && c <= 0xFAFF) || // CJK Compatibility Ideographs
|
||||
(c >= 0x2F800 && c <= 0x2FA1F); // CJK Compatibility Supplement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
|
||||
<ProjectGuid>{FF742965-9A80-41A5-B042-D6C7D3A21708}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using Flow.Launcher.Infrastructure;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
|
||||
|
|
@ -34,22 +36,21 @@ namespace Flow.Launcher.Test
|
|||
mapping.AddNewIndex(2, 2);
|
||||
mapping.AddNewIndex(5, 3);
|
||||
|
||||
|
||||
var result = mapping.MapToOriginalIndex(translatedIndex);
|
||||
ClassicAssert.AreEqual(expectedOriginalIndex, result);
|
||||
}
|
||||
|
||||
private int GetOriginalToTranslatedCount(TranslationMapping mapping)
|
||||
private static int GetOriginalToTranslatedCount(TranslationMapping mapping)
|
||||
{
|
||||
var field = typeof(TranslationMapping).GetField("originalToTranslated", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var list = (System.Collections.Generic.List<int>)field.GetValue(mapping);
|
||||
var field = typeof(TranslationMapping).GetField("_originalToTranslated", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var list = (List<int>)field.GetValue(mapping);
|
||||
return list.Count;
|
||||
}
|
||||
|
||||
private int GetOriginalToTranslatedAt(TranslationMapping mapping, int index)
|
||||
private static int GetOriginalToTranslatedAt(TranslationMapping mapping, int index)
|
||||
{
|
||||
var field = typeof(TranslationMapping).GetField("originalToTranslated", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var list = (System.Collections.Generic.List<int>)field.GetValue(mapping);
|
||||
var field = typeof(TranslationMapping).GetField("_originalToTranslated", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var list = (List<int>)field.GetValue(mapping);
|
||||
return list[index];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using Flow.Launcher.Infrastructure;
|
|||
using Flow.Launcher.Infrastructure.Http;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.DialogJump;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
|
@ -191,6 +192,9 @@ namespace Flow.Launcher
|
|||
// Enable Win32 dark mode if the system is in dark mode before creating all windows
|
||||
Win32Helper.EnableWin32DarkMode(_settings.ColorScheme);
|
||||
|
||||
// Initialize language before portable clean up since it needs translations
|
||||
await Ioc.Default.GetRequiredService<Internationalization>().InitializeLanguageAsync();
|
||||
|
||||
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
|
||||
|
||||
API.LogInfo(ClassName, "Begin Flow Launcher startup ----------------------------------------------------");
|
||||
|
|
@ -216,8 +220,8 @@ namespace Flow.Launcher
|
|||
|
||||
await PluginManager.InitializePluginsAsync();
|
||||
|
||||
// Change language after all plugins are initialized because we need to update plugin title based on their api
|
||||
await Ioc.Default.GetRequiredService<Internationalization>().InitializeLanguageAsync();
|
||||
// Update plugin titles after plugins are initialized with their api instances
|
||||
Internationalization.UpdatePluginMetadataTranslations();
|
||||
|
||||
await imageLoadertask;
|
||||
|
||||
|
|
@ -233,12 +237,16 @@ namespace Flow.Launcher
|
|||
// Initialize theme for main window
|
||||
Ioc.Default.GetRequiredService<Theme>().ChangeTheme();
|
||||
|
||||
DialogJump.InitializeDialogJump(PluginManager.GetDialogJumpExplorers(), PluginManager.GetDialogJumpDialogs());
|
||||
DialogJump.SetupDialogJump(_settings.EnableDialogJump);
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
RegisterExitEvents();
|
||||
|
||||
AutoStartup();
|
||||
AutoUpdates();
|
||||
AutoPluginUpdates();
|
||||
|
||||
API.SaveAppAllSettings();
|
||||
API.LogInfo(ClassName, "End Flow Launcher startup ----------------------------------------------------");
|
||||
|
|
@ -251,7 +259,7 @@ namespace Flow.Launcher
|
|||
/// Check startup only for Release
|
||||
/// </summary>
|
||||
[Conditional("RELEASE")]
|
||||
private void AutoStartup()
|
||||
private static void AutoStartup()
|
||||
{
|
||||
// we try to enable auto-startup on first launch, or reenable if it was removed
|
||||
// but the user still has the setting set
|
||||
|
|
@ -266,13 +274,13 @@ namespace Flow.Launcher
|
|||
// but if it fails (permissions, etc) then don't keep retrying
|
||||
// this also gives the user a visual indication in the Settings widget
|
||||
_settings.StartFlowLauncherOnSystemStartup = false;
|
||||
API.ShowMsg(API.GetTranslation("setAutoStartFailed"), e.Message);
|
||||
API.ShowMsgError(API.GetTranslation("setAutoStartFailed"), e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("RELEASE")]
|
||||
private void AutoUpdates()
|
||||
private static void AutoUpdates()
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
|
|
@ -289,6 +297,37 @@ namespace Flow.Launcher
|
|||
});
|
||||
}
|
||||
|
||||
private static void AutoPluginUpdates()
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (_settings.AutoUpdatePlugins)
|
||||
{
|
||||
// check plugin updates every 5 hour
|
||||
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
|
||||
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
|
||||
{
|
||||
Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
|
||||
pluginUpdateWindow.ShowDialog();
|
||||
});
|
||||
});
|
||||
|
||||
while (await timer.WaitForNextTickAsync())
|
||||
// check updates on startup
|
||||
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
|
||||
{
|
||||
Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
|
||||
pluginUpdateWindow.ShowDialog();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Register Events
|
||||
|
|
@ -380,6 +419,7 @@ namespace Flow.Launcher
|
|||
// since some resources owned by the thread need to be disposed.
|
||||
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
|
||||
_mainVM?.Dispose();
|
||||
DialogJump.Dispose();
|
||||
}
|
||||
|
||||
API.LogInfo(ClassName, "End Flow Launcher dispose ----------------------------------------------------");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>false</UseWindowsForms>
|
||||
<StartupObject>Flow.Launcher.App</StartupObject>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
|
@ -77,7 +78,7 @@
|
|||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Segoe Fluent Icons.ttf">
|
||||
<Content Include="Resources\SegoeFluentIcons.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
|
@ -89,7 +90,6 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="InputSimulator" Version="1.0.4" />
|
||||
<PackageReference Include="MdXaml" Version="1.27.0" />
|
||||
<PackageReference Include="MdXaml.AnimatedGif" Version="1.27.0" />
|
||||
<PackageReference Include="MdXaml.Html" Version="1.27.0" />
|
||||
|
|
@ -102,7 +102,6 @@
|
|||
<!-- ModernWpfUI v0.9.5 introduced WinRT changes that causes Notification platform unavailable error on some machines -->
|
||||
<!-- https://github.com/Flow-Launcher/Flow.Launcher/issues/1772#issuecomment-1502440801 -->
|
||||
<PackageReference Include="ModernWpfUI" Version="0.9.4" />
|
||||
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using System;
|
||||
using NHotkey;
|
||||
using NHotkey.Wpf;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using System;
|
||||
using ChefKeys;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.DialogJump;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using NHotkey;
|
||||
using NHotkey.Wpf;
|
||||
|
||||
namespace Flow.Launcher.Helper;
|
||||
|
||||
|
|
@ -22,6 +23,10 @@ internal static class HotKeyMapper
|
|||
_settings = Ioc.Default.GetService<Settings>();
|
||||
|
||||
SetHotkey(_settings.Hotkey, OnToggleHotkey);
|
||||
if (_settings.EnableDialogJump)
|
||||
{
|
||||
SetHotkey(_settings.DialogJumpHotkey, DialogJump.OnToggleHotkey);
|
||||
}
|
||||
LoadCustomPluginHotkey();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
|
@ -110,7 +110,8 @@ namespace Flow.Launcher
|
|||
SelectPrevItemHotkey,
|
||||
SelectPrevItemHotkey2,
|
||||
SelectNextItemHotkey,
|
||||
SelectNextItemHotkey2
|
||||
SelectNextItemHotkey2,
|
||||
DialogJumpHotkey,
|
||||
}
|
||||
|
||||
// We can initialize settings in static field because it has been constructed in App constuctor
|
||||
|
|
@ -142,6 +143,7 @@ namespace Flow.Launcher
|
|||
HotkeyType.SelectPrevItemHotkey2 => _settings.SelectPrevItemHotkey2,
|
||||
HotkeyType.SelectNextItemHotkey => _settings.SelectNextItemHotkey,
|
||||
HotkeyType.SelectNextItemHotkey2 => _settings.SelectNextItemHotkey2,
|
||||
HotkeyType.DialogJumpHotkey => _settings.DialogJumpHotkey,
|
||||
_ => throw new System.NotImplementedException("Hotkey type not set")
|
||||
};
|
||||
}
|
||||
|
|
@ -201,6 +203,9 @@ namespace Flow.Launcher
|
|||
case HotkeyType.SelectNextItemHotkey2:
|
||||
_settings.SelectNextItemHotkey2 = value;
|
||||
break;
|
||||
case HotkeyType.DialogJumpHotkey:
|
||||
_settings.DialogJumpHotkey = value;
|
||||
break;
|
||||
default:
|
||||
throw new System.NotImplementedException("Hotkey type not set");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,22 @@
|
|||
<system:String x:Key="failedToInitializePluginsTitle">Fail to Init Plugins</system:String>
|
||||
<system:String x:Key="failedToInitializePluginsMessage">Plugins: {0} - fail to load and would be disabled, please contact plugin creator for help</system:String>
|
||||
|
||||
<!-- Portable -->
|
||||
<system:String x:Key="restartToDisablePortableMode">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</system:String>
|
||||
<system:String x:Key="restartToEnablePortableMode">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</system:String>
|
||||
<system:String x:Key="moveToDifferentLocation">Flow Launcher has detected you enabled portable mode, would you like to move it to a different location?</system:String>
|
||||
<system:String x:Key="shortcutsUninstallerCreated">Flow Launcher has detected you disabled portable mode, the relevant shortcuts and uninstaller entry have been created</system:String>
|
||||
<system:String x:Key="userDataDuplicated">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.</system:String>
|
||||
|
||||
<!-- Plugin Loader -->
|
||||
<system:String x:Key="pluginHasErrored">The following plugin has errored and cannot be loaded:</system:String>
|
||||
<system:String x:Key="pluginsHaveErrored">The following plugins have errored and cannot be loaded:</system:String>
|
||||
<system:String x:Key="referToLogs">Please refer to the logs for more information</system:String>
|
||||
|
||||
<!-- Http -->
|
||||
<system:String x:Key="pleaseTryAgain">Please try again</system:String>
|
||||
<system:String x:Key="parseProxyFailed">Unable to parse Http Proxy</system:String>
|
||||
|
||||
<!-- 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="unregisterHotkeyFailed">Failed to unregister hotkey "{0}". Please try again or see log for details</system:String>
|
||||
|
|
@ -93,6 +109,7 @@
|
|||
<system:String x:Key="typingStartEn">Always Start Typing in English Mode</system:String>
|
||||
<system:String x:Key="typingStartEnTooltip">Temporarily change your input method to English mode when activating Flow.</system:String>
|
||||
<system:String x:Key="autoUpdates">Auto Update</system:String>
|
||||
<system:String x:Key="autoUpdatesTooltip">Automatically check and update the app when available</system:String>
|
||||
<system:String x:Key="select">Select</system:String>
|
||||
<system:String x:Key="hideOnStartup">Hide Flow Launcher on startup</system:String>
|
||||
<system:String x:Key="hideOnStartupToolTip">Flow Launcher search window is hidden in the tray after starting up.</system:String>
|
||||
|
|
@ -106,7 +123,7 @@
|
|||
<system:String x:Key="ShouldUsePinyin">Search with Pinyin</system:String>
|
||||
<system:String x:Key="ShouldUsePinyinToolTip">Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese.</system:String>
|
||||
<system:String x:Key="ShouldUseDoublePinyin">Use Double Pinyin</system:String>
|
||||
<system:String x:Key="ShouldUseDoublePinyinToolTip">Allows using Double Pinyin to search. Double Pinyin is a variation of Pinyin that uses two characters.</system:String>
|
||||
<system:String x:Key="ShouldUseDoublePinyinToolTip">Use Double Pinyin instead of Full Pinyin to search.</system:String>
|
||||
<system:String x:Key="DoublePinyinSchema">Double Pinyin Schema</system:String>
|
||||
<system:String x:Key="DoublePinyinSchemasXiaoHe">Xiao He</system:String>
|
||||
<system:String x:Key="DoublePinyinSchemasZiRanMa">Zi Ran Ma</system:String>
|
||||
|
|
@ -117,7 +134,7 @@
|
|||
<system:String x:Key="DoublePinyinSchemasXingKongJianDao">Xing Kong Jian Dao</system:String>
|
||||
<system:String x:Key="DoublePinyinSchemasDaNiu">Da Niu</system:String>
|
||||
<system:String x:Key="DoublePinyinSchemasXiaoLang">Xiao Lang</system:String>
|
||||
|
||||
|
||||
<system:String x:Key="AlwaysPreview">Always Preview</system:String>
|
||||
<system:String x:Key="AlwaysPreviewToolTip">Always open preview panel when Flow activates. Press {0} to toggle preview.</system:String>
|
||||
<system:String x:Key="shadowEffectNotAllowed">Shadow effect is not allowed while current theme has blur effect enabled</system:String>
|
||||
|
|
@ -139,6 +156,8 @@
|
|||
<system:String x:Key="KoreanImeOpenLinkButton">Open</system:String>
|
||||
<system:String x:Key="KoreanImeRegistry">Use Previous Korean IME</system:String>
|
||||
<system:String x:Key="KoreanImeRegistryTooltip">You can change the Previous Korean IME settings directly from here</system:String>
|
||||
<system:String x:Key="KoreanImeSettingChangeFailTitle">Failed to change Korean IME setting</system:String>
|
||||
<system:String x:Key="KoreanImeSettingChangeFailSubTitle">Please check your system registry access or contact support.</system:String>
|
||||
<system:String x:Key="homePage">Home Page</system:String>
|
||||
<system:String x:Key="homePageToolTip">Show home page results when query text is empty.</system:String>
|
||||
<system:String x:Key="historyResultsForHomePage">Show History Results in Home Page</system:String>
|
||||
|
|
@ -150,6 +169,8 @@
|
|||
<system:String x:Key="autoRestartAfterChangingToolTip">Restart Flow Launcher automatically after installing/uninstalling/updating plugin via Plugin Store</system:String>
|
||||
<system:String x:Key="showUnknownSourceWarning">Show unknown source warning</system:String>
|
||||
<system:String x:Key="showUnknownSourceWarningToolTip">Show warning when installing plugins from unknown sources</system:String>
|
||||
<system:String x:Key="autoUpdatePlugins">Auto update plugins</system:String>
|
||||
<system:String x:Key="autoUpdatePluginsToolTip">Automatically check plugin updates and notify if there are any updates available</system:String>
|
||||
|
||||
<!-- Setting Plugin -->
|
||||
<system:String x:Key="searchplugin">Search Plugin</system:String>
|
||||
|
|
@ -231,6 +252,12 @@
|
|||
<system:String x:Key="ZipFiles">Zip files</system:String>
|
||||
<system:String x:Key="SelectZipFile">Please select zip file</system:String>
|
||||
<system:String x:Key="installLocalPluginTooltip">Install plugin from local path</system:String>
|
||||
<system:String x:Key="updateNoResultTitle">No update available</system:String>
|
||||
<system:String x:Key="updateNoResultSubtitle">All plugins are up to date</system:String>
|
||||
<system:String x:Key="updateAllPluginsTitle">Plugin updates available</system:String>
|
||||
<system:String x:Key="updateAllPluginsButtonContent">Update plugins</system:String>
|
||||
<system:String x:Key="checkPluginUpdatesTooltip">Check plugin updates</system:String>
|
||||
<system:String x:Key="PluginsUpdateSuccessNoRestart">Plugins are successfully updated. Please restart Flow.</system:String>
|
||||
|
||||
<!-- Setting Theme -->
|
||||
<system:String x:Key="theme">Theme</system:String>
|
||||
|
|
@ -358,6 +385,28 @@
|
|||
<system:String x:Key="showBadges">Show Result Badges</system:String>
|
||||
<system:String x:Key="showBadgesToolTip">For supported plugins, badges are displayed to help distinguish them more easily.</system:String>
|
||||
<system:String x:Key="showBadgesGlobalOnly">Show Result Badges for Global Query Only</system:String>
|
||||
<system:String x:Key="showBadgesGlobalOnlyToolTip">Show badges for global query results only</system:String>
|
||||
<system:String x:Key="dialogJumpHotkey">Dialog Jump</system:String>
|
||||
<system:String x:Key="dialogJumpHotkeyToolTip">Enter shortcut to quickly navigate the Open/Save As dialog window to the path of the current file manager.</system:String>
|
||||
<system:String x:Key="dialogJump">Dialog Jump</system:String>
|
||||
<system:String x:Key="dialogJumpToolTip">When Open/Save As dialog window opens, quickly navigate to the current path of the file manager.</system:String>
|
||||
<system:String x:Key="autoDialogJump">Dialog Jump Automatically</system:String>
|
||||
<system:String x:Key="autoDialogJumpToolTip">When Open/Save As dialog window is displayed, automatically navigate to the path of the current file manager. (Experimental)</system:String>
|
||||
<system:String x:Key="showDialogJumpWindow">Show Dialog Jump Window</system:String>
|
||||
<system:String x:Key="showDialogJumpWindowToolTip">Display Dialog Jump search window when the open/save dialog window is shown to quickly navigate to file/folder locations.</system:String>
|
||||
<system:String x:Key="dialogJumpWindowPosition">Dialog Jump Window Position</system:String>
|
||||
<system:String x:Key="dialogJumpWindowPositionToolTip">Select position for the Dialog Jump search window</system:String>
|
||||
<system:String x:Key="DialogJumpWindowPositionUnderDialog">Fixed under the Open/Save As dialog window. Displayed on open and stays until the window is closed</system:String>
|
||||
<system:String x:Key="DialogJumpWindowPositionFollowDefault">Default search window position. Displayed when triggered by search window hotkey</system:String>
|
||||
<system:String x:Key="dialogJumpResultBehaviour">Dialog Jump Result Navigation Behaviour</system:String>
|
||||
<system:String x:Key="dialogJumpResultBehaviourToolTip">Behaviour to navigate Open/Save As dialog window to the selected result path</system:String>
|
||||
<system:String x:Key="DialogJumpResultBehaviourLeftClick">Left click or Enter key</system:String>
|
||||
<system:String x:Key="DialogJumpResultBehaviourRightClick">Right click</system:String>
|
||||
<system:String x:Key="dialogJumpFileResultBehaviour">Dialog Jump File Navigation Behaviour</system:String>
|
||||
<system:String x:Key="dialogJumpFileResultBehaviourToolTip">Behaviour to navigate Open/Save As dialog window when the result is a file path</system:String>
|
||||
<system:String x:Key="DialogJumpFileResultBehaviourFullPath">Fill full path in file name box</system:String>
|
||||
<system:String x:Key="DialogJumpFileResultBehaviourFullPathOpen">Fill full path in file name box and open</system:String>
|
||||
<system:String x:Key="DialogJumpFileResultBehaviourDirectory">Fill directory in path box</system:String>
|
||||
|
||||
<!-- Setting Proxy -->
|
||||
<system:String x:Key="proxy">HTTP Proxy</system:String>
|
||||
|
|
@ -555,6 +604,11 @@
|
|||
<system:String x:Key="update_flowlauncher_update_files">Update files</system:String>
|
||||
<system:String x:Key="update_flowlauncher_update_update_description">Update description</system:String>
|
||||
|
||||
<!-- Plugin Update Window -->
|
||||
<system:String x:Key="restartAfterUpdating">Restart Flow Launcher after updating plugins</system:String>
|
||||
<system:String x:Key="updatePluginCheckboxContent">{0}: Update from v{1} to v{2}</system:String>
|
||||
<system:String x:Key="updatePluginNoSelected">No plugin selected</system:String>
|
||||
|
||||
<!-- Welcome Window -->
|
||||
<system:String x:Key="Skip">Skip</system:String>
|
||||
<system:String x:Key="Welcome_Page1_Title">Welcome to Flow Launcher</system:String>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Media;
|
||||
|
|
@ -19,6 +19,7 @@ using Flow.Launcher.Core.Resource;
|
|||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Infrastructure.DialogJump;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
|
@ -119,7 +120,7 @@ namespace Flow.Launcher
|
|||
Win32Helper.DisableControlBox(this);
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs _)
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Check first launch
|
||||
if (_settings.FirstLaunch)
|
||||
|
|
@ -168,10 +169,12 @@ namespace Flow.Launcher
|
|||
if (_settings.HideOnStartup)
|
||||
{
|
||||
_viewModel.Hide();
|
||||
_viewModel.InitializeVisibilityStatus(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_viewModel.Show();
|
||||
_viewModel.InitializeVisibilityStatus(true);
|
||||
// When HideOnStartup is off and UseAnimation is on,
|
||||
// there was a bug where the clock would not appear at all on the initial launch
|
||||
// So we need to forcibly trigger animation here to ensure the clock is visible
|
||||
|
|
@ -214,6 +217,9 @@ namespace Flow.Launcher
|
|||
// Without this part, when shown for the first time, switching the context menu does not move the cursor to the end.
|
||||
_viewModel.QueryTextCursorMovedToEnd = false;
|
||||
|
||||
// Register Dialog Jump events
|
||||
InitializeDialogJump();
|
||||
|
||||
// View model property changed event
|
||||
_viewModel.PropertyChanged += (o, e) =>
|
||||
{
|
||||
|
|
@ -226,7 +232,7 @@ namespace Flow.Launcher
|
|||
if (_viewModel.MainWindowVisibilityStatus)
|
||||
{
|
||||
// Play sound effect before activing the window
|
||||
if (_settings.UseSound)
|
||||
if (_settings.UseSound && !_viewModel.IsDialogJumpWindowUnderDialog())
|
||||
{
|
||||
SoundPlay();
|
||||
}
|
||||
|
|
@ -249,7 +255,7 @@ namespace Flow.Launcher
|
|||
QueryTextBox.Focus();
|
||||
|
||||
// Play window animation
|
||||
if (_settings.UseAnimation)
|
||||
if (_settings.UseAnimation && !_viewModel.IsDialogJumpWindowUnderDialog())
|
||||
{
|
||||
WindowAnimation();
|
||||
}
|
||||
|
|
@ -379,6 +385,11 @@ namespace Flow.Launcher
|
|||
|
||||
private void OnLocationChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_viewModel.IsDialogJumpWindowUnderDialog())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsLoaded)
|
||||
{
|
||||
_settings.WindowLeft = Left;
|
||||
|
|
@ -388,6 +399,11 @@ namespace Flow.Launcher
|
|||
|
||||
private async void OnDeactivated(object sender, EventArgs e)
|
||||
{
|
||||
if (_viewModel.IsDialogJumpWindowUnderDialog())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.WindowLeft = Left;
|
||||
_settings.WindowTop = Top;
|
||||
|
||||
|
|
@ -577,11 +593,23 @@ namespace Flow.Launcher
|
|||
switch (msg)
|
||||
{
|
||||
case Win32Helper.WM_ENTERSIZEMOVE:
|
||||
// Do do handle size move event for dialog jump window
|
||||
if (_viewModel.IsDialogJumpWindowUnderDialog())
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
_initialWidth = (int)Width;
|
||||
_initialHeight = (int)Height;
|
||||
handled = true;
|
||||
break;
|
||||
case Win32Helper.WM_EXITSIZEMOVE:
|
||||
// Do do handle size move event for Dialog Jump window
|
||||
if (_viewModel.IsDialogJumpWindowUnderDialog())
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
//Prevent updating the number of results when the window height is below the height of a single result item.
|
||||
//This situation occurs not only when the user manually resizes the window, but also when the window is released from a side snap, as the OS automatically adjusts the window height.
|
||||
//(Without this check, releasing from a snap can cause the window height to hit the minimum, resulting in only 2 results being shown.)
|
||||
|
|
@ -792,11 +820,19 @@ namespace Flow.Launcher
|
|||
|
||||
#region Window Position
|
||||
|
||||
private void UpdatePosition()
|
||||
public void UpdatePosition()
|
||||
{
|
||||
// Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910
|
||||
InitializePosition();
|
||||
InitializePosition();
|
||||
if (_viewModel.IsDialogJumpWindowUnderDialog())
|
||||
{
|
||||
InitializeDialogJumpPosition();
|
||||
InitializeDialogJumpPosition();
|
||||
}
|
||||
else
|
||||
{
|
||||
InitializePosition();
|
||||
InitializePosition();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PositionResetAsync()
|
||||
|
|
@ -1354,6 +1390,46 @@ namespace Flow.Launcher
|
|||
|
||||
#endregion
|
||||
|
||||
#region Dialog Jump
|
||||
|
||||
private void InitializeDialogJump()
|
||||
{
|
||||
DialogJump.ShowDialogJumpWindowAsync = _viewModel.SetupDialogJumpAsync;
|
||||
DialogJump.UpdateDialogJumpWindow = InitializeDialogJumpPosition;
|
||||
DialogJump.ResetDialogJumpWindow = _viewModel.ResetDialogJump;
|
||||
DialogJump.HideDialogJumpWindow = _viewModel.HideDialogJump;
|
||||
}
|
||||
|
||||
private void InitializeDialogJumpPosition()
|
||||
{
|
||||
if (_viewModel.DialogWindowHandle == nint.Zero || !_viewModel.MainWindowVisibilityStatus) return;
|
||||
if (!_viewModel.IsDialogJumpWindowUnderDialog()) return;
|
||||
|
||||
// Get dialog window rect
|
||||
var result = Win32Helper.GetWindowRect(_viewModel.DialogWindowHandle, out var window);
|
||||
if (!result) return;
|
||||
|
||||
// Move window below the bottom of the dialog and keep it center
|
||||
Top = VerticalBottom(window);
|
||||
Left = HorizonCenter(window);
|
||||
}
|
||||
|
||||
private double HorizonCenter(Rect window)
|
||||
{
|
||||
var dip1 = Win32Helper.TransformPixelsToDIP(this, window.X, 0);
|
||||
var dip2 = Win32Helper.TransformPixelsToDIP(this, window.Width, 0);
|
||||
var left = (dip2.X - ActualWidth) / 2 + dip1.X;
|
||||
return left;
|
||||
}
|
||||
|
||||
private double VerticalBottom(Rect window)
|
||||
{
|
||||
var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, window.Bottom);
|
||||
return dip1.Y;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
|
|
|
|||
|
|
@ -37,8 +37,9 @@ namespace Flow.Launcher
|
|||
try
|
||||
{
|
||||
msgBox = new MessageBoxEx(button);
|
||||
if (caption == string.Empty && button == MessageBoxButton.OK && icon == MessageBoxImage.None)
|
||||
if (caption == string.Empty && icon == MessageBoxImage.None)
|
||||
{
|
||||
// If there is no caption and no icon, use DescOnlyTextBlock for vertically centered text
|
||||
msgBox.Title = messageBoxText;
|
||||
msgBox.DescOnlyTextBlock.Visibility = Visibility.Visible;
|
||||
msgBox.DescOnlyTextBlock.Text = messageBoxText;
|
||||
|
|
|
|||
111
Flow.Launcher/PluginUpdateWindow.xaml
Normal file
111
Flow.Launcher/PluginUpdateWindow.xaml
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<Window
|
||||
x:Class="Flow.Launcher.PluginUpdateWindow"
|
||||
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:flowlauncher="clr-namespace:Flow.Launcher"
|
||||
Title="{DynamicResource updateAllPluginsButtonContent}"
|
||||
Width="530"
|
||||
Background="{DynamicResource PopuBGColor}"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}"
|
||||
Foreground="{DynamicResource PopupTextColor}"
|
||||
Icon="Images\app.png"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="Height"
|
||||
Topmost="True"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<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="cmdEsc_OnPress" />
|
||||
</Window.CommandBindings>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="80" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0">
|
||||
<StackPanel>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
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>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="26 0 26 0">
|
||||
<TextBlock
|
||||
Margin="0 0 0 12"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Text="{DynamicResource updateAllPluginsButtonContent}"
|
||||
TextAlignment="Left" />
|
||||
|
||||
<ScrollViewer
|
||||
MaxHeight="300"
|
||||
Margin="0 5 0 5"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel x:Name="UpdatePluginStackPanel" />
|
||||
</ScrollViewer>
|
||||
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="0 5 0 5"
|
||||
Fill="{DynamicResource SeparatorForeground}" />
|
||||
|
||||
<CheckBox
|
||||
Margin="0 10 0 10"
|
||||
Content="{DynamicResource restartAfterUpdating}"
|
||||
IsChecked="{Binding Restart, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="0 14 0 0"
|
||||
Background="{DynamicResource PopupButtonAreaBGColor}"
|
||||
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
|
||||
BorderThickness="0 1 0 0">
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
MinWidth="140"
|
||||
Margin="10 0 5 0"
|
||||
Click="BtnCancel_OnClick"
|
||||
Content="{DynamicResource cancel}" />
|
||||
<Button
|
||||
x:Name="btnUpdate"
|
||||
MinWidth="140"
|
||||
Margin="5 0 10 0"
|
||||
Click="btnUpdate_OnClick"
|
||||
Content="{DynamicResource update}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
85
Flow.Launcher/PluginUpdateWindow.xaml.cs
Normal file
85
Flow.Launcher/PluginUpdateWindow.xaml.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class PluginUpdateWindow : Window
|
||||
{
|
||||
public List<PluginUpdateInfo> Plugins { get; set; } = new();
|
||||
public bool Restart { get; set; }
|
||||
|
||||
private readonly Settings _settings = Ioc.Default.GetRequiredService<Settings>();
|
||||
|
||||
public PluginUpdateWindow(List<PluginUpdateInfo> allPlugins)
|
||||
{
|
||||
Restart = _settings.AutoRestartAfterChanging;
|
||||
InitializeComponent();
|
||||
foreach (var plugin in allPlugins)
|
||||
{
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
Content = string.Format(App.API.GetTranslation("updatePluginCheckboxContent"), plugin.Name, plugin.CurrentVersion, plugin.NewVersion),
|
||||
IsChecked = true,
|
||||
Margin = new Thickness(0, 5, 0, 5),
|
||||
Tag = plugin,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
checkBox.Checked += CheckBox_Checked;
|
||||
checkBox.Unchecked += CheckBox_Unchecked;
|
||||
UpdatePluginStackPanel.Children.Add(checkBox);
|
||||
Plugins.Add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckBox_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not CheckBox cb) return;
|
||||
if (cb.Tag is not PluginUpdateInfo plugin) return;
|
||||
if (!Plugins.Contains(plugin))
|
||||
{
|
||||
Plugins.Add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not CheckBox cb) return;
|
||||
if (cb.Tag is not PluginUpdateInfo plugin) return;
|
||||
if (Plugins.Contains(plugin))
|
||||
{
|
||||
Plugins.Remove(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void btnUpdate_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Plugins.Count == 0)
|
||||
{
|
||||
App.API.ShowMsgBox(App.API.GetTranslation("updatePluginNoSelected"));
|
||||
return;
|
||||
}
|
||||
|
||||
_ = PluginInstaller.UpdateAllPluginsAsync(Plugins, Restart);
|
||||
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void cmdEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
|||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
|
||||
<PublishDir>..\Output\Release\</PublishDir>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
|
|
@ -599,6 +599,10 @@ namespace Flow.Launcher
|
|||
remove => _mainVM.ActualApplicationThemeChanged -= value;
|
||||
}
|
||||
|
||||
public string GetDataDirectory() => DataLocation.DataDirectory();
|
||||
|
||||
public string GetLogDirectory() => DataLocation.VersionLogDirectory;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
using Flow.Launcher.Core.ExternalPlugins;
|
||||
using System;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using Flow.Launcher.Core.ExternalPlugins;
|
||||
using Flow.Launcher.Helper;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
|
|
@ -44,7 +44,7 @@ namespace Flow.Launcher
|
|||
|
||||
var websiteUrl = exception switch
|
||||
{
|
||||
FlowPluginException pluginException =>GetIssuesUrl(pluginException.Metadata.Website),
|
||||
FlowPluginException pluginException => GetIssuesUrl(pluginException.Metadata.Website),
|
||||
_ => Constant.IssuesUrl
|
||||
};
|
||||
|
||||
|
|
@ -73,17 +73,36 @@ namespace Flow.Launcher
|
|||
Margin = new Thickness(0)
|
||||
};
|
||||
|
||||
var link = new Hyperlink
|
||||
Hyperlink link = null;
|
||||
try
|
||||
{
|
||||
IsEnabled = true
|
||||
};
|
||||
link.Inlines.Add(url);
|
||||
link.NavigateUri = new Uri(url);
|
||||
link.Click += (s, e) => SearchWeb.OpenInBrowserTab(url);
|
||||
var uri = new Uri(url);
|
||||
|
||||
link = new Hyperlink
|
||||
{
|
||||
IsEnabled = true
|
||||
};
|
||||
link.Inlines.Add(url);
|
||||
link.NavigateUri = uri;
|
||||
link.Click += (s, e) => SearchWeb.OpenInBrowserTab(url);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Leave link as null if the URL is invalid
|
||||
}
|
||||
|
||||
paragraph.Inlines.Add(textBeforeUrl);
|
||||
paragraph.Inlines.Add(" ");
|
||||
paragraph.Inlines.Add(link);
|
||||
if (link is null)
|
||||
{
|
||||
// Add the URL as plain text if it is invalid
|
||||
paragraph.Inlines.Add(url);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the hyperlink if it is valid
|
||||
paragraph.Inlines.Add(link);
|
||||
}
|
||||
paragraph.Inlines.Add("\n");
|
||||
|
||||
return paragraph;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ namespace Flow.Launcher.Resources.Pages
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.API.ShowMsg(App.API.GetTranslation("setAutoStartFailed"), e.Message);
|
||||
App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
|
@ -8,6 +8,7 @@ using Flow.Launcher.Core.Configuration;
|
|||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Helper;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.DialogJump;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
|
@ -64,7 +65,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.API.ShowMsg(App.API.GetTranslation("setAutoStartFailed"), e.Message);
|
||||
App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +92,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.API.ShowMsg(App.API.GetTranslation("setAutoStartFailed"), e.Message);
|
||||
App.API.ShowMsgError(App.API.GetTranslation("setAutoStartFailed"), e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -146,6 +147,40 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
|||
public List<LastQueryModeData> LastQueryModes { get; } =
|
||||
DropdownDataGeneric<LastQueryMode>.GetValues<LastQueryModeData>("LastQuery");
|
||||
|
||||
public bool EnableDialogJump
|
||||
{
|
||||
get => Settings.EnableDialogJump;
|
||||
set
|
||||
{
|
||||
if (Settings.EnableDialogJump != value)
|
||||
{
|
||||
Settings.EnableDialogJump = value;
|
||||
DialogJump.SetupDialogJump(value);
|
||||
if (Settings.EnableDialogJump)
|
||||
{
|
||||
HotKeyMapper.SetHotkey(new(Settings.DialogJumpHotkey), DialogJump.OnToggleHotkey);
|
||||
}
|
||||
else
|
||||
{
|
||||
HotKeyMapper.RemoveHotkey(Settings.DialogJumpHotkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DialogJumpWindowPositionData : DropdownDataGeneric<DialogJumpWindowPositions> { }
|
||||
public class DialogJumpResultBehaviourData : DropdownDataGeneric<DialogJumpResultBehaviours> { }
|
||||
public class DialogJumpFileResultBehaviourData : DropdownDataGeneric<DialogJumpFileResultBehaviours> { }
|
||||
|
||||
public List<DialogJumpWindowPositionData> DialogJumpWindowPositions { get; } =
|
||||
DropdownDataGeneric<DialogJumpWindowPositions>.GetValues<DialogJumpWindowPositionData>("DialogJumpWindowPosition");
|
||||
|
||||
public List<DialogJumpResultBehaviourData> DialogJumpResultBehaviours { get; } =
|
||||
DropdownDataGeneric<DialogJumpResultBehaviours>.GetValues<DialogJumpResultBehaviourData>("DialogJumpResultBehaviour");
|
||||
|
||||
public List<DialogJumpFileResultBehaviourData> DialogJumpFileResultBehaviours { get; } =
|
||||
DropdownDataGeneric<DialogJumpFileResultBehaviours>.GetValues<DialogJumpFileResultBehaviourData>("DialogJumpFileResultBehaviour");
|
||||
|
||||
public int SearchDelayTimeValue
|
||||
{
|
||||
get => Settings.SearchDelayTime;
|
||||
|
|
@ -179,6 +214,9 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
|||
DropdownDataGeneric<SearchPrecisionScore>.UpdateLabels(SearchPrecisionScores);
|
||||
DropdownDataGeneric<LastQueryMode>.UpdateLabels(LastQueryModes);
|
||||
DropdownDataGeneric<DoublePinyinSchemas>.UpdateLabels(DoublePinyinSchemas);
|
||||
DropdownDataGeneric<DialogJumpWindowPositions>.UpdateLabels(DialogJumpWindowPositions);
|
||||
DropdownDataGeneric<DialogJumpResultBehaviours>.UpdateLabels(DialogJumpResultBehaviours);
|
||||
DropdownDataGeneric<DialogJumpFileResultBehaviours>.UpdateLabels(DialogJumpFileResultBehaviours);
|
||||
// Since we are using Binding instead of DynamicResource, we need to manually trigger the update
|
||||
OnPropertyChanged(nameof(AlwaysPreviewToolTip));
|
||||
}
|
||||
|
|
@ -216,8 +254,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
|||
}
|
||||
else
|
||||
{
|
||||
//Since this is rarely seen text, language support is not provided.
|
||||
App.API.ShowMsg("Failed to change Korean IME setting", "Please check your system registry access or contact support.");
|
||||
// Since this is rarely seen text, language support is not provided.
|
||||
App.API.ShowMsgError(App.API.GetTranslation("KoreanImeSettingChangeFailTitle"), App.API.GetTranslation("KoreanImeSettingChangeFailSubTitle"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.Input;
|
|||
using Flow.Launcher.Helper;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.DialogJump;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
|
|
@ -34,6 +35,15 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
|
|||
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SetDialogJumpHotkey(HotkeyModel hotkey)
|
||||
{
|
||||
if (Settings.EnableDialogJump)
|
||||
{
|
||||
HotKeyMapper.SetHotkey(hotkey, DialogJump.OnToggleHotkey);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CustomHotkeyDelete()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
|
@ -109,6 +110,19 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
|
|||
await PluginInstaller.InstallPluginAndCheckRestartAsync(file);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CheckPluginUpdatesAsync()
|
||||
{
|
||||
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
|
||||
pluginUpdateWindow.ShowDialog();
|
||||
});
|
||||
}, silentUpdate: false);
|
||||
}
|
||||
|
||||
private static string GetFileFromDialog(string title, string filter = "")
|
||||
{
|
||||
var dlg = new Microsoft.Win32.OpenFileDialog
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<ui:Page
|
||||
<ui:Page
|
||||
x:Class="Flow.Launcher.SettingPages.Views.SettingsPaneGeneral"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
|
|
@ -182,7 +182,8 @@
|
|||
<cc:Card
|
||||
Title="{DynamicResource autoUpdates}"
|
||||
Margin="0 14 0 0"
|
||||
Icon="">
|
||||
Icon=""
|
||||
Sub="{DynamicResource autoUpdatesTooltip}">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding AutoUpdates}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
|
|
@ -241,14 +242,99 @@
|
|||
Title="{DynamicResource showUnknownSourceWarning}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource showUnknownSourceWarningToolTip}"
|
||||
Type="Last">
|
||||
Type="Middle">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding Settings.ShowUnknownSourceWarning}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:Card
|
||||
Title="{DynamicResource autoUpdatePlugins}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource autoUpdatePluginsToolTip}"
|
||||
Type="Last">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding Settings.AutoUpdatePlugins}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
</cc:CardGroup>
|
||||
|
||||
<cc:ExCard
|
||||
Title="{DynamicResource dialogJump}"
|
||||
Margin="0 14 0 0"
|
||||
Icon=""
|
||||
Sub="{DynamicResource dialogJumpToolTip}">
|
||||
<cc:ExCard.SideContent>
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding EnableDialogJump}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:ExCard.SideContent>
|
||||
|
||||
<StackPanel>
|
||||
<cc:Card
|
||||
Title="{DynamicResource autoDialogJump}"
|
||||
Sub="{DynamicResource autoDialogJumpToolTip}"
|
||||
Type="InsideFit"
|
||||
Visibility="Collapsed">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding Settings.AutoDialogJump}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:Card
|
||||
Title="{DynamicResource showDialogJumpWindow}"
|
||||
Sub="{DynamicResource showDialogJumpWindowToolTip}"
|
||||
Type="InsideFit">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding Settings.ShowDialogJumpWindow}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:Card
|
||||
Title="{DynamicResource dialogJumpWindowPosition}"
|
||||
Sub="{DynamicResource dialogJumpWindowPositionToolTip}"
|
||||
Type="InsideFit">
|
||||
<ComboBox
|
||||
MinWidth="120"
|
||||
MaxWidth="210"
|
||||
DisplayMemberPath="Display"
|
||||
ItemsSource="{Binding DialogJumpWindowPositions}"
|
||||
SelectedValue="{Binding Settings.DialogJumpWindowPosition}"
|
||||
SelectedValuePath="Value" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:Card
|
||||
Title="{DynamicResource dialogJumpResultBehaviour}"
|
||||
Sub="{DynamicResource dialogJumpResultBehaviourToolTip}"
|
||||
Type="InsideFit">
|
||||
<ComboBox
|
||||
MinWidth="120"
|
||||
DisplayMemberPath="Display"
|
||||
ItemsSource="{Binding DialogJumpResultBehaviours}"
|
||||
SelectedValue="{Binding Settings.DialogJumpResultBehaviour}"
|
||||
SelectedValuePath="Value" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:Card
|
||||
Title="{DynamicResource dialogJumpFileResultBehaviour}"
|
||||
Sub="{DynamicResource dialogJumpFileResultBehaviourToolTip}"
|
||||
Type="InsideFit">
|
||||
<ComboBox
|
||||
MinWidth="120"
|
||||
MaxWidth="240"
|
||||
DisplayMemberPath="Display"
|
||||
ItemsSource="{Binding DialogJumpFileResultBehaviours}"
|
||||
SelectedValue="{Binding Settings.DialogJumpFileResultBehaviour}"
|
||||
SelectedValuePath="Value" />
|
||||
</cc:Card>
|
||||
</StackPanel>
|
||||
</cc:ExCard>
|
||||
|
||||
<cc:ExCard
|
||||
Title="{DynamicResource searchDelay}"
|
||||
Margin="0 14 0 0"
|
||||
|
|
@ -371,44 +457,39 @@
|
|||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:CardGroup Margin="0 4 0 0">
|
||||
<cc:Card
|
||||
Title="{DynamicResource ShouldUsePinyin}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource ShouldUsePinyinToolTip}"
|
||||
Type="First">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding ShouldUsePinyin}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}"
|
||||
ToolTip="{DynamicResource ShouldUsePinyinToolTip}" />
|
||||
</cc:Card>
|
||||
<cc:Card
|
||||
Visibility="{ext:VisibleWhen {Binding ShouldUsePinyin},
|
||||
<cc:Card
|
||||
Title="{DynamicResource ShouldUsePinyin}"
|
||||
Margin="0 4 0 0"
|
||||
Icon=""
|
||||
Sub="{DynamicResource ShouldUsePinyinToolTip}">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding ShouldUsePinyin}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}"
|
||||
ToolTip="{DynamicResource ShouldUsePinyinToolTip}" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:ExCard
|
||||
Title="{DynamicResource ShouldUseDoublePinyin}"
|
||||
Icon=""
|
||||
Visibility="{ext:VisibleWhen {Binding ShouldUsePinyin},
|
||||
IsEqualToBool=True}"
|
||||
Title="{DynamicResource ShouldUseDoublePinyin}"
|
||||
Icon=""
|
||||
Sub="{DynamicResource ShouldUseDoublePinyinToolTip}"
|
||||
Type="Middle">
|
||||
Sub="{DynamicResource ShouldUseDoublePinyinToolTip}">
|
||||
<cc:ExCard.SideContent>
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding UseDoublePinyin}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}"
|
||||
ToolTip="{DynamicResource ShouldUseDoublePinyinToolTip}" />
|
||||
</cc:Card>
|
||||
<cc:Card
|
||||
Visibility="{ext:VisibleWhen {Binding UseDoublePinyin},
|
||||
IsEqualToBool=True}"
|
||||
Title="{DynamicResource DoublePinyinSchema}"
|
||||
Sub="{DynamicResource DoublePinyinSchemaToolTip}"
|
||||
Type="Last">
|
||||
</cc:ExCard.SideContent>
|
||||
<cc:Card Title="{DynamicResource DoublePinyinSchema}" Type="InsideFit">
|
||||
<ComboBox
|
||||
DisplayMemberPath="Display"
|
||||
ItemsSource="{Binding DoublePinyinSchemas}"
|
||||
SelectedValue="{Binding Settings.DoublePinyinSchema}"
|
||||
SelectedValuePath="Value" />
|
||||
</cc:Card>
|
||||
</cc:CardGroup>
|
||||
</cc:ExCard>
|
||||
|
||||
<cc:Card
|
||||
Title="{DynamicResource language}"
|
||||
|
|
|
|||
|
|
@ -73,6 +73,19 @@
|
|||
</cc:Card>
|
||||
</cc:CardGroup>
|
||||
|
||||
<cc:Card
|
||||
Title="{DynamicResource dialogJumpHotkey}"
|
||||
Margin="0 14 0 0"
|
||||
Icon=""
|
||||
Sub="{DynamicResource dialogJumpHotkeyToolTip}">
|
||||
<flowlauncher:HotkeyControl
|
||||
ChangeHotkey="{Binding SetDialogJumpHotkeyCommand}"
|
||||
DefaultHotkey="Alt+G"
|
||||
Type="DialogJumpHotkey"
|
||||
ValidateKeyGesture="False"
|
||||
WindowTitle="{DynamicResource dialogJumpHotkey}" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:ExCard
|
||||
Title="{DynamicResource hotkeyPresets}"
|
||||
Margin="0 14 0 0"
|
||||
|
|
|
|||
|
|
@ -99,6 +99,13 @@
|
|||
ToolTip="{DynamicResource installLocalPluginTooltip}">
|
||||
<ui:FontIcon FontSize="14" Glyph="" />
|
||||
</Button>
|
||||
<Button
|
||||
Height="34"
|
||||
Margin="0 0 10 0"
|
||||
Command="{Binding CheckPluginUpdatesCommand}"
|
||||
ToolTip="{DynamicResource checkPluginUpdatesTooltip}">
|
||||
<ui:FontIcon FontSize="14" Glyph="" />
|
||||
</Button>
|
||||
<TextBox
|
||||
Name="PluginStoreFilterTextbox"
|
||||
Width="150"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using CommunityToolkit.Mvvm.Input;
|
|||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.DialogJump;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
|
@ -52,6 +53,7 @@ namespace Flow.Launcher.ViewModel
|
|||
private Task _resultsViewUpdateTask;
|
||||
|
||||
private readonly IReadOnlyList<Result> _emptyResult = new List<Result>();
|
||||
private readonly IReadOnlyList<DialogJumpResult> _emptyDialogJumpResult = new List<DialogJumpResult>();
|
||||
|
||||
private readonly PluginMetadata _historyMetadata = new()
|
||||
{
|
||||
|
|
@ -215,7 +217,8 @@ namespace Flow.Launcher.ViewModel
|
|||
var resultUpdateChannel = Channel.CreateUnbounded<ResultsForUpdate>();
|
||||
_resultsUpdateChannelWriter = resultUpdateChannel.Writer;
|
||||
_resultsViewUpdateTask =
|
||||
Task.Run(UpdateActionAsync).ContinueWith(continueAction, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
|
||||
Task.Run(UpdateActionAsync).ContinueWith(continueAction,
|
||||
CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
|
||||
|
||||
async Task UpdateActionAsync()
|
||||
{
|
||||
|
|
@ -285,8 +288,16 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
var token = e.Token == default ? _updateToken : e.Token;
|
||||
|
||||
// make a clone to avoid possible issue that plugin will also change the list and items when updating view model
|
||||
var resultsCopy = DeepCloneResults(e.Results, token);
|
||||
IReadOnlyList<Result> resultsCopy;
|
||||
if (e.Results == null)
|
||||
{
|
||||
resultsCopy = _emptyResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
// make a clone to avoid possible issue that plugin will also change the list and items when updating view model
|
||||
resultsCopy = DeepCloneResults(e.Results, false, token);
|
||||
}
|
||||
|
||||
foreach (var result in resultsCopy)
|
||||
{
|
||||
|
|
@ -394,12 +405,30 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
private void LoadContextMenu()
|
||||
{
|
||||
// For Dialog Jump and right click mode, we need to navigate to the path
|
||||
if (_isDialogJump && Settings.DialogJumpResultBehaviour == DialogJumpResultBehaviours.RightClick)
|
||||
{
|
||||
if (SelectedResults.SelectedItem != null && DialogWindowHandle != nint.Zero)
|
||||
{
|
||||
var result = SelectedResults.SelectedItem.Result;
|
||||
if (result is DialogJumpResult dialogJumpResult)
|
||||
{
|
||||
Win32Helper.SetForegroundWindow(DialogWindowHandle);
|
||||
_ = Task.Run(() => DialogJump.JumpToPathAsync(DialogWindowHandle, dialogJumpResult.DialogJumpPath));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For query mode, we load context menu
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
// When switch to ContextMenu from QueryResults, but no item being chosen, should do nothing
|
||||
// i.e. Shift+Enter/Ctrl+O right after Alt + Space should do nothing
|
||||
if (SelectedResults.SelectedItem != null)
|
||||
{
|
||||
SelectedResults = ContextMenu;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -469,12 +498,34 @@ namespace Flow.Launcher.ViewModel
|
|||
return;
|
||||
}
|
||||
|
||||
var hideWindow = await result.ExecuteAsync(new ActionContext
|
||||
// For Dialog Jump and left click mode, we need to navigate to the path
|
||||
if (_isDialogJump && Settings.DialogJumpResultBehaviour == DialogJumpResultBehaviours.LeftClick)
|
||||
{
|
||||
// not null means pressing modifier key + number, should ignore the modifier key
|
||||
SpecialKeyState = index is not null ? SpecialKeyState.Default : GlobalHotkey.CheckModifiers()
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
Hide();
|
||||
|
||||
if (SelectedResults.SelectedItem != null && DialogWindowHandle != nint.Zero)
|
||||
{
|
||||
if (result is DialogJumpResult dialogJumpResult)
|
||||
{
|
||||
Win32Helper.SetForegroundWindow(DialogWindowHandle);
|
||||
_ = Task.Run(() => DialogJump.JumpToPathAsync(DialogWindowHandle, dialogJumpResult.DialogJumpPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
// For query mode, we execute the result
|
||||
else
|
||||
{
|
||||
var hideWindow = await result.ExecuteAsync(new ActionContext
|
||||
{
|
||||
// not null means pressing modifier key + number, should ignore the modifier key
|
||||
SpecialKeyState = index is not null ? SpecialKeyState.Default : GlobalHotkey.CheckModifiers()
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (hideWindow)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
if (QueryResultsSelected())
|
||||
{
|
||||
|
|
@ -482,26 +533,33 @@ namespace Flow.Launcher.ViewModel
|
|||
_history.Add(result.OriginQuery.RawQuery);
|
||||
lastHistoryIndex = 1;
|
||||
}
|
||||
|
||||
if (hideWindow)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Result> DeepCloneResults(IReadOnlyList<Result> results, CancellationToken token = default)
|
||||
private static IReadOnlyList<Result> DeepCloneResults(IReadOnlyList<Result> results, bool isDialogJump, 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);
|
||||
if (isDialogJump)
|
||||
{
|
||||
foreach (var result in results.ToList())
|
||||
{
|
||||
if (token.IsCancellationRequested) break;
|
||||
|
||||
var resultCopy = ((DialogJumpResult)result).Clone();
|
||||
resultsCopy.Add(resultCopy);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var result in results.ToList())
|
||||
{
|
||||
if (token.IsCancellationRequested) break;
|
||||
|
||||
var resultCopy = result.Clone();
|
||||
resultsCopy.Add(resultCopy);
|
||||
}
|
||||
}
|
||||
|
||||
return resultsCopy;
|
||||
}
|
||||
|
||||
|
|
@ -1279,25 +1337,21 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
if (query == null) // shortcut expanded
|
||||
{
|
||||
App.API.LogDebug(ClassName, $"Clear query results");
|
||||
|
||||
// Hide and clear results again because running query may show and add some results
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
Results.Clear();
|
||||
|
||||
// Reset plugin icon
|
||||
PluginIconPath = null;
|
||||
PluginIconSource = null;
|
||||
SearchIconVisibility = Visibility.Visible;
|
||||
|
||||
// Hide progress bar again because running query may set this to visible
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
ClearResults();
|
||||
return;
|
||||
}
|
||||
|
||||
App.API.LogDebug(ClassName, $"Start query with ActionKeyword <{query.ActionKeyword}> and RawQuery <{query.RawQuery}>");
|
||||
|
||||
var currentIsHomeQuery = query.IsHomeQuery;
|
||||
var currentIsDialogJump = _isDialogJump;
|
||||
|
||||
// Do not show home page for Dialog Jump window
|
||||
if (currentIsHomeQuery && currentIsDialogJump)
|
||||
{
|
||||
ClearResults();
|
||||
return;
|
||||
}
|
||||
|
||||
_updateSource?.Dispose();
|
||||
|
||||
|
|
@ -1331,7 +1385,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
else
|
||||
{
|
||||
plugins = PluginManager.ValidPluginsForQuery(query);
|
||||
plugins = PluginManager.ValidPluginsForQuery(query, currentIsDialogJump);
|
||||
|
||||
if (plugins.Count == 1)
|
||||
{
|
||||
|
|
@ -1425,6 +1479,23 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
|
||||
// Local function
|
||||
void ClearResults()
|
||||
{
|
||||
App.API.LogDebug(ClassName, $"Clear query results");
|
||||
|
||||
// Hide and clear results again because running query may show and add some results
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
Results.Clear();
|
||||
|
||||
// Reset plugin icon
|
||||
PluginIconPath = null;
|
||||
PluginIconSource = null;
|
||||
SearchIconVisibility = Visibility.Visible;
|
||||
|
||||
// Hide progress bar again because running query may set this to visible
|
||||
ProgressBarVisibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
async Task QueryTaskAsync(PluginPair plugin, CancellationToken token)
|
||||
{
|
||||
App.API.LogDebug(ClassName, $"Wait for querying plugin <{plugin.Metadata.Name}>");
|
||||
|
|
@ -1442,21 +1513,23 @@ namespace Flow.Launcher.ViewModel
|
|||
// Task.Yield will force it to run in ThreadPool
|
||||
await Task.Yield();
|
||||
|
||||
var results = currentIsHomeQuery ?
|
||||
await PluginManager.QueryHomeForPluginAsync(plugin, query, token) :
|
||||
await PluginManager.QueryForPluginAsync(plugin, query, token);
|
||||
IReadOnlyList<Result> results = currentIsDialogJump ?
|
||||
await PluginManager.QueryDialogJumpForPluginAsync(plugin, query, token) :
|
||||
currentIsHomeQuery ?
|
||||
await PluginManager.QueryHomeForPluginAsync(plugin, query, token) :
|
||||
await PluginManager.QueryForPluginAsync(plugin, query, token);
|
||||
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
IReadOnlyList<Result> resultsCopy;
|
||||
if (results == null)
|
||||
{
|
||||
resultsCopy = _emptyResult;
|
||||
resultsCopy = currentIsDialogJump ? _emptyDialogJumpResult : _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, token);
|
||||
resultsCopy = DeepCloneResults(results, currentIsDialogJump, token);
|
||||
}
|
||||
|
||||
foreach (var result in resultsCopy)
|
||||
|
|
@ -1751,6 +1824,208 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
#endregion
|
||||
|
||||
#region Dialog Jump
|
||||
|
||||
public nint DialogWindowHandle { get; private set; } = nint.Zero;
|
||||
|
||||
private bool _isDialogJump = false;
|
||||
|
||||
private bool _previousMainWindowVisibilityStatus;
|
||||
|
||||
private CancellationTokenSource _dialogJumpSource;
|
||||
|
||||
public void InitializeVisibilityStatus(bool visibilityStatus)
|
||||
{
|
||||
_previousMainWindowVisibilityStatus = visibilityStatus;
|
||||
}
|
||||
|
||||
public bool IsDialogJumpWindowUnderDialog()
|
||||
{
|
||||
return _isDialogJump && DialogJump.DialogJumpWindowPosition == DialogJumpWindowPositions.UnderDialog;
|
||||
}
|
||||
|
||||
public async Task SetupDialogJumpAsync(nint handle)
|
||||
{
|
||||
if (handle == nint.Zero) return;
|
||||
|
||||
// Only set flag & reset window once for one file dialog
|
||||
var dialogWindowHandleChanged = false;
|
||||
if (DialogWindowHandle != handle)
|
||||
{
|
||||
DialogWindowHandle = handle;
|
||||
_previousMainWindowVisibilityStatus = MainWindowVisibilityStatus;
|
||||
_isDialogJump = true;
|
||||
|
||||
dialogWindowHandleChanged = true;
|
||||
|
||||
// If don't give a time, Positioning will be weird
|
||||
await Task.Delay(300);
|
||||
}
|
||||
|
||||
// If handle is cleared, which means the dialog is closed, clear Dialog Jump state
|
||||
if (DialogWindowHandle == nint.Zero)
|
||||
{
|
||||
_isDialogJump = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Dialog Jump window
|
||||
if (MainWindowVisibilityStatus)
|
||||
{
|
||||
if (dialogWindowHandleChanged)
|
||||
{
|
||||
// Only update the position
|
||||
Application.Current?.Dispatcher.Invoke(() =>
|
||||
{
|
||||
(Application.Current?.MainWindow as MainWindow)?.UpdatePosition();
|
||||
});
|
||||
|
||||
_ = ResetWindowAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DialogJump.DialogJumpWindowPosition == DialogJumpWindowPositions.UnderDialog)
|
||||
{
|
||||
// We wait for window to be reset before showing it because if window has results,
|
||||
// showing it before resetting will cause flickering when results are clearing
|
||||
if (dialogWindowHandleChanged)
|
||||
{
|
||||
await ResetWindowAsync();
|
||||
}
|
||||
|
||||
Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dialogWindowHandleChanged)
|
||||
{
|
||||
_ = ResetWindowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DialogJump.DialogJumpWindowPosition == DialogJumpWindowPositions.UnderDialog)
|
||||
{
|
||||
// Cancel the previous Dialog Jump task
|
||||
_dialogJumpSource?.Cancel();
|
||||
|
||||
// Create a new cancellation token source
|
||||
_dialogJumpSource = new CancellationTokenSource();
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check task cancellation
|
||||
if (_dialogJumpSource.Token.IsCancellationRequested) return;
|
||||
|
||||
// Check dialog handle
|
||||
if (DialogWindowHandle == nint.Zero) return;
|
||||
|
||||
// Wait 150ms to check if Dialog Jump window gets the focus
|
||||
var timeOut = !SpinWait.SpinUntil(() => !Win32Helper.IsForegroundWindow(DialogWindowHandle), 150);
|
||||
if (timeOut) return;
|
||||
|
||||
// Bring focus back to the dialog
|
||||
Win32Helper.SetForegroundWindow(DialogWindowHandle);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.API.LogException(ClassName, "Failed to focus on dialog window", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable VSTHRD100 // Avoid async void methods
|
||||
|
||||
public async void ResetDialogJump()
|
||||
{
|
||||
// Cache original dialog window handle
|
||||
var dialogWindowHandle = DialogWindowHandle;
|
||||
|
||||
// Reset the Dialog Jump state
|
||||
DialogWindowHandle = nint.Zero;
|
||||
_isDialogJump = false;
|
||||
|
||||
// If dialog window handle is not set, we should not reset the main window visibility
|
||||
if (dialogWindowHandle == nint.Zero) return;
|
||||
|
||||
if (_previousMainWindowVisibilityStatus != MainWindowVisibilityStatus)
|
||||
{
|
||||
// We wait for window to be reset before showing it because if window has results,
|
||||
// showing it before resetting will cause flickering when results are clearing
|
||||
await ResetWindowAsync();
|
||||
|
||||
// Show or hide to change visibility
|
||||
if (_previousMainWindowVisibilityStatus)
|
||||
{
|
||||
Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
Hide(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_previousMainWindowVisibilityStatus)
|
||||
{
|
||||
// Only update the position
|
||||
Application.Current?.Dispatcher.Invoke(() =>
|
||||
{
|
||||
(Application.Current?.MainWindow as MainWindow)?.UpdatePosition();
|
||||
});
|
||||
|
||||
_ = ResetWindowAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = ResetWindowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore VSTHRD100 // Avoid async void methods
|
||||
|
||||
public void HideDialogJump()
|
||||
{
|
||||
if (DialogWindowHandle != nint.Zero)
|
||||
{
|
||||
if (DialogJump.DialogJumpWindowPosition == DialogJumpWindowPositions.UnderDialog)
|
||||
{
|
||||
// Warning: Main window is already in foreground
|
||||
// This is because if you click popup menus in other applications to hide Dialog Jump window,
|
||||
// they can steal focus before showing main window
|
||||
if (MainWindowVisibilityStatus)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset index & preview & selected results & query text
|
||||
private async Task ResetWindowAsync()
|
||||
{
|
||||
lastHistoryIndex = 1;
|
||||
|
||||
if (ExternalPreviewVisible)
|
||||
{
|
||||
await CloseExternalPreviewAsync();
|
||||
}
|
||||
|
||||
if (!QueryResultsSelected())
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
|
||||
await ChangeQueryTextAsync(string.Empty, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
#pragma warning disable VSTHRD100 // Avoid async void methods
|
||||
|
|
@ -1770,7 +2045,7 @@ namespace Flow.Launcher.ViewModel
|
|||
Win32Helper.DWMSetCloakForWindow(mainWindow, false);
|
||||
|
||||
// Set clock and search icon opacity
|
||||
var opacity = Settings.UseAnimation ? 0.0 : 1.0;
|
||||
var opacity = (Settings.UseAnimation && !_isDialogJump) ? 0.0 : 1.0;
|
||||
ClockPanelOpacity = opacity;
|
||||
SearchIconOpacity = opacity;
|
||||
|
||||
|
|
@ -1799,37 +2074,40 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public async void Hide()
|
||||
public async void Hide(bool reset = true)
|
||||
{
|
||||
lastHistoryIndex = 1;
|
||||
|
||||
if (ExternalPreviewVisible)
|
||||
if (reset)
|
||||
{
|
||||
await CloseExternalPreviewAsync();
|
||||
}
|
||||
lastHistoryIndex = 1;
|
||||
|
||||
BackToQueryResults();
|
||||
if (ExternalPreviewVisible)
|
||||
{
|
||||
await CloseExternalPreviewAsync();
|
||||
}
|
||||
|
||||
switch (Settings.LastQueryMode)
|
||||
{
|
||||
case LastQueryMode.Empty:
|
||||
await ChangeQueryTextAsync(string.Empty);
|
||||
break;
|
||||
case LastQueryMode.Preserved:
|
||||
case LastQueryMode.Selected:
|
||||
LastQuerySelected = Settings.LastQueryMode == LastQueryMode.Preserved;
|
||||
break;
|
||||
case LastQueryMode.ActionKeywordPreserved:
|
||||
case LastQueryMode.ActionKeywordSelected:
|
||||
var newQuery = _lastQuery?.ActionKeyword;
|
||||
BackToQueryResults();
|
||||
|
||||
if (!string.IsNullOrEmpty(newQuery))
|
||||
newQuery += " ";
|
||||
await ChangeQueryTextAsync(newQuery);
|
||||
switch (Settings.LastQueryMode)
|
||||
{
|
||||
case LastQueryMode.Empty:
|
||||
await ChangeQueryTextAsync(string.Empty);
|
||||
break;
|
||||
case LastQueryMode.Preserved:
|
||||
case LastQueryMode.Selected:
|
||||
LastQuerySelected = Settings.LastQueryMode == LastQueryMode.Preserved;
|
||||
break;
|
||||
case LastQueryMode.ActionKeywordPreserved:
|
||||
case LastQueryMode.ActionKeywordSelected:
|
||||
var newQuery = _lastQuery.ActionKeyword;
|
||||
|
||||
if (Settings.LastQueryMode == LastQueryMode.ActionKeywordSelected)
|
||||
LastQuerySelected = false;
|
||||
break;
|
||||
if (!string.IsNullOrEmpty(newQuery))
|
||||
newQuery += " ";
|
||||
await ChangeQueryTextAsync(newQuery);
|
||||
|
||||
if (Settings.LastQueryMode == LastQueryMode.ActionKeywordSelected)
|
||||
LastQuerySelected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// When application is exiting, the Application.Current will be null
|
||||
|
|
@ -1839,7 +2117,7 @@ namespace Flow.Launcher.ViewModel
|
|||
if (Application.Current?.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
// Set clock and search icon opacity
|
||||
var opacity = Settings.UseAnimation ? 0.0 : 1.0;
|
||||
var opacity = (Settings.UseAnimation && !_isDialogJump) ? 0.0 : 1.0;
|
||||
ClockPanelOpacity = opacity;
|
||||
SearchIconOpacity = opacity;
|
||||
|
||||
|
|
@ -1984,6 +2262,7 @@ namespace Flow.Launcher.ViewModel
|
|||
if (disposing)
|
||||
{
|
||||
_updateSource?.Dispose();
|
||||
_dialogJumpSource?.Dispose();
|
||||
_resultsUpdateChannelWriter?.Complete();
|
||||
if (_resultsViewUpdateTask?.IsCompleted == true)
|
||||
{
|
||||
|
|
|
|||
661
Flow.Launcher/packages.lock.json
Normal file
661
Flow.Launcher/packages.lock.json
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net9.0-windows10.0.19041": {
|
||||
"ChefKeys": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.1.2, )",
|
||||
"resolved": "0.1.2",
|
||||
"contentHash": "hnayWejg57tg8+lZ1Q/zPR8tj9ezUtB1sY8aCv9jiZ+3wcqK0eGL+Skt9OzT9mjSsBIg4o9Jv1HdQdzjd1lkQw=="
|
||||
},
|
||||
"CommunityToolkit.Mvvm": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.4.0, )",
|
||||
"resolved": "8.4.0",
|
||||
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
|
||||
},
|
||||
"Fody": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.5.4, )",
|
||||
"resolved": "6.5.4",
|
||||
"contentHash": "GXZuti428IZctfby10xkMbWLCibcb6s29I/psLbBoO2vHJI5eTNVybnlV/Wi1tlIu9GG0bgW/PQwMH+MCldHxw=="
|
||||
},
|
||||
"InputSimulator": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.4, )",
|
||||
"resolved": "1.0.4",
|
||||
"contentHash": "D0LvRCPQMX6/FJHBjng+RO+wRDuHTJrfo7IAc7rmkPvRqchdVGJWg3y70peOtDy3OLNK+HSOwVkH4GiuLnkKgA=="
|
||||
},
|
||||
"Jack251970.TaskScheduler": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.12.1, )",
|
||||
"resolved": "2.12.1",
|
||||
"contentHash": "+epAtsLMugiznJCNRYCYB6eBcr+bx+CVlwPWMprO5CbnNkWu9mlSV8XN5BQJrGYwmlAtlGfZA3p3PcFFlrgR6A==",
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.Registry": "5.0.0",
|
||||
"System.Diagnostics.EventLog": "8.0.0",
|
||||
"System.Security.AccessControl": "6.0.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "Direct",
|
||||
"requested": "[7.0.0, )",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Hosting": {
|
||||
"type": "Direct",
|
||||
"requested": "[7.0.1, )",
|
||||
"resolved": "7.0.1",
|
||||
"contentHash": "aoeMou6XSW84wiqd895OdaGyO9PfH6nohQJ0XBcshRDafbdIU6PQIVl8TpOCssPYq3ciRseP5064hbFyCR9J9w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Binder": "7.0.3",
|
||||
"Microsoft.Extensions.Configuration.CommandLine": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Json": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "7.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection": "7.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "7.0.0",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Logging": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Configuration": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Console": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Debug": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.EventLog": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.EventSource": "7.0.0",
|
||||
"Microsoft.Extensions.Options": "7.0.1",
|
||||
"System.Diagnostics.DiagnosticSource": "7.0.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.Toolkit.Uwp.Notifications": {
|
||||
"type": "Direct",
|
||||
"requested": "[7.1.3, )",
|
||||
"resolved": "7.1.3",
|
||||
"contentHash": "A1dglAzb24gjehmb7DwGd07mfyZ1gacAK7ObE0KwDlRc3mayH2QW7cSOy3TkkyELjLg19OQBuhPOj4SpXET9lg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.Registry": "4.7.0",
|
||||
"System.Drawing.Common": "4.7.0",
|
||||
"System.Reflection.Emit": "4.7.0",
|
||||
"System.ValueTuple": "4.5.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Windows.CsWin32": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.3.106, )",
|
||||
"resolved": "0.3.106",
|
||||
"contentHash": "Mx5fK7uN6fwLR4wUghs6//HonAnwPBNmC2oonyJVhCUlHS/r6SUS3NkBc3+gaQiv+0/9bqdj1oSCKQFkNI+21Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha",
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "60.0.34-preview",
|
||||
"Microsoft.Windows.WDK.Win32Metadata": "0.11.4-experimental"
|
||||
}
|
||||
},
|
||||
"ModernWpfUI": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.9.4, )",
|
||||
"resolved": "0.9.4",
|
||||
"contentHash": "HJ07Be9KOiGKGcMLz/AwY+84h3yGHRPuYpYXCE6h1yPtaFwGMWfanZ70jX7W5XWx8+Qk1vGox+WGKgxxsy6EHw=="
|
||||
},
|
||||
"NHotkey.Wpf": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.0, )",
|
||||
"resolved": "3.0.0",
|
||||
"contentHash": "BIUKlhTG5KtFf9OQzWvkmVmktt5/FFj6AOEgag8Uf0R2YdZt5ajUzs3sVskcJcT2TztWlEHKQr1jFj3KQ0D9Nw==",
|
||||
"dependencies": {
|
||||
"NHotkey": "3.0.0"
|
||||
}
|
||||
},
|
||||
"PropertyChanged.Fody": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.4.0, )",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "IAZyq0uolKo2WYm4mjx+q7A8fSGFT0x2e1s3y+ODn4JI0kqTDoo9GF2tdaypUzRFJZfdMxfC5HZW9QzdJLtOnA==",
|
||||
"dependencies": {
|
||||
"Fody": "6.5.1"
|
||||
}
|
||||
},
|
||||
"SemanticVersioning": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.0.0, )",
|
||||
"resolved": "3.0.0",
|
||||
"contentHash": "RR+8GbPQ/gjDqov/1QN1OPoUlbUruNwcL3WjWCeLw+MY7+od/ENhnkYxCfAC6rQLIu3QifaJt3kPYyP3RumqMQ=="
|
||||
},
|
||||
"VirtualizingWrapPanel": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.1.1, )",
|
||||
"resolved": "2.1.1",
|
||||
"contentHash": "Fc/yjU8jqC3qpIsNxeO5RjK2lPU7xnJtBLMSQ6L9egA2PyJLQeVeXpG8WBb5N1kN15rlJEYG8dHWJ5qUGgaNrg=="
|
||||
},
|
||||
"Ben.Demystifier": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.4.1",
|
||||
"contentHash": "axFeEMfmEORy3ipAzOXG/lE+KcNptRbei3F0C4kQCdeiQtW+qJW90K5iIovITGrdLt8AjhNCwk5qLSX9/rFpoA==",
|
||||
"dependencies": {
|
||||
"System.Reflection.Metadata": "5.0.0"
|
||||
}
|
||||
},
|
||||
"BitFaster.Caching": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.5.3",
|
||||
"contentHash": "Vo/39qcam5Xe+DbyfH0JZyqPswdOoa7jv4PGtRJ6Wj8AU+aZ+TuJRlJcIe+MQjRTJwliI8k8VSQpN8sEoBIv2g=="
|
||||
},
|
||||
"DeltaCompressionDotNet": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "nwbZAYd+DblXAIzlnwDSnl0CiCm8jWLfHSYnoN4wYhtIav6AegB3+T/vKzLbU2IZlPB8Bvl8U3NXpx3eaz+N5w=="
|
||||
},
|
||||
"Droplex": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.7.0",
|
||||
"contentHash": "wutfIus/Ufw/9TDsp86R1ycnIH+wWrj4UhcmrzAHWjsdyC2iM07WEQ9+APTB7pQynsDnYH1r2i58XgAJ3lxUXA==",
|
||||
"dependencies": {
|
||||
"YamlDotNet": "9.1.0"
|
||||
}
|
||||
},
|
||||
"FSharp.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.101",
|
||||
"contentHash": "3/YR1SDWFA+Ojx9HiBwND+0UR8ZWoeZfkhD0DWAPCDdr/YI+CyFkArmMGzGSyPXeYtjG0sy0emzfyNwjt7zhig=="
|
||||
},
|
||||
"JetBrains.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2024.3.0",
|
||||
"contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug=="
|
||||
},
|
||||
"MemoryPack": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.21.3",
|
||||
"contentHash": "cwCtED8y400vMWx/Vp0QCSeEpVFjDU4JwF52VX9WTaqVERUvNqjG9n6osFlmFuytegyXnHvYEu1qRJ8rv/rkbg==",
|
||||
"dependencies": {
|
||||
"MemoryPack.Core": "1.21.3",
|
||||
"MemoryPack.Generator": "1.21.3"
|
||||
}
|
||||
},
|
||||
"MemoryPack.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.21.3",
|
||||
"contentHash": "ajrYoBWT2aKeH4tlY8q/1C9qK1R/NK+7FkuVOX58ebOSxkABoFTqCR7W+Zk2rakUHZiEgNdRqO67hiRZPq6fLA=="
|
||||
},
|
||||
"MemoryPack.Generator": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.21.3",
|
||||
"contentHash": "hYU0TAIarDKnbkNIWvb7P4zBUL+CTahkuNkczsKvycSMR5kiwQ4IfLexywNKX3s05Izp4gzDSPbueepNWZRpWA=="
|
||||
},
|
||||
"MessagePack": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.5.187",
|
||||
"contentHash": "uW4j8m4Nc+2Mk5n6arOChavJ9bLjkis0qWASOj2h2OwmfINuzYv+mjCHUymrYhmyyKTu3N+ObtTXAY4uQ7jIhg==",
|
||||
"dependencies": {
|
||||
"MessagePack.Annotations": "2.5.187",
|
||||
"Microsoft.NET.StringTools": "17.6.3"
|
||||
}
|
||||
},
|
||||
"MessagePack.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.5.187",
|
||||
"contentHash": "/IvvMMS8opvlHjEJ/fR2Cal4Co726Kj77Z8KiohFhuHfLHHmb9uUxW5+tSCL4ToKFfkQlrS3HD638mRq83ySqA=="
|
||||
},
|
||||
"Meziantou.Framework.Win32.Jobs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.0",
|
||||
"contentHash": "5GGLckfpwoC1jznInEYfK2INrHyD7K1RtwZJ98kNPKBU6jeu24i4zfgDGHHfb+eK3J+eFPAxo0aYcbUxNXIbNw=="
|
||||
},
|
||||
"Microsoft.Extensions.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "tldQUBWt/xeH2K7/hMPPo5g8zuLc3Ro9I5d4o/XrxvxOCA2EZBtW7bCHHTc49fcBtvB8tLAb/Qsmfrq+2SJ4vA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Primitives": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Binder": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.3",
|
||||
"contentHash": "1eRFwJBrkkncTpvh6mivB8zg4uBVm6+Y6stEJERrVEqZZc8Hvf+N1iIgj2ySYDUQko4J1Gw1rLf1M8bG83F0eA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.CommandLine": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "a8Iq8SCw5m8W5pZJcPCgBpBO4E89+NaObPng+ApIhrGSv9X4JPrcFAaGM4sDgR0X83uhLgsNJq8VnGP/wqhr8A==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "RIkfqCkvrAogirjsqSrG1E1FxgrLsOZU2nhRbl07lrajnxzSU2isj2lwQah0CtCbLWo/pOIukQzM1GfneBUnxA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "xk2lRJ1RDuqe57BmgvRPyCt6zyePKUmvT6iuXqiHR+/OIIgWVR8Ff5k2p6DwmqY8a17hx/OnrekEhziEIeQP6Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "7.0.0",
|
||||
"Microsoft.Extensions.Primitives": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "LDNYe3uw76W35Jci+be4LDf2lkQZe0A7EEYQVChFbc509CpZ4Iupod8li4PUXPBhEUOFI/rlQNf5xkzJRQGvtA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": "7.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "7.0.0",
|
||||
"System.Text.Json": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "33HPW1PmB2RS0ietBQyvOxjp4O3wlt+4tIs8KPyMn1kqp04goiZGa7+3mc69NRLv6bphkLDy0YR7Uw3aZyf8Zw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Json": "7.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw=="
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "NyawiW9ZT/liQb34k9YqBSNPLuuPkrjMgQZ24Y/xXX1RoiBkLUdPMaQTmxhZ5TYu8ZKZ9qayzil75JX95vGQUg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Physical": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "K8D2MTR+EtzkbZ8z80LrG7Ur64R7ZZdRLt1J5cgpc/pUWl0C6IkAUapPuK28oionHueCPELUqq0oYEvZfalNdg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.FileSystemGlobbing": "7.0.0",
|
||||
"Microsoft.Extensions.Primitives": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileSystemGlobbing": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "2jONjKHiF+E92ynz2ZFcr9OvxIw+rTGMPEH+UZGeHTEComVav93jQUWGkso8yWwVBcEJGcNcZAaqY01FFJcj7w=="
|
||||
},
|
||||
"Microsoft.Extensions.Hosting.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "43n9Je09z0p/7ViPxfRqs5BUItRLNVh5b6JH40F2Agkh2NBsY/jpNYTtbCcxrHCsA3oRmbR6RJBzUutB4VZvNQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "7.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Options": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw=="
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "FLDA0HcffKA8ycoDQLJuCNGIE42cLWPxgdQGRBaSzZrYTkMBjnf9zrr8pGT06psLq9Q+RKWmmZczQ9bCrXEBcA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Binder": "7.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Logging": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Options": "7.0.0",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Console": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "qt5n8bHLZPUfuRnFxJKW5q9ZwOTncdh96rtWzWpX3Y/064MlxzCSw2ELF5Jlwdo+Y4wK3I47NmUTFsV7Sg8rqg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Logging": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Configuration": "7.0.0",
|
||||
"Microsoft.Extensions.Options": "7.0.0",
|
||||
"System.Text.Json": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Debug": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "tFGGyPDpJ8ZdQdeckCArP7nZuoY3am9zJWuvp4OD1bHq65S0epW9BNHzAWeaIO4eYwWnGm1jRNt3vRciH8H6MA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Logging": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.EventLog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "Rp7cYL9xQRVTgjMl77H5YDxszAaO+mlA+KT0BnLSVhuCoKQQOOs1sSK2/x8BK2dZ/lKeAC/CVF+20Ef2dpKXwg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Logging": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Options": "7.0.0",
|
||||
"System.Diagnostics.EventLog": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.EventSource": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "MxQXndQFviIyOPqyMeLNshXnmqcfzEHE2wWcr7BF1unSisJgouZ3tItnq+aJLGPojrW8OZSC/ZdRoR6wAq+c7w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Logging": "7.0.0",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Options": "7.0.0",
|
||||
"Microsoft.Extensions.Primitives": "7.0.0",
|
||||
"System.Text.Json": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.1",
|
||||
"contentHash": "pZRDYdN1FpepOIfHU62QoBQ6zdAoTvnjxFfqAzEd9Jhb2dfhA5i6jeTdgGgcgTWFRC7oT0+3XrbQu4LjvgX1Nw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Primitives": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "95UnxZkkFdXxF6vSrtJsMHCzkDeSMuUWGs2hDT54cX+U5eVajrCJ3qLyQRW+CtpTt5OJ8bmTvpQVHu1DLhH+cA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Configuration.Binder": "7.0.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0",
|
||||
"Microsoft.Extensions.Options": "7.0.0",
|
||||
"Microsoft.Extensions.Primitives": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q=="
|
||||
},
|
||||
"Microsoft.IO.RecyclableMemoryStream": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.1",
|
||||
"contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g=="
|
||||
},
|
||||
"Microsoft.NET.StringTools": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.6.3",
|
||||
"contentHash": "N0ZIanl1QCgvUumEL1laasU0a7sOE5ZwLZVTn0pAePnfhq8P7SvTjF8Axq+CnavuQkmdQpGNXQ1efZtu5kDFbA=="
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.12.19",
|
||||
"contentHash": "eLiGMkMYyaSguqHs3lsrFxy3tAWSLuPEL2pIWRcADMDVAs2xqm3dr1d9QYjiEusTgiClF9KD6OB2NdZP72Oy0Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": "17.12.19",
|
||||
"Microsoft.VisualStudio.Validation": "17.8.8"
|
||||
}
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.12.19",
|
||||
"contentHash": "v3IYeedjoktvZ+GqYmLudxZJngmf/YWIxNT2Uy6QMMN19cvw+nkWoip1Gr1RtnFkUo1MPUVMis4C8Kj8d8DpSQ=="
|
||||
},
|
||||
"Microsoft.VisualStudio.Validation": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.8.8",
|
||||
"contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g=="
|
||||
},
|
||||
"Microsoft.Win32.Registry": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==",
|
||||
"dependencies": {
|
||||
"System.Security.AccessControl": "5.0.0",
|
||||
"System.Security.Principal.Windows": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Win32.SystemEvents": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "5BkGZ6mHp2dHydR29sb0fDfAuqkv30AHtTih8wMzvPZysOmBFvHfnkR2w3tsc0pSiIg8ZoKyefJXWy9r3pBh0w=="
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Docs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.1.42-alpha",
|
||||
"contentHash": "Z/9po23gUA9aoukirh2ItMU2ZS9++Js9Gdds9fu5yuMojDrmArvY2y+tq9985tR3cxFxpZO1O35Wjfo0khj5HA=="
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "60.0.34-preview",
|
||||
"contentHash": "TA3DUNi4CTeo+ItTXBnGZFt2159XOGSl0UOlG5vjDj4WHqZjhwYyyUnzOtrbCERiSaP2Hzg7otJNWwOSZgutyA=="
|
||||
},
|
||||
"Microsoft.Windows.WDK.Win32Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.11.4-experimental",
|
||||
"contentHash": "bf5MCmUyZf0gBlYQjx9UpRAZWBkRndyt9XicR+UNLvAUAFTZQbu6YaX/sNKZlR98Grn0gydfh/yT4I3vc0AIQA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "60.0.34-preview"
|
||||
}
|
||||
},
|
||||
"Mono.Cecil": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.9.6.1",
|
||||
"contentHash": "yMsurNaOxxKIjyW9pEB+tRrR1S3DFnN1+iBgKvYvXG8kW0Y6yknJeMAe/tl3+P78/2C6304TgF7aVqpqXgEQ9Q=="
|
||||
},
|
||||
"Nerdbank.Streams": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.11.74",
|
||||
"contentHash": "r4G7uHHfoo8LCilPOdtf2C+Q5ymHOAXtciT4ZtB2xRlAvv4gPkWBYNAijFblStv3+uidp81j5DP11jMZl4BfJw==",
|
||||
"dependencies": {
|
||||
"Microsoft.VisualStudio.Threading": "17.10.48",
|
||||
"Microsoft.VisualStudio.Validation": "17.8.8",
|
||||
"System.IO.Pipelines": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "13.0.1",
|
||||
"contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
|
||||
},
|
||||
"NHotkey": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.0",
|
||||
"contentHash": "IEghs0QqWsQYH0uUmvIl0Ye6RaebWRh38eB6ToOkDnQucTYRGFOgtig0gSxlwCszTilYFz3n1ZuY762x+kDR3A=="
|
||||
},
|
||||
"NLog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.7.10",
|
||||
"contentHash": "rcegW7kYOCjl7wX0SzsqpPBqnJ51JKi1WkYb6QBVX0Wc5IgH19Pv4t/co+T0s06OS0Ne44xgkY/mHg0PdrmJow=="
|
||||
},
|
||||
"Splat": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.6.2",
|
||||
"contentHash": "DeH0MxPU+D4JchkIDPYG4vUT+hsWs9S41cFle0/4K5EJMXWurx5DzAkj2366DfK14/XKNhsu6tCl4dZXJ3CD4w=="
|
||||
},
|
||||
"squirrel.windows": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.5.2",
|
||||
"contentHash": "89Y/CFxWm7SEOjvuV2stVa8p+SNM9GOLk4tUNm2nUF792nfkimAgwRA/umVsdyd/OXBH8byXSh4V1qck88ZAyQ==",
|
||||
"dependencies": {
|
||||
"DeltaCompressionDotNet": "[1.0.0, 2.0.0)",
|
||||
"Mono.Cecil": "0.9.6.1",
|
||||
"Splat": "1.6.2"
|
||||
}
|
||||
},
|
||||
"StreamJsonRpc": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.20.20",
|
||||
"contentHash": "gwG7KViLbSWS7EI0kYevinVmIga9wZNrpSY/FnWyC6DbdjKJ1xlv/FV1L9b0rLkVP8cGxfIMexdvo/+2W5eq6Q==",
|
||||
"dependencies": {
|
||||
"MessagePack": "2.5.187",
|
||||
"Microsoft.VisualStudio.Threading": "17.10.48",
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": "17.10.48",
|
||||
"Microsoft.VisualStudio.Validation": "17.8.8",
|
||||
"Nerdbank.Streams": "2.11.74",
|
||||
"Newtonsoft.Json": "13.0.1",
|
||||
"System.IO.Pipelines": "8.0.0"
|
||||
}
|
||||
},
|
||||
"System.Diagnostics.DiagnosticSource": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.1",
|
||||
"contentHash": "T9SLFxzDp0SreCffRDXSAS5G+lq6E8qP4knHS2IBjwCdx2KEvGnGZsq7gFpselYOda7l6gXsJMD93TQsFj/URA=="
|
||||
},
|
||||
"System.Diagnostics.EventLog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A=="
|
||||
},
|
||||
"System.Drawing.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.2",
|
||||
"contentHash": "JU947wzf8JbBS16Y5EIZzAlyQU+k68D7LRx6y03s2wlhlvLqkt/8uPBrjv2hJnnaJKbdb0GhQ3JZsfYXhrRjyg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Win32.SystemEvents": "9.0.2"
|
||||
}
|
||||
},
|
||||
"System.IO.Pipelines": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "FHNOatmUq0sqJOkTx+UF/9YK1f180cnW5FVqnQMvYUN0elp6wFzbtPSiqbo1/ru8ICp43JM1i7kKkk6GsNGHlA=="
|
||||
},
|
||||
"System.Reflection.Emit": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.7.0",
|
||||
"contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ=="
|
||||
},
|
||||
"System.Reflection.Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ=="
|
||||
},
|
||||
"System.Security.AccessControl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.1",
|
||||
"contentHash": "IQ4NXP/B3Ayzvw0rDQzVTYsCKyy0Jp9KI6aYcK7UnGVlR9+Awz++TIPCQtPYfLJfOpm8ajowMR09V7quD3sEHw=="
|
||||
},
|
||||
"System.Security.Principal.Windows": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA=="
|
||||
},
|
||||
"System.Text.Encodings.Web": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "OP6umVGxc0Z0MvZQBVigj4/U31Pw72ITihDWP9WiWDm+q5aoe0GaJivsfYGq53o6dxH7DcXWiCTl7+0o2CGdmg=="
|
||||
},
|
||||
"System.Text.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "DaGSsVqKsn/ia6RG8frjwmJonfos0srquhw09TlT8KRw5I43E+4gs+/bZj4K0vShJ5H9imCuXupb4RmS+dBy3w==",
|
||||
"dependencies": {
|
||||
"System.Text.Encodings.Web": "7.0.0"
|
||||
}
|
||||
},
|
||||
"System.ValueTuple": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ=="
|
||||
},
|
||||
"ToolGood.Words.Pinyin": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.1.4",
|
||||
"contentHash": "uQo97618y9yzLDxrnehPN+/tuiOlk5BqieEdwctHZOAS9miMXnHKgMFYVw8CSGXRglyTYXlrW7qtUlU7Fje5Ew=="
|
||||
},
|
||||
"YamlDotNet": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.1.0",
|
||||
"contentHash": "fuvGXU4Ec5HrsmEc+BiFTNPCRf1cGBI2kh/3RzMWgddM2M4ALhbSPoI3X3mhXZUD1qqQd9oSkFAtWjpz8z9eRg=="
|
||||
},
|
||||
"flow.launcher.core": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Droplex": "[1.7.0, )",
|
||||
"FSharp.Core": "[9.0.101, )",
|
||||
"Flow.Launcher.Infrastructure": "[1.0.0, )",
|
||||
"Flow.Launcher.Plugin": "[4.4.0, )",
|
||||
"Meziantou.Framework.Win32.Jobs": "[3.4.0, )",
|
||||
"Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )",
|
||||
"StreamJsonRpc": "[2.20.20, )",
|
||||
"squirrel.windows": "[1.5.2, )"
|
||||
}
|
||||
},
|
||||
"flow.launcher.infrastructure": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Ben.Demystifier": "[0.4.1, )",
|
||||
"BitFaster.Caching": "[2.5.3, )",
|
||||
"CommunityToolkit.Mvvm": "[8.4.0, )",
|
||||
"Flow.Launcher.Plugin": "[4.4.0, )",
|
||||
"MemoryPack": "[1.21.3, )",
|
||||
"Microsoft.VisualStudio.Threading": "[17.12.19, )",
|
||||
"NLog": "[4.7.10, )",
|
||||
"PropertyChanged.Fody": "[3.4.0, )",
|
||||
"System.Drawing.Common": "[9.0.2, )",
|
||||
"ToolGood.Words.Pinyin": "[3.0.1.4, )"
|
||||
}
|
||||
},
|
||||
"flow.launcher.plugin": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"JetBrains.Annotations": "[2024.3.0, )",
|
||||
"PropertyChanged.Fody": "[3.4.0, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ProjectGuid>{9B130CC5-14FB-41FF-B310-0A95B6894C37}</ProjectGuid>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
<system:String x:Key="flowlauncher_plugin_browserbookmark_plugin_name">Browser Bookmarks</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_plugin_description">Search your browser bookmarks</system:String>
|
||||
|
||||
<!-- Main -->
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_copy_failed">Failed to set url in clipboard</system:String>
|
||||
|
||||
<!-- Settings -->
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_bookmarkDataSetting">Bookmark Data</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_browserbookmark_settings_openBookmarks">Open bookmarks in:</system:String>
|
||||
|
|
|
|||
|
|
@ -223,11 +223,8 @@ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContex
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = "Failed to set url in clipboard";
|
||||
_context.API.LogException(ClassName, message, e);
|
||||
|
||||
_context.API.ShowMsg(message);
|
||||
|
||||
_context.API.LogException(ClassName, "Failed to set url in clipboard", e);
|
||||
_context.API.ShowMsgError(_context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copy_failed"));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ProjectGuid>{59BD9891-3837-438A-958D-ADC7F91F6F7E}</ProjectGuid>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Flow.Launcher.Plugin.Calculator</RootNamespace>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_caculator_plugin_name">Calculator</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_caculator_plugin_description">Allows to do mathematical calculations.(Try 5*3-2 in Flow Launcher)</system:String>
|
||||
|
|
@ -13,4 +14,5 @@
|
|||
<system:String x:Key="flowlauncher_plugin_calculator_decimal_seperator_comma">Comma (,)</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_calculator_decimal_seperator_dot">Dot (.)</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_calculator_max_decimal_places">Max. decimal places</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_calculator_failed_to_copy">Copy failed, please try later</system:String>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -100,7 +100,7 @@ namespace Flow.Launcher.Plugin.Calculator
|
|||
}
|
||||
catch (ExternalException)
|
||||
{
|
||||
Context.API.ShowMsgBox("Copy failed, please try later");
|
||||
Context.API.ShowMsgBox(Context.API.GetTranslation("flowlauncher_plugin_calculator_failed_to_copy"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,9 +132,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = "Fail to set text in clipboard";
|
||||
LogException(message, e);
|
||||
Context.API.ShowMsg(message);
|
||||
LogException("Fail to set text in clipboard", e);
|
||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_text"));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
|
@ -155,9 +154,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = "Fail to set text in clipboard";
|
||||
LogException(message, e);
|
||||
Context.API.ShowMsg(message);
|
||||
LogException("Fail to set text in clipboard", e);
|
||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_text"));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
|
@ -178,9 +176,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"Fail to set file/folder in clipboard";
|
||||
LogException(message, e);
|
||||
Context.API.ShowMsg(message);
|
||||
LogException($"Fail to set file/folder in clipboard", e);
|
||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_files"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -221,9 +218,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"Fail to delete {record.FullPath}";
|
||||
LogException(message, e);
|
||||
Context.API.ShowMsgError(message);
|
||||
LogException($"Fail to delete {record.FullPath}", e);
|
||||
Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_explorer_fail_to_delete"), record.FullPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -265,9 +261,9 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
var name = "Plugin: Folder";
|
||||
var message = $"File not found: {e.Message}";
|
||||
Context.API.ShowMsgError(name, message);
|
||||
Context.API.ShowMsgError(
|
||||
Context.API.GetTranslation("plugin_explorer_plugin_name"),
|
||||
string.Format(Context.API.GetTranslation("plugin_explorer_file_not_found"), e.Message));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -334,9 +330,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"Fail to open file at {record.FullPath}";
|
||||
LogException(message, e);
|
||||
Context.API.ShowMsgError(message);
|
||||
LogException($"Fail to open file at {record.FullPath}", e);
|
||||
Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_explorer_fail_to_open"), record.FullPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="Droplex" Version="1.7.0" />
|
||||
<!-- 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="9.0.3" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
<system:String x:Key="plugin_explorer_make_selection_warning">Please make a selection first</system:String>
|
||||
<system:String x:Key="plugin_explorer_quick_access_link_no_folder_selected">Please select a folder path.</system:String>
|
||||
<system:String x:Key="plugin_explorer_quick_access_link_path_already_exists">Please choose a different name or folder path.</system:String>
|
||||
<system:String x:Key="plugin_explorer_delete_quick_access_link">Are you sure you want to delete this quick access link?</system:String>
|
||||
<system:String x:Key="plugin_explorer_delete_index_search_excluded_path">Are you sure you want to delete this index search excluded path?</system:String>
|
||||
<system:String x:Key="plugin_explorer_select_folder_link_warning">Please select a folder link</system:String>
|
||||
<system:String x:Key="plugin_explorer_delete_folder_link">Are you sure you want to delete {0}?</system:String>
|
||||
<system:String x:Key="plugin_explorer_deletefileconfirm">Are you sure you want to permanently delete this file?</system:String>
|
||||
|
|
@ -130,6 +132,11 @@
|
|||
<system:String x:Key="plugin_explorer_show_contextmenu_title">Show Windows Context Menu</system:String>
|
||||
<system:String x:Key="plugin_explorer_openwith">Open With</system:String>
|
||||
<system:String x:Key="plugin_explorer_openwith_subtitle">Select a program to open with</system:String>
|
||||
<system:String x:Key="plugin_explorer_fail_to_delete">Fail to delete {0}</system:String>
|
||||
<system:String x:Key="plugin_explorer_file_not_found">File not found: {0}</system:String>
|
||||
<system:String x:Key="plugin_explorer_fail_to_open">Fail to open {0}</system:String>
|
||||
<system:String x:Key="plugin_explorer_fail_to_set_text">Fail to set text in clipboard</system:String>
|
||||
<system:String x:Key="plugin_explorer_fail_to_set_files">Fail to set files/folders in clipboard</system:String>
|
||||
|
||||
<!-- Special Results -->
|
||||
<system:String x:Key="plugin_explorer_diskfreespace">{0} free of {1}</system:String>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Flow.Launcher.Plugin.Explorer.Helper;
|
||||
using Flow.Launcher.Plugin.Explorer.Helper;
|
||||
using Flow.Launcher.Plugin.Explorer.Search;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything;
|
||||
using Flow.Launcher.Plugin.Explorer.ViewModels;
|
||||
|
|
@ -10,10 +10,11 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Plugin.Explorer.Exceptions;
|
||||
using System.Linq;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer
|
||||
{
|
||||
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n
|
||||
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n, IAsyncDialogJump
|
||||
{
|
||||
internal static PluginInitContext Context { get; set; }
|
||||
|
||||
|
|
@ -25,6 +26,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
|
||||
private SearchManager searchManager;
|
||||
|
||||
private static readonly List<DialogJumpResult> _emptyDialogJumpResultList = new();
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
return new ExplorerSettings(viewModel);
|
||||
|
|
@ -108,5 +111,18 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<DialogJumpResult>> QueryDialogJumpAsync(Query query, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var results = await searchManager.SearchAsync(query, token);
|
||||
return results.Select(r => DialogJumpResult.From(r, r.CopyText)).ToList();
|
||||
}
|
||||
catch (Exception e) when (e is SearchException or EngineNotAvailableException)
|
||||
{
|
||||
return _emptyDialogJumpResultList;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,15 +283,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
internal static Result CreateFileResult(string filePath, Query query, int score = 0, bool windowsIndexed = false)
|
||||
{
|
||||
bool isMedia = IsMedia(Path.GetExtension(filePath));
|
||||
var title = Path.GetFileName(filePath);
|
||||
var isMedia = IsMedia(Path.GetExtension(filePath));
|
||||
var title = Path.GetFileName(filePath) ?? string.Empty;
|
||||
var directory = Path.GetDirectoryName(filePath) ?? string.Empty;
|
||||
|
||||
/* Preview Detail */
|
||||
|
||||
var result = new Result
|
||||
{
|
||||
Title = title,
|
||||
SubTitle = Path.GetDirectoryName(filePath),
|
||||
SubTitle = directory,
|
||||
IcoPath = filePath,
|
||||
Preview = new Result.PreviewInfo
|
||||
{
|
||||
|
|
@ -315,7 +316,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
if (c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift))
|
||||
{
|
||||
OpenFile(filePath, Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty, true);
|
||||
OpenFile(filePath, Settings.UseLocationAsWorkingDir ? directory : string.Empty, true);
|
||||
}
|
||||
else if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control)
|
||||
{
|
||||
|
|
@ -323,7 +324,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
}
|
||||
else
|
||||
{
|
||||
OpenFile(filePath, Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty);
|
||||
OpenFile(filePath, Settings.UseLocationAsWorkingDir ? directory : string.Empty);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -431,10 +431,24 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
{
|
||||
case "QuickAccessLink":
|
||||
if (SelectedQuickAccessLink == null) return;
|
||||
if (Context.API.ShowMsgBox(
|
||||
Context.API.GetTranslation("plugin_explorer_delete_quick_access_link"),
|
||||
Context.API.GetTranslation("plugin_explorer_delete"),
|
||||
MessageBoxButton.OKCancel,
|
||||
MessageBoxImage.Warning)
|
||||
== MessageBoxResult.Cancel)
|
||||
return;
|
||||
Settings.QuickAccessLinks.Remove(SelectedQuickAccessLink);
|
||||
break;
|
||||
case "IndexSearchExcludedPaths":
|
||||
if (SelectedIndexSearchExcludedPath == null) return;
|
||||
if (Context.API.ShowMsgBox(
|
||||
Context.API.GetTranslation("plugin_explorer_delete_index_search_excluded_path"),
|
||||
Context.API.GetTranslation("plugin_explorer_delete"),
|
||||
MessageBoxButton.OKCancel,
|
||||
MessageBoxImage.Warning)
|
||||
== MessageBoxResult.Cancel)
|
||||
return;
|
||||
Settings.IndexSearchExcludedSubdirectoryPaths.Remove(SelectedIndexSearchExcludedPath);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ProjectGuid>{FDED22C8-B637-42E8-824A-63B5B6E05A3A}</ProjectGuid>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Flow.Launcher.Plugin.PluginIndicator</RootNamespace>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<AssemblyName>Flow.Launcher.Plugin.ProcessKiller</AssemblyName>
|
||||
<PackageId>Flow.Launcher.Plugin.ProcessKiller</PackageId>
|
||||
<Authors>Flow-Launcher</Authors>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
|
||||
<ProjectGuid>{FDB3555B-58EF-4AE6-B5F1-904719637AB4}</ProjectGuid>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Flow.Launcher.Plugin.Program</RootNamespace>
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@ namespace Flow.Launcher.Plugin.Program
|
|||
var title = Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error");
|
||||
var message = string.Format(Context.API.GetTranslation("flowlauncher_plugin_program_run_failed"),
|
||||
info.FileName);
|
||||
Context.API.ShowMsg(title, string.Format(message, info.FileName), string.Empty);
|
||||
Context.API.ShowMsgError(title, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -462,7 +462,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
var message =
|
||||
api.GetTranslation(
|
||||
"flowlauncher_plugin_program_run_as_administrator_not_supported_message");
|
||||
api.ShowMsg(title, message, string.Empty);
|
||||
api.ShowMsgError(title, message);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ProjectGuid>{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}</ProjectGuid>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Flow.Launcher.Plugin.Shell</RootNamespace>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_cmd_relace_winr">Replace Win+R</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_cmd_close_cmd_after_press">Close Command Prompt after pressing any key</system:String>
|
||||
|
|
@ -16,4 +17,6 @@
|
|||
<system:String x:Key="flowlauncher_plugin_cmd_run_as_administrator">Run As Administrator</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_cmd_copy">Copy the command</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_cmd_history">Only show number of most used commands:</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_cmd_command_not_found">Command not found: {0}</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_cmd_error_running_command">Error running the command: {0}</system:String>
|
||||
</ResourceDictionary>
|
||||
|
|
|
|||
|
|
@ -335,15 +335,17 @@ namespace Flow.Launcher.Plugin.Shell
|
|||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
var name = "Plugin: Shell";
|
||||
var message = $"Command not found: {e.Message}";
|
||||
Context.API.ShowMsg(name, message);
|
||||
Context.API.ShowMsgError(GetTranslatedPluginTitle(),
|
||||
string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_command_not_found"), e.Message));
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
var name = "Plugin: Shell";
|
||||
var message = $"Error running the command: {e.Message}";
|
||||
Context.API.ShowMsg(name, message);
|
||||
Context.API.ShowMsgError(GetTranslatedPluginTitle(),
|
||||
string.Format(Context.API.GetTranslation("flowlauncher_plugin_cmd_error_running_command"), e.Message));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Context.API.LogException(ClassName, $"Error executing command: {info.FileName} {string.Join(" ", info.ArgumentList)}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ProjectGuid>{0B9DE348-9361-4940-ADB6-F5953BFFCCEC}</ProjectGuid>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Flow.Launcher.Plugin.Sys</RootNamespace>
|
||||
|
|
@ -37,7 +37,6 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@
|
|||
<system:String x:Key="flowlauncher_plugin_sys_dlgtext_restart_computer">Are you sure you want to restart the computer?</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_sys_dlgtext_restart_computer_advanced">Are you sure you want to restart the computer with Advanced Boot Options?</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_sys_dlgtext_logoff_computer">Are you sure you want to log off?</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_sys_dlgtitle_error">Error</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_sys_dlgtext_empty_recycle_bin_failed">Failed to empty the recycle bin. This might happen if:{0}- Some items are currently in use{0}- Some items can't be deleted due to permissions{0}Please close any applications that might be using these files and try again.</system:String>
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_sys_command_keyword_setting_window_title">Command Keyword Setting</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_sys_custom_command_keyword">Custom Command Keyword</system:String>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Security;
|
||||
|
|
@ -52,6 +50,8 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
private const SHUTDOWN_REASON REASON = SHUTDOWN_REASON.SHTDN_REASON_MAJOR_OTHER |
|
||||
SHUTDOWN_REASON.SHTDN_REASON_FLAG_PLANNED;
|
||||
|
||||
private const string Documentation = "https://flowlauncher.com/docs/#/usage-tips";
|
||||
|
||||
private PluginInitContext _context;
|
||||
private Settings _settings;
|
||||
private ThemeSelector _themeSelector;
|
||||
|
|
@ -70,13 +70,19 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
return _themeSelector.Query(query);
|
||||
}
|
||||
|
||||
var commands = Commands();
|
||||
var commands = Commands(query);
|
||||
var results = new List<Result>();
|
||||
var isEmptyQuery = string.IsNullOrWhiteSpace(query.Search);
|
||||
foreach (var c in commands)
|
||||
{
|
||||
var command = _settings.Commands.First(x => x.Key == c.Title);
|
||||
c.Title = command.Name;
|
||||
c.SubTitle = command.Description;
|
||||
if (isEmptyQuery)
|
||||
{
|
||||
results.Add(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Match from localized title & localized subtitle & keyword
|
||||
var titleMatch = _context.API.FuzzySearch(query.Search, c.Title);
|
||||
|
|
@ -188,7 +194,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
}
|
||||
}
|
||||
|
||||
private List<Result> Commands()
|
||||
private List<Result> Commands(Query query)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
var recycleBinFolder = "shell:RecycleBinFolder";
|
||||
|
|
@ -332,11 +338,9 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
var result = PInvoke.SHEmptyRecycleBin(new(), string.Empty, 0);
|
||||
if (result != HRESULT.S_OK && result != HRESULT.E_UNEXPECTED)
|
||||
{
|
||||
_context.API.ShowMsgBox("Failed to empty the recycle bin. This might happen if:\n" +
|
||||
"- A file in the recycle bin is in use\n" +
|
||||
"- You don't have permission to delete some items\n" +
|
||||
"Please close any applications that might be using these files and try again.",
|
||||
"Error",
|
||||
_context.API.ShowMsgBox(
|
||||
string.Format(_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_empty_recycle_bin_failed"), Environment.NewLine),
|
||||
_context.API.GetTranslation("flowlauncher_plugin_sys_dlgtitle_error"),
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
|
||||
|
|
@ -398,6 +402,8 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
IcoPath = "Images\\app.png",
|
||||
Action = c =>
|
||||
{
|
||||
// Hide the window first then open setting dialog because main window can be topmost window which will still display on top of the setting dialog for a while
|
||||
_context.API.HideMainWindow();
|
||||
_context.API.OpenSettingDialog();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -439,11 +445,11 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"),
|
||||
Title = "Open Log Location",
|
||||
IcoPath = "Images\\app.png",
|
||||
CopyText = DataLocation.VersionLogDirectory,
|
||||
AutoCompleteText = DataLocation.VersionLogDirectory,
|
||||
CopyText = _context.API.GetLogDirectory(),
|
||||
AutoCompleteText = _context.API.GetLogDirectory(),
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.OpenDirectory(DataLocation.VersionLogDirectory);
|
||||
_context.API.OpenDirectory(_context.API.GetLogDirectory());
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
|
@ -452,11 +458,11 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
Title = "Flow Launcher Tips",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe897"),
|
||||
IcoPath = "Images\\app.png",
|
||||
CopyText = Constant.Documentation,
|
||||
AutoCompleteText = Constant.Documentation,
|
||||
CopyText = Documentation,
|
||||
AutoCompleteText = Documentation,
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.OpenUrl(Constant.Documentation);
|
||||
_context.API.OpenUrl(Documentation);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
|
@ -465,11 +471,11 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
Title = "Flow Launcher UserData Folder",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xf12b"),
|
||||
IcoPath = "Images\\app.png",
|
||||
CopyText = DataLocation.DataDirectory(),
|
||||
AutoCompleteText = DataLocation.DataDirectory(),
|
||||
CopyText = _context.API.GetDataDirectory(),
|
||||
AutoCompleteText = _context.API.GetDataDirectory(),
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.OpenDirectory(DataLocation.DataDirectory());
|
||||
_context.API.OpenDirectory(_context.API.GetDataDirectory());
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
|
@ -491,7 +497,15 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue790"),
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.ChangeQuery($"{ThemeSelector.Keyword} ");
|
||||
if (string.IsNullOrEmpty(query.ActionKeyword))
|
||||
{
|
||||
_context.API.ChangeQuery($"{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.API.ChangeQuery($"{query.ActionKeyword}{Plugin.Query.ActionKeywordSeparator}{ThemeSelector.Keyword}{Plugin.Query.ActionKeywordSeparator}");
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ProjectGuid>{A3DCCBCA-ACC1-421D-B16E-210896234C26}</ProjectGuid>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ namespace Flow.Launcher.Plugin.Url
|
|||
}
|
||||
catch(Exception)
|
||||
{
|
||||
context.API.ShowMsg(string.Format(context.API.GetTranslation("flowlauncher_plugin_url_cannot_open_url"), raw));
|
||||
context.API.ShowMsgError(string.Format(context.API.GetTranslation("flowlauncher_plugin_url_cannot_open_url"), raw));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ProjectGuid>{403B57F2-1856-4FC7-8A24-36AB346B763E}</ProjectGuid>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<UseWPF>true</UseWPF>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@
|
|||
<description>Flow Launcher - Quick file search and app launcher for Windows with community-made plugins</description>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="**\*.*" target="lib\net7.0\" exclude="Flow.Launcher.vshost.exe;Flow.Launcher.vshost.exe.config;Flow.Launcher.vshost.exe.manifest;*.nupkg;Setup.exe;RELEASES"/>
|
||||
<file src="**\*.*" target="lib\net9.0\" exclude="Flow.Launcher.vshost.exe;Flow.Launcher.vshost.exe.config;Flow.Launcher.vshost.exe.manifest;*.nupkg;Setup.exe;RELEASES"/>
|
||||
</files>
|
||||
</package>
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ function Pack-Squirrel-Installer ($path, $version, $output) {
|
|||
function Publish-Self-Contained ($p) {
|
||||
|
||||
$csproj = Join-Path "$p" "Flow.Launcher/Flow.Launcher.csproj" -Resolve
|
||||
$profile = Join-Path "$p" "Flow.Launcher/Properties/PublishProfiles/Net7.0-SelfContained.pubxml" -Resolve
|
||||
$profile = Join-Path "$p" "Flow.Launcher/Properties/PublishProfiles/Net9.0-SelfContained.pubxml" -Resolve
|
||||
|
||||
# we call dotnet publish on the main project.
|
||||
# The other projects should have been built in Release at this point.
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ deploy:
|
|||
This build includes new changes from commit:
|
||||
$(APPVEYOR_REPO_COMMIT_MESSAGE)
|
||||
|
||||
See all changes in this early access by going to the [milstones](https://github.com/Flow-Launcher/Flow.Launcher/milestones?sort=title&direction=asc) section and choosing the upcoming milestone.
|
||||
See all changes in this early access by going to the [milestones](https://github.com/Flow-Launcher/Flow.Launcher/milestones?sort=title&direction=asc) section and choosing the upcoming milestone.
|
||||
For latest production release visit [here](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest)
|
||||
|
||||
Please report any bugs or issues over at the [main repository](https://github.com/Flow-Launcher/Flow.Launcher/issues)'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "7.0.*",
|
||||
"version": "9.0.*",
|
||||
"rollForward": "latestPatch"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue