diff --git a/.editorconfig b/.editorconfig index 11a0bcdf6..e1b8ed79e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -58,7 +58,7 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent ############################### # Style Definitions dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# Use PascalCase for constant fields +# Use PascalCase for constant fields dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style @@ -134,7 +134,7 @@ csharp_preserve_single_line_statements = true csharp_preserve_single_line_blocks = true csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion -csharp_style_namespace_declarations = block_scoped:silent +csharp_style_namespace_declarations = file_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = false:silent diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 00cc67ea0..a36a6af3e 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -3,3 +3,6 @@ https ssh ubuntu runcount +Firefox +Português +Português (Brasil) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index d6860516c..6ba41e410 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -1,7 +1,6 @@ crowdin DWM workflows -Wpf wpf actionkeyword stackoverflow @@ -20,9 +19,7 @@ Prioritise Segoe Google Customise -UWP uwp -Uwp Bokmal Bokm uninstallation @@ -61,7 +58,6 @@ popup ptr pluginindicator TobiasSekan -Img img resx bak @@ -87,7 +83,6 @@ txb btn otf searchplugin -Noresult wpftk mkv flac @@ -104,9 +99,14 @@ Português (Brasil) Italiano Slovenský quicklook +Tiếng Việt Droplex Preinstalled errormetadatafile noresult pluginsmanager alreadyexists +JsonRPC +JsonRPCV2 +Softpedia +img diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt index 903714aef..f29f57ad5 100644 --- a/.github/actions/spelling/patterns.txt +++ b/.github/actions/spelling/patterns.txt @@ -118,3 +118,6 @@ # UWP [Uu][Ww][Pp] + +# version suffix v# +(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) diff --git a/.github/workflows/default_plugins.yml b/.github/workflows/default_plugins.yml index 8000c5456..a0a816877 100644 --- a/.github/workflows/default_plugins.yml +++ b/.github/workflows/default_plugins.yml @@ -13,12 +13,12 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 7.0.x - name: Determine New Plugin Updates - uses: dorny/paths-filter@v2 + uses: dorny/paths-filter@v3 id: changes with: filters: | @@ -65,7 +65,7 @@ jobs: - name: Publish BrowserBookmark if: steps.changes.outputs.browserbookmark == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.BrowserBookmark" files: "Flow.Launcher.Plugin.BrowserBookmark.zip" @@ -92,7 +92,7 @@ jobs: - name: Publish Calculator if: steps.changes.outputs.calculator == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Calculator" files: "Flow.Launcher.Plugin.Calculator.zip" @@ -119,7 +119,7 @@ jobs: - name: Publish Explorer if: steps.changes.outputs.explorer == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Explorer" files: "Flow.Launcher.Plugin.Explorer.zip" @@ -146,7 +146,7 @@ jobs: - name: Publish PluginIndicator if: steps.changes.outputs.pluginindicator == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.PluginIndicator" files: "Flow.Launcher.Plugin.PluginIndicator.zip" @@ -173,7 +173,7 @@ jobs: - name: Publish PluginsManager if: steps.changes.outputs.pluginsmanager == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.PluginsManager" files: "Flow.Launcher.Plugin.PluginsManager.zip" @@ -200,7 +200,7 @@ jobs: - name: Publish ProcessKiller if: steps.changes.outputs.processkiller == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller" files: "Flow.Launcher.Plugin.ProcessKiller.zip" @@ -227,7 +227,7 @@ jobs: - name: Publish Program if: steps.changes.outputs.program == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Program" files: "Flow.Launcher.Plugin.Program.zip" @@ -254,7 +254,7 @@ jobs: - name: Publish Shell if: steps.changes.outputs.shell == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Shell" files: "Flow.Launcher.Plugin.Shell.zip" @@ -281,7 +281,7 @@ jobs: - name: Publish Sys if: steps.changes.outputs.sys == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Sys" files: "Flow.Launcher.Plugin.Sys.zip" @@ -308,7 +308,7 @@ jobs: - name: Publish Url if: steps.changes.outputs.url == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Url" files: "Flow.Launcher.Plugin.Url.zip" @@ -335,7 +335,7 @@ jobs: - name: Publish WebSearch if: steps.changes.outputs.websearch == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.WebSearch" files: "Flow.Launcher.Plugin.WebSearch.zip" @@ -362,7 +362,7 @@ jobs: - name: Publish WindowsSettings if: steps.changes.outputs.windowssettings == 'true' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.WindowsSettings" files: "Flow.Launcher.Plugin.WindowsSettings.zip" diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 97d3cccb3..7aaa9296a 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -73,7 +73,7 @@ jobs: steps: - name: check-spelling id: spelling - uses: check-spelling/check-spelling@v0.0.22 + uses: check-spelling/check-spelling@prerelease with: suppress_push_for_open_pull_request: 1 checkout: true @@ -91,10 +91,9 @@ jobs: extra_dictionaries: cspell:software-terms/dict/softwareTerms.txt cspell:win32/src/win32.txt - cspell:php/src/php.txt cspell:filetypes/filetypes.txt cspell:csharp/csharp.txt - cspell:dotnet/src/dotnet.txt + cspell:dotnet/dict/dotnet.txt cspell:python/src/common/extra.txt cspell:python/src/python/python-lib.txt cspell:aws/aws.txt @@ -130,7 +129,7 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') steps: - name: comment - uses: check-spelling/check-spelling@v0.0.22 + uses: check-spelling/check-spelling@prerelease with: checkout: true spell_check_this: check-spelling/spell-check-this@main diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index caac10c93..719f2556e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,6 +6,11 @@ on: schedule: - cron: '30 1 * * *' +env: + days-before-stale: 60 + days-before-close: 7 + exempt-issue-labels: 'keep-fresh' + jobs: stale: runs-on: ubuntu-latest @@ -13,14 +18,14 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 with: - stale-issue-message: 'This issue is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 5 days.' - days-before-stale: 45 - days-before-close: 7 + stale-issue-message: 'This issue is stale because it has been open ${{ env.days-before-stale }} days with no activity. Remove stale label or comment or this will be closed in ${{ env.days-before-stale }} days.\n\nAlternatively this issue can be kept open by adding one of the following labels:\n${{ env.exempt-issue-labels }}' + days-before-stale: ${{ env.days-before-stale }} + days-before-close: ${{ env.days-before-close }} days-before-pr-close: -1 exempt-all-milestones: true close-issue-message: 'This issue was closed because it has been stale for 7 days with no activity. If you feel this issue still needs attention please feel free to reopen.' stale-pr-label: 'no-pr-activity' - exempt-issue-labels: 'keep-fresh' + exempt-issue-labels: ${{ env.exempt-issue-labels }} exempt-pr-labels: 'keep-fresh,awaiting-approval,work-in-progress' diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs index d3ee4695c..68be746f2 100644 --- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs +++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; @@ -16,6 +18,11 @@ namespace Flow.Launcher.Core.ExternalPlugins private List plugins = new(); + private static JsonSerializerOptions PluginStoreItemSerializationOption = new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }; + /// /// Fetch and deserialize the contents of a plugins.json file found at . /// We use conditional http requests to keep repeat requests fast. @@ -32,12 +39,15 @@ namespace Flow.Launcher.Core.ExternalPlugins request.Headers.Add("If-None-Match", latestEtag); - using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false); + using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token) + .ConfigureAwait(false); if (response.StatusCode == HttpStatusCode.OK) { - this.plugins = await response.Content.ReadFromJsonAsync>(cancellationToken: token).ConfigureAwait(false); - this.latestEtag = response.Headers.ETag.Tag; + this.plugins = await response.Content + .ReadFromJsonAsync>(PluginStoreItemSerializationOption, cancellationToken: token) + .ConfigureAwait(false); + this.latestEtag = response.Headers.ETag?.Tag; Log.Info(nameof(CommunityPluginSource), $"Loaded {this.plugins.Count} plugins from {ManifestFileUrl}"); return this.plugins; @@ -49,7 +59,8 @@ namespace Flow.Launcher.Core.ExternalPlugins } else { - Log.Warn(nameof(CommunityPluginSource), $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}"); + Log.Warn(nameof(CommunityPluginSource), + $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}"); throw new Exception($"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}"); } } diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs index 0c139f521..40eb1be3e 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -58,7 +58,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments string.Empty, MessageBoxButtons.YesNo) == DialogResult.No) { var msg = $"Please select the {EnvName} executable"; - var selectedFile = string.Empty; + string selectedFile; selectedFile = GetFileFromDialog(msg, FileDialogFilter); @@ -131,14 +131,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments }; var result = dlg.ShowDialog(); - if (result == DialogResult.OK) - { - return dlg.FileName; - } - else - { - return string.Empty; - } + return result == DialogResult.OK ? dlg.FileName : string.Empty; + } /// diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs new file mode 100644 index 000000000..6c8c5aa57 --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Core.ExternalPlugins.Environments +{ + + internal class JavaScriptV2Environment : TypeScriptV2Environment + { + internal override string Language => AllowedLanguage.JavaScriptV2; + + internal JavaScriptV2Environment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } + } +} diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs new file mode 100644 index 000000000..4d75e1b8f --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonV2Environment.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Core.ExternalPlugins.Environments +{ + internal class PythonV2Environment : PythonEnvironment + { + internal override string Language => AllowedLanguage.PythonV2; + + internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata) + { + return new PluginPair + { + Plugin = new PythonPluginV2(filePath), + Metadata = metadata + }; + } + + internal PythonV2Environment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } + } +} diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs new file mode 100644 index 000000000..11ed94d3f --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Droplex; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin.SharedCommands; +using Flow.Launcher.Plugin; +using System.IO; +using Flow.Launcher.Core.Plugin; + +namespace Flow.Launcher.Core.ExternalPlugins.Environments +{ + internal class TypeScriptV2Environment : AbstractPluginEnvironment + { + internal override string Language => AllowedLanguage.TypeScriptV2; + + internal override string EnvName => DataLocation.NodeEnvironmentName; + + internal override string EnvPath => Path.Combine(DataLocation.PluginEnvironmentsPath, EnvName); + + internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0"); + internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe"); + + internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; } + + internal TypeScriptV2Environment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } + + internal override void InstallEnvironment() + { + FilesFolders.RemoveFolderIfExists(InstallPath); + + DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait(); + + PluginsSettingsFilePath = ExecutablePath; + } + + internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata) + { + return new PluginPair + { + Plugin = new NodePluginV2(filePath), + Metadata = metadata + }; + } + } +} diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs index c4dcef3e3..63f21c1d6 100644 --- a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs +++ b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.Logger; using System; using System.Collections.Generic; using System.Threading; diff --git a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs index bb1279b2c..79d6d7605 100644 --- a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs +++ b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Flow.Launcher.Core.ExternalPlugins { @@ -13,9 +13,11 @@ namespace Flow.Launcher.Core.ExternalPlugins public string Website { get; set; } public string UrlDownload { get; set; } public string UrlSourceCode { get; set; } + public string LocalInstallPath { get; set; } public string IcoPath { get; set; } - public DateTime LatestReleaseDate { get; set; } - public DateTime DateAdded { get; set; } + public DateTime? LatestReleaseDate { get; set; } + public DateTime? DateAdded { get; set; } + public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath); } } diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 39474c3c5..18101ccf0 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -54,9 +54,11 @@ - - + + + + diff --git a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs index a7bbccfec..857122aa6 100644 --- a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs +++ b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.IO; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -27,14 +28,14 @@ namespace Flow.Launcher.Core.Plugin protected override Task RequestAsync(JsonRPCRequestModel request, CancellationToken token = default) { // since this is not static, request strings will build up in ArgumentList if index is not specified - _startInfo.ArgumentList[0] = request.ToString(); + _startInfo.ArgumentList[0] = JsonSerializer.Serialize(request, RequestSerializeOption); return ExecuteAsync(_startInfo, token); } protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default) { // since this is not static, request strings will build up in ArgumentList if index is not specified - _startInfo.ArgumentList[0] = rpcRequest.ToString(); + _startInfo.ArgumentList[0] = JsonSerializer.Serialize(rpcRequest, RequestSerializeOption); return Execute(_startInfo); } } diff --git a/Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs b/Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs new file mode 100644 index 000000000..852f57b9f --- /dev/null +++ b/Flow.Launcher.Core/Plugin/ExecutablePluginV2.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using System.IO; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Core.Plugin +{ + internal sealed class ExecutablePluginV2 : ProcessStreamPluginV2 + { + protected override ProcessStartInfo StartInfo { get; set; } + + public ExecutablePluginV2(string filename) + { + StartInfo = new ProcessStartInfo { FileName = filename }; + } + + protected override MessageHandlerType MessageHandler { get; } = MessageHandlerType.NewLineDelimited; + } +} diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs index eaaae25b0..5b2a9f6cb 100644 --- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs +++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs @@ -19,67 +19,35 @@ using System.Text.Json; namespace Flow.Launcher.Core.Plugin { - public class JsonRPCErrorModel - { - public int Code { get; set; } + public record JsonRPCBase(int Id, JsonRPCErrorModel Error = default); + public record JsonRPCErrorModel(int Code, string Message, string Data); - public string Message { get; set; } + public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error); + public record JsonRPCQueryResponseModel(int Id, + [property: JsonPropertyName("result")] List Result, + IReadOnlyDictionary SettingsChange = null, + string DebugMessage = "", + JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error); - public string Data { get; set; } - } + public record JsonRPCRequestModel(int Id, + string Method, + object[] Parameters, + IReadOnlyDictionary Settings = default, + JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error); - public class JsonRPCResponseModel - { - public string Result { get; set; } - - public JsonRPCErrorModel Error { get; set; } - } - - public class JsonRPCQueryResponseModel : JsonRPCResponseModel - { - [JsonPropertyName("result")] - public new List Result { get; set; } - - public Dictionary SettingsChange { get; set; } - - public string DebugMessage { get; set; } - } - - public class JsonRPCRequestModel - { - public string Method { get; set; } - - public object[] Parameters { get; set; } - - public Dictionary Settings { get; set; } - - private static readonly JsonSerializerOptions options = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - public override string ToString() - { - return JsonSerializer.Serialize(this, options); - } - } - - /// - /// Json RPC Request that Flow Launcher sent to client - /// - public class JsonRPCServerRequestModel : JsonRPCRequestModel - { - - } - /// /// Json RPC Request(in query response) that client sent to Flow Launcher /// - public class JsonRPCClientRequestModel : JsonRPCRequestModel - { - public bool DontHideAfterAction { get; set; } - } - + public record JsonRPCClientRequestModel( + int Id, + string Method, + object[] Parameters, + IReadOnlyDictionary Settings, + bool DontHideAfterAction = false, + JsonRPCErrorModel Error = default) : JsonRPCRequestModel(Id, Method, Parameters, Settings, Error); + + /// /// Represent the json-rpc result item that client send to Flow Launcher /// Typically, we will send back this request model to client after user select the result item diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 438c1dd8a..97c3c8981 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -30,9 +30,8 @@ namespace Flow.Launcher.Core.Plugin /// Represent the plugin that using JsonPRC /// every JsonRPC plugin should has its own plugin instance /// - internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable + internal abstract class JsonRPCPlugin : JsonRPCPluginBase { - protected PluginInitContext context; public const string JsonRPC = "JsonRPC"; protected abstract Task RequestAsync(JsonRPCRequestModel rpcRequest, CancellationToken token = default); @@ -40,19 +39,16 @@ namespace Flow.Launcher.Core.Plugin private static readonly RecyclableMemoryStreamManager BufferManager = new(); - private string SettingConfigurationPath => Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); - private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name, "Settings.json"); + private int RequestId { get; set; } - public List LoadContextMenus(Result selectedResult) + private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); + private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json"); + + public override List LoadContextMenus(Result selectedResult) { - var request = new JsonRPCRequestModel - { - Method = "context_menu", - Parameters = new[] - { - selectedResult.ContextData - } - }; + var request = new JsonRPCRequestModel(RequestId++, + "context_menu", + new[] { selectedResult.ContextData }); var output = Request(request); return DeserializedResult(output); } @@ -77,7 +73,6 @@ namespace Flow.Launcher.Core.Plugin { WriteIndented = true }; - private Dictionary Settings { get; set; } private readonly Dictionary _settingControls = new(); @@ -103,85 +98,42 @@ namespace Flow.Launcher.Core.Plugin return ParseResults(queryResponseModel); } - - private List ParseResults(JsonRPCQueryResponseModel queryResponseModel) + protected override async Task ExecuteResultAsync(JsonRPCResult result) { - if (queryResponseModel.Result == null) return null; + if (result.JsonRPCAction == null) return false; - if (!string.IsNullOrEmpty(queryResponseModel.DebugMessage)) + if (string.IsNullOrEmpty(result.JsonRPCAction.Method)) { - context.API.ShowMsg(queryResponseModel.DebugMessage); + return !result.JsonRPCAction.DontHideAfterAction; } - foreach (var result in queryResponseModel.Result) + if (result.JsonRPCAction.Method.StartsWith("Flow.Launcher.")) { - result.AsyncAction = async c => + ExecuteFlowLauncherAPI(result.JsonRPCAction.Method["Flow.Launcher.".Length..], + result.JsonRPCAction.Parameters); + } + else + { + await using var actionResponse = await RequestAsync(result.JsonRPCAction); + + if (actionResponse.Length == 0) { - UpdateSettings(result.SettingsChange); - - if (result.JsonRPCAction == null) return false; - - if (string.IsNullOrEmpty(result.JsonRPCAction.Method)) - { - return !result.JsonRPCAction.DontHideAfterAction; - } - - if (result.JsonRPCAction.Method.StartsWith("Flow.Launcher.")) - { - ExecuteFlowLauncherAPI(result.JsonRPCAction.Method["Flow.Launcher.".Length..], - result.JsonRPCAction.Parameters); - } - else - { - await using var actionResponse = await RequestAsync(result.JsonRPCAction); - - if (actionResponse.Length == 0) - { - return !result.JsonRPCAction.DontHideAfterAction; - } - - var jsonRpcRequestModel = await - JsonSerializer.DeserializeAsync(actionResponse, options); - - if (jsonRpcRequestModel?.Method?.StartsWith("Flow.Launcher.") ?? false) - { - ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method["Flow.Launcher.".Length..], - jsonRpcRequestModel.Parameters); - } - } - return !result.JsonRPCAction.DontHideAfterAction; - }; + } + + var jsonRpcRequestModel = await + JsonSerializer.DeserializeAsync(actionResponse, options); + + if (jsonRpcRequestModel?.Method?.StartsWith("Flow.Launcher.") ?? false) + { + ExecuteFlowLauncherAPI(jsonRpcRequestModel.Method["Flow.Launcher.".Length..], + jsonRpcRequestModel.Parameters); + } } - var results = new List(); - - results.AddRange(queryResponseModel.Result); - - UpdateSettings(queryResponseModel.SettingsChange); - - return results; + return !result.JsonRPCAction.DontHideAfterAction; } - private void ExecuteFlowLauncherAPI(string method, object[] parameters) - { - var parametersTypeArray = parameters.Select(param => param.GetType()).ToArray(); - var methodInfo = typeof(IPublicAPI).GetMethod(method, parametersTypeArray); - if (methodInfo == null) - { - return; - } - try - { - methodInfo.Invoke(PluginManager.API, parameters); - } - catch (Exception) - { -#if (DEBUG) - throw; -#endif - } - } /// /// Execute external program and return the output @@ -296,379 +248,19 @@ namespace Flow.Launcher.Core.Plugin return sourceBuffer; } - - public async Task> QueryAsync(Query query, CancellationToken token) + public override async Task> QueryAsync(Query query, CancellationToken token) { - var request = new JsonRPCRequestModel - { - Method = "query", - Parameters = new object[] + var request = new JsonRPCRequestModel(RequestId++, + "query", + new object[] { query.Search }, - Settings = Settings - }; + Settings?.Inner); + var output = await RequestAsync(request, token); + return await DeserializedResultAsync(output); } - - public async Task InitSettingAsync() - { - if (!File.Exists(SettingConfigurationPath)) - return; - - if (File.Exists(SettingPath)) - { - await using var fileStream = File.OpenRead(SettingPath); - Settings = await JsonSerializer.DeserializeAsync>(fileStream, options); - } - - var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); - _settingsTemplate = deserializer.Deserialize(await File.ReadAllTextAsync(SettingConfigurationPath)); - - Settings ??= new Dictionary(); - - foreach (var (type, attribute) in _settingsTemplate.Body) - { - if (type == "textBlock") - continue; - if (!Settings.ContainsKey(attribute.Name)) - { - Settings[attribute.Name] = attribute.DefaultValue; - } - } - } - - public virtual async Task InitAsync(PluginInitContext context) - { - this.context = context; - await InitSettingAsync(); - } - private static readonly Thickness settingControlMargin = new(0, 9, 18, 9); - private static readonly Thickness settingCheckboxMargin = new(0, 9, 9, 9); - private static readonly Thickness settingPanelMargin = new(0, 0, 0, 0); - private static readonly Thickness settingTextBlockMargin = new(70, 9, 18, 9); - private static readonly Thickness settingLabelPanelMargin = new(70, 9, 18, 9); - private static readonly Thickness settingLabelMargin = new(0, 0, 0, 0); - private static readonly Thickness settingDescMargin = new(0, 2, 0, 0); - private static readonly Thickness settingSepMargin = new(0, 0, 0, 2); - private JsonRpcConfigurationModel _settingsTemplate; - - public Control CreateSettingPanel() - { - if (Settings == null) - return new(); - var settingWindow = new UserControl(); - var mainPanel = new Grid - { - Margin = settingPanelMargin, VerticalAlignment = VerticalAlignment.Center - }; - ColumnDefinition gridCol1 = new ColumnDefinition(); - ColumnDefinition gridCol2 = new ColumnDefinition(); - - gridCol1.Width = new GridLength(70, GridUnitType.Star); - gridCol2.Width = new GridLength(30, GridUnitType.Star); - mainPanel.ColumnDefinitions.Add(gridCol1); - mainPanel.ColumnDefinitions.Add(gridCol2); - settingWindow.Content = mainPanel; - int rowCount = 0; - foreach (var (type, attribute) in _settingsTemplate.Body) - { - Separator sep = new Separator(); - sep.VerticalAlignment = VerticalAlignment.Top; - sep.Margin = settingSepMargin; - sep.SetResourceReference(Separator.BackgroundProperty, "Color03B"); /* for theme change */ - var panel = new StackPanel - { - Orientation = Orientation.Vertical, - VerticalAlignment = VerticalAlignment.Center, - Margin = settingLabelPanelMargin - }; - RowDefinition gridRow = new RowDefinition(); - mainPanel.RowDefinitions.Add(gridRow); - var name = new TextBlock() - { - Text = attribute.Label, - VerticalAlignment = VerticalAlignment.Center, - Margin = settingLabelMargin, - TextWrapping = TextWrapping.WrapWithOverflow - }; - var desc = new TextBlock() - { - Text = attribute.Description, - FontSize = 12, - VerticalAlignment = VerticalAlignment.Center, - Margin = settingDescMargin, - TextWrapping = TextWrapping.WrapWithOverflow - }; - desc.SetResourceReference(TextBlock.ForegroundProperty, "Color04B"); - - if (attribute.Description == null) /* if no description, hide */ - desc.Visibility = Visibility.Collapsed; - - - if (type != "textBlock") /* if textBlock, hide desc */ - { - panel.Children.Add(name); - panel.Children.Add(desc); - } - - - Grid.SetColumn(panel, 0); - Grid.SetRow(panel, rowCount); - - FrameworkElement contentControl; - - switch (type) - { - case "textBlock": - { - contentControl = new TextBlock - { - Text = attribute.Description.Replace("\\r\\n", "\r\n"), - Margin = settingTextBlockMargin, - Padding = new Thickness(0, 0, 0, 0), - HorizontalAlignment = System.Windows.HorizontalAlignment.Left, - TextAlignment = TextAlignment.Left, - TextWrapping = TextWrapping.Wrap - }; - Grid.SetColumn(contentControl, 0); - Grid.SetColumnSpan(contentControl, 2); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; - } - case "input": - { - var textBox = new TextBox() - { - Text = Settings[attribute.Name] as string ?? string.Empty, - Margin = settingControlMargin, - HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, - ToolTip = attribute.Description - }; - textBox.TextChanged += (_, _) => - { - Settings[attribute.Name] = textBox.Text; - }; - contentControl = textBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; - } - case "inputWithFileBtn": - { - var textBox = new TextBox() - { - Margin = new Thickness(10, 0, 0, 0), - Text = Settings[attribute.Name] as string ?? string.Empty, - HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, - ToolTip = attribute.Description - }; - textBox.TextChanged += (_, _) => - { - Settings[attribute.Name] = textBox.Text; - }; - var Btn = new System.Windows.Controls.Button() - { - Margin = new Thickness(10, 0, 0, 0), Content = "Browse" - }; - var dockPanel = new DockPanel() - { - Margin = settingControlMargin - }; - DockPanel.SetDock(Btn, Dock.Right); - dockPanel.Children.Add(Btn); - dockPanel.Children.Add(textBox); - contentControl = dockPanel; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; - } - case "textarea": - { - var textBox = new TextBox() - { - Height = 120, - Margin = settingControlMargin, - VerticalAlignment = VerticalAlignment.Center, - TextWrapping = TextWrapping.WrapWithOverflow, - AcceptsReturn = true, - HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, - Text = Settings[attribute.Name] as string ?? string.Empty, - ToolTip = attribute.Description - }; - textBox.TextChanged += (sender, _) => - { - Settings[attribute.Name] = ((TextBox)sender).Text; - }; - contentControl = textBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; - } - case "passwordBox": - { - var passwordBox = new PasswordBox() - { - Margin = settingControlMargin, - Password = Settings[attribute.Name] as string ?? string.Empty, - PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar, - HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, - ToolTip = attribute.Description - }; - passwordBox.PasswordChanged += (sender, _) => - { - Settings[attribute.Name] = ((PasswordBox)sender).Password; - }; - contentControl = passwordBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; - } - case "dropdown": - { - var comboBox = new System.Windows.Controls.ComboBox() - { - ItemsSource = attribute.Options, - SelectedItem = Settings[attribute.Name], - Margin = settingControlMargin, - HorizontalAlignment = System.Windows.HorizontalAlignment.Right, - ToolTip = attribute.Description - }; - comboBox.SelectionChanged += (sender, _) => - { - Settings[attribute.Name] = (string)((System.Windows.Controls.ComboBox)sender).SelectedItem; - }; - contentControl = comboBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; - } - case "checkbox": - var checkBox = new CheckBox - { - IsChecked = Settings[attribute.Name] is bool isChecked ? isChecked : bool.Parse(attribute.DefaultValue), - Margin = settingCheckboxMargin, - HorizontalAlignment = System.Windows.HorizontalAlignment.Right, - ToolTip = attribute.Description - }; - checkBox.Click += (sender, _) => - { - Settings[attribute.Name] = ((CheckBox)sender).IsChecked; - }; - contentControl = checkBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; - case "hyperlink": - var hyperlink = new Hyperlink - { - ToolTip = attribute.Description, NavigateUri = attribute.url - }; - var linkbtn = new System.Windows.Controls.Button - { - HorizontalAlignment = System.Windows.HorizontalAlignment.Right, Margin = settingControlMargin - }; - linkbtn.Content = attribute.urlLabel; - - contentControl = linkbtn; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; - default: - continue; - } - if (type != "textBlock") - _settingControls[attribute.Name] = contentControl; - mainPanel.Children.Add(panel); - mainPanel.Children.Add(contentControl); - rowCount++; - - } - return settingWindow; - } - - public void Save() - { - if (Settings != null) - { - Helper.ValidateDirectory(Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name)); - File.WriteAllText(SettingPath, JsonSerializer.Serialize(Settings, settingSerializeOption)); - } - } - - public void UpdateSettings(Dictionary settings) - { - if (settings == null || settings.Count == 0) - return; - - foreach (var (key, value) in settings) - { - if (Settings.ContainsKey(key)) - { - Settings[key] = value; - } - if (_settingControls.ContainsKey(key)) - { - - switch (_settingControls[key]) - { - case TextBox textBox: - textBox.Dispatcher.Invoke(() => textBox.Text = value as string); - break; - case PasswordBox passwordBox: - passwordBox.Dispatcher.Invoke(() => passwordBox.Password = value as string); - break; - case System.Windows.Controls.ComboBox comboBox: - comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value); - break; - case CheckBox checkBox: - checkBox.Dispatcher.Invoke(() => checkBox.IsChecked = value is bool isChecked ? isChecked : bool.Parse(value as string)); - break; - } - } - } - } } - } diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs new file mode 100644 index 000000000..f6e5e5879 --- /dev/null +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginBase.cs @@ -0,0 +1,163 @@ +using Flow.Launcher.Core.Resource; +using Flow.Launcher.Infrastructure; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; +using Microsoft.IO; +using System.Windows; +using System.Windows.Controls; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; +using CheckBox = System.Windows.Controls.CheckBox; +using Control = System.Windows.Controls.Control; +using Orientation = System.Windows.Controls.Orientation; +using TextBox = System.Windows.Controls.TextBox; +using UserControl = System.Windows.Controls.UserControl; +using System.Windows.Documents; +using static System.Windows.Forms.LinkLabel; +using Droplex; +using System.Windows.Forms; +using Microsoft.VisualStudio.Threading; + +namespace Flow.Launcher.Core.Plugin +{ + /// + /// Represent the plugin that using JsonPRC + /// every JsonRPC plugin should has its own plugin instance + /// + internal abstract class JsonRPCPluginBase : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable + { + protected PluginInitContext Context; + public const string JsonRPC = "JsonRPC"; + + private int RequestId { get; set; } + + private string SettingConfigurationPath => + Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); + + private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, + Context.CurrentPluginMetadata.Name, "Settings.json"); + + public abstract List LoadContextMenus(Result selectedResult); + + protected static readonly JsonSerializerOptions DeserializeOption = new() + { + PropertyNameCaseInsensitive = true, +#pragma warning disable SYSLIB0020 + // IgnoreNullValues is obsolete, but the replacement JsonIgnoreCondition.WhenWritingNull still + // deserializes null, instead of ignoring it and leaving the default (empty list). We can change the behaviour + // to accept null and fallback to a default etc, or just keep IgnoreNullValues for now + // see: https://github.com/dotnet/runtime/issues/39152 + IgnoreNullValues = true, +#pragma warning restore SYSLIB0020 // Type or member is obsolete + Converters = { new JsonObjectConverter() } + }; + + protected static readonly JsonSerializerOptions RequestSerializeOption = new() + { + PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + + protected abstract Task ExecuteResultAsync(JsonRPCResult result); + + protected JsonRPCPluginSettings Settings { get; set; } + + protected List ParseResults(JsonRPCQueryResponseModel queryResponseModel) + { + if (queryResponseModel.Result == null) return null; + + if (!string.IsNullOrEmpty(queryResponseModel.DebugMessage)) + { + Context.API.ShowMsg(queryResponseModel.DebugMessage); + } + + foreach (var result in queryResponseModel.Result) + { + result.AsyncAction = async _ => + { + Settings?.UpdateSettings(result.SettingsChange); + + return await ExecuteResultAsync(result); + }; + } + + var results = new List(); + + results.AddRange(queryResponseModel.Result); + + Settings?.UpdateSettings(queryResponseModel.SettingsChange); + + return results; + } + + protected void ExecuteFlowLauncherAPI(string method, object[] parameters) + { + var parametersTypeArray = parameters.Select(param => param.GetType()).ToArray(); + var methodInfo = typeof(IPublicAPI).GetMethod(method, parametersTypeArray); + + if (methodInfo == null) + { + return; + } + + try + { + methodInfo.Invoke(Context.API, parameters); + } + catch (Exception) + { +#if (DEBUG) + throw; +#endif + } + } + + public abstract Task> QueryAsync(Query query, CancellationToken token); + + + private async Task InitSettingAsync() + { + JsonRpcConfigurationModel configuration = null; + if (File.Exists(SettingConfigurationPath)) + { + var deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); + configuration = + deserializer.Deserialize( + await File.ReadAllTextAsync(SettingConfigurationPath)); + } + + + Settings ??= new JsonRPCPluginSettings + { + Configuration = configuration, SettingPath = SettingPath, API = Context.API + }; + + await Settings.InitializeAsync(); + } + + public virtual async Task InitAsync(PluginInitContext context) + { + this.Context = context; + await InitSettingAsync(); + } + + public void Save() + { + Settings?.Save(); + } + + public Control CreateSettingPanel() + { + return Settings.CreateSettingPanel(); + } + } +} diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs new file mode 100644 index 000000000..408d9785d --- /dev/null +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs @@ -0,0 +1,415 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Core.Plugin +{ + public class JsonRPCPluginSettings + { + public required JsonRpcConfigurationModel? Configuration { get; init; } + + public required string SettingPath { get; init; } + public Dictionary SettingControls { get; } = new(); + + public IReadOnlyDictionary Inner => Settings; + protected ConcurrentDictionary Settings { get; set; } + public required IPublicAPI API { get; init; } + + private JsonStorage> _storage; + + // maybe move to resource? + private static readonly Thickness settingControlMargin = new(0, 9, 18, 9); + private static readonly Thickness settingCheckboxMargin = new(0, 9, 9, 9); + private static readonly Thickness settingPanelMargin = new(0, 0, 0, 0); + private static readonly Thickness settingTextBlockMargin = new(70, 9, 18, 9); + private static readonly Thickness settingLabelPanelMargin = new(70, 9, 18, 9); + private static readonly Thickness settingLabelMargin = new(0, 0, 0, 0); + private static readonly Thickness settingDescMargin = new(0, 2, 0, 0); + private static readonly Thickness settingSepMargin = new(0, 0, 0, 2); + + public async Task InitializeAsync() + { + _storage = new JsonStorage>(SettingPath); + Settings = await _storage.LoadAsync(); + + if (Configuration == null) + { + return; + } + + foreach (var (type, attributes) in Configuration.Body) + { + if (attributes.Name == null) + { + continue; + } + + if (!Settings.ContainsKey(attributes.Name)) + { + Settings[attributes.Name] = attributes.DefaultValue; + } + } + } + + + public void UpdateSettings(IReadOnlyDictionary settings) + { + if (settings == null || settings.Count == 0) + return; + + foreach (var (key, value) in settings) + { + Settings[key] = value; + + if (SettingControls.TryGetValue(key, out var control)) + { + switch (control) + { + case TextBox textBox: + textBox.Dispatcher.Invoke(() => textBox.Text = value as string ?? string.Empty); + break; + case PasswordBox passwordBox: + passwordBox.Dispatcher.Invoke(() => passwordBox.Password = value as string ?? string.Empty); + break; + case ComboBox comboBox: + comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value); + break; + case CheckBox checkBox: + checkBox.Dispatcher.Invoke(() => + checkBox.IsChecked = value is bool isChecked + ? isChecked + : bool.Parse(value as string ?? string.Empty)); + break; + } + } + } + + Save(); + } + + public async Task SaveAsync() + { + await _storage.SaveAsync(); + } + + public void Save() + { + _storage.Save(); + } + + public Control CreateSettingPanel() + { + if (Settings == null || Settings.Count == 0) + return new(); + + var settingWindow = new UserControl(); + var mainPanel = new Grid { Margin = settingPanelMargin, VerticalAlignment = VerticalAlignment.Center }; + + ColumnDefinition gridCol1 = new ColumnDefinition(); + ColumnDefinition gridCol2 = new ColumnDefinition(); + + gridCol1.Width = new GridLength(70, GridUnitType.Star); + gridCol2.Width = new GridLength(30, GridUnitType.Star); + mainPanel.ColumnDefinitions.Add(gridCol1); + mainPanel.ColumnDefinitions.Add(gridCol2); + settingWindow.Content = mainPanel; + int rowCount = 0; + + foreach (var (type, attribute) in Configuration.Body) + { + Separator sep = new Separator(); + sep.VerticalAlignment = VerticalAlignment.Top; + sep.Margin = settingSepMargin; + sep.SetResourceReference(Separator.BackgroundProperty, "Color03B"); /* for theme change */ + var panel = new StackPanel + { + Orientation = Orientation.Vertical, + VerticalAlignment = VerticalAlignment.Center, + Margin = settingLabelPanelMargin + }; + + RowDefinition gridRow = new RowDefinition(); + mainPanel.RowDefinitions.Add(gridRow); + var name = new TextBlock() + { + Text = attribute.Label, + VerticalAlignment = VerticalAlignment.Center, + Margin = settingLabelMargin, + TextWrapping = TextWrapping.WrapWithOverflow + }; + + var desc = new TextBlock() + { + Text = attribute.Description, + FontSize = 12, + VerticalAlignment = VerticalAlignment.Center, + Margin = settingDescMargin, + TextWrapping = TextWrapping.WrapWithOverflow + }; + + desc.SetResourceReference(TextBlock.ForegroundProperty, "Color04B"); + + if (attribute.Description == null) /* if no description, hide */ + desc.Visibility = Visibility.Collapsed; + + + if (type != "textBlock") /* if textBlock, hide desc */ + { + panel.Children.Add(name); + panel.Children.Add(desc); + } + + + Grid.SetColumn(panel, 0); + Grid.SetRow(panel, rowCount); + + FrameworkElement contentControl; + + switch (type) + { + case "textBlock": + { + contentControl = new TextBlock + { + Text = attribute.Description.Replace("\\r\\n", "\r\n"), + Margin = settingTextBlockMargin, + Padding = new Thickness(0, 0, 0, 0), + HorizontalAlignment = System.Windows.HorizontalAlignment.Left, + TextAlignment = TextAlignment.Left, + TextWrapping = TextWrapping.Wrap + }; + + Grid.SetColumn(contentControl, 0); + Grid.SetColumnSpan(contentControl, 2); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + + break; + } + case "input": + { + var textBox = new TextBox() + { + Text = Settings[attribute.Name] as string ?? string.Empty, + Margin = settingControlMargin, + HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, + ToolTip = attribute.Description + }; + + textBox.TextChanged += (_, _) => + { + Settings[attribute.Name] = textBox.Text; + }; + + contentControl = textBox; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + + break; + } + case "inputWithFileBtn": + { + var textBox = new TextBox() + { + Margin = new Thickness(10, 0, 0, 0), + Text = Settings[attribute.Name] as string ?? string.Empty, + HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, + ToolTip = attribute.Description + }; + + textBox.TextChanged += (_, _) => + { + Settings[attribute.Name] = textBox.Text; + }; + + var Btn = new System.Windows.Controls.Button() + { + Margin = new Thickness(10, 0, 0, 0), Content = "Browse" + }; + + var dockPanel = new DockPanel() { Margin = settingControlMargin }; + + DockPanel.SetDock(Btn, Dock.Right); + dockPanel.Children.Add(Btn); + dockPanel.Children.Add(textBox); + contentControl = dockPanel; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + + break; + } + case "textarea": + { + var textBox = new TextBox() + { + Height = 120, + Margin = settingControlMargin, + VerticalAlignment = VerticalAlignment.Center, + TextWrapping = TextWrapping.WrapWithOverflow, + AcceptsReturn = true, + HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, + Text = Settings[attribute.Name] as string ?? string.Empty, + ToolTip = attribute.Description + }; + + textBox.TextChanged += (sender, _) => + { + Settings[attribute.Name] = ((TextBox)sender).Text; + }; + + contentControl = textBox; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + + break; + } + case "passwordBox": + { + var passwordBox = new PasswordBox() + { + Margin = settingControlMargin, + Password = Settings[attribute.Name] as string ?? string.Empty, + PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar, + HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, + ToolTip = attribute.Description + }; + + passwordBox.PasswordChanged += (sender, _) => + { + Settings[attribute.Name] = ((PasswordBox)sender).Password; + }; + + contentControl = passwordBox; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + + break; + } + case "dropdown": + { + var comboBox = new System.Windows.Controls.ComboBox() + { + ItemsSource = attribute.Options, + SelectedItem = Settings[attribute.Name], + Margin = settingControlMargin, + HorizontalAlignment = System.Windows.HorizontalAlignment.Right, + ToolTip = attribute.Description + }; + + comboBox.SelectionChanged += (sender, _) => + { + Settings[attribute.Name] = (string)((System.Windows.Controls.ComboBox)sender).SelectedItem; + }; + + contentControl = comboBox; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + + break; + } + case "checkbox": + var checkBox = new CheckBox + { + IsChecked = + Settings[attribute.Name] is bool isChecked + ? isChecked + : bool.Parse(attribute.DefaultValue), + Margin = settingCheckboxMargin, + HorizontalAlignment = System.Windows.HorizontalAlignment.Right, + ToolTip = attribute.Description + }; + + checkBox.Click += (sender, _) => + { + Settings[attribute.Name] = ((CheckBox)sender).IsChecked; + }; + + contentControl = checkBox; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + + break; + case "hyperlink": + var hyperlink = new Hyperlink { ToolTip = attribute.Description, NavigateUri = attribute.url }; + + var linkbtn = new System.Windows.Controls.Button + { + HorizontalAlignment = System.Windows.HorizontalAlignment.Right, + Margin = settingControlMargin + }; + + linkbtn.Content = attribute.urlLabel; + + contentControl = linkbtn; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + + break; + default: + continue; + } + + if (type != "textBlock") + SettingControls[attribute.Name] = contentControl; + + mainPanel.Children.Add(panel); + mainPanel.Children.Add(contentControl); + rowCount++; + } + + return settingWindow; + } + } +} diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs new file mode 100644 index 000000000..5a6633525 --- /dev/null +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Flow.Launcher.Core.Plugin.JsonRPCV2Models; +using Flow.Launcher.Plugin; +using Microsoft.VisualStudio.Threading; +using StreamJsonRpc; +using IAsyncDisposable = System.IAsyncDisposable; + + +namespace Flow.Launcher.Core.Plugin +{ + internal abstract class JsonRPCPluginV2 : JsonRPCPluginBase, IAsyncDisposable, IAsyncReloadable, IResultUpdated + { + public const string JsonRpc = "JsonRPC"; + + protected abstract IDuplexPipe ClientPipe { get; set; } + + protected StreamReader ErrorStream { get; set; } + + private JsonRpc RPC { get; set; } + + + protected override async Task ExecuteResultAsync(JsonRPCResult result) + { + try + { + var res = await RPC.InvokeAsync(result.JsonRPCAction.Method, + argument: result.JsonRPCAction.Parameters); + + return res.Hide; + } + catch + { + return false; + } + } + + private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext()); + + public override List LoadContextMenus(Result selectedResult) + { + try + { + var res = JTF.Run(() => RPC.InvokeWithCancellationAsync("context_menu", + new object[] { selectedResult.ContextData })); + + var results = ParseResults(res); + + return results; + } + catch + { + return new List(); + } + } + + public override async Task> QueryAsync(Query query, CancellationToken token) + { + try + { + var res = await RPC.InvokeWithCancellationAsync("query", + new object[] { query, Settings.Inner }, + token); + + var results = ParseResults(res); + + return results; + } + catch + { + return new List(); + } + } + + + public override async Task InitAsync(PluginInitContext context) + { + await base.InitAsync(context); + + SetupJsonRPC(); + + _ = ReadErrorAsync(); + + await RPC.InvokeAsync("initialize", context); + + async Task ReadErrorAsync() + { + var error = await ErrorStream.ReadToEndAsync(); + + if (!string.IsNullOrEmpty(error)) + { + throw new Exception(error); + } + } + } + + public event ResultUpdatedEventHandler ResultsUpdated; + + protected enum MessageHandlerType + { + HeaderDelimited, + LengthHeaderDelimited, + NewLineDelimited + } + + protected abstract MessageHandlerType MessageHandler { get; } + + + private void SetupJsonRPC() + { + var formatter = new SystemTextJsonFormatter { JsonSerializerOptions = RequestSerializeOption }; + IJsonRpcMessageHandler handler = MessageHandler switch + { + MessageHandlerType.HeaderDelimited => new HeaderDelimitedMessageHandler(ClientPipe, formatter), + MessageHandlerType.LengthHeaderDelimited => new LengthHeaderMessageHandler(ClientPipe, formatter), + MessageHandlerType.NewLineDelimited => new NewLineDelimitedMessageHandler(ClientPipe, formatter), + _ => throw new ArgumentOutOfRangeException() + }; + + RPC = new JsonRpc(handler, new JsonRPCPublicAPI(Context.API)); + + RPC.AddLocalRpcMethod("UpdateResults", new Action((rawQuery, response) => + { + var results = ParseResults(response); + ResultsUpdated?.Invoke(this, + new ResultUpdatedEventArgs { Query = new Query() { RawQuery = rawQuery }, Results = results }); + })); + RPC.SynchronizationContext = null; + RPC.StartListening(); + } + + public virtual Task ReloadDataAsync() + { + SetupJsonRPC(); + return Task.CompletedTask; + } + + public virtual async ValueTask DisposeAsync() + { + try + { + await RPC.InvokeAsync("close"); + } + catch (RemoteMethodNotFoundException e) + { + } + finally + { + RPC?.Dispose(); + ErrorStream?.Dispose(); + } + } + } +} diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs new file mode 100644 index 000000000..6a130f70f --- /dev/null +++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCExecuteResponse.cs @@ -0,0 +1,4 @@ +namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models +{ + public record JsonRPCExecuteResponse(bool Hide = true); +} diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs new file mode 100644 index 000000000..b8bfee591 --- /dev/null +++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCPublicAPI.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedModels; + +namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models +{ + public class JsonRPCPublicAPI + { + private IPublicAPI _api; + + public JsonRPCPublicAPI(IPublicAPI api) + { + _api = api; + } + + public void ChangeQuery(string query, bool requery = false) + { + _api.ChangeQuery(query, requery); + } + + public void RestartApp() + { + _api.RestartApp(); + } + + public void ShellRun(string cmd, string filename = "cmd.exe") + { + _api.ShellRun(cmd, filename); + } + + public void CopyToClipboard(string text, bool directCopy = false, bool showDefaultNotification = true) + { + _api.CopyToClipboard(text, directCopy, showDefaultNotification); + } + + public void SaveAppAllSettings() + { + _api.SaveAppAllSettings(); + } + + public void SavePluginSettings() + { + _api.SavePluginSettings(); + } + + public Task ReloadAllPluginDataAsync() + { + return _api.ReloadAllPluginData(); + } + + public void CheckForNewUpdate() + { + _api.CheckForNewUpdate(); + } + + public void ShowMsgError(string title, string subTitle = "") + { + _api.ShowMsgError(title, subTitle); + } + + public void ShowMainWindow() + { + _api.ShowMainWindow(); + } + + public void HideMainWindow() + { + _api.HideMainWindow(); + } + + public bool IsMainWindowVisible() + { + return _api.IsMainWindowVisible(); + } + + public void ShowMsg(string title, string subTitle = "", string iconPath = "") + { + _api.ShowMsg(title, subTitle, iconPath); + } + + public void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true) + { + _api.ShowMsg(title, subTitle, iconPath, useMainWindowAsOwner); + } + + public void OpenSettingDialog() + { + _api.OpenSettingDialog(); + } + + public string GetTranslation(string key) + { + return _api.GetTranslation(key); + } + + public List GetAllPlugins() + { + return _api.GetAllPlugins(); + } + + + public MatchResult FuzzySearch(string query, string stringToCompare) + { + return _api.FuzzySearch(query, stringToCompare); + } + + public Task HttpGetStringAsync(string url, CancellationToken token = default) + { + return _api.HttpGetStringAsync(url, token); + } + + public Task HttpGetStreamAsync(string url, CancellationToken token = default) + { + return _api.HttpGetStreamAsync(url, token); + } + + public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, + CancellationToken token = default) + { + return _api.HttpDownloadAsync(url, filePath, token); + } + + public void AddActionKeyword(string pluginId, string newActionKeyword) + { + _api.AddActionKeyword(pluginId, newActionKeyword); + } + + public void RemoveActionKeyword(string pluginId, string oldActionKeyword) + { + _api.RemoveActionKeyword(pluginId, oldActionKeyword); + } + + public bool ActionKeywordAssigned(string actionKeyword) + { + return _api.ActionKeywordAssigned(actionKeyword); + } + + public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") + { + _api.LogDebug(className, message, methodName); + } + + public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") + { + _api.LogInfo(className, message, methodName); + } + + public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") + { + _api.LogWarn(className, message, methodName); + } + + public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null) + { + _api.OpenDirectory(DirectoryPath, FileNameOrFilePath); + } + + + public void OpenUrl(string url, bool? inPrivate = null) + { + _api.OpenUrl(url, inPrivate); + } + + + public void OpenAppUri(string appUri) + { + _api.OpenAppUri(appUri); + } + } +} diff --git a/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCQueryRequest.cs b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCQueryRequest.cs new file mode 100644 index 000000000..003724a23 --- /dev/null +++ b/Flow.Launcher.Core/Plugin/JsonRPCV2Models/JsonRPCQueryRequest.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Core.Plugin.JsonRPCV2Models +{ + public record JsonRPCQueryRequest( + List Results + ); +} diff --git a/Flow.Launcher.Core/Plugin/NodePlugin.cs b/Flow.Launcher.Core/Plugin/NodePlugin.cs index 8ea5c4b78..aeac18ca9 100644 --- a/Flow.Launcher.Core/Plugin/NodePlugin.cs +++ b/Flow.Launcher.Core/Plugin/NodePlugin.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.IO; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Flow.Launcher.Plugin; @@ -27,14 +28,14 @@ namespace Flow.Launcher.Core.Plugin protected override Task RequestAsync(JsonRPCRequestModel request, CancellationToken token = default) { - _startInfo.ArgumentList[1] = request.ToString(); + _startInfo.ArgumentList[1] = JsonSerializer.Serialize(request, RequestSerializeOption); return ExecuteAsync(_startInfo, token); } protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default) { // since this is not static, request strings will build up in ArgumentList if index is not specified - _startInfo.ArgumentList[1] = rpcRequest.ToString(); + _startInfo.ArgumentList[1] = JsonSerializer.Serialize(rpcRequest, RequestSerializeOption); return Execute(_startInfo); } @@ -42,8 +43,8 @@ namespace Flow.Launcher.Core.Plugin { _startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath); _startInfo.ArgumentList.Add(string.Empty); - await base.InitAsync(context); _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; + await base.InitAsync(context); } } } diff --git a/Flow.Launcher.Core/Plugin/NodePluginV2.cs b/Flow.Launcher.Core/Plugin/NodePluginV2.cs new file mode 100644 index 000000000..c8cc37c57 --- /dev/null +++ b/Flow.Launcher.Core/Plugin/NodePluginV2.cs @@ -0,0 +1,30 @@ +using System.Diagnostics; +using System.IO; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Core.Plugin +{ + /// + /// Execution of JavaScript & TypeScript plugins + /// + internal sealed class NodePluginV2 : ProcessStreamPluginV2 + { + public NodePluginV2(string filename) + { + StartInfo = new ProcessStartInfo { FileName = filename, }; + } + + protected override ProcessStartInfo StartInfo { get; set; } + + public override async Task InitAsync(PluginInitContext context) + { + StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath); + await base.InitAsync(context); + } + + protected override MessageHandlerType MessageHandler { get; } = MessageHandlerType.HeaderDelimited; + } +} diff --git a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs index 1dd0683f0..87bf8e6e8 100644 --- a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs @@ -34,6 +34,17 @@ namespace Flow.Launcher.Core.Plugin return existAssembly ?? (assemblyPath == null ? null : LoadFromAssemblyPath(assemblyPath)); } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + var path = dependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (!string.IsNullOrEmpty(path)) + { + return LoadUnmanagedDllFromPath(path); + } + + return IntPtr.Zero; + } internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, Type type) { @@ -41,4 +52,4 @@ namespace Flow.Launcher.Core.Plugin return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Any(t => t == type)); } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 5d6751507..837de90f1 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -11,6 +11,8 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using ISavable = Flow.Launcher.Plugin.ISavable; +using Flow.Launcher.Plugin.SharedCommands; +using System.Text.Json; namespace Flow.Launcher.Core.Plugin { @@ -27,9 +29,9 @@ namespace Flow.Launcher.Core.Plugin public static IPublicAPI API { private set; get; } - // todo happlebao, this should not be public, the indicator function should be embeded - public static PluginsSettings Settings; + private static PluginsSettings Settings; private static List _metadatas; + private static List _modifiedPlugins = new List(); /// /// Directories that will hold Flow Launcher plugin directory @@ -242,7 +244,19 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - throw new FlowPluginException(metadata, e); + Result r = new() + { + Title = $"{metadata.Name}: Failed to respond!", + SubTitle = "Select this result for more info", + IcoPath = Flow.Launcher.Infrastructure.Constant.ErrorIcon, + PluginDirectory = metadata.PluginDirectory, + ActionKeywordAssigned = query.ActionKeyword, + PluginID = metadata.ID, + OriginQuery = query, + Action = _ => { throw new FlowPluginException(metadata, e);}, + Score = -100 + }; + results.Add(r); } return results; } @@ -363,5 +377,159 @@ namespace Flow.Launcher.Core.Plugin RemoveActionKeyword(id, oldActionKeyword); } } + + private static string GetContainingFolderPathAfterUnzip(string unzippedParentFolderPath) + { + var unzippedFolderCount = Directory.GetDirectories(unzippedParentFolderPath).Length; + var unzippedFilesCount = Directory.GetFiles(unzippedParentFolderPath).Length; + + // adjust path depending on how the plugin is zipped up + // the recommended should be to zip up the folder not the contents + if (unzippedFolderCount == 1 && unzippedFilesCount == 0) + // folder is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/pluginFolderName/ + return Directory.GetDirectories(unzippedParentFolderPath)[0]; + + if (unzippedFilesCount > 1) + // content is zipped up, unzipped plugin directory structure: tempPath/unzippedParentPluginFolder/ + return unzippedParentFolderPath; + + return string.Empty; + } + + private static bool SameOrLesserPluginVersionExists(string metadataPath) + { + var newMetadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath)); + return AllPlugins.Any(x => x.Metadata.ID == newMetadata.ID + && newMetadata.Version.CompareTo(x.Metadata.Version) <= 0); + } + + #region Public functions + + public static bool PluginModified(string uuid) + { + return _modifiedPlugins.Contains(uuid); + } + + + /// + /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url, + /// unless it's a local path installation + /// + public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) + { + InstallPlugin(newVersion, zipFilePath, checkModified:false); + UninstallPlugin(existingVersion, removeSettings:false, checkModified:false); + _modifiedPlugins.Add(existingVersion.ID); + } + + /// + /// Install a plugin. By default will remove the zip file if installation is from url, unless it's a local path installation + /// + public static void InstallPlugin(UserPlugin plugin, string zipFilePath) + { + InstallPlugin(plugin, zipFilePath, checkModified: true); + } + + /// + /// Uninstall a plugin. + /// + public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true) + { + UninstallPlugin(plugin, removeSettings, true); + } + + #endregion + + #region Internal functions + + internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified) + { + if (checkModified && PluginModified(plugin.ID)) + { + // Distinguish exception from installing same or less version + throw new ArgumentException($"Plugin {plugin.Name} {plugin.ID} has been modified.", nameof(plugin)); + } + + // Unzip plugin files to temp folder + var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath); + + if(!plugin.IsFromLocalInstallPath) + File.Delete(zipFilePath); + + var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath); + + var metadataJsonFilePath = string.Empty; + if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName))) + metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName); + + if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath)) + { + throw new FileNotFoundException($"Unable to find plugin.json from the extracted zip file, or this path {pluginFolderPath} does not exist"); + } + + if (SameOrLesserPluginVersionExists(metadataJsonFilePath)) + { + throw new InvalidOperationException($"A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin {plugin.Name}"); + } + + var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}"; + + var defaultPluginIDs = new List + { + "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark + "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator + "572be03c74c642baae319fc283e561a8", // Explorer + "6A122269676E40EB86EB543B945932B9", // PluginIndicator + "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager + "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller + "791FC278BA414111B8D1886DFE447410", // Program + "D409510CD0D2481F853690A07E6DC426", // Shell + "CEA08895D2544B019B2E9C5009600DF4", // Sys + "0308FD86DE0A4DEE8D62B9B535370992", // URL + "565B73353DBF4806919830B9202EE3BF", // WebSearch + "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings + }; + + // Treat default plugin differently, it needs to be removable along with each flow release + var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID) + ? DataLocation.PluginsDirectory + : Constant.PreinstalledDirectory; + + var newPluginPath = Path.Combine(installDirectory, folderName); + + FilesFolders.CopyAll(pluginFolderPath, newPluginPath); + + Directory.Delete(tempFolderPluginPath, true); + + if (checkModified) + { + _modifiedPlugins.Add(plugin.ID); + } + } + + internal static void UninstallPlugin(PluginMetadata plugin, bool removeSettings, bool checkModified) + { + if (checkModified && PluginModified(plugin.ID)) + { + throw new ArgumentException($"Plugin {plugin.Name} has been modified"); + } + + if (removeSettings) + { + Settings.Plugins.Remove(plugin.ID); + AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); + } + + // Marked for deletion. Will be deleted on next start up + using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); + + if (checkModified) + { + _modifiedPlugins.Add(plugin.ID); + } + } + + #endregion } } diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index fea7f55b9..0f2e4f996 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -19,22 +19,33 @@ namespace Flow.Launcher.Core.Plugin public static List Plugins(List metadatas, PluginsSettings settings) { var dotnetPlugins = DotNetPlugins(metadatas); - + var pythonEnv = new PythonEnvironment(metadatas, settings); + var pythonV2Env = new PythonV2Environment(metadatas, settings); var tsEnv = new TypeScriptEnvironment(metadatas, settings); var jsEnv = new JavaScriptEnvironment(metadatas, settings); + var tsV2Env = new TypeScriptV2Environment(metadatas, settings); + var jsV2Env = new JavaScriptV2Environment(metadatas, settings); var pythonPlugins = pythonEnv.Setup(); + var pythonV2Plugins = pythonV2Env.Setup(); var tsPlugins = tsEnv.Setup(); var jsPlugins = jsEnv.Setup(); - + var tsV2Plugins = tsV2Env.Setup(); + var jsV2Plugins = jsV2Env.Setup(); + var executablePlugins = ExecutablePlugins(metadatas); - + var executableV2Plugins = ExecutableV2Plugins(metadatas); + var plugins = dotnetPlugins - .Concat(pythonPlugins) - .Concat(tsPlugins) - .Concat(jsPlugins) - .Concat(executablePlugins) - .ToList(); + .Concat(pythonPlugins) + .Concat(pythonV2Plugins) + .Concat(tsPlugins) + .Concat(jsPlugins) + .Concat(tsV2Plugins) + .Concat(jsV2Plugins) + .Concat(executablePlugins) + .Concat(executableV2Plugins) + .ToList(); return plugins; } @@ -93,7 +104,7 @@ namespace Flow.Launcher.Core.Plugin return; } - plugins.Add(new PluginPair {Plugin = plugin, Metadata = metadata}); + plugins.Add(new PluginPair { Plugin = plugin, Metadata = metadata }); }); metadata.InitTime += milliseconds; } @@ -118,7 +129,7 @@ namespace Flow.Launcher.Core.Plugin return plugins; } - public static IEnumerable ExecutablePlugins(IEnumerable source) + public static IEnumerable ExecutablePlugins(IEnumerable source) { return source .Where(o => o.Language.Equals(AllowedLanguage.Executable, StringComparison.OrdinalIgnoreCase)) @@ -127,5 +138,15 @@ namespace Flow.Launcher.Core.Plugin Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), Metadata = metadata }); } + + public static IEnumerable ExecutableV2Plugins(IEnumerable source) + { + return source + .Where(o => o.Language.Equals(AllowedLanguage.ExecutableV2, StringComparison.OrdinalIgnoreCase)) + .Select(metadata => new PluginPair + { + Plugin = new ExecutablePluginV2(metadata.ExecuteFilePath), Metadata = metadata + }); + } } } diff --git a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs new file mode 100644 index 000000000..a14baf271 --- /dev/null +++ b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs @@ -0,0 +1,91 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Threading.Tasks; +using Flow.Launcher.Infrastructure; +using Flow.Launcher.Plugin; +using Meziantou.Framework.Win32; +using Microsoft.VisualBasic.ApplicationServices; +using Nerdbank.Streams; + +namespace Flow.Launcher.Core.Plugin +{ + internal abstract class ProcessStreamPluginV2 : JsonRPCPluginV2 + { + private static JobObject _jobObject = new JobObject(); + + static ProcessStreamPluginV2() + { + _jobObject.SetLimits(new JobObjectLimits() + { + Flags = JobObjectLimitFlags.KillOnJobClose | JobObjectLimitFlags.DieOnUnhandledException | + JobObjectLimitFlags.SilentBreakawayOk + }); + + _jobObject.AssignProcess(Process.GetCurrentProcess()); + } + + protected sealed override IDuplexPipe ClientPipe { get; set; } = null!; + + protected abstract ProcessStartInfo StartInfo { get; set; } + + protected Process ClientProcess { get; set; } = null!; + + public override async Task InitAsync(PluginInitContext context) + { + StartInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version; + StartInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory; + StartInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory; + + StartInfo.RedirectStandardError = true; + StartInfo.RedirectStandardInput = true; + StartInfo.RedirectStandardOutput = true; + StartInfo.CreateNoWindow = true; + StartInfo.UseShellExecute = false; + StartInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; + + var process = Process.Start(StartInfo); + ArgumentNullException.ThrowIfNull(process); + ClientProcess = process; + _jobObject.AssignProcess(ClientProcess); + + SetupPipe(ClientProcess); + + ErrorStream = ClientProcess.StandardError; + + await base.InitAsync(context); + } + + private void SetupPipe(Process process) + { + var (reader, writer) = (PipeReader.Create(process.StandardOutput.BaseStream), + PipeWriter.Create(process.StandardInput.BaseStream)); + ClientPipe = new DuplexPipe(reader, writer); + } + + + public override async Task ReloadDataAsync() + { + var oldProcess = ClientProcess; + ClientProcess = Process.Start(StartInfo); + ArgumentNullException.ThrowIfNull(ClientProcess); + SetupPipe(ClientProcess); + await base.ReloadDataAsync(); + oldProcess.Kill(true); + await oldProcess.WaitForExitAsync(); + oldProcess.Dispose(); + } + + + public override async ValueTask DisposeAsync() + { + ClientProcess.Kill(true); + await ClientProcess.WaitForExitAsync(); + ClientProcess.Dispose(); + await base.DisposeAsync(); + } + } +} diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs index d8df21226..536e69b3d 100644 --- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs +++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.IO; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Flow.Launcher.Infrastructure; @@ -22,7 +23,6 @@ namespace Flow.Launcher.Core.Plugin RedirectStandardError = true, }; - // temp fix for issue #667 var path = Path.Combine(Constant.ProgramDirectory, JsonRPC); _startInfo.EnvironmentVariables["PYTHONPATH"] = path; @@ -37,7 +37,7 @@ namespace Flow.Launcher.Core.Plugin protected override Task RequestAsync(JsonRPCRequestModel request, CancellationToken token = default) { - _startInfo.ArgumentList[2] = request.ToString(); + _startInfo.ArgumentList[2] = JsonSerializer.Serialize(request, RequestSerializeOption); return ExecuteAsync(_startInfo, token); } @@ -45,8 +45,8 @@ namespace Flow.Launcher.Core.Plugin protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default) { // since this is not static, request strings will build up in ArgumentList if index is not specified - _startInfo.ArgumentList[2] = rpcRequest.ToString(); - _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; + _startInfo.ArgumentList[2] = JsonSerializer.Serialize(rpcRequest, RequestSerializeOption); + _startInfo.WorkingDirectory = Context.CurrentPluginMetadata.PluginDirectory; // TODO: Async Action return Execute(_startInfo); } diff --git a/Flow.Launcher.Core/Plugin/PythonPluginV2.cs b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs new file mode 100644 index 000000000..5c36e0eea --- /dev/null +++ b/Flow.Launcher.Core/Plugin/PythonPluginV2.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Pipelines; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using Flow.Launcher.Core.Plugin.JsonRPCV2Models; +using Flow.Launcher.Infrastructure; +using Flow.Launcher.Plugin; +using Microsoft.VisualStudio.Threading; +using Nerdbank.Streams; +using StreamJsonRpc; + +namespace Flow.Launcher.Core.Plugin +{ + internal sealed class PythonPluginV2 : ProcessStreamPluginV2 + { + protected override ProcessStartInfo StartInfo { get; set; } + + public PythonPluginV2(string filename) + { + StartInfo = new ProcessStartInfo { FileName = filename, }; + + var path = Path.Combine(Constant.ProgramDirectory, JsonRpc); + StartInfo.EnvironmentVariables["PYTHONPATH"] = path; + + //Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable + StartInfo.ArgumentList.Add("-B"); + } + + public override async Task InitAsync(PluginInitContext context) + { + StartInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath); + await base.InitAsync(context); + } + + protected override MessageHandlerType MessageHandler { get; } = MessageHandlerType.NewLineDelimited; + } +} diff --git a/Flow.Launcher.Core/Resource/AvailableLanguages.cs b/Flow.Launcher.Core/Resource/AvailableLanguages.cs index b6d394d11..46c1ecb54 100644 --- a/Flow.Launcher.Core/Resource/AvailableLanguages.cs +++ b/Flow.Launcher.Core/Resource/AvailableLanguages.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Flow.Launcher.Core.Resource { @@ -27,6 +27,8 @@ namespace Flow.Launcher.Core.Resource public static Language Turkish = new Language("tr", "Türkçe"); public static Language Czech = new Language("cs", "čeština"); public static Language Arabic = new Language("ar", "اللغة العربية"); + public static Language Vietnamese = new Language("vi-vn", "Tiếng Việt"); + public static List GetAvailableLanguages() { @@ -54,7 +56,8 @@ namespace Flow.Launcher.Core.Resource Slovak, Turkish, Czech, - Arabic + Arabic, + Vietnamese }; return languages; } diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index acc693ed5..06eb868b8 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -96,13 +96,10 @@ namespace Flow.Launcher.Core.Resource { LoadLanguage(language); } - // Culture of this thread - // Use CreateSpecificCulture to preserve possible user-override settings in Windows + // Culture of main thread + // Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(language.LanguageCode); CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; - // App domain - CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CreateSpecificCulture(language.LanguageCode); - CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.DefaultThreadCurrentCulture; // Raise event after culture is set Settings.Language = language.LanguageCode; @@ -193,7 +190,7 @@ namespace Flow.Launcher.Core.Resource { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); - pluginI18N.OnCultureInfoChanged(CultureInfo.DefaultThreadCurrentCulture); + pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e) { diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 96338cf6a..61f0b18e0 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -176,8 +176,6 @@ namespace Flow.Launcher.Core.Resource } if (dict["ItemTitleStyle"] is Style resultItemStyle && - dict["ItemSubTitleStyle"] is Style resultSubItemStyle && - dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle && dict["ItemTitleSelectedStyle"] is Style resultItemSelectedStyle && dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle && dict["ItemHotkeySelectedStyle"] is Style resultHotkeyItemSelectedStyle) @@ -189,9 +187,25 @@ namespace Flow.Launcher.Core.Resource Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch }; Array.ForEach( - new[] { resultItemStyle, resultSubItemStyle, resultItemSelectedStyle, resultSubItemSelectedStyle, resultHotkeyItemStyle, resultHotkeyItemSelectedStyle }, o + new[] { resultItemStyle, resultItemSelectedStyle, resultHotkeyItemStyle, resultHotkeyItemSelectedStyle }, o => Array.ForEach(setters, p => o.Setters.Add(p))); } + + if ( + dict["ItemSubTitleStyle"] is Style resultSubItemStyle && + dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle) + { + Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(Settings.ResultSubFont)); + Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(Settings.ResultSubFontStyle)); + Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(Settings.ResultSubFontWeight)); + Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(Settings.ResultSubFontStretch)); + + Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch }; + Array.ForEach( + new[] { resultSubItemStyle,resultSubItemSelectedStyle}, o + => Array.ForEach(setters, p => o.Setters.Add(p))); + } + /* Ignore Theme Window Width and use setting */ var windowStyle = dict["WindowStyle"] as Style; var width = Settings.WindowSize; diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index 7a95b52d5..8a95ee79f 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -7,6 +7,7 @@ namespace Flow.Launcher.Infrastructure public static class Constant { public const string FlowLauncher = "Flow.Launcher"; + public const string FlowLauncherFullName = "Flow Launcher"; public const string Plugins = "Plugins"; public const string PluginMetadataFileName = "plugin.json"; diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index 2f5259039..e36b9c5e0 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -49,14 +49,14 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive + - - diff --git a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs index 937059436..25bc75a56 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -6,7 +6,7 @@ using System.Windows.Input; namespace Flow.Launcher.Infrastructure.Hotkey { - public class HotkeyModel + public record struct HotkeyModel { public bool Alt { get; set; } public bool Shift { get; set; } @@ -17,8 +17,7 @@ namespace Flow.Launcher.Infrastructure.Hotkey private static readonly Dictionary specialSymbolDictionary = new Dictionary { - {Key.Space, "Space"}, - {Key.Oem3, "~"} + { Key.Space, "Space" }, { Key.Oem3, "~" } }; public ModifierKeys ModifierKeys @@ -30,18 +29,22 @@ namespace Flow.Launcher.Infrastructure.Hotkey { modifierKeys |= ModifierKeys.Alt; } + if (Shift) { modifierKeys |= ModifierKeys.Shift; } + if (Win) { modifierKeys |= ModifierKeys.Windows; } + if (Ctrl) { modifierKeys |= ModifierKeys.Control; } + return modifierKeys; } } @@ -66,31 +69,37 @@ namespace Flow.Launcher.Infrastructure.Hotkey { return; } + List keys = hotkeyString.Replace(" ", "").Split('+').ToList(); if (keys.Contains("Alt")) { Alt = true; keys.Remove("Alt"); } + if (keys.Contains("Shift")) { Shift = true; keys.Remove("Shift"); } + if (keys.Contains("Win")) { Win = true; keys.Remove("Win"); } + if (keys.Contains("Ctrl")) { Ctrl = true; keys.Remove("Ctrl"); } + if (keys.Count == 1) { string charKey = keys[0]; - KeyValuePair? specialSymbolPair = specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey); + KeyValuePair? specialSymbolPair = + specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey); if (specialSymbolPair.Value.Value != null) { CharKey = specialSymbolPair.Value.Key; @@ -103,7 +112,6 @@ namespace Flow.Launcher.Infrastructure.Hotkey } catch (ArgumentException) { - } } } @@ -111,33 +119,39 @@ namespace Flow.Launcher.Infrastructure.Hotkey public override string ToString() { - List keys = new List(); - if (Ctrl) + return string.Join(" + ", EnumerateDisplayKeys()); + } + + public IEnumerable EnumerateDisplayKeys() + { + if (Ctrl && CharKey is not (Key.LeftCtrl or Key.RightCtrl)) { - keys.Add("Ctrl"); + yield return "Ctrl"; } - if (Alt) + + if (Alt && CharKey is not (Key.LeftAlt or Key.RightAlt)) { - keys.Add("Alt"); + yield return "Alt"; } - if (Shift) + + if (Shift && CharKey is not (Key.LeftShift or Key.RightShift)) { - keys.Add("Shift"); + yield return "Shift"; } - if (Win) + + if (Win && CharKey is not (Key.LWin or Key.RWin)) { - keys.Add("Win"); + yield return "Win"; } if (CharKey != Key.None) { - keys.Add(specialSymbolDictionary.ContainsKey(CharKey) - ? specialSymbolDictionary[CharKey] - : CharKey.ToString()); + yield return specialSymbolDictionary.TryGetValue(CharKey, out var value) + ? value + : CharKey.ToString(); } - return string.Join(" + ", keys); } - + /// /// Validate hotkey /// @@ -164,11 +178,13 @@ namespace Flow.Launcher.Infrastructure.Hotkey { KeyGesture keyGesture = new KeyGesture(CharKey, ModifierKeys); } - catch (System.Exception e) when (e is NotSupportedException || e is InvalidEnumArgumentException) + catch (System.Exception e) when + (e is NotSupportedException || e is InvalidEnumArgumentException) { return false; } } + if (ModifierKeys == ModifierKeys.None) { return !IsPrintableCharacter(CharKey); @@ -206,18 +222,6 @@ namespace Flow.Launcher.Infrastructure.Hotkey key == Key.Decimal; } - public override bool Equals(object obj) - { - if (obj is HotkeyModel other) - { - return ModifierKeys == other.ModifierKeys && CharKey == other.CharKey; - } - else - { - return false; - } - } - public override int GetHashCode() { return HashCode.Combine(ModifierKeys, CharKey); diff --git a/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs b/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs new file mode 100644 index 000000000..448a70d19 --- /dev/null +++ b/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Flow.Launcher.Infrastructure.Hotkey; + +/// +/// Interface that you should implement in your settings class to be able to pass it to +/// Flow.Launcher.HotkeyControlDialog. It allows the dialog to display the hotkeys that have already been +/// registered, and optionally provide a way to unregister them. +/// +public interface IHotkeySettings +{ + /// + /// A list of hotkeys that have already been registered. The dialog will display these hotkeys and provide a way to + /// unregister them. + /// + public List RegisteredHotkeys { get; } +} diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs new file mode 100644 index 000000000..b6a10fc30 --- /dev/null +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -0,0 +1,119 @@ +using System; + +namespace Flow.Launcher.Infrastructure.Hotkey; + +#nullable enable + +/// +/// Represents a hotkey that has been registered. Used in Flow.Launcher.HotkeyControlDialog via +/// and to display errors if user tries to register a hotkey +/// that has already been registered, and optionally provides a way to unregister the hotkey. +/// +public record RegisteredHotkeyData +{ + /// + /// representation of this hotkey. + /// + public HotkeyModel Hotkey { get; } + + /// + /// String key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + public string DescriptionResourceKey { get; } + + /// + /// Array of values that will replace {0}, {1}, {2}, etc. in the localized string found via + /// . + /// + public object?[] DescriptionFormatVariables { get; } = Array.Empty(); + + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that + /// this hotkey can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public Action? RemoveHotkey { get; } + + /// + /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in + /// descriptionResourceKey doesn't need any arguments for string.Format. If it does, + /// use one of the other constructors. + /// + /// + /// The hotkey this class will represent. + /// Example values: F1, Ctrl+Shift+Enter + /// + /// + /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey + /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public RegisteredHotkeyData(string hotkey, string descriptionResourceKey, Action? removeHotkey = null) + { + Hotkey = new HotkeyModel(hotkey); + DescriptionResourceKey = descriptionResourceKey; + RemoveHotkey = removeHotkey; + } + + /// + /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in + /// descriptionResourceKey needs exactly one argument for string.Format. + /// + /// + /// The hotkey this class will represent. + /// Example values: F1, Ctrl+Shift+Enter + /// + /// + /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + /// + /// The value that will replace {0} in the localized string found via description. + /// + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey + /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public RegisteredHotkeyData( + string hotkey, string descriptionResourceKey, object? descriptionFormatVariable, Action? removeHotkey = null + ) + { + Hotkey = new HotkeyModel(hotkey); + DescriptionResourceKey = descriptionResourceKey; + DescriptionFormatVariables = new[] { descriptionFormatVariable }; + RemoveHotkey = removeHotkey; + } + + /// + /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in + /// needs multiple arguments for string.Format. + /// + /// + /// The hotkey this class will represent. + /// Example values: F1, Ctrl+Shift+Enter + /// + /// + /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + /// + /// Array of values that will replace {0}, {1}, {2}, etc. + /// in the localized string found via description. + /// + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey + /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public RegisteredHotkeyData( + string hotkey, string descriptionResourceKey, object?[] descriptionFormatVariables, Action? removeHotkey = null + ) + { + Hotkey = new HotkeyModel(hotkey); + DescriptionResourceKey = descriptionResourceKey; + DescriptionFormatVariables = descriptionFormatVariables; + RemoveHotkey = removeHotkey; + } +} diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 7a2b57637..55545b9a7 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -4,13 +4,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Windows.Media; +using FastCache; +using FastCache.Services; namespace Flow.Launcher.Infrastructure.Image { - [Serializable] public class ImageUsage { - public int usage; public ImageSource imageSource; @@ -23,16 +23,13 @@ namespace Flow.Launcher.Infrastructure.Image public class ImageCache { - private const int MaxCached = 50; - public ConcurrentDictionary<(string, bool), ImageUsage> Data { get; } = new(); - private const int permissibleFactor = 2; - private SemaphoreSlim semaphore = new(1, 1); + private const int MaxCached = 150; public void Initialize(Dictionary<(string, bool), int> usage) { foreach (var key in usage.Keys) { - Data[key] = new ImageUsage(usage[key], null); + Cached.Save(key, new ImageUsage(usage[key], null), TimeSpan.MaxValue, MaxCached); } } @@ -40,70 +37,48 @@ namespace Flow.Launcher.Infrastructure.Image { get { - if (!Data.TryGetValue((path, isFullImage), out var value)) + if (!Cached.TryGet((path, isFullImage), out var value)) { return null; } - value.usage++; - return value.imageSource; + value.Value.usage++; + return value.Value.imageSource; } set { - Data.AddOrUpdate( - (path, isFullImage), - new ImageUsage(0, value), - (k, v) => - { - v.imageSource = value; - v.usage++; - return v; - } - ); - - SliceExtra(); - - async void SliceExtra() + if (Cached.TryGet((path, isFullImage), out var cached)) { - // To prevent the dictionary from drastically increasing in size by caching images, the dictionary size is not allowed to grow more than the permissibleFactor * maxCached size - // This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time - if (Data.Count > permissibleFactor * MaxCached) - { - await semaphore.WaitAsync().ConfigureAwait(false); - // To delete the images from the data dictionary based on the resizing of the Usage Dictionary - // Double Check to avoid concurrent remove - if (Data.Count > permissibleFactor * MaxCached) - foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) - Data.TryRemove(key, out _); - semaphore.Release(); - } + cached.Value.imageSource = value; + cached.Value.usage++; } + + Cached.Save((path, isFullImage), new ImageUsage(0, value), TimeSpan.MaxValue, + MaxCached); } } public bool ContainsKey(string key, bool isFullImage) { - return key is not null && Data.ContainsKey((key, isFullImage)) && Data[(key, isFullImage)].imageSource != null; + return Cached.TryGet((key, isFullImage), out _); } public bool TryGetValue(string key, bool isFullImage, out ImageSource image) { - if (key is not null) + if (Cached.TryGet((key, isFullImage), out var value)) { - bool hasKey = Data.TryGetValue((key, isFullImage), out var imageUsage); - image = hasKey ? imageUsage.imageSource : null; - return hasKey; - } - else - { - image = null; - return false; + image = value.Value.imageSource; + value.Value.usage++; + return image != null; } + + image = null; + return false; } public int CacheSize() { - return Data.Count; + return CacheManager.TotalCount<(string, bool), ImageUsage>(); } /// @@ -111,7 +86,14 @@ namespace Flow.Launcher.Infrastructure.Image /// public int UniqueImagesInCache() { - return Data.Values.Select(x => x.imageSource).Distinct().Count(); + return CacheManager.EnumerateEntries<(string, bool), ImageUsage>().Select(x => x.Value.imageSource) + .Distinct() + .Count(); + } + + public IEnumerable> EnumerateEntries() + { + return CacheManager.EnumerateEntries<(string, bool), ImageUsage>(); } } } diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 203c5646a..75c2a4ec9 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -15,6 +16,7 @@ namespace Flow.Launcher.Infrastructure.Image public static class ImageLoader { private static readonly ImageCache ImageCache = new(); + private static SemaphoreSlim storageLock { get; } = new SemaphoreSlim(1, 1); private static BinaryStorage> _storage; private static readonly ConcurrentDictionary GuidToKey = new(); private static IImageHashGenerator _hashGenerator; @@ -25,24 +27,18 @@ namespace Flow.Launcher.Infrastructure.Image public const int FullIconSize = 256; - private static readonly string[] ImageExtensions = - { - ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" - }; + private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" }; - public static void Initialize() + public static async Task InitializeAsync() { _storage = new BinaryStorage>("Image"); _hashGenerator = new ImageHashGenerator(); - var usage = LoadStorageToConcurrentDictionary(); + var usage = await LoadStorageToConcurrentDictionaryAsync(); ImageCache.Initialize(usage.ToDictionary(x => x.Key, x => x.Value)); - foreach (var icon in new[] - { - Constant.DefaultIcon, Constant.MissingImgIcon - }) + foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) { ImageSource img = new BitmapImage(new Uri(icon)); img.Freeze(); @@ -53,34 +49,46 @@ namespace Flow.Launcher.Infrastructure.Image { await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async () => { - foreach (var ((path, isFullImage), _) in ImageCache.Data) + foreach (var ((path, isFullImage), _) in usage) { await LoadAsync(path, isFullImage); } }); - Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); + Log.Info( + $"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); }); } - public static void Save() + public static async Task Save() { - lock (_storage) + await storageLock.WaitAsync(); + + try { - _storage.Save(ImageCache.Data + await _storage.SaveAsync(ImageCache.EnumerateEntries() .ToDictionary( x => x.Key, x => x.Value.usage)); } + finally + { + storageLock.Release(); + } } - private static ConcurrentDictionary<(string, bool), int> LoadStorageToConcurrentDictionary() + private static async Task> LoadStorageToConcurrentDictionaryAsync() { - lock (_storage) + await storageLock.WaitAsync(); + try { - var loaded = _storage.TryLoad(new Dictionary<(string, bool), int>()); + var loaded = await _storage.TryLoadAsync(new Dictionary<(string, bool), int>()); return new ConcurrentDictionary<(string, bool), int>(loaded); } + finally + { + storageLock.Release(); + } } private class ImageResult @@ -117,9 +125,12 @@ namespace Flow.Launcher.Infrastructure.Image return new ImageResult(MissingImage, ImageType.Error); } - if (ImageCache.ContainsKey(path, loadFullImage)) + // extra scope for use of same variable name { - return new ImageResult(ImageCache[path, loadFullImage], ImageType.Cache); + if (ImageCache.TryGetValue(path, loadFullImage, out var imageSource)) + { + return new ImageResult(imageSource, ImageType.Cache); + } } if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uriResult) @@ -129,6 +140,7 @@ namespace Flow.Launcher.Infrastructure.Image ImageCache[path, loadFullImage] = image; return new ImageResult(image, ImageType.ImageFile); } + if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) { var imageSource = new BitmapImage(new Uri(path)); @@ -158,6 +170,7 @@ namespace Flow.Launcher.Infrastructure.Image return imageResult; } + private static async Task LoadRemoteImageAsync(bool loadFullImage, Uri uriResult) { // Download image from url @@ -173,6 +186,7 @@ namespace Flow.Launcher.Infrastructure.Image image.DecodePixelHeight = SmallIconSize; image.DecodePixelWidth = SmallIconSize; } + image.StreamSource = buffer; image.EndInit(); image.StreamSource = null; @@ -188,8 +202,8 @@ namespace Flow.Launcher.Infrastructure.Image if (Directory.Exists(path)) { /* Directories can also have thumbnails instead of shell icons. - * Generating thumbnails for a bunch of folder results while scrolling - * could have a big impact on performance and Flow.Launcher responsibility. + * Generating thumbnails for a bunch of folder results while scrolling + * could have a big impact on performance and Flow.Launcher responsibility. * - Solution: just load the icon */ type = ImageType.Folder; @@ -208,9 +222,9 @@ namespace Flow.Launcher.Infrastructure.Image } else { - /* Although the documentation for GetImage on MSDN indicates that + /* Although the documentation for GetImage on MSDN indicates that * if a thumbnail is available it will return one, this has proved to not - * be the case in many situations while testing. + * be the case in many situations while testing. * - Solution: explicitly pass the ThumbnailOnly flag */ image = GetThumbnail(path, ThumbnailOptions.ThumbnailOnly); @@ -236,7 +250,8 @@ namespace Flow.Launcher.Infrastructure.Image return new ImageResult(image, type); } - private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly, int size = SmallIconSize) + private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly, + int size = SmallIconSize) { return WindowsThumbnailProvider.GetThumbnail( path, @@ -261,17 +276,19 @@ namespace Flow.Launcher.Infrastructure.Image var img = imageResult.ImageSource; if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache) - { // we need to get image hash + { + // we need to get image hash string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; if (hash != null) { - if (GuidToKey.TryGetValue(hash, out string key)) - { // image already exists + { + // image already exists img = ImageCache[key, loadFullImage] ?? img; } else - { // new guid + { + // new guid GuidToKey[hash] = path; } @@ -289,7 +306,7 @@ namespace Flow.Launcher.Infrastructure.Image BitmapImage image = new BitmapImage(); image.BeginInit(); image.CacheOption = BitmapCacheOption.OnLoad; - image.UriSource = new Uri(path); + image.UriSource = new Uri(path); image.CreateOptions = BitmapCreateOptions.IgnoreColorProfile; image.EndInit(); @@ -314,8 +331,10 @@ namespace Flow.Launcher.Infrastructure.Image resizedHeight.EndInit(); return resizedHeight; } + return resizedWidth; } + return image; } } diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index ea2d42773..a679643fd 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -4,67 +4,65 @@ using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters; using System.Runtime.Serialization.Formatters.Binary; +using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; +using MemoryPack; namespace Flow.Launcher.Infrastructure.Storage { -#pragma warning disable SYSLIB0011 // BinaryFormatter is obsolete. /// /// Stroage object using binary data /// Normally, it has better performance, but not readable /// + /// + /// It utilize MemoryPack, which means the object must be MemoryPackSerializable + /// https://github.com/Cysharp/MemoryPack + /// public class BinaryStorage { + const string DirectoryName = "Cache"; + + const string FileSuffix = ".cache"; + public BinaryStorage(string filename) { - const string directoryName = "Cache"; - var directoryPath = Path.Combine(DataLocation.DataDirectory(), directoryName); + var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName); Helper.ValidateDirectory(directoryPath); - const string fileSuffix = ".cache"; - FilePath = Path.Combine(directoryPath, $"{filename}{fileSuffix}"); + FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}"); } public string FilePath { get; } - public T TryLoad(T defaultData) + public async ValueTask TryLoadAsync(T defaultData) { if (File.Exists(FilePath)) { if (new FileInfo(FilePath).Length == 0) { Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>"); - Save(defaultData); + await SaveAsync(defaultData); return defaultData; } - using (var stream = new FileStream(FilePath, FileMode.Open)) - { - var d = Deserialize(stream, defaultData); - return d; - } + await using var stream = new FileStream(FilePath, FileMode.Open); + var d = await DeserializeAsync(stream, defaultData); + return d; } else { Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data"); - Save(defaultData); + await SaveAsync(defaultData); return defaultData; } } - private T Deserialize(FileStream stream, T defaultData) + private async ValueTask DeserializeAsync(Stream stream, T defaultData) { - //http://stackoverflow.com/questions/2120055/binaryformatter-deserialize-gives-serializationexception - AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; - BinaryFormatter binaryFormatter = new BinaryFormatter - { - AssemblyFormat = FormatterAssemblyStyle.Simple - }; - try { - var t = ((T)binaryFormatter.Deserialize(stream)).NonNull(); + var t = await MemoryPackSerializer.DeserializeAsync(stream); return t; } catch (System.Exception e) @@ -72,47 +70,12 @@ namespace Flow.Launcher.Infrastructure.Storage Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e); return defaultData; } - finally - { - AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve; - } } - private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + public async ValueTask SaveAsync(T data) { - Assembly ayResult = null; - string sShortAssemblyName = args.Name.Split(',')[0]; - Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (Assembly ayAssembly in ayAssemblies) - { - if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0]) - { - ayResult = ayAssembly; - break; - } - } - return ayResult; - } - - public void Save(T data) - { - using (var stream = new FileStream(FilePath, FileMode.Create)) - { - BinaryFormatter binaryFormatter = new BinaryFormatter - { - AssemblyFormat = FormatterAssemblyStyle.Simple - }; - - try - { - binaryFormatter.Serialize(stream, data); - } - catch (SerializationException e) - { - Log.Exception($"|BinaryStorage.Save|serialize error for file <{FilePath}>", e); - } - } + await using var stream = new FileStream(FilePath, FileMode.Create); + await MemoryPackSerializer.SerializeAsync(stream, data); } } -#pragma warning restore SYSLIB0011 } diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index 45456ddeb..642250627 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -3,6 +3,7 @@ using System; using System.Globalization; using System.IO; using System.Text.Json; +using System.Threading.Tasks; using Flow.Launcher.Infrastructure.Logger; namespace Flow.Launcher.Infrastructure.Storage @@ -26,6 +27,85 @@ namespace Flow.Launcher.Infrastructure.Storage protected string DirectoryPath { get; init; } = null!; + // Let the derived class to set the file path + protected JsonStorage() + { + } + public JsonStorage(string filePath) + { + FilePath = filePath; + DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path"); + + Helper.ValidateDirectory(DirectoryPath); + } + + public async Task LoadAsync() + { + if (Data != null) + return Data; + + string? serialized = null; + + if (File.Exists(FilePath)) + { + serialized = await File.ReadAllTextAsync(FilePath); + } + + if (!string.IsNullOrEmpty(serialized)) + { + try + { + Data = JsonSerializer.Deserialize(serialized) ?? await LoadBackupOrDefaultAsync(); + } + catch (JsonException) + { + Data = await LoadBackupOrDefaultAsync(); + } + } + else + { + Data = await LoadBackupOrDefaultAsync(); + } + + return Data.NonNull(); + } + + private async ValueTask LoadBackupOrDefaultAsync() + { + var backup = await TryLoadBackupAsync(); + + return backup ?? LoadDefault(); + } + + private async ValueTask TryLoadBackupAsync() + { + if (!File.Exists(BackupFilePath)) + return default; + + try + { + await using var source = File.OpenRead(BackupFilePath); + var data = await JsonSerializer.DeserializeAsync(source) ?? default; + + if (data != null) + RestoreBackup(); + + return data; + } + catch (JsonException) + { + return default; + } + } + private void RestoreBackup() + { + Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully"); + + if (File.Exists(FilePath)) + File.Replace(BackupFilePath, FilePath, null); + else + File.Move(BackupFilePath, FilePath); + } public T Load() { @@ -75,18 +155,9 @@ namespace Flow.Launcher.Infrastructure.Storage var data = JsonSerializer.Deserialize(File.ReadAllText(BackupFilePath)); if (data != null) - { - Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully"); - - if(File.Exists(FilePath)) - File.Replace(BackupFilePath, FilePath, null); - else - File.Move(BackupFilePath, FilePath); + RestoreBackup(); - return data; - } - - return default; + return data; } catch (JsonException) { @@ -115,6 +186,20 @@ namespace Flow.Launcher.Infrastructure.Storage File.WriteAllText(TempFilePath, serialized); + AtomicWriteSetting(); + } + public async Task SaveAsync() + { + var tempOutput = File.OpenWrite(TempFilePath); + await JsonSerializer.SerializeAsync(tempOutput, Data, + new JsonSerializerOptions + { + WriteIndented = true + }); + AtomicWriteSetting(); + } + private void AtomicWriteSetting() + { if (!File.Exists(FilePath)) { File.Move(TempFilePath, FilePath); @@ -124,5 +209,6 @@ namespace Flow.Launcher.Infrastructure.Storage File.Replace(TempFilePath, FilePath, BackupFilePath); } } + } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 114c98708..892cb3876 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -4,13 +4,14 @@ using System.Collections.ObjectModel; using System.Drawing; using System.Text.Json.Serialization; using System.Windows; +using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.ViewModel; namespace Flow.Launcher.Infrastructure.UserSettings { - public class Settings : BaseModel + public class Settings : BaseModel, IHotkeySettings { private string language = "en"; private string _theme = Constant.DefaultTheme; @@ -20,6 +21,18 @@ namespace Flow.Launcher.Infrastructure.UserSettings public bool ShowOpenResultHotkey { get; set; } = true; public double WindowSize { get; set; } = 580; public string PreviewHotkey { get; set; } = $"F1"; + public string AutoCompleteHotkey { get; set; } = $"{KeyConstant.Ctrl} + Tab"; + public string AutoCompleteHotkey2 { get; set; } = $""; + public string SelectNextItemHotkey { get; set; } = $"Tab"; + public string SelectNextItemHotkey2 { get; set; } = $""; + public string SelectPrevItemHotkey { get; set; } = $"Shift + Tab"; + public string SelectPrevItemHotkey2 { get; set; } = $""; + public string SelectNextPageHotkey { get; set; } = $"PageUp"; + public string SelectPrevPageHotkey { get; set; } = $"PageDown"; + public string OpenContextMenuHotkey { get; set; } = $"Ctrl+O"; + public string SettingWindowHotkey { get; set; } = $"Ctrl+I"; + public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up"; + public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down"; public string Language { @@ -43,6 +56,13 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } public bool UseDropShadowEffect { get; set; } = false; + + /* Appearance Settings. It should be separated from the setting later.*/ + public double WindowHeightSize { get; set; } = 42; + public double ItemHeightSize { get; set; } = 58; + public double QueryBoxFontSize { get; set; } = 20; + public double ResultItemFontSize { get; set; } = 16; + public double ResultSubItemFontSize { get; set; } = 13; public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name; public string QueryBoxFontStyle { get; set; } public string QueryBoxFontWeight { get; set; } @@ -51,9 +71,15 @@ namespace Flow.Launcher.Infrastructure.UserSettings public string ResultFontStyle { get; set; } public string ResultFontWeight { get; set; } public string ResultFontStretch { get; set; } + public string ResultSubFont { get; set; } = FontFamily.GenericSansSerif.Name; + public string ResultSubFontStyle { get; set; } + public string ResultSubFontWeight { get; set; } + public string ResultSubFontStretch { get; set; } public bool UseGlyphIcons { get; set; } = true; public bool UseAnimation { get; set; } = true; public bool UseSound { get; set; } = true; + public double SoundVolume { get; set; } = 50; + public bool UseClock { get; set; } = true; public bool UseDate { get; set; } = false; public string TimeFormat { get; set; } = "hh:mm tt"; @@ -166,32 +192,16 @@ namespace Flow.Launcher.Infrastructure.UserSettings public bool AlwaysStartEn { get; set; } = false; + private SearchPrecisionScore _querySearchPrecision = SearchPrecisionScore.Regular; [JsonInclude, JsonConverter(typeof(JsonStringEnumConverter))] - public SearchPrecisionScore QuerySearchPrecision { get; private set; } = SearchPrecisionScore.Regular; - - [JsonIgnore] - public string QuerySearchPrecisionString + public SearchPrecisionScore QuerySearchPrecision { - get { return QuerySearchPrecision.ToString(); } + get => _querySearchPrecision; set { - try - { - var precisionScore = (SearchPrecisionScore)Enum - .Parse(typeof(SearchPrecisionScore), value); - - QuerySearchPrecision = precisionScore; - StringMatcher.Instance.UserSettingSearchPrecision = precisionScore; - } - catch (ArgumentException e) - { - Logger.Log.Exception(nameof(Settings), "Failed to load QuerySearchPrecisionString value from Settings file", e); - - QuerySearchPrecision = SearchPrecisionScore.Regular; - StringMatcher.Instance.UserSettingSearchPrecision = SearchPrecisionScore.Regular; - - throw; - } + _querySearchPrecision = value; + if (StringMatcher.Instance != null) + StringMatcher.Instance.UserSettingSearchPrecision = value; } } @@ -199,17 +209,18 @@ namespace Flow.Launcher.Infrastructure.UserSettings public double WindowLeft { get; set; } public double WindowTop { get; set; } - + /// /// Custom left position on selected monitor /// public double CustomWindowLeft { get; set; } = 0; - + /// /// Custom top position on selected monitor /// public double CustomWindowTop { get; set; } = 0; - + + public bool KeepMaxResults { get; set; } = false; public int MaxResultsToShow { get; set; } = 5; public int ActivateTimes { get; set; } @@ -221,7 +232,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings [JsonIgnore] public ObservableCollection BuiltinShortcuts { get; set; } = new() { - new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText), + new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText), new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath) }; @@ -245,7 +256,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings [JsonConverter(typeof(JsonStringEnumConverter))] public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor; - + [JsonConverter(typeof(JsonStringEnumConverter))] public SearchWindowAligns SearchWindowAlign { get; set; } = SearchWindowAligns.Center; @@ -262,9 +273,99 @@ namespace Flow.Launcher.Infrastructure.UserSettings public AnimationSpeeds AnimationSpeed { get; set; } = AnimationSpeeds.Medium; public int CustomAnimationLength { get; set; } = 360; + [JsonIgnore] + public bool WMPInstalled { get; set; } = true; + // This needs to be loaded last by staying at the bottom public PluginsSettings PluginSettings { get; set; } = new PluginsSettings(); + + [JsonIgnore] + public List RegisteredHotkeys + { + get + { + var list = FixedHotkeys(); + + // Customizeable hotkeys + if(!string.IsNullOrEmpty(Hotkey)) + list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = "")); + if(!string.IsNullOrEmpty(PreviewHotkey)) + list.Add(new(PreviewHotkey, "previewHotkey", () => PreviewHotkey = "")); + if(!string.IsNullOrEmpty(AutoCompleteHotkey)) + list.Add(new(AutoCompleteHotkey, "autoCompleteHotkey", () => AutoCompleteHotkey = "")); + if(!string.IsNullOrEmpty(AutoCompleteHotkey2)) + list.Add(new(AutoCompleteHotkey2, "autoCompleteHotkey", () => AutoCompleteHotkey2 = "")); + if(!string.IsNullOrEmpty(SelectNextItemHotkey)) + list.Add(new(SelectNextItemHotkey, "SelectNextItemHotkey", () => SelectNextItemHotkey = "")); + if(!string.IsNullOrEmpty(SelectNextItemHotkey2)) + list.Add(new(SelectNextItemHotkey2, "SelectNextItemHotkey", () => SelectNextItemHotkey2 = "")); + if(!string.IsNullOrEmpty(SelectPrevItemHotkey)) + list.Add(new(SelectPrevItemHotkey, "SelectPrevItemHotkey", () => SelectPrevItemHotkey = "")); + if(!string.IsNullOrEmpty(SelectPrevItemHotkey2)) + list.Add(new(SelectPrevItemHotkey2, "SelectPrevItemHotkey", () => SelectPrevItemHotkey2 = "")); + if(!string.IsNullOrEmpty(SettingWindowHotkey)) + list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = "")); + if(!string.IsNullOrEmpty(OpenContextMenuHotkey)) + list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = "")); + if(!string.IsNullOrEmpty(SelectNextPageHotkey)) + list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = "")); + if(!string.IsNullOrEmpty(SelectPrevPageHotkey)) + list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = "")); + if (!string.IsNullOrEmpty(CycleHistoryUpHotkey)) + list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = "")); + if (!string.IsNullOrEmpty(CycleHistoryDownHotkey)) + list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = "")); + + // Custom Query Hotkeys + foreach (var customPluginHotkey in CustomPluginHotkeys) + { + if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) + list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = "")); + } + + return list; + } + } + + private List FixedHotkeys() + { + return new List + { + new("Up", "HotkeyLeftRightDesc"), + new("Down", "HotkeyLeftRightDesc"), + new("Left", "HotkeyUpDownDesc"), + new("Right", "HotkeyUpDownDesc"), + new("Escape", "HotkeyESCDesc"), + new("F5", "ReloadPluginHotkey"), + new("Alt+Home", "HotkeySelectFirstResult"), + new("Alt+End", "HotkeySelectLastResult"), + new("Ctrl+R", "HotkeyRequery"), + new("Ctrl+H", "ToggleHistoryHotkey"), + new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"), + new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"), + new("Ctrl+OemPlus", "QuickHeightHotkey"), + new("Ctrl+OemMinus", "QuickHeightHotkey"), + new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"), + new("Shift+Enter", "OpenContextMenuHotkey"), + new("Enter", "HotkeyRunDesc"), + new("Ctrl+Enter", "OpenContainFolderHotkey"), + new("Alt+Enter", "HotkeyOpenResult"), + new("Ctrl+F12", "ToggleGameModeHotkey"), + new("Ctrl+Shift+C", "CopyFilePathHotkey"), + + new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1), + new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2), + new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3), + new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4), + new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5), + new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6), + new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7), + new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8), + new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9), + new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10) + }; + } } public enum LastQueryMode @@ -280,7 +381,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings Light, Dark } - + public enum SearchWindowScreens { RememberLastLaunchLocation, @@ -289,7 +390,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings Primary, Custom } - + public enum SearchWindowAligns { Center, diff --git a/Flow.Launcher.Plugin/ActionContext.cs b/Flow.Launcher.Plugin/ActionContext.cs index d6ba4894e..e31c8e31d 100644 --- a/Flow.Launcher.Plugin/ActionContext.cs +++ b/Flow.Launcher.Plugin/ActionContext.cs @@ -50,5 +50,12 @@ namespace Flow.Launcher.Plugin (AltPressed ? ModifierKeys.Alt : ModifierKeys.None) | (WinPressed ? ModifierKeys.Windows : ModifierKeys.None); } + + public static readonly SpecialKeyState Default = new () { + CtrlPressed = false, + ShiftPressed = false, + AltPressed = false, + WinPressed = false + }; } } diff --git a/Flow.Launcher.Plugin/AllowedLanguage.cs b/Flow.Launcher.Plugin/AllowedLanguage.cs index d5eea4fa5..619a94deb 100644 --- a/Flow.Launcher.Plugin/AllowedLanguage.cs +++ b/Flow.Launcher.Plugin/AllowedLanguage.cs @@ -12,6 +12,11 @@ namespace Flow.Launcher.Plugin /// public const string Python = "Python"; + /// + /// Python V2 + /// + public const string PythonV2 = "Python_v2"; + /// /// C# /// @@ -27,16 +32,31 @@ namespace Flow.Launcher.Plugin /// public const string Executable = "Executable"; + /// + /// Standard .exe + /// + public const string ExecutableV2 = "Executable_V2"; + /// /// TypeScript /// public const string TypeScript = "TypeScript"; + /// + /// TypeScript + /// + public const string TypeScriptV2 = "TypeScript_V2"; + /// /// JavaScript /// public const string JavaScript = "JavaScript"; + /// + /// JavaScript + /// + public const string JavaScriptV2 = "JavaScript_V2"; + /// /// Determines if this language is a .NET language /// @@ -45,7 +65,7 @@ namespace Flow.Launcher.Plugin public static bool IsDotNet(string language) { return language.Equals(CSharp, StringComparison.OrdinalIgnoreCase) - || language.Equals(FSharp, StringComparison.OrdinalIgnoreCase); + || language.Equals(FSharp, StringComparison.OrdinalIgnoreCase); } /// @@ -56,10 +76,15 @@ namespace Flow.Launcher.Plugin public static bool IsAllowed(string language) { return IsDotNet(language) - || language.Equals(Python, StringComparison.OrdinalIgnoreCase) - || language.Equals(Executable, StringComparison.OrdinalIgnoreCase) - || language.Equals(TypeScript, StringComparison.OrdinalIgnoreCase) - || language.Equals(JavaScript, StringComparison.OrdinalIgnoreCase); + || language.Equals(Python, StringComparison.OrdinalIgnoreCase) + || language.Equals(PythonV2, StringComparison.OrdinalIgnoreCase) + || language.Equals(Executable, StringComparison.OrdinalIgnoreCase) + || language.Equals(TypeScript, StringComparison.OrdinalIgnoreCase) + || language.Equals(JavaScript, StringComparison.OrdinalIgnoreCase) + || language.Equals(ExecutableV2, StringComparison.OrdinalIgnoreCase) + || language.Equals(TypeScriptV2, StringComparison.OrdinalIgnoreCase) + || language.Equals(JavaScriptV2, StringComparison.OrdinalIgnoreCase); + ; } } } diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 1f28f5d32..7b0880b67 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -14,10 +14,10 @@ - 4.1.1 - 4.1.1 - 4.1.1 - 4.1.1 + 4.3.0 + 4.3.0 + 4.3.0 + 4.3.0 Flow.Launcher.Plugin Flow-Launcher MIT @@ -67,7 +67,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 474ad6f0a..c95a8ce7b 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -274,5 +274,29 @@ namespace Flow.Launcher.Plugin /// Non-C# plugins should use this method /// public void OpenAppUri(string appUri); + + /// + /// Toggles Game Mode. off -> on and backwards + /// + public void ToggleGameMode(); + + /// + /// Switches Game Mode to given value + /// + /// New Game Mode status + public void SetGameMode(bool value); + + /// + /// Representing Game Mode status + /// + /// + public bool IsGameModeOn(); + + /// + /// Reloads the query. + /// This method should run + /// + /// Choose the first result after reload if true; keep the last selected result if false. Default is true. + public void ReQuery(bool reselect = true); } } diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs index 672285840..b41675a1a 100644 --- a/Flow.Launcher.Plugin/Query.cs +++ b/Flow.Launcher.Plugin/Query.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin { @@ -64,6 +67,7 @@ namespace Flow.Launcher.Plugin /// public string ActionKeyword { get; init; } + [JsonIgnore] /// /// Splits by spaces and returns the first item. /// @@ -71,12 +75,14 @@ namespace Flow.Launcher.Plugin /// returns an empty string when does not have enough items. /// public string FirstSearch => SplitSearch(0); - + + [JsonIgnore] private string _secondToEndSearch; - + /// /// strings from second search (including) to last search /// + [JsonIgnore] public string SecondToEndSearch => SearchTerms.Length > 1 ? (_secondToEndSearch ??= string.Join(' ', SearchTerms[1..])) : ""; /// @@ -85,6 +91,7 @@ namespace Flow.Launcher.Plugin /// /// returns an empty string when does not have enough items. /// + [JsonIgnore] public string SecondSearch => SplitSearch(1); /// @@ -93,6 +100,7 @@ namespace Flow.Launcher.Plugin /// /// returns an empty string when does not have enough items. /// + [JsonIgnore] public string ThirdSearch => SplitSearch(2); private string SplitSearch(int index) diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index a231a0ed6..179fc5ef8 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -183,6 +183,31 @@ namespace Flow.Launcher.Plugin return Title + SubTitle + Score; } + /// + /// Clones the current result + /// + public Result Clone() + { + return new Result + { + Title = Title, + SubTitle = SubTitle, + ActionKeywordAssigned = ActionKeywordAssigned, + CopyText = CopyText, + AutoCompleteText = AutoCompleteText, + IcoPath = IcoPath, + RoundedIcon = RoundedIcon, + Icon = Icon, + Glyph = Glyph, + Action = Action, + AsyncAction = AsyncAction, + Score = Score, + TitleHighlightData = TitleHighlightData, + OriginQuery = OriginQuery, + PluginDirectory = PluginDirectory, + }; + } + /// /// Additional data associated with this result /// @@ -233,7 +258,7 @@ namespace Flow.Launcher.Plugin public string ProgressBarColor { get; set; } = "#26a0da"; /// - /// Contains data used to populate the the preview section of this result. + /// Contains data used to populate the preview section of this result. /// public PreviewInfo Preview { get; set; } = PreviewInfo.Default; diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 4137ca8d0..dd8c4b112 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; #pragma warning disable IDE0005 using System.Windows; #pragma warning restore IDE0005 @@ -200,6 +201,24 @@ namespace Flow.Launcher.Plugin.SharedCommands } } + /// + /// This checks whether a given string is a zip file path. + /// By default does not check if the zip file actually exist on disk, can do so by + /// setting checkFileExists = true. + /// + public static bool IsZipFilePath(string querySearchString, bool checkFileExists = false) + { + if (IsLocationPathString(querySearchString) && querySearchString.Split('.').Last() == "zip") + { + if (checkFileExists) + return FileExists(querySearchString); + + return true; + } + + return false; + } + /// /// This checks whether a given string is a directory path or network location string. /// It does not check if location actually exists. diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj index 3cd9e3df7..fd967de4a 100644 --- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj +++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj @@ -49,12 +49,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + \ No newline at end of file diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index e9d37433f..80cb74729 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -191,11 +191,15 @@ namespace Flow.Launcher.Test.Plugins [TestCase(@"\c:\", false)] [TestCase(@"cc:\", false)] [TestCase(@"\\\SomeNetworkLocation\", false)] + [TestCase(@"\\SomeNetworkLocation\", true)] [TestCase("RandomFile", false)] [TestCase(@"c:\>*", true)] [TestCase(@"c:\>", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation", true)] + [TestCase(@"c:\SomeLocation\SomeOtherLocation\SomeFile.exe", true)] + [TestCase(@"\\SomeNetworkLocation\SomeFile.exe", true)] + public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult) { // When, Given diff --git a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs index d071545ba..3d05e5679 100644 --- a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs +++ b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs @@ -53,21 +53,14 @@ namespace Flow.Launcher.Test.Plugins public static List ResponseModelsSource = new() { - new() + new JsonRPCQueryResponseModel(0, new List()), + new JsonRPCQueryResponseModel(0, new List { - Result = new() - }, - new() - { - Result = new() + new JsonRPCResult { - new JsonRPCResult - { - Title = "Test1", - SubTitle = "Test2" - } + Title = "Test1", SubTitle = "Test2" } - } + }) }; [TestCaseSource(typeof(JsonRPCPluginTest), nameof(ResponseModelsSource))] @@ -94,4 +87,4 @@ namespace Flow.Launcher.Test.Plugins } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.sln b/Flow.Launcher.sln index 9b376ca14..0f195ff0e 100644 --- a/Flow.Launcher.sln +++ b/Flow.Launcher.sln @@ -55,6 +55,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Scripts\post_build.ps1 = Scripts\post_build.ps1 README.md = README.md SolutionAssemblyInfo.cs = SolutionAssemblyInfo.cs + Settings.XamlStyler = Settings.XamlStyler EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Shell", "Plugins\Flow.Launcher.Plugin.Shell\Flow.Launcher.Plugin.Shell.csproj", "{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}" diff --git a/Flow.Launcher/App.xaml b/Flow.Launcher/App.xaml index b8e2a1cfe..76a613c3f 100644 --- a/Flow.Launcher/App.xaml +++ b/Flow.Launcher/App.xaml @@ -24,6 +24,7 @@ + diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 295dd3e7a..765a1a559 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -52,15 +52,17 @@ namespace Flow.Launcher { _portable.PreStartCleanUpAfterPortabilityUpdate(); - Log.Info("|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------"); + Log.Info( + "|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------"); Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}"); RegisterAppDomainExceptions(); RegisterDispatcherUnhandledException(); - ImageLoader.Initialize(); + var imageLoadertask = ImageLoader.InitializeAsync(); _settingsVM = new SettingWindowViewModel(_updater, _portable); _settings = _settingsVM.Settings; + _settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled(); AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings); @@ -78,6 +80,8 @@ namespace Flow.Launcher Http.Proxy = _settings.Proxy; await PluginManager.InitializePluginsAsync(API); + await imageLoadertask; + var window = new MainWindow(_settings, _mainVM); Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}"); @@ -85,12 +89,13 @@ namespace Flow.Launcher Current.MainWindow = window; Current.MainWindow.Title = Constant.FlowLauncher; - HotKeyMapper.Initialize(_mainVM); - // todo temp fix for instance code logic // load plugin before change language, because plugin language also needs be changed InternationalizationManager.Instance.Settings = _settings; InternationalizationManager.Instance.ChangeLanguage(_settings.Language); + + HotKeyMapper.Initialize(_mainVM); + // main windows needs initialized before theme change because of blur settings ThemeManager.Instance.Settings = _settings; ThemeManager.Instance.ChangeTheme(_settings.Theme); @@ -103,7 +108,8 @@ namespace Flow.Launcher AutoUpdates(); API.SaveAppAllSettings(); - Log.Info("|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- "); + Log.Info( + "|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- "); }); } @@ -122,7 +128,8 @@ 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; - Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), e.Message); + Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), + e.Message); } } } diff --git a/Flow.Launcher/Converters/BoolToIMEConversionModeConverter.cs b/Flow.Launcher/Converters/BoolToIMEConversionModeConverter.cs index 0bff23fe1..41e879913 100644 --- a/Flow.Launcher/Converters/BoolToIMEConversionModeConverter.cs +++ b/Flow.Launcher/Converters/BoolToIMEConversionModeConverter.cs @@ -3,53 +3,38 @@ using System.Globalization; using System.Windows.Data; using System.Windows.Input; -namespace Flow.Launcher.Converters -{ - internal class BoolToIMEConversionModeConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is bool v) - { - if (v) - { - return ImeConversionModeValues.Alphanumeric; - } - else - { - return ImeConversionModeValues.DoNotCare; - } - } - return ImeConversionModeValues.DoNotCare; - } +namespace Flow.Launcher.Converters; - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) +internal class BoolToIMEConversionModeConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value switch { - throw new NotImplementedException(); - } + true => ImeConversionModeValues.Alphanumeric, + _ => ImeConversionModeValues.DoNotCare + }; } - internal class BoolToIMEStateConverter : IValueConverter + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is bool v) - { - if (v) - { - return InputMethodState.Off; - } - else - { - return InputMethodState.DoNotCare; - } - } - return InputMethodState.DoNotCare; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } +} + +internal class BoolToIMEStateConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value switch + { + true => InputMethodState.Off, + _ => InputMethodState.DoNotCare + }; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } diff --git a/Flow.Launcher/Converters/BoolToVisibilityConverter.cs b/Flow.Launcher/Converters/BoolToVisibilityConverter.cs index 627937c7f..c0fe6ab55 100644 --- a/Flow.Launcher/Converters/BoolToVisibilityConverter.cs +++ b/Flow.Launcher/Converters/BoolToVisibilityConverter.cs @@ -2,70 +2,39 @@ using System.Windows; using System.Windows.Data; -namespace Flow.Launcher.Converters +namespace Flow.Launcher.Converters; + +public class BoolToVisibilityConverter : IValueConverter { - public class BoolToVisibilityConverter : IValueConverter + public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture) { - public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture) + return (value, parameter) switch { - if (parameter != null) - { - if (value is true) - { - return Visibility.Collapsed; - } + (true, not null) => Visibility.Collapsed, + (_, not null) => Visibility.Visible, - else - { - return Visibility.Visible; - } - } - else { - if (value is true) - { - return Visibility.Visible; - } - - else { - return Visibility.Collapsed; - } - } - } - - public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); + (true, null) => Visibility.Visible, + (_, null) => Visibility.Collapsed + }; } - public class SplitterConverter : IValueConverter - /* Prevents the dragging part of the preview area from working when preview is turned off. */ - { - public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture) - { - if (parameter != null) - { - if (value is true) - { - return 0; - } - - else - { - return 5; - } - } - else - { - if (value is true) - { - return 5; - } - - else - { - return 0; - } - } - } - - public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); - } + public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); +} + +public class SplitterConverter : IValueConverter +/* Prevents the dragging part of the preview area from working when preview is turned off. */ +{ + public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture) + { + return (value, parameter) switch + { + (true, not null) => 0, + (_, not null) => 5, + + (true, null) => 5, + (_, null) => 0 + }; + } + + public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); } diff --git a/Flow.Launcher/Converters/BorderClipConverter.cs b/Flow.Launcher/Converters/BorderClipConverter.cs index c0bce2cd9..9b0579c33 100644 --- a/Flow.Launcher/Converters/BorderClipConverter.cs +++ b/Flow.Launcher/Converters/BorderClipConverter.cs @@ -7,46 +7,41 @@ using System.Windows.Shapes; // For Clipping inside listbox item -namespace Flow.Launcher.Converters +namespace Flow.Launcher.Converters; + +public class BorderClipConverter : IMultiValueConverter { - public class BorderClipConverter : IMultiValueConverter + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + if (values is not [double width, double height, CornerRadius radius]) { - if (values.Length == 3 && values[0] is double && values[1] is double && values[2] is CornerRadius) - { - var width = (double)values[0]; - var height = (double)values[1]; - Path myPath = new Path(); - if (width < Double.Epsilon || height < Double.Epsilon) - { - return Geometry.Empty; - } - var radius = (CornerRadius)values[2]; - var radiusHeight = radius.TopLeft; - - // Drawing Round box for bottom round, and rect for top area of listbox. - var corner = new RectangleGeometry(new Rect(0, 0, width, height), radius.TopLeft, radius.TopLeft); - var box = new RectangleGeometry(new Rect(0, 0, width, radiusHeight), 0, 0); - - GeometryGroup myGeometryGroup = new GeometryGroup(); - myGeometryGroup.Children.Add(corner); - myGeometryGroup.Children.Add(box); - - CombinedGeometry c1 = new CombinedGeometry(GeometryCombineMode.Union, corner, box); - myPath.Data = c1; - - myPath.Data.Freeze(); - return myPath.Data; - } - return DependencyProperty.UnsetValue; } - public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + Path myPath = new Path(); + if (width < Double.Epsilon || height < Double.Epsilon) { - throw new NotSupportedException(); + return Geometry.Empty; } + var radiusHeight = radius.TopLeft; + + // Drawing Round box for bottom round, and rect for top area of listbox. + var corner = new RectangleGeometry(new Rect(0, 0, width, height), radius.TopLeft, radius.TopLeft); + var box = new RectangleGeometry(new Rect(0, 0, width, radiusHeight), 0, 0); + + GeometryGroup myGeometryGroup = new GeometryGroup(); + myGeometryGroup.Children.Add(corner); + myGeometryGroup.Children.Add(box); + + CombinedGeometry c1 = new CombinedGeometry(GeometryCombineMode.Union, corner, box); + myPath.Data = c1; + + myPath.Data.Freeze(); + return myPath.Data; } + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } } diff --git a/Flow.Launcher/Converters/DateTimeFormatToNowConverter.cs b/Flow.Launcher/Converters/DateTimeFormatToNowConverter.cs index 3c46fd01a..7d75ffd5c 100644 --- a/Flow.Launcher/Converters/DateTimeFormatToNowConverter.cs +++ b/Flow.Launcher/Converters/DateTimeFormatToNowConverter.cs @@ -2,18 +2,17 @@ using System.Globalization; using System.Windows.Data; -namespace Flow.Launcher.Converters -{ - public class DateTimeFormatToNowConverter : IValueConverter - { +namespace Flow.Launcher.Converters; - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value is not string format ? null : DateTime.Now.ToString(format); - } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } +public class DateTimeFormatToNowConverter : IValueConverter +{ + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is not string format ? null : DateTime.Now.ToString(format); + } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } diff --git a/Flow.Launcher/Converters/DiameterToCenterPointConverter.cs b/Flow.Launcher/Converters/DiameterToCenterPointConverter.cs index e81bb2507..24a316603 100644 --- a/Flow.Launcher/Converters/DiameterToCenterPointConverter.cs +++ b/Flow.Launcher/Converters/DiameterToCenterPointConverter.cs @@ -3,23 +3,22 @@ using System.Globalization; using System.Windows; using System.Windows.Data; -namespace Flow.Launcher.Converters +namespace Flow.Launcher.Converters; + +public class DiameterToCenterPointConverter : IValueConverter { - public class DiameterToCenterPointConverter : IValueConverter + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + if (value is double d) { - if (value is double d) - { - return new Point(d / 2, d / 2); - } - - return new Point(0, 0); + return new Point(d / 2, d / 2); } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotSupportedException(); - } + return new Point(0, 0); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); } } diff --git a/Flow.Launcher/Converters/HighlightTextConverter.cs b/Flow.Launcher/Converters/HighlightTextConverter.cs index cb62c0d3d..436f5fed5 100644 --- a/Flow.Launcher/Converters/HighlightTextConverter.cs +++ b/Flow.Launcher/Converters/HighlightTextConverter.cs @@ -1,53 +1,48 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Documents; -namespace Flow.Launcher.Converters +namespace Flow.Launcher.Converters; + +public class HighlightTextConverter : IMultiValueConverter { - public class HighlightTextConverter : IMultiValueConverter + public object Convert(object[] value, Type targetType, object parameter, CultureInfo cultureInfo) { - public object Convert(object[] value, Type targetType, object parameter, CultureInfo cultureInfo) + if (value.Length < 2) + return new Run(string.Empty); + + if (value[0] is not string text) + return new Run(string.Empty); + + if (value[1] is not List { Count: > 0 } highlightData) + // No highlight data, just return the text + return new Run(text); + + var highlightStyle = (Style)Application.Current.FindResource("HighlightStyle"); + var textBlock = new Span(); + + for (var i = 0; i < text.Length; i++) { - var text = value[0] as string; - var highlightData = value[1] as List; - - var textBlock = new Span(); - - if (highlightData == null || !highlightData.Any()) + var currentCharacter = text.Substring(i, 1); + var run = new Run(currentCharacter) { - // No highlight data, just return the text - return new Run(text); - } - - for (var i = 0; i < text.Length; i++) - { - var currentCharacter = text.Substring(i, 1); - if (this.ShouldHighlight(highlightData, i)) - { - - textBlock.Inlines.Add(new Run(currentCharacter) { Style = (Style)Application.Current.FindResource("HighlightStyle") }); - - } - else - { - textBlock.Inlines.Add(new Run(currentCharacter)); - } - } - return textBlock; + Style = ShouldHighlight(highlightData, i) ? highlightStyle : null + }; + textBlock.Inlines.Add(run); } + return textBlock; + } - public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) - { - return new[] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue }; - } + public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) + { + return new[] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue }; + } - private bool ShouldHighlight(List highlightData, int index) - { - return highlightData.Contains(index); - } + private bool ShouldHighlight(List highlightData, int index) + { + return highlightData.Contains(index); } } diff --git a/Flow.Launcher/Converters/IconRadiusConverter.cs b/Flow.Launcher/Converters/IconRadiusConverter.cs index c73bef8b2..d26b39d6b 100644 --- a/Flow.Launcher/Converters/IconRadiusConverter.cs +++ b/Flow.Launcher/Converters/IconRadiusConverter.cs @@ -2,25 +2,19 @@ using System.Globalization; using System.Windows.Data; -namespace Flow.Launcher.Converters -{ - public class IconRadiusConverter : IMultiValueConverter - { - public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) - { - if (values.Length != 2) - throw new ArgumentException("IconRadiusConverter must have 2 parameters"); +namespace Flow.Launcher.Converters; - return values[1] switch - { - true => (double)values[0] / 2, - false => (double)values[0], - _ => throw new ArgumentException("The second argument should be boolean", nameof(values)) - }; - } - public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) - { - throw new NotSupportedException(); - } +public class IconRadiusConverter : IMultiValueConverter +{ + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values is not [double size, bool isIconCircular]) + throw new ArgumentException("IconRadiusConverter must have 2 parameters: [double, bool]"); + + return isIconCircular ? size / 2 : size; + } + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); } } diff --git a/Flow.Launcher/Converters/OpenResultHotkeyVisibilityConverter.cs b/Flow.Launcher/Converters/OpenResultHotkeyVisibilityConverter.cs index a44b07cab..7ab13190a 100644 --- a/Flow.Launcher/Converters/OpenResultHotkeyVisibilityConverter.cs +++ b/Flow.Launcher/Converters/OpenResultHotkeyVisibilityConverter.cs @@ -4,24 +4,23 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Data; -namespace Flow.Launcher.Converters +namespace Flow.Launcher.Converters; + +[ValueConversion(typeof(bool), typeof(Visibility))] +public class OpenResultHotkeyVisibilityConverter : IValueConverter { - [ValueConversion(typeof(bool), typeof(Visibility))] - public class OpenResultHotkeyVisibilityConverter : IValueConverter + private const int MaxVisibleHotkeys = 10; + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - private const int MaxVisibleHotkeys = 10; + var number = int.MaxValue; - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - var number = int.MaxValue; + if (value is ListBoxItem listBoxItem + && ItemsControl.ItemsControlFromItemContainer(listBoxItem) is ListBox listBox) + number = listBox.ItemContainerGenerator.IndexFromContainer(listBoxItem) + 1; - if (value is ListBoxItem listBoxItem - && ItemsControl.ItemsControlFromItemContainer(listBoxItem) is ListBox listBox) - number = listBox.ItemContainerGenerator.IndexFromContainer(listBoxItem) + 1; - - return number <= MaxVisibleHotkeys ? Visibility.Visible : Visibility.Collapsed; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); + return number <= MaxVisibleHotkeys ? Visibility.Visible : Visibility.Collapsed; } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new InvalidOperationException(); } diff --git a/Flow.Launcher/Converters/OrdinalConverter.cs b/Flow.Launcher/Converters/OrdinalConverter.cs index 02b9bdbde..9aed2e07b 100644 --- a/Flow.Launcher/Converters/OrdinalConverter.cs +++ b/Flow.Launcher/Converters/OrdinalConverter.cs @@ -1,23 +1,24 @@ +using System; using System.Globalization; using System.Windows.Controls; using System.Windows.Data; -namespace Flow.Launcher.Converters -{ - public class OrdinalConverter : IValueConverter - { - public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture) - { - if (value is ListBoxItem listBoxItem - && ItemsControl.ItemsControlFromItemContainer(listBoxItem) is ListBox listBox) - { - var res = listBox.ItemContainerGenerator.IndexFromContainer(listBoxItem) + 1; - return res == 10 ? 0 : res; // 10th item => HOTKEY+0 - } +namespace Flow.Launcher.Converters; +public class OrdinalConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not ListBoxItem listBoxItem + || ItemsControl.ItemsControlFromItemContainer(listBoxItem) is not ListBox listBox) + { return 0; } - public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); + var res = listBox.ItemContainerGenerator.IndexFromContainer(listBoxItem) + 1; + return res == 10 ? 0 : res; // 10th item => HOTKEY+0 + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new InvalidOperationException(); } diff --git a/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs b/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs index 1e39473e0..ed94771f0 100644 --- a/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs +++ b/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs @@ -6,74 +6,62 @@ using System.Windows.Media; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.ViewModel; -namespace Flow.Launcher.Converters +namespace Flow.Launcher.Converters; + +public class QuerySuggestionBoxConverter : IMultiValueConverter { - public class QuerySuggestionBoxConverter : IMultiValueConverter + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + // values[0] is TextBox: The textbox displaying the autocomplete suggestion + // values[1] is ResultViewModel: Currently selected item in the list + // values[2] is string: Query text + if ( + values.Length != 3 || + values[0] is not TextBox queryTextBox || + values[1] is null || + values[2] is not string queryText || + string.IsNullOrEmpty(queryText) + ) + return string.Empty; + + if (values[1] is not ResultViewModel selectedItem) + return Binding.DoNothing; + + try { - if (values.Length != 3) - { - return string.Empty; - } - var QueryTextBox = values[0] as TextBox; + var selectedResult = selectedItem.Result; + var selectedResultActionKeyword = string.IsNullOrEmpty(selectedResult.ActionKeywordAssigned) ? "" : selectedResult.ActionKeywordAssigned + " "; + var selectedResultPossibleSuggestion = selectedResultActionKeyword + selectedResult.Title; - var queryText = (string)values[2]; - - if (string.IsNullOrEmpty(queryText)) + if (!selectedResultPossibleSuggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)) return string.Empty; - // second prop is the current selected item result - var val = values[1]; - if (val == null) - { + + // For AutocompleteQueryCommand. + // When user typed lower case and result title is uppercase, we still want to display suggestion + selectedItem.QuerySuggestionText = queryText + selectedResultPossibleSuggestion.Substring(queryText.Length); + + // Check if Text will be larger than our QueryTextBox + Typeface typeface = new Typeface(queryTextBox.FontFamily, queryTextBox.FontStyle, queryTextBox.FontWeight, queryTextBox.FontStretch); + // TODO: Obsolete warning? + var ft = new FormattedText(queryTextBox.Text, CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, typeface, queryTextBox.FontSize, Brushes.Black); + + var offset = queryTextBox.Padding.Right; + + if (ft.Width + offset > queryTextBox.ActualWidth || queryTextBox.HorizontalOffset != 0) return string.Empty; - } - if (!(val is ResultViewModel)) - { - return System.Windows.Data.Binding.DoNothing; - } - try - { - var selectedItem = (ResultViewModel)val; - - var selectedResult = selectedItem.Result; - var selectedResultActionKeyword = string.IsNullOrEmpty(selectedResult.ActionKeywordAssigned) ? "" : selectedResult.ActionKeywordAssigned + " "; - var selectedResultPossibleSuggestion = selectedResultActionKeyword + selectedResult.Title; - - if (!selectedResultPossibleSuggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)) - return string.Empty; - - - // For AutocompleteQueryCommand. - // When user typed lower case and result title is uppercase, we still want to display suggestion - selectedItem.QuerySuggestionText = queryText + selectedResultPossibleSuggestion.Substring(queryText.Length); - - // Check if Text will be larger then our QueryTextBox - System.Windows.Media.Typeface typeface = new Typeface(QueryTextBox.FontFamily, QueryTextBox.FontStyle, QueryTextBox.FontWeight, QueryTextBox.FontStretch); - // TODO: Obsolete warning? - System.Windows.Media.FormattedText ft = new FormattedText(QueryTextBox.Text, System.Globalization.CultureInfo.DefaultThreadCurrentCulture, System.Windows.FlowDirection.LeftToRight, typeface, QueryTextBox.FontSize, Brushes.Black); - - var offset = QueryTextBox.Padding.Right; - - if ((ft.Width + offset) > QueryTextBox.ActualWidth || QueryTextBox.HorizontalOffset != 0) - { - return string.Empty; - }; - - return selectedItem.QuerySuggestionText; - } - catch (Exception e) - { - Log.Exception(nameof(QuerySuggestionBoxConverter), "fail to convert text for suggestion box", e); - return string.Empty; - } + return selectedItem.QuerySuggestionText; } - - public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + catch (Exception e) { - throw new NotImplementedException(); + Log.Exception(nameof(QuerySuggestionBoxConverter), "fail to convert text for suggestion box", e); + return string.Empty; } } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } } diff --git a/Flow.Launcher/Converters/StringToKeyBindingConverter.cs b/Flow.Launcher/Converters/StringToKeyBindingConverter.cs index 3675f06fc..21bf584e7 100644 --- a/Flow.Launcher/Converters/StringToKeyBindingConverter.cs +++ b/Flow.Launcher/Converters/StringToKeyBindingConverter.cs @@ -3,30 +3,27 @@ using System.Globalization; using System.Windows.Data; using System.Windows.Input; -namespace Flow.Launcher.Converters -{ - class StringToKeyBindingConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - var mode = parameter as string; - var hotkeyStr = value as string; - var converter = new KeyGestureConverter(); - var key = (KeyGesture)converter.ConvertFromString(hotkeyStr); - if (mode == "key") - { - return key.Key; - } - else if (mode == "modifiers") - { - return key.Modifiers; - } - return null; - } +namespace Flow.Launcher.Converters; - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) +class StringToKeyBindingConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (parameter is not string mode || value is not string hotkeyStr) + return null; + + var converter = new KeyGestureConverter(); + var key = (KeyGesture)converter.ConvertFromString(hotkeyStr); + return mode switch { - throw new NotImplementedException(); - } + "key" => key?.Key, + "modifiers" => key?.Modifiers, + _ => null + }; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } diff --git a/Flow.Launcher/Converters/TextConverter.cs b/Flow.Launcher/Converters/TextConverter.cs index 90d445776..5f0e1ea82 100644 --- a/Flow.Launcher/Converters/TextConverter.cs +++ b/Flow.Launcher/Converters/TextConverter.cs @@ -4,29 +4,27 @@ using System.Windows.Data; using Flow.Launcher.Core.Resource; using Flow.Launcher.ViewModel; -namespace Flow.Launcher.Converters -{ - public class TextConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - var ID = value.ToString(); - switch(ID) - { - case PluginStoreItemViewModel.NewRelease: - return InternationalizationManager.Instance.GetTranslation("pluginStore_NewRelease"); - case PluginStoreItemViewModel.RecentlyUpdated: - return InternationalizationManager.Instance.GetTranslation("pluginStore_RecentlyUpdated"); - case PluginStoreItemViewModel.None: - return InternationalizationManager.Instance.GetTranslation("pluginStore_None"); - case PluginStoreItemViewModel.Installed: - return InternationalizationManager.Instance.GetTranslation("pluginStore_Installed"); - default: - return ID; - } - - } +namespace Flow.Launcher.Converters; - public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException(); +public class TextConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var id = value?.ToString(); + var translationKey = id switch + { + PluginStoreItemViewModel.NewRelease => "pluginStore_NewRelease", + PluginStoreItemViewModel.RecentlyUpdated => "pluginStore_RecentlyUpdated", + PluginStoreItemViewModel.None => "pluginStore_None", + PluginStoreItemViewModel.Installed => "pluginStore_Installed", + _ => null + }; + + if (translationKey is null) + return id; + + return InternationalizationManager.Instance.GetTranslation(translationKey); } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new InvalidOperationException(); } diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml index 1113cb24d..068afda15 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml @@ -2,10 +2,12 @@ x:Class="Flow.Launcher.CustomQueryHotkeySetting" 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 customeQueryHotkeyTitle}" Width="530" Background="{DynamicResource PopuBGColor}" + DataContext="{Binding RelativeSource={RelativeSource Self}}" Foreground="{DynamicResource PopupTextColor}" Icon="Images\app.png" MouseDown="window_MouseDown" @@ -60,94 +62,76 @@ - - - - - - - + + + - - - - - - - - - - - - - - - - - - + diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 3da66caeb..a42bde7c9 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -1,141 +1,204 @@ -using System; +#nullable enable + +using System.Collections.ObjectModel; using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; using System.Windows.Input; -using System.Windows.Media; using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.Hotkey; -using Flow.Launcher.Plugin; -using System.Threading; namespace Flow.Launcher { - public partial class HotkeyControl : UserControl + public partial class HotkeyControl { - public HotkeyModel CurrentHotkey { get; private set; } - public bool CurrentHotkeyAvailable { get; private set; } + public IHotkeySettings HotkeySettings { + get { return (IHotkeySettings)GetValue(HotkeySettingsProperty); } + set { SetValue(HotkeySettingsProperty, value); } + } - public event EventHandler HotkeyChanged; + public static readonly DependencyProperty HotkeySettingsProperty = DependencyProperty.Register( + nameof(HotkeySettings), + typeof(IHotkeySettings), + typeof(HotkeyControl), + new PropertyMetadata() + ); + public string WindowTitle { + get { return (string)GetValue(WindowTitleProperty); } + set { SetValue(WindowTitleProperty, value); } + } + + public static readonly DependencyProperty WindowTitleProperty = DependencyProperty.Register( + nameof(WindowTitle), + typeof(string), + typeof(HotkeyControl), + new PropertyMetadata(string.Empty) + ); /// /// Designed for Preview Hotkey and KeyGesture. /// - public bool ValidateKeyGesture { get; set; } = false; + public static readonly DependencyProperty ValidateKeyGestureProperty = DependencyProperty.Register( + nameof(ValidateKeyGesture), + typeof(bool), + typeof(HotkeyControl), + new PropertyMetadata(default(bool)) + ); - protected virtual void OnHotkeyChanged() => HotkeyChanged?.Invoke(this, EventArgs.Empty); - - public HotkeyControl() + public bool ValidateKeyGesture { - InitializeComponent(); + get { return (bool)GetValue(ValidateKeyGestureProperty); } + set { SetValue(ValidateKeyGestureProperty, value); } } - private CancellationTokenSource hotkeyUpdateSource; + public static readonly DependencyProperty DefaultHotkeyProperty = DependencyProperty.Register( + nameof(DefaultHotkey), + typeof(string), + typeof(HotkeyControl), + new PropertyMetadata(default(string)) + ); - private void TbHotkey_OnPreviewKeyDown(object sender, KeyEventArgs e) + public string DefaultHotkey { - hotkeyUpdateSource?.Cancel(); - hotkeyUpdateSource?.Dispose(); - hotkeyUpdateSource = new(); - var token = hotkeyUpdateSource.Token; - e.Handled = true; + get { return (string)GetValue(DefaultHotkeyProperty); } + set { SetValue(DefaultHotkeyProperty, value); } + } - //when alt is pressed, the real key should be e.SystemKey - Key key = e.Key == Key.System ? e.SystemKey : e.Key; - - SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers(); - - var hotkeyModel = new HotkeyModel( - specialKeyState.AltPressed, - specialKeyState.ShiftPressed, - specialKeyState.WinPressed, - specialKeyState.CtrlPressed, - key); - - if (hotkeyModel.Equals(CurrentHotkey)) + private static void OnHotkeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not HotkeyControl hotkeyControl) { return; } - _ = Dispatcher.InvokeAsync(async () => - { - await Task.Delay(500, token); - if (!token.IsCancellationRequested) - await SetHotkeyAsync(hotkeyModel); - }); + hotkeyControl.SetKeysToDisplay(new HotkeyModel(hotkeyControl.Hotkey)); + hotkeyControl.CurrentHotkey = new HotkeyModel(hotkeyControl.Hotkey); } - public async Task SetHotkeyAsync(HotkeyModel keyModel, bool triggerValidate = true) - { - tbHotkey.Text = keyModel.ToString(); - tbHotkey.Select(tbHotkey.Text.Length, 0); + public static readonly DependencyProperty ChangeHotkeyProperty = DependencyProperty.Register( + nameof(ChangeHotkey), + typeof(ICommand), + typeof(HotkeyControl), + new PropertyMetadata(default(ICommand)) + ); + + public ICommand? ChangeHotkey + { + get { return (ICommand)GetValue(ChangeHotkeyProperty); } + set { SetValue(ChangeHotkeyProperty, value); } + } + + + public static readonly DependencyProperty HotkeyProperty = DependencyProperty.Register( + nameof(Hotkey), + typeof(string), + typeof(HotkeyControl), + new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnHotkeyChanged) + ); + + public string Hotkey + { + get { return (string)GetValue(HotkeyProperty); } + set { SetValue(HotkeyProperty, value); } + } + + public HotkeyControl() + { + InitializeComponent(); + + HotkeyList.ItemsSource = KeysToDisplay; + SetKeysToDisplay(CurrentHotkey); + } + + private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => + hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); + + public string EmptyHotkey => InternationalizationManager.Instance.GetTranslation("none"); + + public ObservableCollection KeysToDisplay { get; set; } = new(); + + public HotkeyModel CurrentHotkey { get; private set; } = new(false, false, false, false, Key.None); + + + public void GetNewHotkey(object sender, RoutedEventArgs e) + { + OpenHotkeyDialog(); + } + + private async Task OpenHotkeyDialog() + { + if (!string.IsNullOrEmpty(Hotkey)) + { + HotKeyMapper.RemoveHotkey(Hotkey); + } + + var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, HotkeySettings, WindowTitle); + await dialog.ShowAsync(); + switch (dialog.ResultType) + { + case HotkeyControlDialog.EResultType.Cancel: + SetHotkey(Hotkey); + return; + case HotkeyControlDialog.EResultType.Save: + SetHotkey(dialog.ResultValue); + break; + case HotkeyControlDialog.EResultType.Delete: + Delete(); + break; + } + } + + + private void SetHotkey(HotkeyModel keyModel, bool triggerValidate = true) + { if (triggerValidate) { bool hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture); - CurrentHotkeyAvailable = hotkeyAvailable; - SetMessage(hotkeyAvailable); - OnHotkeyChanged(); - var token = hotkeyUpdateSource.Token; - await Task.Delay(500, token); - if (token.IsCancellationRequested) - return; - - if (CurrentHotkeyAvailable) + if (!hotkeyAvailable) { - CurrentHotkey = keyModel; - // To trigger LostFocus - FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null); - Keyboard.ClearFocus(); + return; } + + Hotkey = keyModel.ToString(); + SetKeysToDisplay(CurrentHotkey); + ChangeHotkey?.Execute(keyModel); } else { - CurrentHotkey = keyModel; + Hotkey = keyModel.ToString(); + ChangeHotkey?.Execute(keyModel); } } - - public Task SetHotkeyAsync(string keyStr, bool triggerValidate = true) + + public void Delete() { - return SetHotkeyAsync(new HotkeyModel(keyStr), triggerValidate); + if (!string.IsNullOrEmpty(Hotkey)) + HotKeyMapper.RemoveHotkey(Hotkey); + Hotkey = ""; + SetKeysToDisplay(new HotkeyModel(false, false, false, false, Key.None)); } - private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); - - public new bool IsFocused => tbHotkey.IsFocused; - - private void tbHotkey_LostFocus(object sender, RoutedEventArgs e) + private void SetKeysToDisplay(HotkeyModel? hotkey) { - tbHotkey.Text = CurrentHotkey?.ToString() ?? ""; - tbHotkey.Select(tbHotkey.Text.Length, 0); - } + KeysToDisplay.Clear(); - private void tbHotkey_GotFocus(object sender, RoutedEventArgs e) - { - ResetMessage(); - } - - private void ResetMessage() - { - tbMsg.Text = InternationalizationManager.Instance.GetTranslation("flowlauncherPressHotkey"); - tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B"); - } - - private void SetMessage(bool hotkeyAvailable) - { - if (!hotkeyAvailable) + if (hotkey == null || hotkey == default(HotkeyModel)) { - tbMsg.Foreground = new SolidColorBrush(Colors.Red); - tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable"); + KeysToDisplay.Add(EmptyHotkey); + return; } - else + + foreach (var key in hotkey.Value.EnumerateDisplayKeys()!) { - tbMsg.Foreground = new SolidColorBrush(Colors.Green); - tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success"); + KeysToDisplay.Add(key); } - tbMsg.Visibility = Visibility.Visible; + } + + public void SetHotkey(string? keyStr, bool triggerValidate = true) + { + SetHotkey(new HotkeyModel(keyStr), triggerValidate); } } } diff --git a/Flow.Launcher/HotkeyControlDialog.xaml b/Flow.Launcher/HotkeyControlDialog.xaml new file mode 100644 index 000000000..9a5872f4d --- /dev/null +++ b/Flow.Launcher/HotkeyControlDialog.xaml @@ -0,0 +1,169 @@ + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/Resources/Controls/HotkeyDisplay.xaml.cs b/Flow.Launcher/Resources/Controls/HotkeyDisplay.xaml.cs new file mode 100644 index 000000000..9b19ffd86 --- /dev/null +++ b/Flow.Launcher/Resources/Controls/HotkeyDisplay.xaml.cs @@ -0,0 +1,63 @@ +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; + +namespace Flow.Launcher.Resources.Controls +{ + public partial class HotkeyDisplay : UserControl + { + public enum DisplayType + { + Default, + Small + } + + public HotkeyDisplay() + { + InitializeComponent(); + //List stringList =e.NewValue.Split('+').ToList(); + Values = new ObservableCollection(); + KeysControl.ItemsSource = Values; + } + + public string Keys + { + get { return (string)GetValue(KeysProperty); } + set { SetValue(KeysProperty, value); } + } + + public static readonly DependencyProperty KeysProperty = + DependencyProperty.Register(nameof(Keys), typeof(string), typeof(HotkeyDisplay), + new PropertyMetadata(string.Empty, keyChanged)); + + public DisplayType Type + { + get { return (DisplayType)GetValue(TypeProperty); } + set { SetValue(TypeProperty, value); } + } + + public static readonly DependencyProperty TypeProperty = + DependencyProperty.Register(nameof(Type), typeof(DisplayType), typeof(HotkeyDisplay), + new PropertyMetadata(DisplayType.Default)); + + private static void keyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var control = d as UserControl; + if (null == control) return; // This should not be possible + + var newValue = e.NewValue as string; + if (null == newValue) return; + + if (d is not HotkeyDisplay hotkeyDisplay) + return; + + hotkeyDisplay.Values.Clear(); + foreach (var key in newValue.Split('+')) + { + hotkeyDisplay.Values.Add(key); + } + } + + public ObservableCollection Values { get; set; } + } +} diff --git a/Flow.Launcher/Resources/Controls/HyperLink.xaml b/Flow.Launcher/Resources/Controls/HyperLink.xaml new file mode 100644 index 000000000..9ea550afd --- /dev/null +++ b/Flow.Launcher/Resources/Controls/HyperLink.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs b/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs new file mode 100644 index 000000000..855cccdbd --- /dev/null +++ b/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs @@ -0,0 +1,39 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Navigation; + +namespace Flow.Launcher.Resources.Controls; + +public partial class HyperLink : UserControl +{ + public static readonly DependencyProperty UriProperty = DependencyProperty.Register( + nameof(Uri), typeof(string), typeof(HyperLink), new PropertyMetadata(default(string)) + ); + + public string Uri + { + get => (string)GetValue(UriProperty); + set => SetValue(UriProperty, value); + } + + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + nameof(Text), typeof(string), typeof(HyperLink), new PropertyMetadata(default(string)) + ); + + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public HyperLink() + { + InitializeComponent(); + } + + private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + App.API.OpenUrl(e.Uri); + e.Handled = true; + } +} diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml new file mode 100644 index 000000000..ed3c29690 --- /dev/null +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml.cs b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml.cs new file mode 100644 index 000000000..dfa03a204 --- /dev/null +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml.cs @@ -0,0 +1,9 @@ +namespace Flow.Launcher.Resources.Controls; + +public partial class InstalledPluginDisplay +{ + public InstalledPluginDisplay() + { + InitializeComponent(); + } +} diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml new file mode 100644 index 000000000..83a771728 --- /dev/null +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml.cs b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml.cs new file mode 100644 index 000000000..f8d0afe61 --- /dev/null +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace Flow.Launcher.Resources.Controls; + +public partial class InstalledPluginDisplayBottomData : UserControl +{ + public InstalledPluginDisplayBottomData() + { + InitializeComponent(); + } +} diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplayKeyword.xaml b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayKeyword.xaml new file mode 100644 index 000000000..ff2f14c4b --- /dev/null +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayKeyword.xaml @@ -0,0 +1,47 @@ + + + + +  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs new file mode 100644 index 000000000..de6a4df5e --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs @@ -0,0 +1,29 @@ +using System; +using System.Windows.Navigation; +using Flow.Launcher.SettingPages.ViewModels; + +namespace Flow.Launcher.SettingPages.Views; + +public partial class SettingsPaneAbout +{ + private SettingsPaneAboutViewModel _viewModel = null!; + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (!IsInitialized) + { + if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: { } updater }) + throw new ArgumentException("Settings are required for SettingsPaneAbout."); + _viewModel = new SettingsPaneAboutViewModel(settings, updater); + DataContext = _viewModel; + InitializeComponent(); + } + base.OnNavigatedTo(e); + } + + private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + App.API.OpenUrl(e.Uri.AbsoluteUri); + e.Handled = true; + } +} diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml new file mode 100644 index 000000000..6b301e33a --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs new file mode 100644 index 000000000..dfb4a7eaf --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs @@ -0,0 +1,66 @@ +using System; +using System.ComponentModel; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Navigation; +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.SettingPages.ViewModels; +using Flow.Launcher.ViewModel; + +namespace Flow.Launcher.SettingPages.Views; + +public partial class SettingsPanePluginStore +{ + private SettingsPanePluginStoreViewModel _viewModel = null!; + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (!IsInitialized) + { + if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings }) + throw new ArgumentException($"Settings are required for {nameof(SettingsPanePluginStore)}."); + _viewModel = new SettingsPanePluginStoreViewModel(); + DataContext = _viewModel; + InitializeComponent(); + } + _viewModel.PropertyChanged += ViewModel_PropertyChanged; + base.OnNavigatedTo(e); + } + + private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(SettingsPanePluginStoreViewModel.FilterText)) + { + ((CollectionViewSource)FindResource("PluginStoreCollectionView")).View.Refresh(); + } + } + + protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) + { + _viewModel.PropertyChanged -= ViewModel_PropertyChanged; + base.OnNavigatingFrom(e); + } + + private void SettingsPanePlugins_OnKeyDown(object sender, KeyEventArgs e) + { + if (e.Key is not Key.F || Keyboard.Modifiers is not ModifierKeys.Control) return; + PluginStoreFilterTextbox.Focus(); + } + + private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + PluginManager.API.OpenUrl(e.Uri.AbsoluteUri); + e.Handled = true; + } + + private void PluginStoreCollectionView_OnFilter(object sender, FilterEventArgs e) + { + if (e.Item is not PluginStoreItemViewModel plugin) + { + e.Accepted = false; + return; + } + + e.Accepted = _viewModel.SatisfiesFilter(plugin); + } +} diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml new file mode 100644 index 000000000..37079a46f --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs new file mode 100644 index 000000000..d48505c3d --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs @@ -0,0 +1,30 @@ +using System; +using System.Windows.Input; +using System.Windows.Navigation; +using Flow.Launcher.SettingPages.ViewModels; + +namespace Flow.Launcher.SettingPages.Views; + +public partial class SettingsPanePlugins +{ + private SettingsPanePluginsViewModel _viewModel = null!; + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (!IsInitialized) + { + if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings }) + throw new ArgumentException("Settings are required for SettingsPaneHotkey."); + _viewModel = new SettingsPanePluginsViewModel(settings); + DataContext = _viewModel; + InitializeComponent(); + } + base.OnNavigatedTo(e); + } + + private void SettingsPanePlugins_OnKeyDown(object sender, KeyEventArgs e) + { + if (e.Key is not Key.F || Keyboard.Modifiers is not ModifierKeys.Control) return; + PluginFilterTextbox.Focus(); + } +} diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml new file mode 100644 index 000000000..768abbf97 --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + @@ -590,2685 +197,72 @@ Grid.Row="0" Width="50" Height="50" + RenderOptions.BitmapScalingMode="HighQuality" Source="images/app.png" /> + TextAlignment="Center" /> - - - - - - - - - - - -  - - - - + + - - - + + + + + - - - - - - - -  - - - + + + + + - - - - - - - -  - - - + + + + + - - - - - - - - + + + + + + + + + + - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - -  - - - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - -  - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - -  - - - - - - - - - - - - + diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs index 7301be130..957379ce4 100644 --- a/Flow.Launcher/SettingWindow.xaml.cs +++ b/Flow.Launcher/SettingWindow.xaml.cs @@ -1,538 +1,172 @@ -using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Core.Resource; -using Flow.Launcher.Helper; -using Flow.Launcher.Infrastructure; -using Flow.Launcher.Infrastructure.Hotkey; -using Flow.Launcher.Infrastructure.UserSettings; -using Flow.Launcher.Plugin; -using Flow.Launcher.ViewModel; -using ModernWpf; -using ModernWpf.Controls; -using System; -using System.ComponentModel; -using System.IO; +using System; using System.Windows; -using System.Windows.Data; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Interop; -using System.Windows.Navigation; -using Button = System.Windows.Controls.Button; -using Control = System.Windows.Controls.Control; -using KeyEventArgs = System.Windows.Input.KeyEventArgs; -using MessageBox = System.Windows.MessageBox; +using Flow.Launcher.Core; +using Flow.Launcher.Core.Configuration; +using Flow.Launcher.Helper; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; +using Flow.Launcher.SettingPages.Views; +using Flow.Launcher.ViewModel; +using ModernWpf.Controls; using TextBox = System.Windows.Controls.TextBox; -using ThemeManager = ModernWpf.ThemeManager; -namespace Flow.Launcher +namespace Flow.Launcher; + +public partial class SettingWindow { - public partial class SettingWindow + private readonly IPublicAPI _api; + private readonly Settings _settings; + private readonly SettingWindowViewModel _viewModel; + + public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel) { - public readonly IPublicAPI API; - private Settings settings; - private SettingWindowViewModel viewModel; + _settings = viewModel.Settings; + DataContext = viewModel; + _viewModel = viewModel; + _api = api; + InitializePosition(); + InitializeComponent(); + NavView.SelectedItem = NavView.MenuItems[0]; /* Set First Page */ + } - public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel) + private void OnLoaded(object sender, RoutedEventArgs e) + { + RefreshMaximizeRestoreButton(); + // Fix (workaround) for the window freezes after lock screen (Win+L) + // https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf + HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource; + HwndTarget hwndTarget = hwndSource.CompositionTarget; + hwndTarget.RenderMode = RenderMode.Default; + + InitializePosition(); + } + + private void OnClosed(object sender, EventArgs e) + { + _settings.SettingWindowState = WindowState; + _settings.SettingWindowTop = Top; + _settings.SettingWindowLeft = Left; + _viewModel.Save(); + _api.SavePluginSettings(); + } + + private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e) + { + Close(); + } + + private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */ + { + if (Keyboard.FocusedElement is not TextBox textBox) { - settings = viewModel.Settings; - DataContext = viewModel; - this.viewModel = viewModel; - API = api; - InitializePosition(); - InitializeComponent(); - + return; } + var tRequest = new TraversalRequest(FocusNavigationDirection.Next); + textBox.MoveFocus(tRequest); + } - #region General + /* Custom TitleBar */ - private void OnLoaded(object sender, RoutedEventArgs e) + private void OnMinimizeButtonClick(object sender, RoutedEventArgs e) + { + WindowState = WindowState.Minimized; + } + + private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e) + { + WindowState = WindowState switch { - RefreshMaximizeRestoreButton(); - // Fix (workaround) for the window freezes after lock screen (Win+L) - // https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf - HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource; - HwndTarget hwndTarget = hwndSource.CompositionTarget; - hwndTarget.RenderMode = RenderMode.SoftwareOnly; + WindowState.Maximized => WindowState.Normal, + _ => WindowState.Maximized + }; + } - pluginListView = (CollectionView)CollectionViewSource.GetDefaultView(Plugins.ItemsSource); - pluginListView.Filter = PluginListFilter; + private void OnCloseButtonClick(object sender, RoutedEventArgs e) + { + Close(); + } - pluginStoreView = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource); - pluginStoreView.Filter = PluginStoreFilter; - - viewModel.PropertyChanged += new PropertyChangedEventHandler(SettingsWindowViewModelChanged); - - InitializePosition(); - } - - private void SettingsWindowViewModelChanged(object sender, PropertyChangedEventArgs e) + private void RefreshMaximizeRestoreButton() + { + if (WindowState == WindowState.Maximized) { - if (e.PropertyName == nameof(viewModel.ExternalPlugins)) - { - pluginStoreView = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource); - pluginStoreView.Filter = PluginStoreFilter; - pluginStoreView.Refresh(); - } + MaximizeButton.Visibility = Visibility.Collapsed; + RestoreButton.Visibility = Visibility.Visible; } - - private void OnSelectPythonPathClick(object sender, RoutedEventArgs e) + else { - var selectedFile = viewModel.GetFileFromDialog( - InternationalizationManager.Instance.GetTranslation("selectPythonExecutable"), - "Python|pythonw.exe"); - - if (!string.IsNullOrEmpty(selectedFile)) - settings.PluginSettings.PythonExecutablePath = selectedFile; - } - - private void OnSelectNodePathClick(object sender, RoutedEventArgs e) - { - var selectedFile = viewModel.GetFileFromDialog( - InternationalizationManager.Instance.GetTranslation("selectNodeExecutable")); - - if (!string.IsNullOrEmpty(selectedFile)) - settings.PluginSettings.NodeExecutablePath = selectedFile; - } - - private void OnSelectFileManagerClick(object sender, RoutedEventArgs e) - { - SelectFileManagerWindow fileManagerChangeWindow = new SelectFileManagerWindow(settings); - fileManagerChangeWindow.ShowDialog(); - } - - private void OnSelectDefaultBrowserClick(object sender, RoutedEventArgs e) - { - var browserWindow = new SelectBrowserWindow(settings); - browserWindow.ShowDialog(); - } - - #endregion - - #region Hotkey - - private void OnHotkeyControlLoaded(object sender, RoutedEventArgs e) - { - _ = HotkeyControl.SetHotkeyAsync(viewModel.Settings.Hotkey, false); - } - - private void OnHotkeyControlFocused(object sender, RoutedEventArgs e) - { - HotKeyMapper.RemoveHotkey(settings.Hotkey); - } - - private void OnHotkeyControlFocusLost(object sender, RoutedEventArgs e) - { - if (HotkeyControl.CurrentHotkeyAvailable) - { - HotKeyMapper.SetHotkey(HotkeyControl.CurrentHotkey, HotKeyMapper.OnToggleHotkey); - HotKeyMapper.RemoveHotkey(settings.Hotkey); - settings.Hotkey = HotkeyControl.CurrentHotkey.ToString(); - } - else - { - HotKeyMapper.SetHotkey(new HotkeyModel(settings.Hotkey), HotKeyMapper.OnToggleHotkey); - } - } - - private void OnPreviewHotkeyControlLoaded(object sender, RoutedEventArgs e) - { - _ = PreviewHotkeyControl.SetHotkeyAsync(settings.PreviewHotkey, false); - } - - private void OnPreviewHotkeyControlFocusLost(object sender, RoutedEventArgs e) - { - if (PreviewHotkeyControl.CurrentHotkeyAvailable) - { - settings.PreviewHotkey = PreviewHotkeyControl.CurrentHotkey.ToString(); - } - } - - private void OnDeleteCustomHotkeyClick(object sender, RoutedEventArgs e) - { - var item = viewModel.SelectedCustomPluginHotkey; - if (item == null) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); - return; - } - - string deleteWarning = - string.Format(InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"), - item.Hotkey); - if ( - MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - settings.CustomPluginHotkeys.Remove(item); - HotKeyMapper.RemoveHotkey(item.Hotkey); - } - } - - private void OnEditCustomHotkeyClick(object sender, RoutedEventArgs e) - { - var item = viewModel.SelectedCustomPluginHotkey; - if (item != null) - { - CustomQueryHotkeySetting window = new CustomQueryHotkeySetting(this, settings); - window.UpdateItem(item); - window.ShowDialog(); - } - else - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); - } - } - - private void OnAddCustomHotkeyClick(object sender, RoutedEventArgs e) - { - new CustomQueryHotkeySetting(this, settings).ShowDialog(); - } - - #endregion - - #region Plugin - - private void OnPluginToggled(object sender, RoutedEventArgs e) - { - var id = viewModel.SelectedPlugin.PluginPair.Metadata.ID; - // used to sync the current status from the plugin manager into the setting to keep consistency after save - settings.PluginSettings.Plugins[id].Disabled = viewModel.SelectedPlugin.PluginPair.Metadata.Disabled; - } - - private void OnPluginPriorityClick(object sender, RoutedEventArgs e) - { - if (sender is Control { DataContext: PluginViewModel pluginViewModel }) - { - PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(pluginViewModel.PluginPair.Metadata.ID, pluginViewModel); - priorityChangeWindow.ShowDialog(); - } - } - - #endregion - - #region Proxy - - private void OnTestProxyClick(object sender, RoutedEventArgs e) - { // TODO: change to command - var msg = viewModel.TestProxy(); - MessageBox.Show(msg); // TODO: add message box service - } - - #endregion - - private void OnCheckUpdates(object sender, RoutedEventArgs e) - { - viewModel.UpdateApp(); // TODO: change to command - } - - private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) - { - API.OpenUrl(e.Uri.AbsoluteUri); - e.Handled = true; - } - - private void OnClosed(object sender, EventArgs e) - { - settings.SettingWindowState = WindowState; - settings.SettingWindowTop = Top; - settings.SettingWindowLeft = Left; - viewModel.Save(); - API.SavePluginSettings(); - } - - private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e) - { - Close(); - } - - private void OpenThemeFolder(object sender, RoutedEventArgs e) - { - PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes)); - } - - private void OpenSettingFolder(object sender, RoutedEventArgs e) - { - PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings)); - } - - private void OpenWelcomeWindow(object sender, RoutedEventArgs e) - { - var WelcomeWindow = new WelcomeWindow(settings); - WelcomeWindow.ShowDialog(); - } - private void OpenLogFolder(object sender, RoutedEventArgs e) - { - viewModel.OpenLogFolder(); - } - private void ClearLogFolder(object sender, RoutedEventArgs e) - { - var confirmResult = MessageBox.Show( - InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"), - InternationalizationManager.Instance.GetTranslation("clearlogfolder"), - MessageBoxButton.YesNo); - - if (confirmResult == MessageBoxResult.Yes) - { - viewModel.ClearLogFolder(); - } - } - - private void OnExternalPluginInstallClick(object sender, RoutedEventArgs e) - { - if (sender is not Button { DataContext: PluginStoreItemViewModel plugin } button) - { - return; - } - - if (storeClickedButton != null) - { - FlyoutService.GetFlyout(storeClickedButton).Hide(); - } - - viewModel.DisplayPluginQuery($"install {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); - } - - private void OnExternalPluginUninstallClick(object sender, MouseButtonEventArgs e) - { - if (e.ChangedButton == MouseButton.Left) - { - var name = viewModel.SelectedPlugin.PluginPair.Metadata.Name; - viewModel.DisplayPluginQuery($"uninstall {name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); - } - } - - private void OnExternalPluginUninstallClick(object sender, RoutedEventArgs e) - { - if (storeClickedButton != null) - { - FlyoutService.GetFlyout(storeClickedButton).Hide(); - } - - if (sender is Button { DataContext: PluginStoreItemViewModel plugin }) - viewModel.DisplayPluginQuery($"uninstall {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); - - } - - private void OnExternalPluginUpdateClick(object sender, RoutedEventArgs e) - { - if (storeClickedButton != null) - { - FlyoutService.GetFlyout(storeClickedButton).Hide(); - } - if (sender is Button { DataContext: PluginStoreItemViewModel plugin }) - viewModel.DisplayPluginQuery($"update {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); - - } - - private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */ - { - if (Keyboard.FocusedElement is not TextBox textBox) - { - return; - } - var tRequest = new TraversalRequest(FocusNavigationDirection.Next); - textBox.MoveFocus(tRequest); - } - - private void ColorSchemeSelectedIndexChanged(object sender, EventArgs e) - => ThemeManager.Current.ApplicationTheme = settings.ColorScheme switch - { - Constant.Light => ApplicationTheme.Light, - Constant.Dark => ApplicationTheme.Dark, - Constant.System => null, - _ => ThemeManager.Current.ApplicationTheme - }; - - /* Custom TitleBar */ - - private void OnMinimizeButtonClick(object sender, RoutedEventArgs e) - { - WindowState = WindowState.Minimized; - } - - private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e) - { - WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; - } - - private void OnCloseButtonClick(object sender, RoutedEventArgs e) - { - - Close(); - } - - private void RefreshMaximizeRestoreButton() - { - if (WindowState == WindowState.Maximized) - { - maximizeButton.Visibility = Visibility.Collapsed; - restoreButton.Visibility = Visibility.Visible; - } - else - { - maximizeButton.Visibility = Visibility.Visible; - restoreButton.Visibility = Visibility.Collapsed; - } - } - - private void Window_StateChanged(object sender, EventArgs e) - { - RefreshMaximizeRestoreButton(); - } - - #region Shortcut - - private void OnDeleteCustomShortCutClick(object sender, RoutedEventArgs e) - { - viewModel.DeleteSelectedCustomShortcut(); - } - - private void OnEditCustomShortCutClick(object sender, RoutedEventArgs e) - { - if (viewModel.EditSelectedCustomShortcut()) - { - customShortcutView.Items.Refresh(); - } - } - - private void OnAddCustomShortCutClick(object sender, RoutedEventArgs e) - { - viewModel.AddCustomShortcut(); - } - - #endregion - - private CollectionView pluginListView; - private CollectionView pluginStoreView; - - private bool PluginListFilter(object item) - { - if (string.IsNullOrEmpty(pluginFilterTxb.Text)) - return true; - if (item is PluginViewModel model) - { - return StringMatcher.FuzzySearch(pluginFilterTxb.Text, model.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet(); - } - return false; - } - - private bool PluginStoreFilter(object item) - { - if (string.IsNullOrEmpty(pluginStoreFilterTxb.Text)) - return true; - if (item is PluginStoreItemViewModel model) - { - return StringMatcher.FuzzySearch(pluginStoreFilterTxb.Text, model.Name).IsSearchPrecisionScoreMet() - || StringMatcher.FuzzySearch(pluginStoreFilterTxb.Text, model.Description).IsSearchPrecisionScoreMet(); - } - return false; - } - - private string lastPluginListSearch = ""; - private string lastPluginStoreSearch = ""; - - private void RefreshPluginListEventHandler(object sender, RoutedEventArgs e) - { - if (pluginFilterTxb.Text != lastPluginListSearch) - { - lastPluginListSearch = pluginFilterTxb.Text; - pluginListView.Refresh(); - } - } - - private void RefreshPluginStoreEventHandler(object sender, RoutedEventArgs e) - { - if (pluginStoreFilterTxb.Text != lastPluginStoreSearch) - { - lastPluginStoreSearch = pluginStoreFilterTxb.Text; - pluginStoreView.Refresh(); - } - } - - private void PluginFilterTxb_OnKeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.Enter) - RefreshPluginListEventHandler(sender, e); - } - - private void PluginStoreFilterTxb_OnKeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.Enter) - RefreshPluginStoreEventHandler(sender, e); - } - - private void OnPluginSettingKeydown(object sender, KeyEventArgs e) - { - if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && e.Key == Key.F) - pluginFilterTxb.Focus(); - } - - private void PluginStore_OnKeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.F && (Keyboard.Modifiers & ModifierKeys.Control) != 0) - { - pluginStoreFilterTxb.Focus(); - } - } - - public void InitializePosition() - { - if (settings.SettingWindowTop >= 0 && settings.SettingWindowLeft >= 0) - { - Top = settings.SettingWindowTop; - Left = settings.SettingWindowLeft; - } - else - { - Top = WindowTop(); - Left = WindowLeft(); - } - WindowState = settings.SettingWindowState; - } - - public double WindowLeft() - { - var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); - var left = (dip2.X - this.ActualWidth) / 2 + dip1.X; - return left; - } - - public double WindowTop() - { - var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); - var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20; - return top; - } - - private Button storeClickedButton; - - private void StoreListItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not Button button) - return; - - storeClickedButton = button; - - var flyout = FlyoutService.GetFlyout(button); - flyout.Closed += (_, _) => - { - storeClickedButton = null; - }; - - } - - private void PluginStore_GotFocus(object sender, RoutedEventArgs e) - { - Keyboard.Focus(pluginStoreFilterTxb); - } - - private void Plugin_GotFocus(object sender, RoutedEventArgs e) - { - Keyboard.Focus(pluginFilterTxb); + MaximizeButton.Visibility = Visibility.Visible; + RestoreButton.Visibility = Visibility.Collapsed; } } + + private void Window_StateChanged(object sender, EventArgs e) + { + RefreshMaximizeRestoreButton(); + } + + public void InitializePosition() + { + if (_settings.SettingWindowTop == null) + { + Top = WindowTop(); + Left = WindowLeft(); + } + else + { + Top = _settings.SettingWindowTop; + Left = _settings.SettingWindowLeft; + } + WindowState = _settings.SettingWindowState; + } + + private double WindowLeft() + { + var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); + var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); + var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); + var left = (dip2.X - this.ActualWidth) / 2 + dip1.X; + return left; + } + + private double WindowTop() + { + var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); + var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); + var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); + var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20; + return top; + } + + private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + var paneData = new PaneData(_settings, _viewModel.Updater, _viewModel.Portable); + if (args.IsSettingsSelected) + { + ContentFrame.Navigate(typeof(SettingsPaneGeneral), paneData); + } + else + { + var selectedItem = (NavigationViewItem)args.SelectedItem; + if (selectedItem == null) return; + + var pageType = selectedItem.Name switch + { + nameof(General) => typeof(SettingsPaneGeneral), + nameof(Plugins) => typeof(SettingsPanePlugins), + nameof(PluginStore) => typeof(SettingsPanePluginStore), + nameof(Theme) => typeof(SettingsPaneTheme), + nameof(Hotkey) => typeof(SettingsPaneHotkey), + nameof(Proxy) => typeof(SettingsPaneProxy), + nameof(About) => typeof(SettingsPaneAbout), + _ => typeof(SettingsPaneGeneral) + }; + ContentFrame.Navigate(pageType, paneData); + } + } + + public record PaneData(Settings Settings, Updater Updater, IPortable Portable); } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index a42307e87..7f3ec1ff3 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -19,8 +19,6 @@ using Microsoft.VisualStudio.Threading; using System.Text; using System.Threading.Channels; using ISavable = Flow.Launcher.Plugin.ISavable; -using System.IO; -using System.Collections.Specialized; using CommunityToolkit.Mvvm.Input; using System.Globalization; using System.Windows.Input; @@ -34,12 +32,15 @@ namespace Flow.Launcher.ViewModel private bool _isQueryRunning; private Query _lastQuery; + private Result lastContextMenuResult = new Result(); + private List lastContextMenuResults = new List(); private string _queryTextBeforeLeaveResults; private readonly FlowLauncherJsonStorage _historyItemsStorage; private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; private readonly FlowLauncherJsonStorage _topMostRecordStorage; private readonly History _history; + private int lastHistoryIndex = 1; private readonly UserSelectedRecord _userSelectedRecord; private readonly TopMostRecord _topMostRecord; @@ -69,6 +70,21 @@ namespace Flow.Launcher.ViewModel case nameof(Settings.WindowSize): OnPropertyChanged(nameof(MainWindowWidth)); break; + case nameof(Settings.WindowHeightSize): + OnPropertyChanged(nameof(MainWindowHeight)); + break; + case nameof(Settings.QueryBoxFontSize): + OnPropertyChanged(nameof(QueryBoxFontSize)); + break; + case nameof(Settings.ItemHeightSize): + OnPropertyChanged(nameof(ItemHeightSize)); + break; + case nameof(Settings.ResultItemFontSize): + OnPropertyChanged(nameof(ResultItemFontSize)); + break; + case nameof(Settings.ResultSubItemFontSize): + OnPropertyChanged(nameof(ResultSubItemFontSize)); + break; case nameof(Settings.AlwaysStartEn): OnPropertyChanged(nameof(StartWithEnglishMode)); break; @@ -78,6 +94,42 @@ namespace Flow.Launcher.ViewModel case nameof(Settings.PreviewHotkey): OnPropertyChanged(nameof(PreviewHotkey)); break; + case nameof(Settings.AutoCompleteHotkey): + OnPropertyChanged(nameof(AutoCompleteHotkey)); + break; + case nameof(Settings.CycleHistoryUpHotkey): + OnPropertyChanged(nameof(CycleHistoryUpHotkey)); + break; + case nameof(Settings.CycleHistoryDownHotkey): + OnPropertyChanged(nameof(CycleHistoryDownHotkey)); + break; + case nameof(Settings.AutoCompleteHotkey2): + OnPropertyChanged(nameof(AutoCompleteHotkey2)); + break; + case nameof(Settings.SelectNextItemHotkey): + OnPropertyChanged(nameof(SelectNextItemHotkey)); + break; + case nameof(Settings.SelectNextItemHotkey2): + OnPropertyChanged(nameof(SelectNextItemHotkey2)); + break; + case nameof(Settings.SelectPrevItemHotkey): + OnPropertyChanged(nameof(SelectPrevItemHotkey)); + break; + case nameof(Settings.SelectPrevItemHotkey2): + OnPropertyChanged(nameof(SelectPrevItemHotkey2)); + break; + case nameof(Settings.SelectNextPageHotkey): + OnPropertyChanged(nameof(SelectNextPageHotkey)); + break; + case nameof(Settings.SelectPrevPageHotkey): + OnPropertyChanged(nameof(SelectPrevPageHotkey)); + break; + case nameof(Settings.OpenContextMenuHotkey): + OnPropertyChanged(nameof(OpenContextMenuHotkey)); + break; + case nameof(Settings.SettingWindowHotkey): + OnPropertyChanged(nameof(SettingWindowHotkey)); + break; } }; @@ -174,7 +226,8 @@ namespace Flow.Launcher.ViewModel var token = e.Token == default ? _updateToken : e.Token; PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, token))) + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(e.Results, pair.Metadata, e.Query, + token))) { Log.Error("MainViewModel", "Unable to add item to Result Update Queue"); } @@ -188,7 +241,8 @@ namespace Flow.Launcher.ViewModel Hide(); await PluginManager.ReloadDataAsync().ConfigureAwait(false); - Notification.Show(InternationalizationManager.Instance.GetTranslation("success"), InternationalizationManager.Instance.GetTranslation("completedSuccessfully")); + Notification.Show(InternationalizationManager.Instance.GetTranslation("success"), + InternationalizationManager.Instance.GetTranslation("completedSuccessfully")); } [RelayCommand] @@ -206,7 +260,7 @@ namespace Flow.Launcher.ViewModel } [RelayCommand] - private void ReQuery() + public void ReQuery() { if (SelectedIsFromQueryResults()) { @@ -214,6 +268,40 @@ namespace Flow.Launcher.ViewModel } } + public void ReQuery(bool reselect) + { + if (SelectedIsFromQueryResults()) + { + QueryResults(isReQuery: true, reSelect: reselect); + } + } + + [RelayCommand] + public void ReverseHistory() + { + if (_history.Items.Count > 0) + { + ChangeQueryText(_history.Items[_history.Items.Count - lastHistoryIndex].Query.ToString()); + if (lastHistoryIndex < _history.Items.Count) + { + lastHistoryIndex++; + } + } + } + + [RelayCommand] + public void ForwardHistory() + { + if (_history.Items.Count > 0) + { + ChangeQueryText(_history.Items[_history.Items.Count - lastHistoryIndex].Query.ToString()); + if (lastHistoryIndex > 1) + { + lastHistoryIndex--; + } + } + } + [RelayCommand] private void LoadContextMenu() { @@ -257,6 +345,13 @@ namespace Flow.Launcher.ViewModel } else if (!string.IsNullOrEmpty(SelectedResults.SelectedItem?.QuerySuggestionText)) { + var defaultSuggestion = SelectedResults.SelectedItem.QuerySuggestionText; + // check if result.actionkeywordassigned is empty + if (!string.IsNullOrEmpty(result.ActionKeywordAssigned)) + { + autoCompleteText = $"{result.ActionKeywordAssigned} {defaultSuggestion}"; + } + autoCompleteText = SelectedResults.SelectedItem.QuerySuggestionText; } @@ -278,14 +373,17 @@ namespace Flow.Launcher.ViewModel { results.SelectedIndex = int.Parse(index); } + var result = results.SelectedItem?.Result; if (result == null) { return; } + var hideWindow = await result.ExecuteAsync(new ActionContext { - SpecialKeyState = GlobalHotkey.CheckModifiers() + // not null means pressing modifier key + number, should ignore the modifier key + SpecialKeyState = index is not null ? SpecialKeyState.Default : GlobalHotkey.CheckModifiers() }) .ConfigureAwait(false); @@ -294,6 +392,7 @@ namespace Flow.Launcher.ViewModel { _userSelectedRecord.Add(result); _history.Add(result.OriginQuery.RawQuery); + lastHistoryIndex = 1; } if (hideWindow) @@ -324,6 +423,13 @@ namespace Flow.Launcher.ViewModel SelectedResults.SelectFirstResult(); } + [RelayCommand] + private void SelectLastResult() + { + SelectedResults.SelectLastResult(); + } + + [RelayCommand] private void SelectPrevPage() { @@ -339,7 +445,18 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void SelectPrevItem() { - SelectedResults.SelectPrevResult(); + if (_history.Items.Count > 0 + && QueryText == string.Empty + && SelectedIsFromQueryResults()) + { + lastHistoryIndex = 1; + ReverseHistory(); + } + else + { + SelectedResults.SelectPrevResult(); + } + } [RelayCommand] @@ -367,6 +484,17 @@ namespace Flow.Launcher.ViewModel GameModeStatus = !GameModeStatus; } + [RelayCommand] + public void CopyAlternative() + { + var result = Results.SelectedItem?.Result?.CopyText; + + if (result != null) + { + App.API.CopyToClipboard(result, directCopy: false); + } + } + #endregion #region ViewModel Properties @@ -374,7 +502,6 @@ namespace Flow.Launcher.ViewModel public Settings Settings { get; } public string ClockText { get; private set; } public string DateText { get; private set; } - public CultureInfo Culture => CultureInfo.DefaultThreadCurrentCulture; private async Task RegisterClockAndDateUpdateAsync() { @@ -383,9 +510,9 @@ namespace Flow.Launcher.ViewModel while (await timer.WaitForNextTickAsync().ConfigureAwait(false)) { if (Settings.UseClock) - ClockText = DateTime.Now.ToString(Settings.TimeFormat, Culture); + ClockText = DateTime.Now.ToString(Settings.TimeFormat, CultureInfo.CurrentCulture); if (Settings.UseDate) - DateText = DateTime.Now.ToString(Settings.DateFormat, Culture); + DateText = DateTime.Now.ToString(Settings.DateFormat, CultureInfo.CurrentCulture); } } @@ -398,6 +525,7 @@ namespace Flow.Launcher.ViewModel public bool GameModeStatus { get; set; } = false; private string _queryText; + public string QueryText { get => _queryText; @@ -412,16 +540,9 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void IncreaseWidth() { - if (MainWindowWidth + 100 > 1920 || Settings.WindowSize == 1920) - { - Settings.WindowSize = 1920; - } - else - { - Settings.WindowSize += 100; - Settings.WindowLeft -= 50; - } - OnPropertyChanged(); + Settings.WindowSize += 100; + Settings.WindowLeft -= 50; + OnPropertyChanged(nameof(MainWindowWidth)); } [RelayCommand] @@ -436,7 +557,8 @@ namespace Flow.Launcher.ViewModel Settings.WindowLeft += 50; Settings.WindowSize -= 100; } - OnPropertyChanged(); + + OnPropertyChanged(nameof(MainWindowWidth)); } [RelayCommand] @@ -469,18 +591,17 @@ namespace Flow.Launcher.ViewModel { if (QueryText != queryText) { - // re-query is done in QueryText's setter method QueryText = queryText; // set to false so the subsequent set true triggers // PropertyChanged and MoveQueryTextToEnd is called QueryTextCursorMovedToEnd = false; - } else if (isReQuery) { Query(isReQuery: true); } + QueryTextCursorMovedToEnd = true; }); } @@ -543,32 +664,77 @@ namespace Flow.Launcher.ViewModel public double MainWindowWidth { get => Settings.WindowSize; - set => Settings.WindowSize = value; + set + { + if (!MainWindowVisibilityStatus) return; + Settings.WindowSize = value; + } + } + + public double MainWindowHeight + { + get => Settings.WindowHeightSize; + set => Settings.WindowHeightSize = value; + } + + public double QueryBoxFontSize + { + get => Settings.QueryBoxFontSize; + set => Settings.QueryBoxFontSize = value; + } + + public double ItemHeightSize + { + get => Settings.ItemHeightSize; + set => Settings.ItemHeightSize = value; + } + + public double ResultItemFontSize + { + get => Settings.ResultItemFontSize; + set => Settings.ResultItemFontSize = value; + } + + public double ResultSubItemFontSize + { + get => Settings.ResultSubItemFontSize; + set => Settings.ResultSubItemFontSize = value; } public string PluginIconPath { get; set; } = null; public string OpenResultCommandModifiers => Settings.OpenResultModifiers; - public string PreviewHotkey - { - get + public string VerifyOrSetDefaultHotkey(string hotkey, string defaultHotkey) + { + try { - // TODO try to patch issue #1755 - // Added in v1.14.0, remove after v1.16.0. - try - { - var converter = new KeyGestureConverter(); - var key = (KeyGesture)converter.ConvertFromString(Settings.PreviewHotkey); - } - catch (Exception e) when (e is NotSupportedException || e is InvalidEnumArgumentException) - { - Settings.PreviewHotkey = "F1"; - } - return Settings.PreviewHotkey; + var converter = new KeyGestureConverter(); + var key = (KeyGesture)converter.ConvertFromString(hotkey); } + catch (Exception e) when (e is NotSupportedException || e is InvalidEnumArgumentException) + { + return defaultHotkey; + } + + return hotkey; } + public string PreviewHotkey => VerifyOrSetDefaultHotkey(Settings.PreviewHotkey, "F1"); + public string AutoCompleteHotkey => VerifyOrSetDefaultHotkey(Settings.AutoCompleteHotkey, "Ctrl+Tab"); + public string AutoCompleteHotkey2 => VerifyOrSetDefaultHotkey(Settings.AutoCompleteHotkey2, ""); + public string SelectNextItemHotkey => VerifyOrSetDefaultHotkey(Settings.SelectNextItemHotkey, "Tab"); + public string SelectNextItemHotkey2 => VerifyOrSetDefaultHotkey(Settings.SelectNextItemHotkey2, ""); + public string SelectPrevItemHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevItemHotkey, "Shift+Tab"); + public string SelectPrevItemHotkey2 => VerifyOrSetDefaultHotkey(Settings.SelectPrevItemHotkey2, ""); + public string SelectNextPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectNextPageHotkey, ""); + public string SelectPrevPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevPageHotkey, ""); + public string OpenContextMenuHotkey => VerifyOrSetDefaultHotkey(Settings.OpenContextMenuHotkey, "Ctrl+O"); + public string SettingWindowHotkey => VerifyOrSetDefaultHotkey(Settings.SettingWindowHotkey, "Ctrl+I"); + public string CycleHistoryUpHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryUpHotkey, "Alt+Up"); + public string CycleHistoryDownHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryDownHotkey, "Alt+Down"); + + public string Image => Constant.QueryTextBoxIconImagePath; public bool StartWithEnglishMode => Settings.AlwaysStartEn; @@ -766,13 +932,24 @@ namespace Flow.Launcher.ViewModel if (selected != null) // SelectedItem returns null if selection is empty. { - var results = PluginManager.GetContextMenusForPlugin(selected); - results.Add(ContextMenuTopMost(selected)); - results.Add(ContextMenuPluginInfo(selected.PluginID)); + List results; + if (selected == lastContextMenuResult) + { + results = lastContextMenuResults; + } + else + { + results = PluginManager.GetContextMenusForPlugin(selected); + lastContextMenuResults = results; + lastContextMenuResult = selected; + results.Add(ContextMenuTopMost(selected)); + results.Add(ContextMenuPluginInfo(selected.PluginID)); + } + if (!string.IsNullOrEmpty(query)) { - var filtered = results.Where + var filtered = results.Select(x => x.Clone()).Where ( r => { @@ -786,7 +963,6 @@ namespace Flow.Launcher.ViewModel r.Score = match.Score; return true; - }).ToList(); ContextMenu.AddResults(filtered, id); } @@ -813,10 +989,7 @@ namespace Flow.Launcher.ViewModel Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), IcoPath = "Images\\history.png", - OriginQuery = new Query - { - RawQuery = h.Query - }, + OriginQuery = new Query { RawQuery = h.Query }, Action = _ => { SelectedResults = Results; @@ -844,7 +1017,7 @@ namespace Flow.Launcher.ViewModel private readonly IReadOnlyList _emptyResult = new List(); - private async void QueryResults(bool isReQuery = false) + private async void QueryResults(bool isReQuery = false, bool reSelect = true) { _updateSource?.Cancel(); @@ -919,7 +1092,7 @@ namespace Flow.Launcher.ViewModel var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch { - false => QueryTask(plugin), + false => QueryTask(plugin, reSelect), true => Task.CompletedTask }).ToArray(); @@ -947,26 +1120,29 @@ namespace Flow.Launcher.ViewModel } // Local function - async Task QueryTask(PluginPair plugin) + async Task QueryTask(PluginPair plugin, bool reSelect = true) { // Since it is wrapped within a ThreadPool Thread, the synchronous context is null // Task.Yield will force it to run in ThreadPool await Task.Yield(); - IReadOnlyList results = await PluginManager.QueryForPluginAsync(plugin, query, currentCancellationToken); + IReadOnlyList results = + await PluginManager.QueryForPluginAsync(plugin, query, currentCancellationToken); currentCancellationToken.ThrowIfCancellationRequested(); results ??= _emptyResult; - if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, currentCancellationToken))) + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, + currentCancellationToken, reSelect))) { Log.Error("MainViewModel", "Unable to add item to Result Update Queue"); } } } - private Query ConstructQuery(string queryText, IEnumerable customShortcuts, IEnumerable builtInShortcuts) + private Query ConstructQuery(string queryText, IEnumerable customShortcuts, + IEnumerable builtInShortcuts) { if (string.IsNullOrWhiteSpace(queryText)) { @@ -976,7 +1152,8 @@ namespace Flow.Launcher.ViewModel StringBuilder queryBuilder = new(queryText); StringBuilder queryBuilderTmp = new(queryText); - foreach (var shortcut in customShortcuts) + // Sorting order is important here, the reason is for matching longest shortcut by default + foreach (var shortcut in customShortcuts.OrderByDescending(x => x.Key.Length)) { if (queryBuilder.Equals(shortcut.Key)) { @@ -1003,7 +1180,9 @@ namespace Flow.Launcher.ViewModel } catch (Exception e) { - Log.Exception($"{nameof(MainViewModel)}.{nameof(ConstructQuery)}|Error when expanding shortcut {shortcut.Key}", e); + Log.Exception( + $"{nameof(MainViewModel)}.{nameof(ConstructQuery)}|Error when expanding shortcut {shortcut.Key}", + e); } } }); @@ -1139,8 +1318,11 @@ namespace Flow.Launcher.ViewModel public async void Hide() { + lastHistoryIndex = 1; // Trick for no delay MainWindowOpacity = 0; + lastContextMenuResult = new Result(); + lastContextMenuResults = new List(); if (ExternalPreviewVisible) CloseExternalPreview(); @@ -1149,6 +1331,7 @@ namespace Flow.Launcher.ViewModel { SelectedResults = Results; } + switch (Settings.LastQueryMode) { case LastQueryMode.Empty: @@ -1196,7 +1379,7 @@ namespace Flow.Launcher.ViewModel /// /// To avoid deadlock, this method should not called from main thread /// - public void UpdateResultView(IEnumerable resultsForUpdates) + public void UpdateResultView(ICollection resultsForUpdates) { if (!resultsForUpdates.Any()) return; @@ -1235,7 +1418,10 @@ namespace Flow.Launcher.ViewModel } } - Results.AddResults(resultsForUpdates, token); + // it should be the same for all results + bool reSelect = resultsForUpdates.First().ReSelectFirstResult; + + Results.AddResults(resultsForUpdates, token, reSelect); } #endregion diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs index bc98efabc..513d0443f 100644 --- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs @@ -1,12 +1,15 @@ using System; +using System.Linq; +using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Plugin; namespace Flow.Launcher.ViewModel { - public class PluginStoreItemViewModel : BaseModel + public partial class PluginStoreItemViewModel : BaseModel { + private PluginPair PluginManagerData => PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"); public PluginStoreItemViewModel(UserPlugin plugin) { _plugin = plugin; @@ -26,19 +29,13 @@ namespace Flow.Launcher.ViewModel public string IcoPath => _plugin.IcoPath; public bool LabelInstalled => PluginManager.GetPluginForId(_plugin.ID) != null; - public bool LabelUpdate => LabelInstalled && VersionConvertor(_plugin.Version) > VersionConvertor(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version); + public bool LabelUpdate => LabelInstalled && new Version(_plugin.Version) > new Version(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version); internal const string None = "None"; internal const string RecentlyUpdated = "RecentlyUpdated"; internal const string NewRelease = "NewRelease"; internal const string Installed = "Installed"; - public Version VersionConvertor(string version) - { - Version ResultVersion = new Version(version); - return ResultVersion; - } - public string Category { get @@ -60,5 +57,13 @@ namespace Flow.Launcher.ViewModel return category; } } + + [RelayCommand] + private void ShowCommandQuery(string action) + { + var actionKeyword = PluginManagerData.Metadata.ActionKeywords.Any() ? PluginManagerData.Metadata.ActionKeywords[0] + " " : String.Empty; + App.API.ChangeQuery($"{actionKeyword}{action} {_plugin.Name}"); + App.API.ShowMainWindow(); + } } } diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index d2919507d..4ce8bd470 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -1,4 +1,5 @@ -using System.Windows; +using System.Linq; +using System.Windows; using System.Windows.Media; using Flow.Launcher.Plugin; using Flow.Launcher.Infrastructure.Image; @@ -6,6 +7,7 @@ using Flow.Launcher.Core.Plugin; using System.Windows.Controls; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Resource; +using Flow.Launcher.Resources.Controls; namespace Flow.Launcher.ViewModel { @@ -26,6 +28,21 @@ namespace Flow.Launcher.ViewModel } } + private string PluginManagerActionKeyword + { + get + { + var keyword = PluginManager + .GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7") + .Metadata.ActionKeywords.FirstOrDefault(); + return keyword switch + { + null or "*" => string.Empty, + _ => keyword + }; + } + } + private async void LoadIconAsync() { @@ -46,7 +63,11 @@ namespace Flow.Launcher.ViewModel public bool PluginState { get => !PluginPair.Metadata.Disabled; - set => PluginPair.Metadata.Disabled = !value; + set + { + PluginPair.Metadata.Disabled = !value; + PluginSettingsObject.Disabled = !value; + } } public bool IsExpanded { @@ -62,11 +83,19 @@ namespace Flow.Launcher.ViewModel private Control _settingControl; private bool _isExpanded; + + private Control _bottomPart1; + public Control BottomPart1 => IsExpanded ? _bottomPart1 ??= new InstalledPluginDisplayKeyword() : null; + + private Control _bottomPart2; + public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null; + + public bool HasSettingControl => PluginPair.Plugin is ISettingProvider; public Control SettingControl => IsExpanded ? _settingControl ??= PluginPair.Plugin is not ISettingProvider settingProvider - ? new Control() + ? null : settingProvider.CreateSettingPanel() : null; private ImageSource _image = ImageLoader.MissingImage; @@ -78,6 +107,7 @@ namespace Flow.Launcher.ViewModel public string InitAndQueryTime => InternationalizationManager.Instance.GetTranslation("plugin_init_time") + " " + PluginPair.Metadata.InitTime + "ms, " + InternationalizationManager.Instance.GetTranslation("plugin_query_time") + " " + PluginPair.Metadata.AvgQueryTime + "ms"; public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords); public int Priority => PluginPair.Metadata.Priority; + public Infrastructure.UserSettings.Plugin PluginSettingsObject { get; set; } public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword) { @@ -88,13 +118,14 @@ namespace Flow.Launcher.ViewModel public void ChangePriority(int newPriority) { PluginPair.Metadata.Priority = newPriority; + PluginSettingsObject.Priority = newPriority; OnPropertyChanged(nameof(Priority)); } [RelayCommand] private void EditPluginPriority() { - PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair.Metadata.ID, this); + PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair. Metadata.ID, this); priorityChangeWindow.ShowDialog(); } @@ -106,6 +137,19 @@ namespace Flow.Launcher.ViewModel PluginManager.API.OpenDirectory(directory); } + [RelayCommand] + private void OpenSourceCodeLink() + { + PluginManager.API.OpenUrl(PluginPair.Metadata.Website); + } + + [RelayCommand] + private void OpenDeletePluginWindow() + { + PluginManager.API.ChangeQuery($"{PluginManagerActionKeyword} uninstall {PluginPair.Metadata.Name}".Trim(), true); + PluginManager.API.ShowMainWindow(); + } + public static bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword); [RelayCommand] diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 3f204c16c..5130e7eba 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -29,7 +29,7 @@ namespace Flow.Launcher.ViewModel if (Result.Glyph is { FontFamily: not null } glyph) { - // Checks if it's a system installed font, which does not require path to be provided. + // Checks if it's a system installed font, which does not require path to be provided. if (glyph.FontFamily.EndsWith(".ttf") || glyph.FontFamily.EndsWith(".otf")) { string fontFamilyPath = glyph.FontFamily; @@ -64,7 +64,7 @@ namespace Flow.Launcher.ViewModel } - private Settings Settings { get; } + public Settings Settings { get; } public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Collapsed; diff --git a/Flow.Launcher/ViewModel/ResultsForUpdate.cs b/Flow.Launcher/ViewModel/ResultsForUpdate.cs index 4cb5b1a95..bc0be0de8 100644 --- a/Flow.Launcher/ViewModel/ResultsForUpdate.cs +++ b/Flow.Launcher/ViewModel/ResultsForUpdate.cs @@ -4,23 +4,13 @@ using System.Threading; namespace Flow.Launcher.ViewModel { - public struct ResultsForUpdate + public record struct ResultsForUpdate( + IReadOnlyList Results, + PluginMetadata Metadata, + Query Query, + CancellationToken Token, + bool ReSelectFirstResult = true) { - public IReadOnlyList Results { get; } - - public PluginMetadata Metadata { get; } - public string ID { get; } - - public Query Query { get; } - public CancellationToken Token { get; } - - public ResultsForUpdate(IReadOnlyList results, PluginMetadata metadata, Query query, CancellationToken token) - { - Results = results; - Metadata = metadata; - Query = query; - Token = token; - ID = metadata.ID; - } + public string ID { get; } = Metadata.ID; } } diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index bb07ce085..68e59009f 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -32,9 +32,15 @@ namespace Flow.Launcher.ViewModel _settings = settings; _settings.PropertyChanged += (s, e) => { - if (e.PropertyName == nameof(_settings.MaxResultsToShow)) + switch (e.PropertyName) { - OnPropertyChanged(nameof(MaxHeight)); + case nameof(_settings.MaxResultsToShow): + OnPropertyChanged(nameof(MaxHeight)); + break; + case nameof(_settings.ItemHeightSize): + OnPropertyChanged(nameof(ItemHeightSize)); + OnPropertyChanged(nameof(MaxHeight)); + break; } }; } @@ -43,14 +49,19 @@ namespace Flow.Launcher.ViewModel #region Properties - public double MaxHeight => MaxResults * (double)Application.Current.FindResource("ResultItemHeight")!; + public double MaxHeight => MaxResults * _settings.ItemHeightSize; + public double ItemHeightSize + { + get => _settings.ItemHeightSize; + set => _settings.ItemHeightSize = value; + } public int SelectedIndex { get; set; } public ResultViewModel SelectedItem { get; set; } public Thickness Margin { get; set; } public Visibility Visibility { get; set; } = Visibility.Collapsed; - + public ICommand RightClickResultCommand { get; init; } public ICommand LeftClickResultCommand { get; init; } @@ -117,6 +128,11 @@ namespace Flow.Launcher.ViewModel SelectedIndex = NewIndex(0); } + public void SelectLastResult() + { + SelectedIndex = NewIndex(Results.Count - 1); + } + public void Clear() { lock (_collectionLock) @@ -147,23 +163,23 @@ namespace Flow.Launcher.ViewModel /// /// To avoid deadlock, this method should not called from main thread /// - public void AddResults(IEnumerable resultsForUpdates, CancellationToken token) + public void AddResults(IEnumerable resultsForUpdates, CancellationToken token, bool reselect = true) { var newResults = NewResults(resultsForUpdates); if (token.IsCancellationRequested) return; - UpdateResults(newResults, token); + UpdateResults(newResults, token, reselect); } - private void UpdateResults(List newResults, CancellationToken token = default) + private void UpdateResults(List newResults, CancellationToken token = default, bool reselect = true) { lock (_collectionLock) { // update UI in one run, so it can avoid UI flickering Results.Update(newResults, token); - if (Results.Any()) + if (reselect && Results.Any()) SelectedItem = Results[0]; } diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 231e65539..481802045 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -1,988 +1,66 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using Flow.Launcher.Core; +using Flow.Launcher.Core; using Flow.Launcher.Core.Configuration; -using Flow.Launcher.Core.ExternalPlugins; -using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Core.Resource; -using Flow.Launcher.Helper; -using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using Flow.Launcher.Plugin.SharedModels; -using System.Collections.ObjectModel; -using CommunityToolkit.Mvvm.Input; -using System.Globalization; -namespace Flow.Launcher.ViewModel +namespace Flow.Launcher.ViewModel; + +public class SettingWindowViewModel : BaseModel { - public partial class SettingWindowViewModel : BaseModel + private readonly FlowLauncherJsonStorage _storage; + + public Updater Updater { get; } + + public IPortable Portable { get; } + + public Settings Settings { get; } + + public SettingWindowViewModel(Updater updater, IPortable portable) { - private readonly Updater _updater; - private readonly IPortable _portable; - private readonly FlowLauncherJsonStorage _storage; - - public SettingWindowViewModel(Updater updater, IPortable portable) - { - _updater = updater; - _portable = portable; - _storage = new FlowLauncherJsonStorage(); - Settings = _storage.Load(); - Settings.PropertyChanged += (s, e) => - { - switch (e.PropertyName) - { - case nameof(Settings.ActivateTimes): - OnPropertyChanged(nameof(ActivatedTimes)); - break; - case nameof(Settings.WindowSize): - OnPropertyChanged(nameof(WindowWidthSize)); - break; - case nameof(Settings.UseDate): - case nameof(Settings.DateFormat): - OnPropertyChanged(nameof(DateText)); - break; - case nameof(Settings.UseClock): - case nameof(Settings.TimeFormat): - OnPropertyChanged(nameof(ClockText)); - break; - case nameof(Settings.Language): - OnPropertyChanged(nameof(ClockText)); - OnPropertyChanged(nameof(DateText)); - OnPropertyChanged(nameof(AlwaysPreviewToolTip)); - break; - case nameof(Settings.PreviewHotkey): - OnPropertyChanged(nameof(AlwaysPreviewToolTip)); - break; - } - }; - - } - - public Settings Settings { get; set; } - - public async void UpdateApp() - { - await _updater.UpdateAppAsync(App.API, false); - } - - public bool AutoUpdates - { - get => Settings.AutoUpdates; - set - { - Settings.AutoUpdates = value; - - if (value) - { - UpdateApp(); - } - } - } - - public CultureInfo Culture => CultureInfo.DefaultThreadCurrentCulture; - - public bool StartFlowLauncherOnSystemStartup - { - get => Settings.StartFlowLauncherOnSystemStartup; - set - { - Settings.StartFlowLauncherOnSystemStartup = value; - - try - { - if (value) - AutoStartup.Enable(); - else - AutoStartup.Disable(); - } - catch (Exception e) - { - Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), e.Message); - } - } - } - - // This is only required to set at startup. When portable mode enabled/disabled a restart is always required - private bool _portableMode = DataLocation.PortableDataLocationInUse(); - public bool PortableMode - { - get => _portableMode; - set - { - if (!_portable.CanUpdatePortability()) - return; - - if (DataLocation.PortableDataLocationInUse()) - { - _portable.DisablePortableMode(); - } - else - { - _portable.EnablePortableMode(); - } - } - } - - /// - /// Save Flow settings. Plugins settings are not included. - /// - public void Save() - { - foreach (var vm in PluginViewModels) - { - var id = vm.PluginPair.Metadata.ID; - - Settings.PluginSettings.Plugins[id].Disabled = vm.PluginPair.Metadata.Disabled; - Settings.PluginSettings.Plugins[id].Priority = vm.Priority; - } - - _storage.Save(); - } - - public string GetFileFromDialog(string title, string filter = "") - { - var dlg = new System.Windows.Forms.OpenFileDialog - { - InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), - Multiselect = false, - CheckFileExists = true, - CheckPathExists = true, - Title = title, - Filter = filter - }; - - var result = dlg.ShowDialog(); - if (result == System.Windows.Forms.DialogResult.OK) - { - return dlg.FileName; - } - else - { - return string.Empty; - } - } - - #region general - - // todo a better name? - public class LastQueryMode : BaseModel - { - public string Display { get; set; } - public Infrastructure.UserSettings.LastQueryMode Value { get; set; } - } - - private List _lastQueryModes = new List(); - public List LastQueryModes - { - get - { - if (_lastQueryModes.Count == 0) - { - _lastQueryModes = InitLastQueryModes(); - } - return _lastQueryModes; - } - } - - private List InitLastQueryModes() - { - var modes = new List(); - var enums = (Infrastructure.UserSettings.LastQueryMode[])Enum.GetValues(typeof(Infrastructure.UserSettings.LastQueryMode)); - foreach (var e in enums) - { - var key = $"LastQuery{e}"; - var display = _translater.GetTranslation(key); - var m = new LastQueryMode - { - Display = display, Value = e, - }; - modes.Add(m); - } - return modes; - } - - private void UpdateLastQueryModeDisplay() - { - foreach (var item in LastQueryModes) - { - item.Display = _translater.GetTranslation($"LastQuery{item.Value}"); - } - } - - public string Language - { - get - { - return Settings.Language; - } - set - { - InternationalizationManager.Instance.ChangeLanguage(value); - - if (InternationalizationManager.Instance.PromptShouldUsePinyin(value)) - ShouldUsePinyin = true; - - UpdateLastQueryModeDisplay(); - } - } - - public bool ShouldUsePinyin - { - get - { - return Settings.ShouldUsePinyin; - } - set - { - Settings.ShouldUsePinyin = value; - } - } - - public List QuerySearchPrecisionStrings - { - get - { - var precisionStrings = new List(); - - var enumList = Enum.GetValues(typeof(SearchPrecisionScore)).Cast().ToList(); - - enumList.ForEach(x => precisionStrings.Add(x.ToString())); - - return precisionStrings; - } - } - - public List OpenResultModifiersList => new List - { - KeyConstant.Alt, - KeyConstant.Ctrl, - $"{KeyConstant.Ctrl}+{KeyConstant.Alt}" - }; - private Internationalization _translater => InternationalizationManager.Instance; - public List Languages => _translater.LoadAvailableLanguages(); - public IEnumerable MaxResultsRange => Enumerable.Range(2, 16); - - public string AlwaysPreviewToolTip => string.Format(_translater.GetTranslation("AlwaysPreviewToolTip"), Settings.PreviewHotkey); - - public string TestProxy() - { - var proxyServer = Settings.Proxy.Server; - var proxyUserName = Settings.Proxy.UserName; - if (string.IsNullOrEmpty(proxyServer)) - { - return InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty"); - } - if (Settings.Proxy.Port <= 0) - { - return InternationalizationManager.Instance.GetTranslation("portCantBeEmpty"); - } - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_updater.GitHubRepository); - - if (string.IsNullOrEmpty(proxyUserName) || string.IsNullOrEmpty(Settings.Proxy.Password)) - { - request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port); - } - else - { - request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port) - { - Credentials = new NetworkCredential(proxyUserName, Settings.Proxy.Password) - }; - } - try - { - var response = (HttpWebResponse)request.GetResponse(); - if (response.StatusCode == HttpStatusCode.OK) - { - return InternationalizationManager.Instance.GetTranslation("proxyIsCorrect"); - } - else - { - return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed"); - } - } - catch - { - return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed"); - } - } - - #endregion - - #region plugin - - public static string Plugin => @"https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest"; - public PluginViewModel SelectedPlugin { get; set; } - - public IList PluginViewModels - { - get => PluginManager.AllPlugins - .OrderBy(x => x.Metadata.Disabled) - .ThenBy(y => y.Metadata.Name) - .Select(p => new PluginViewModel - { - PluginPair = p - }) - .ToList(); - } - - public IList ExternalPlugins - { - get - { - return LabelMaker(PluginsManifest.UserPlugins); - } - } - - private IList LabelMaker(IList list) - { - return list.Select(p => new PluginStoreItemViewModel(p)) - .OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease) - .ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated) - .ThenByDescending(p => p.Category == PluginStoreItemViewModel.None) - .ThenByDescending(p => p.Category == PluginStoreItemViewModel.Installed) - .ToList(); - } - - public Control SettingProvider - { - get - { - var settingProvider = SelectedPlugin.PluginPair.Plugin as ISettingProvider; - if (settingProvider != null) - { - var control = settingProvider.CreateSettingPanel(); - control.HorizontalAlignment = HorizontalAlignment.Stretch; - control.VerticalAlignment = VerticalAlignment.Stretch; - return control; - } - else - { - return new Control(); - } - } - } - - [RelayCommand] - private async Task RefreshExternalPluginsAsync() - { - await PluginsManifest.UpdateManifestAsync(); - OnPropertyChanged(nameof(ExternalPlugins)); - } - - - - internal void DisplayPluginQuery(string queryToDisplay, PluginPair plugin, int actionKeywordPosition = 0) - { - var actionKeyword = plugin.Metadata.ActionKeywords.Count == 0 - ? string.Empty - : plugin.Metadata.ActionKeywords[actionKeywordPosition]; - - App.API.ChangeQuery($"{actionKeyword} {queryToDisplay}"); - App.API.ShowMainWindow(); - } - - #endregion - - #region theme - - public static string Theme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme"; - public static string ThemeGallery => @"https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; - - public string SelectedTheme - { - get { return Settings.Theme; } - set - { - ThemeManager.Instance.ChangeTheme(value); - - if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect) - DropShadowEffect = false; - } - } - - public List Themes - => ThemeManager.Instance.LoadAvailableThemes().Select(Path.GetFileNameWithoutExtension).ToList(); - - public bool DropShadowEffect - { - get { return Settings.UseDropShadowEffect; } - set - { - if (ThemeManager.Instance.BlurEnabled && value) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed")); - return; - } - - if (value) - { - ThemeManager.Instance.AddDropShadowEffectToCurrentTheme(); - } - else - { - ThemeManager.Instance.RemoveDropShadowEffectFromCurrentTheme(); - } - - Settings.UseDropShadowEffect = value; - } - } - - public class ColorScheme - { - public string Display { get; set; } - public ColorSchemes Value { get; set; } - } - - public List ColorSchemes - { - get - { - List modes = new List(); - var enums = (ColorSchemes[])Enum.GetValues(typeof(ColorSchemes)); - foreach (var e in enums) - { - var key = $"ColorScheme{e}"; - var display = _translater.GetTranslation(key); - var m = new ColorScheme - { - Display = display, Value = e, - }; - modes.Add(m); - } - return modes; - } - } - - public class SearchWindowScreen - { - public string Display { get; set; } - public SearchWindowScreens Value { get; set; } - } - - public List SearchWindowScreens - { - get - { - List modes = new List(); - var enums = (SearchWindowScreens[])Enum.GetValues(typeof(SearchWindowScreens)); - foreach (var e in enums) - { - var key = $"SearchWindowScreen{e}"; - var display = _translater.GetTranslation(key); - var m = new SearchWindowScreen - { - Display = display, - Value = e, - }; - modes.Add(m); - } - return modes; - } - } - - public class SearchWindowAlign - { - public string Display { get; set; } - public SearchWindowAligns Value { get; set; } - } - - public List SearchWindowAligns - { - get - { - List modes = new List(); - var enums = (SearchWindowAligns[])Enum.GetValues(typeof(SearchWindowAligns)); - foreach (var e in enums) - { - var key = $"SearchWindowAlign{e}"; - var display = _translater.GetTranslation(key); - var m = new SearchWindowAlign - { - Display = display, Value = e, - }; - modes.Add(m); - } - return modes; - } - } - - public List ScreenNumbers - { - get - { - var screens = System.Windows.Forms.Screen.AllScreens; - var screenNumbers = new List(); - for (int i = 1; i <= screens.Length; i++) - { - screenNumbers.Add(i); - } - return screenNumbers; - } - } - - public List TimeFormatList { get; } = new() - { - "h:mm", - "hh:mm", - "H:mm", - "HH:mm", - "tt h:mm", - "tt hh:mm", - "h:mm tt", - "hh:mm tt", - "hh:mm:ss tt", - "HH:mm:ss" - }; - - public List DateFormatList { get; } = new() - { - "MM'/'dd dddd", - "MM'/'dd ddd", - "MM'/'dd", - "MM'-'dd", - "MMMM', 'dd", - "dd'/'MM", - "dd'-'MM", - "ddd MM'/'dd", - "dddd MM'/'dd", - "dddd", - "ddd dd'/'MM", - "dddd dd'/'MM", - "dddd dd', 'MMMM", - "dd', 'MMMM" - }; - - public string TimeFormat - { - get => Settings.TimeFormat; - set => Settings.TimeFormat = value; - } - - public string DateFormat - { - get => Settings.DateFormat; - set => Settings.DateFormat = value; - } - - public string ClockText => DateTime.Now.ToString(TimeFormat, Culture); - - public string DateText => DateTime.Now.ToString(DateFormat, Culture); - - public double WindowWidthSize - { - get => Settings.WindowSize; - set => Settings.WindowSize = value; - } - - public bool UseGlyphIcons - { - get => Settings.UseGlyphIcons; - set => Settings.UseGlyphIcons = value; - } - - public bool UseAnimation - { - get => Settings.UseAnimation; - set => Settings.UseAnimation = value; - } - - public class AnimationSpeed - { - public string Display { get; set; } - public AnimationSpeeds Value { get; set; } - } - - public List AnimationSpeeds - { - get - { - List speeds = new List(); - var enums = (AnimationSpeeds[])Enum.GetValues(typeof(AnimationSpeeds)); - foreach (var e in enums) - { - var key = $"AnimationSpeed{e}"; - var display = _translater.GetTranslation(key); - var m = new AnimationSpeed - { - Display = display, - Value = e, - }; - speeds.Add(m); - } - return speeds; - } - } - - public bool UseSound - { - get => Settings.UseSound; - set => Settings.UseSound = value; - } - - public bool UseClock - { - get => Settings.UseClock; - set => Settings.UseClock = value; - } - - public bool UseDate - { - get => Settings.UseDate; - set => Settings.UseDate = value; - } - - public double SettingWindowWidth - { - get => Settings.SettingWindowWidth; - set => Settings.SettingWindowWidth = value; - } - - public double SettingWindowHeight - { - get => Settings.SettingWindowHeight; - set => Settings.SettingWindowHeight = value; - } - - public double SettingWindowTop - { - get => Settings.SettingWindowTop; - set => Settings.SettingWindowTop = value; - } - - public double SettingWindowLeft - { - get => Settings.SettingWindowLeft; - set => Settings.SettingWindowLeft = value; - } - - public Brush PreviewBackground - { - get - { - var wallpaper = WallpaperPathRetrieval.GetWallpaperPath(); - if (wallpaper != null && File.Exists(wallpaper)) - { - var memStream = new MemoryStream(File.ReadAllBytes(wallpaper)); - var bitmap = new BitmapImage(); - bitmap.BeginInit(); - bitmap.StreamSource = memStream; - bitmap.DecodePixelWidth = 800; - bitmap.DecodePixelHeight = 600; - bitmap.EndInit(); - var brush = new ImageBrush(bitmap) - { - Stretch = Stretch.UniformToFill - }; - return brush; - } - else - { - var wallpaperColor = WallpaperPathRetrieval.GetWallpaperColor(); - var brush = new SolidColorBrush(wallpaperColor); - return brush; - } - } - } - - public ResultsViewModel PreviewResults - { - get - { - var results = new List - { - new Result - { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"), - IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png") - }, - new Result - { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"), - IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png") - }, - new Result - { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"), - IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png") - }, - new Result - { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"), - IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png") - } - }; - var vm = new ResultsViewModel(Settings); - vm.AddResults(results, "PREVIEW"); - return vm; - } - } - - public FontFamily SelectedQueryBoxFont - { - get - { - if (Fonts.SystemFontFamilies.Count(o => - o.FamilyNames.Values != null && - o.FamilyNames.Values.Contains(Settings.QueryBoxFont)) > 0) - { - var font = new FontFamily(Settings.QueryBoxFont); - return font; - } - else - { - var font = new FontFamily("Segoe UI"); - return font; - } - } - set - { - Settings.QueryBoxFont = value.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); - } - } - - public FamilyTypeface SelectedQueryBoxFontFaces - { - get - { - var typeface = SyntaxSugars.CallOrRescueDefault( - () => SelectedQueryBoxFont.ConvertFromInvariantStringsOrNormal( - Settings.QueryBoxFontStyle, - Settings.QueryBoxFontWeight, - Settings.QueryBoxFontStretch - )); - return typeface; - } - set - { - Settings.QueryBoxFontStretch = value.Stretch.ToString(); - Settings.QueryBoxFontWeight = value.Weight.ToString(); - Settings.QueryBoxFontStyle = value.Style.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); - } - } - - public FontFamily SelectedResultFont - { - get - { - if (Fonts.SystemFontFamilies.Count(o => - o.FamilyNames.Values != null && - o.FamilyNames.Values.Contains(Settings.ResultFont)) > 0) - { - var font = new FontFamily(Settings.ResultFont); - return font; - } - else - { - var font = new FontFamily("Segoe UI"); - return font; - } - } - set - { - Settings.ResultFont = value.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); - } - } - - public FamilyTypeface SelectedResultFontFaces - { - get - { - var typeface = SyntaxSugars.CallOrRescueDefault( - () => SelectedResultFont.ConvertFromInvariantStringsOrNormal( - Settings.ResultFontStyle, - Settings.ResultFontWeight, - Settings.ResultFontStretch - )); - return typeface; - } - set - { - Settings.ResultFontStretch = value.Stretch.ToString(); - Settings.ResultFontWeight = value.Weight.ToString(); - Settings.ResultFontStyle = value.Style.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); - } - } - - public string ThemeImage => Constant.QueryTextBoxIconImagePath; - - #endregion - - #region hotkey - - public CustomPluginHotkey SelectedCustomPluginHotkey { get; set; } - - #endregion - - #region shortcut - - public ObservableCollection CustomShortcuts => Settings.CustomShortcuts; - - public ObservableCollection BuiltinShortcuts => Settings.BuiltinShortcuts; - - public CustomShortcutModel? SelectedCustomShortcut { get; set; } - - public void DeleteSelectedCustomShortcut() - { - var item = SelectedCustomShortcut; - if (item == null) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); - return; - } - - string deleteWarning = string.Format( - InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"), - item.Key, item.Value); - if (MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - Settings.CustomShortcuts.Remove(item); - } - } - - public bool EditSelectedCustomShortcut() - { - var item = SelectedCustomShortcut; - if (item == null) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); - return false; - } - - var shortcutSettingWindow = new CustomShortcutSetting(item.Key, item.Value, this); - if (shortcutSettingWindow.ShowDialog() == true) - { - // Fix un-selectable shortcut item after the first selection - // https://stackoverflow.com/questions/16789360/wpf-listbox-items-with-changing-hashcode - SelectedCustomShortcut = null; - item.Key = shortcutSettingWindow.Key; - item.Value = shortcutSettingWindow.Value; - SelectedCustomShortcut = item; - return true; - } - return false; - } - - public void AddCustomShortcut() - { - var shortcutSettingWindow = new CustomShortcutSetting(this); - if (shortcutSettingWindow.ShowDialog() == true) - { - var shortcut = new CustomShortcutModel(shortcutSettingWindow.Key, shortcutSettingWindow.Value); - Settings.CustomShortcuts.Add(shortcut); - } - } - - public bool ShortcutExists(string key) - { - return Settings.CustomShortcuts.Any(x => x.Key == key) || Settings.BuiltinShortcuts.Any(x => x.Key == key); - } - - #endregion - - #region about - - public string Website => Constant.Website; - public string SponsorPage => Constant.SponsorPage; - public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest"; - public string Documentation => Constant.Documentation; - public string Docs => Constant.Docs; - public string Github => Constant.GitHub; - public string Version - { - get - { - if (Constant.Version == "1.0.0") - { - return Constant.Dev; - } - else - { - return Constant.Version; - } - } - } - public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes); - - public string CheckLogFolder - { - get - { - var logFiles = GetLogFiles(); - long size = logFiles.Sum(file => file.Length); - return string.Format("{0} ({1})", _translater.GetTranslation("clearlogfolder"), BytesToReadableString(size)); - } - } - - private static DirectoryInfo GetLogDir(string version = "") - { - return new DirectoryInfo(Path.Combine(DataLocation.DataDirectory(), Constant.Logs, version)); - } - - private static List GetLogFiles(string version = "") - { - return GetLogDir(version).EnumerateFiles("*", SearchOption.AllDirectories).ToList(); - } - - internal void ClearLogFolder() - { - var logDirectory = GetLogDir(); - var logFiles = GetLogFiles(); - - logFiles.ForEach(f => f.Delete()); - - logDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly) - .Where(dir => !Constant.Version.Equals(dir.Name)) - .ToList() - .ForEach(dir => dir.Delete()); - - OnPropertyChanged(nameof(CheckLogFolder)); - } - - internal void OpenLogFolder() - { - App.API.OpenDirectory(GetLogDir(Constant.Version).FullName); - } - - internal static string BytesToReadableString(long bytes) - { - const int scale = 1024; - string[] orders = new string[] - { - "GB", "MB", "KB", "B" - }; - long max = (long)Math.Pow(scale, orders.Length - 1); - - foreach (string order in orders) - { - if (bytes > max) - return string.Format("{0:##.##} {1}", decimal.Divide(bytes, max), order); - - max /= scale; - } - return "0 B"; - } - - #endregion + _storage = new FlowLauncherJsonStorage(); + + Updater = updater; + Portable = portable; + Settings = _storage.Load(); + } + + public async void UpdateApp() + { + await Updater.UpdateAppAsync(App.API, false); + } + + + + /// + /// Save Flow settings. Plugins settings are not included. + /// + public void Save() + { + _storage.Save(); + } + + public double SettingWindowWidth + { + get => Settings.SettingWindowWidth; + set => Settings.SettingWindowWidth = value; + } + + public double SettingWindowHeight + { + get => Settings.SettingWindowHeight; + set => Settings.SettingWindowHeight = value; + } + + public double SettingWindowTop + { + get => Settings.SettingWindowTop; + set => Settings.SettingWindowTop = value; + } + + public double SettingWindowLeft + { + get => Settings.SettingWindowLeft; + set => Settings.SettingWindowLeft = value; } } diff --git a/Flow.Launcher/WelcomeWindow.xaml b/Flow.Launcher/WelcomeWindow.xaml index c7820d436..003dac5bc 100644 --- a/Flow.Launcher/WelcomeWindow.xaml +++ b/Flow.Launcher/WelcomeWindow.xaml @@ -10,6 +10,10 @@ Title="{DynamicResource Welcome_Page1_Title}" Width="550" Height="650" + MinWidth="550" + MinHeight="650" + MaxWidth="550" + MaxHeight="650" Activated="OnActivated" Background="{DynamicResource Color00B}" Foreground="{DynamicResource PopupTextColor}" diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs index 09755fe0c..65757b802 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs @@ -3,23 +3,22 @@ using System; using System.Collections.Generic; using System.IO; -namespace Flow.Launcher.Plugin.BrowserBookmark -{ - public class ChromeBookmarkLoader : ChromiumBookmarkLoader - { - public override List GetBookmarks() - { - return LoadChromeBookmarks(); - } +namespace Flow.Launcher.Plugin.BrowserBookmark; - private List LoadChromeBookmarks() - { - var bookmarks = new List(); - var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium")); - return bookmarks; - } +public class ChromeBookmarkLoader : ChromiumBookmarkLoader +{ + public override List GetBookmarks() + { + return LoadChromeBookmarks(); } -} \ No newline at end of file + + private List LoadChromeBookmarks() + { + var bookmarks = new List(); + var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome")); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary")); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium")); + return bookmarks; + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index 14b791c48..48acf6109 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -2,56 +2,75 @@ using System.Collections.Generic; using System.IO; using System.Text.Json; +using Flow.Launcher.Infrastructure.Logger; -namespace Flow.Launcher.Plugin.BrowserBookmark +namespace Flow.Launcher.Plugin.BrowserBookmark; + +public abstract class ChromiumBookmarkLoader : IBookmarkLoader { - public abstract class ChromiumBookmarkLoader : IBookmarkLoader + public abstract List GetBookmarks(); + + protected List LoadBookmarks(string browserDataPath, string name) { - public abstract List GetBookmarks(); - protected List LoadBookmarks(string browserDataPath, string name) + var bookmarks = new List(); + if (!Directory.Exists(browserDataPath)) return bookmarks; + var paths = Directory.GetDirectories(browserDataPath); + + foreach (var profile in paths) { - var bookmarks = new List(); - if (!Directory.Exists(browserDataPath)) return bookmarks; - var paths = Directory.GetDirectories(browserDataPath); + var bookmarkPath = Path.Combine(profile, "Bookmarks"); + if (!File.Exists(bookmarkPath)) + continue; - foreach (var profile in paths) - { - var bookmarkPath = Path.Combine(profile, "Bookmarks"); - if (!File.Exists(bookmarkPath)) - continue; - - Main.RegisterBookmarkFile(bookmarkPath); + Main.RegisterBookmarkFile(bookmarkPath); - var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); - bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source)); - } - return bookmarks; + var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); + bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source)); } - protected List LoadBookmarksFromFile(string path, string source) - { - if (!File.Exists(path)) - return new(); + return bookmarks; + } - var bookmarks = new List(); - using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); - if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) - return new(); - foreach (var folder in rootElement.EnumerateObject()) - { - if (folder.Value.ValueKind == JsonValueKind.Object) - EnumerateFolderBookmark(folder.Value, bookmarks, source); - } + protected List LoadBookmarksFromFile(string path, string source) + { + var bookmarks = new List(); + + if (!File.Exists(path)) return bookmarks; - } - private void EnumerateFolderBookmark(JsonElement folderElement, List bookmarks, string source) + using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); + if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) + return bookmarks; + EnumerateRoot(rootElement, bookmarks, source); + return bookmarks; + } + + private void EnumerateRoot(JsonElement rootElement, ICollection bookmarks, string source) + { + foreach (var folder in rootElement.EnumerateObject()) { - if (!folderElement.TryGetProperty("children", out var childrenElement)) - return; - foreach (var subElement in childrenElement.EnumerateArray()) + if (folder.Value.ValueKind != JsonValueKind.Object) + continue; + + // Fix for Opera. It stores bookmarks slightly different than chrome. See PR and bug report for this change for details. + // If various exceptions start to build up here consider splitting this Loader into multiple separate ones. + if (folder.Name == "custom_root") + EnumerateRoot(folder.Value, bookmarks, source); + else + EnumerateFolderBookmark(folder.Value, bookmarks, source); + } + } + + private void EnumerateFolderBookmark(JsonElement folderElement, ICollection bookmarks, + string source) + { + if (!folderElement.TryGetProperty("children", out var childrenElement)) + return; + foreach (var subElement in childrenElement.EnumerateArray()) + { + if (subElement.TryGetProperty("type", out var type)) { - switch (subElement.GetProperty("type").GetString()) + switch (type.GetString()) { case "folder": case "workspace": // Edge Workspace @@ -65,7 +84,11 @@ namespace Flow.Launcher.Plugin.BrowserBookmark break; } } - + else + { + Log.Error( + $"ChromiumBookmarkLoader: EnumerateFolderBookmark: type property not found for {subElement.GetString()}"); + } } } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs index d08c05b6b..3468015eb 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs @@ -4,56 +4,55 @@ using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.SharedModels; -namespace Flow.Launcher.Plugin.BrowserBookmark.Commands +namespace Flow.Launcher.Plugin.BrowserBookmark.Commands; + +internal static class BookmarkLoader { - internal static class BookmarkLoader + internal static MatchResult MatchProgram(Bookmark bookmark, string queryString) { - internal static MatchResult MatchProgram(Bookmark bookmark, string queryString) - { - var match = StringMatcher.FuzzySearch(queryString, bookmark.Name); - if (match.IsSearchPrecisionScoreMet()) - return match; + var match = StringMatcher.FuzzySearch(queryString, bookmark.Name); + if (match.IsSearchPrecisionScoreMet()) + return match; - return StringMatcher.FuzzySearch(queryString, bookmark.Url); + return StringMatcher.FuzzySearch(queryString, bookmark.Url); + } + + internal static List LoadAllBookmarks(Settings setting) + { + var allBookmarks = new List(); + + if (setting.LoadChromeBookmark) + { + // Add Chrome bookmarks + var chromeBookmarks = new ChromeBookmarkLoader(); + allBookmarks.AddRange(chromeBookmarks.GetBookmarks()); } - internal static List LoadAllBookmarks(Settings setting) + if (setting.LoadFirefoxBookmark) { - var allBookmarks = new List(); - - if (setting.LoadChromeBookmark) - { - // Add Chrome bookmarks - var chromeBookmarks = new ChromeBookmarkLoader(); - allBookmarks.AddRange(chromeBookmarks.GetBookmarks()); - } - - if (setting.LoadFirefoxBookmark) - { - // Add Firefox bookmarks - var mozBookmarks = new FirefoxBookmarkLoader(); - allBookmarks.AddRange(mozBookmarks.GetBookmarks()); - } - - if (setting.LoadEdgeBookmark) - { - // Add Edge (Chromium) bookmarks - var edgeBookmarks = new EdgeBookmarkLoader(); - allBookmarks.AddRange(edgeBookmarks.GetBookmarks()); - } - - foreach (var browser in setting.CustomChromiumBrowsers) - { - IBookmarkLoader loader = browser.BrowserType switch - { - BrowserType.Chromium => new CustomChromiumBookmarkLoader(browser), - BrowserType.Firefox => new CustomFirefoxBookmarkLoader(browser), - _ => new CustomChromiumBookmarkLoader(browser), - }; - allBookmarks.AddRange(loader.GetBookmarks()); - } - - return allBookmarks.Distinct().ToList(); + // Add Firefox bookmarks + var mozBookmarks = new FirefoxBookmarkLoader(); + allBookmarks.AddRange(mozBookmarks.GetBookmarks()); } + + if (setting.LoadEdgeBookmark) + { + // Add Edge (Chromium) bookmarks + var edgeBookmarks = new EdgeBookmarkLoader(); + allBookmarks.AddRange(edgeBookmarks.GetBookmarks()); + } + + foreach (var browser in setting.CustomChromiumBrowsers) + { + IBookmarkLoader loader = browser.BrowserType switch + { + BrowserType.Chromium => new CustomChromiumBookmarkLoader(browser), + BrowserType.Firefox => new CustomFirefoxBookmarkLoader(browser), + _ => new CustomChromiumBookmarkLoader(browser), + }; + allBookmarks.AddRange(loader.GetBookmarks()); + } + + return allBookmarks.Distinct().ToList(); } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs index fa98f4d7c..005c83992 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs @@ -1,19 +1,18 @@ using Flow.Launcher.Plugin.BrowserBookmark.Models; using System.Collections.Generic; -namespace Flow.Launcher.Plugin.BrowserBookmark -{ - public class CustomChromiumBookmarkLoader : ChromiumBookmarkLoader - { - public CustomChromiumBookmarkLoader(CustomBrowser browser) - { - BrowserName = browser.Name; - BrowserDataPath = browser.DataDirectoryPath; - } - public string BrowserDataPath { get; init; } - public string BookmarkFilePath { get; init; } - public string BrowserName { get; init; } +namespace Flow.Launcher.Plugin.BrowserBookmark; - public override List GetBookmarks() => BrowserDataPath != null ? LoadBookmarks(BrowserDataPath, BrowserName) : LoadBookmarksFromFile(BookmarkFilePath, BrowserName); +public class CustomChromiumBookmarkLoader : ChromiumBookmarkLoader +{ + public CustomChromiumBookmarkLoader(CustomBrowser browser) + { + BrowserName = browser.Name; + BrowserDataPath = browser.DataDirectoryPath; } -} \ No newline at end of file + public string BrowserDataPath { get; init; } + public string BookmarkFilePath { get; init; } + public string BrowserName { get; init; } + + public override List GetBookmarks() => BrowserDataPath != null ? LoadBookmarks(BrowserDataPath, BrowserName) : LoadBookmarksFromFile(BookmarkFilePath, BrowserName); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs index 82bdc29f5..d0bb7b0cc 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs @@ -2,26 +2,25 @@ using System.IO; using Flow.Launcher.Plugin.BrowserBookmark.Models; -namespace Flow.Launcher.Plugin.BrowserBookmark -{ - public class CustomFirefoxBookmarkLoader : FirefoxBookmarkLoaderBase - { - public CustomFirefoxBookmarkLoader(CustomBrowser browser) - { - BrowserName = browser.Name; - BrowserDataPath = browser.DataDirectoryPath; - } - - /// - /// Path to places.sqlite - /// - public string BrowserDataPath { get; init; } - - public string BrowserName { get; init; } +namespace Flow.Launcher.Plugin.BrowserBookmark; - public override List GetBookmarks() - { - return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite")); - } +public class CustomFirefoxBookmarkLoader : FirefoxBookmarkLoaderBase +{ + public CustomFirefoxBookmarkLoader(CustomBrowser browser) + { + BrowserName = browser.Name; + BrowserDataPath = browser.DataDirectoryPath; + } + + /// + /// Path to places.sqlite + /// + public string BrowserDataPath { get; init; } + + public string BrowserName { get; init; } + + public override List GetBookmarks() + { + return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite")); } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs index 79190f0ef..40123b022 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs @@ -3,21 +3,20 @@ using System; using System.Collections.Generic; using System.IO; -namespace Flow.Launcher.Plugin.BrowserBookmark -{ - public class EdgeBookmarkLoader : ChromiumBookmarkLoader - { - private List LoadEdgeBookmarks() - { - var bookmarks = new List(); - var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary")); +namespace Flow.Launcher.Plugin.BrowserBookmark; - return bookmarks; - } - - public override List GetBookmarks() => LoadEdgeBookmarks(); +public class EdgeBookmarkLoader : ChromiumBookmarkLoader +{ + private List LoadEdgeBookmarks() + { + var bookmarks = new List(); + var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge")); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev")); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary")); + + return bookmarks; } -} \ No newline at end of file + + public override List GetBookmarks() => LoadEdgeBookmarks(); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 022f28144..35ad32fb3 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -1,144 +1,137 @@ using Flow.Launcher.Plugin.BrowserBookmark.Models; +using Microsoft.Data.Sqlite; using System; using System.Collections.Generic; -using System.Data.SQLite; using System.IO; using System.Linq; -namespace Flow.Launcher.Plugin.BrowserBookmark +namespace Flow.Launcher.Plugin.BrowserBookmark; + +public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader { - public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader - { - public abstract List GetBookmarks(); + public abstract List GetBookmarks(); - private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title - FROM moz_places - INNER JOIN moz_bookmarks ON ( + private const string QueryAllBookmarks = """ + SELECT moz_places.url, moz_bookmarks.title + FROM moz_places + INNER JOIN moz_bookmarks ON ( moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id - ) - ORDER BY moz_places.visit_count DESC - "; + ) + ORDER BY moz_places.visit_count DESC + """; - private const string dbPathFormat = "Data Source ={0};Version=3;New=False;Compress=True;"; + private const string DbPathFormat = "Data Source ={0}"; - protected static List GetBookmarksFromPath(string placesPath) - { - // Return empty list if the places.sqlite file cannot be found - if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath)) - return new List(); + protected static List GetBookmarksFromPath(string placesPath) + { + // Return empty list if the places.sqlite file cannot be found + if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath)) + return new List(); - var bookmarkList = new List(); + Main.RegisterBookmarkFile(placesPath); - Main.RegisterBookmarkFile(placesPath); + // create the connection string and init the connection + string dbPath = string.Format(DbPathFormat, placesPath); + using var dbConnection = new SqliteConnection(dbPath); + // Open connection to the database file and execute the query + dbConnection.Open(); + var reader = new SqliteCommand(QueryAllBookmarks, dbConnection).ExecuteReader(); - // create the connection string and init the connection - string dbPath = string.Format(dbPathFormat, placesPath); - using var dbConnection = new SQLiteConnection(dbPath); - // Open connection to the database file and execute the query - dbConnection.Open(); - var reader = new SQLiteCommand(queryAllBookmarks, dbConnection).ExecuteReader(); + // return results in List format + return reader + .Select( + x => new Bookmark( + x["title"] is DBNull ? string.Empty : x["title"].ToString(), + x["url"].ToString() + ) + ) + .ToList(); + } +} - // return results in List format - bookmarkList = reader.Select( - x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(), - x["url"].ToString()) - ).ToList(); - return bookmarkList; - } +public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase +{ + /// + /// Searches the places.sqlite db and returns all bookmarks + /// + public override List GetBookmarks() + { + return GetBookmarksFromPath(PlacesPath); } - - public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase + /// + /// Path to places.sqlite + /// + private string PlacesPath { - /// - /// Searches the places.sqlite db and returns all bookmarks - /// - public override List GetBookmarks() + get { - return GetBookmarksFromPath(PlacesPath); - } - - /// - /// Path to places.sqlite - /// - private string PlacesPath - { - get - { - var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox"); - var profileIni = Path.Combine(profileFolderPath, @"profiles.ini"); + var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox"); + var profileIni = Path.Combine(profileFolderPath, @"profiles.ini"); - if (!File.Exists(profileIni)) - return string.Empty; + if (!File.Exists(profileIni)) + return string.Empty; - // get firefox default profile directory from profiles.ini - string ini; - using (var sReader = new StreamReader(profileIni)) - { - ini = sReader.ReadToEnd(); - } + // get firefox default profile directory from profiles.ini + using var sReader = new StreamReader(profileIni); + var ini = sReader.ReadToEnd(); - /* - Current profiles.ini structure example as of Firefox version 69.0.1 - - [Install736426B0AF4A39CB] - Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile - Locked=1 + /* + Current profiles.ini structure example as of Firefox version 69.0.1 - [Profile2] - Name=newblahprofile - IsRelative=0 - Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code. + [Install736426B0AF4A39CB] + Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile + Locked=1 - [Profile1] - Name=default - IsRelative=1 - Path=Profiles/cydum7q4.default - Default=1 + [Profile2] + Name=newblahprofile + IsRelative=0 + Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code. - [Profile0] - Name=default-release - IsRelative=1 - Path=Profiles/7789f565.default-release + [Profile1] + Name=default + IsRelative=1 + Path=Profiles/cydum7q4.default + Default=1 - [General] - StartWithLastProfile=1 - Version=2 - */ + [Profile0] + Name=default-release + IsRelative=1 + Path=Profiles/7789f565.default-release - var lines = ini.Split(new string[] - { - "\r\n" - }, StringSplitOptions.None).ToList(); + [General] + StartWithLastProfile=1 + Version=2 + */ + var lines = ini.Split("\r\n").ToList(); - var defaultProfileFolderNameRaw = lines.Where(x => x.Contains("Default=") && x != "Default=1").FirstOrDefault() ?? string.Empty; + var defaultProfileFolderNameRaw = lines.FirstOrDefault(x => x.Contains("Default=") && x != "Default=1") ?? string.Empty; - if (string.IsNullOrEmpty(defaultProfileFolderNameRaw)) - return string.Empty; + if (string.IsNullOrEmpty(defaultProfileFolderNameRaw)) + return string.Empty; - var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last(); + var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last(); - var indexOfDefaultProfileAtttributePath = lines.IndexOf("Path=" + defaultProfileFolderName); + var indexOfDefaultProfileAttributePath = lines.IndexOf("Path=" + defaultProfileFolderName); - // Seen in the example above, the IsRelative attribute is always above the Path attribute - var relativeAttribute = lines[indexOfDefaultProfileAtttributePath - 1]; + // Seen in the example above, the IsRelative attribute is always above the Path attribute + var relativeAttribute = lines[indexOfDefaultProfileAttributePath - 1]; - return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0 - ? defaultProfileFolderName + @"\places.sqlite" - : Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite"; - } - } - } - - public static class Extensions - { - public static IEnumerable Select(this SQLiteDataReader reader, Func projection) - { - while (reader.Read()) - { - yield return projection(reader); - } + return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0 + ? defaultProfileFolderName + @"\places.sqlite" + : Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite"; + } + } +} + +public static class Extensions +{ + public static IEnumerable Select(this SqliteDataReader reader, Func projection) + { + while (reader.Read()) + { + yield return projection(reader); } } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index 6cd155ecc..234afe28a 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -12,6 +12,7 @@ false false true + en @@ -35,6 +36,44 @@ false + + + + + + + + PreserveNewest @@ -56,22 +95,7 @@ - + - - - - - - - - - - - - - - - - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs index 2c48cfd55..8a9727352 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs @@ -1,10 +1,9 @@ using Flow.Launcher.Plugin.BrowserBookmark.Models; using System.Collections.Generic; -namespace Flow.Launcher.Plugin.BrowserBookmark +namespace Flow.Launcher.Plugin.BrowserBookmark; + +public interface IBookmarkLoader { - public interface IBookmarkLoader - { - public List GetBookmarks(); - } -} \ No newline at end of file + public List GetBookmarks(); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-br.xaml index 18fed7cd7..d8bb949fd 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-br.xaml @@ -6,7 +6,7 @@ Pesquisar favoritos do seu navegador - Dados de Favorito + Dados de Favoritos Abrir favoritos em: Nova janela Nova aba @@ -21,8 +21,8 @@ Editar Apagar Navegar - Others - Browser Engine - If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. - For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. + Outros + Motor do Navegador + Se você não estiver usando o Chrome, Firefox ou Edge, ou se estiver usando suas versões portáteis, você precisa adicionar o diretório de dados dos favoritos e selecionar a ferramenta de busca correta para fazer este plugin funcionar. + Por exemplo: O motor do Brave é o Chromium; e seu diretório padrão de favoritos é "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Para o motor do Firefox, o diretório de favoritos é a pasta do usuário que contém o arquivo "places.sqlite". diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/uk-UA.xaml index a55b9de1b..93b20366e 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/uk-UA.xaml @@ -2,27 +2,27 @@ - Browser Bookmarks - Search your browser bookmarks + Закладки браузера + Пошук у закладках браузера - Bookmark Data - Open bookmarks in: - New window - New tab - Set browser from path: - Choose - Copy url - Copy the bookmark's url to clipboard - Load Browser From: - Browser Name - Data Directory Path + Дані закладок + Відкрити закладки в: + Нове вікно + Нова вкладка + Встановити браузер за шляхом: + Обрати + Скопіювати URL-адресу + Скопіюйте URL-адресу закладки в буфер обміну + Завантажити браузер з: + Назва браузера + Шлях до каталогу з даними Додати Редагувати Видалити - Browse - Others - Browser Engine - If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. - For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. + Перегляд + Інші + Браузерний рушій + Якщо ви не використовуєте Chrome, Firefox або Edge, або використовуєте їхні портативні версії, вам потрібно додати каталог даних закладок і вибрати правильний рушій браузера, щоб цей плагін працював. + Наприклад: Рушій Brave - Chromium, і за замовчуванням розташування даних закладок: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Для браузера Firefox директорія закладок - це папка userdata, що містить файл places.sqlite. diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/vi.xaml new file mode 100644 index 000000000..ddc10950e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/vi.xaml @@ -0,0 +1,28 @@ + + + + + Dấu trang trình duyệt + Tìm kiếm dấu trang trình duyệt của bạn + + + Dữ liệu đánh dấu + Mở dấu trang trong: + Cửa sổ mới + Thêm Tab mới + Đặt trình duyệt từ đường dẫn: + Chọn + Sao chép url + Sao chép url của dấu trang vào clipboard + Tải trình duyệt từ: + Tên trình duyệt + Đường dẫn thư mục dữ liệu + Thêm + Sửa + Xóa + Duyệt + Khác + Công cụ trình duyệt + Nếu bạn không sử dụng Chrome, Firefox hoặc Edge hoặc bạn đang sử dụng phiên bản di động của chúng, bạn cần thêm thư mục dữ liệu dấu trang và chọn đúng công cụ trình duyệt để plugin này hoạt động. + Ví dụ: Engine của Brave là Chrome; và vị trí dữ liệu dấu trang mặc định của nó là: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Đối với công cụ Firefox, thư mục dấu trang là thư mục dữ liệu người dùng chứa tệp địa điểm.sqlite. + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index a13d6c929..a48d70f2d 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Windows; using System.Windows.Controls; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.BrowserBookmark.Commands; @@ -12,233 +11,234 @@ using System.Threading.Channels; using System.Threading.Tasks; using System.Threading; -namespace Flow.Launcher.Plugin.BrowserBookmark +namespace Flow.Launcher.Plugin.BrowserBookmark; + +public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable { - public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable + private static PluginInitContext _context; + + private static List _cachedBookmarks = new List(); + + private static Settings _settings; + + private static bool _initialized = false; + + public void Init(PluginInitContext context) { - private static PluginInitContext context; + _context = context; - private static List cachedBookmarks = new List(); + _settings = context.API.LoadSettingJsonStorage(); - private static Settings _settings; + LoadBookmarksIfEnabled(); + } - private static bool initialized = false; - - public void Init(PluginInitContext context) + private static void LoadBookmarksIfEnabled() + { + if (_context.CurrentPluginMetadata.Disabled) { - Main.context = context; + // Don't load or monitor files if disabled + return; + } - _settings = context.API.LoadSettingJsonStorage(); + _cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings); + _ = MonitorRefreshQueueAsync(); + _initialized = true; + } + public List Query(Query query) + { + // For when the plugin being previously disabled and is now re-enabled + if (!_initialized) + { LoadBookmarksIfEnabled(); } - private static void LoadBookmarksIfEnabled() + string param = query.Search.TrimStart(); + + // Should top results be returned? (true if no search parameters have been passed) + var topResults = string.IsNullOrEmpty(param); + + + if (!topResults) { - if (context.CurrentPluginMetadata.Disabled) - { - // Don't load or monitor files if disabled - return; - } - - cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings); - _ = MonitorRefreshQueueAsync(); - initialized = true; - } - - public List Query(Query query) - { - // For when the plugin being previously disabled and is now renabled - if (!initialized) - { - LoadBookmarksIfEnabled(); - } - - string param = query.Search.TrimStart(); - - // Should top results be returned? (true if no search parameters have been passed) - var topResults = string.IsNullOrEmpty(param); - - - if (!topResults) - { - // Since we mixed chrome and firefox bookmarks, we should order them again - var returnList = cachedBookmarks.Select(c => new Result() - { - Title = c.Name, - SubTitle = c.Url, - IcoPath = @"Images\bookmark.png", - Score = BookmarkLoader.MatchProgram(c, param).Score, - Action = _ => + // Since we mixed chrome and firefox bookmarks, we should order them again + return _cachedBookmarks + .Select( + c => new Result { - context.API.OpenUrl(c.Url); - - return true; - }, - ContextData = new BookmarkAttributes - { - Url = c.Url - } - }).Where(r => r.Score > 0); - return returnList.ToList(); - } - else - { - return cachedBookmarks.Select(c => new Result() - { - Title = c.Name, - SubTitle = c.Url, - IcoPath = @"Images\bookmark.png", - Score = 5, - Action = _ => - { - context.API.OpenUrl(c.Url); - return true; - }, - ContextData = new BookmarkAttributes - { - Url = c.Url - } - }).ToList(); - } - } - - - private static Channel refreshQueue = Channel.CreateBounded(1); - - private static SemaphoreSlim fileMonitorSemaphore = new(1, 1); - - private static async Task MonitorRefreshQueueAsync() - { - if (fileMonitorSemaphore.CurrentCount < 1) - { - return; - } - await fileMonitorSemaphore.WaitAsync(); - var reader = refreshQueue.Reader; - while (await reader.WaitToReadAsync()) - { - if (reader.TryRead(out _)) - { - ReloadAllBookmarks(false); - } - } - fileMonitorSemaphore.Release(); - } - - private static readonly List Watchers = new(); - - internal static void RegisterBookmarkFile(string path) - { - var directory = Path.GetDirectoryName(path); - if (!Directory.Exists(directory) || !File.Exists(path)) - { - return; - } - if (Watchers.Any(x => x.Path.Equals(directory, StringComparison.OrdinalIgnoreCase))) - { - return; - } - - var watcher = new FileSystemWatcher(directory!); - watcher.Filter = Path.GetFileName(path); - - watcher.NotifyFilter = NotifyFilters.FileName | - NotifyFilters.LastWrite | - NotifyFilters.Size; - - watcher.Changed += static (_, _) => - { - refreshQueue.Writer.TryWrite(default); - }; - - watcher.Renamed += static (_, _) => - { - refreshQueue.Writer.TryWrite(default); - }; - - watcher.EnableRaisingEvents = true; - - Watchers.Add(watcher); - } - - public void ReloadData() - { - ReloadAllBookmarks(); - } - - public static void ReloadAllBookmarks(bool disposeFileWatchers = true) - { - cachedBookmarks.Clear(); - if (disposeFileWatchers) - DisposeFileWatchers(); - LoadBookmarksIfEnabled(); - } - - public string GetTranslatedPluginTitle() - { - return context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_name"); - } - - public string GetTranslatedPluginDescription() - { - return context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_description"); - } - - public Control CreateSettingPanel() - { - return new SettingsControl(_settings); - } - - public List LoadContextMenus(Result selectedResult) - { - return new List() - { - new Result - { - Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"), - SubTitle = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"), - Action = _ => - { - try + Title = c.Name, + SubTitle = c.Url, + IcoPath = @"Images\bookmark.png", + Score = BookmarkLoader.MatchProgram(c, param).Score, + Action = _ => { - context.API.CopyToClipboard(((BookmarkAttributes)selectedResult.ContextData).Url); + _context.API.OpenUrl(c.Url); return true; - } - catch (Exception e) + }, + ContextData = new BookmarkAttributes { Url = c.Url } + } + ) + .Where(r => r.Score > 0) + .ToList(); + } + else + { + return _cachedBookmarks + .Select( + c => new Result + { + Title = c.Name, + SubTitle = c.Url, + IcoPath = @"Images\bookmark.png", + Score = 5, + Action = _ => { - var message = "Failed to set url in clipboard"; - Log.Exception("Main", message, e, "LoadContextMenus"); - - context.API.ShowMsg(message); - - return false; - } - }, - IcoPath = "Images\\copylink.png", - Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8") - } - }; - } - - internal class BookmarkAttributes - { - internal string Url { get; set; } - } - - public void Dispose() - { - DisposeFileWatchers(); - } - - private static void DisposeFileWatchers() - { - foreach (var watcher in Watchers) - { - watcher.Dispose(); - } - Watchers.Clear(); + _context.API.OpenUrl(c.Url); + return true; + }, + ContextData = new BookmarkAttributes { Url = c.Url } + } + ) + .ToList(); } } + + + private static Channel _refreshQueue = Channel.CreateBounded(1); + + private static SemaphoreSlim _fileMonitorSemaphore = new(1, 1); + + private static async Task MonitorRefreshQueueAsync() + { + if (_fileMonitorSemaphore.CurrentCount < 1) + { + return; + } + await _fileMonitorSemaphore.WaitAsync(); + var reader = _refreshQueue.Reader; + while (await reader.WaitToReadAsync()) + { + if (reader.TryRead(out _)) + { + ReloadAllBookmarks(false); + } + } + _fileMonitorSemaphore.Release(); + } + + private static readonly List Watchers = new(); + + internal static void RegisterBookmarkFile(string path) + { + var directory = Path.GetDirectoryName(path); + if (!Directory.Exists(directory) || !File.Exists(path)) + { + return; + } + if (Watchers.Any(x => x.Path.Equals(directory, StringComparison.OrdinalIgnoreCase))) + { + return; + } + + var watcher = new FileSystemWatcher(directory!); + watcher.Filter = Path.GetFileName(path); + + watcher.NotifyFilter = NotifyFilters.FileName | + NotifyFilters.LastWrite | + NotifyFilters.Size; + + watcher.Changed += static (_, _) => + { + _refreshQueue.Writer.TryWrite(default); + }; + + watcher.Renamed += static (_, _) => + { + _refreshQueue.Writer.TryWrite(default); + }; + + watcher.EnableRaisingEvents = true; + + Watchers.Add(watcher); + } + + public void ReloadData() + { + ReloadAllBookmarks(); + } + + public static void ReloadAllBookmarks(bool disposeFileWatchers = true) + { + _cachedBookmarks.Clear(); + if (disposeFileWatchers) + DisposeFileWatchers(); + LoadBookmarksIfEnabled(); + } + + public string GetTranslatedPluginTitle() + { + return _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_name"); + } + + public string GetTranslatedPluginDescription() + { + return _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_description"); + } + + public Control CreateSettingPanel() + { + return new SettingsControl(_settings); + } + + public List LoadContextMenus(Result selectedResult) + { + return new List() + { + new Result + { + Title = _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"), + SubTitle = _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"), + Action = _ => + { + try + { + _context.API.CopyToClipboard(((BookmarkAttributes)selectedResult.ContextData).Url); + + return true; + } + catch (Exception e) + { + var message = "Failed to set url in clipboard"; + Log.Exception("Main", message, e, "LoadContextMenus"); + + _context.API.ShowMsg(message); + + return false; + } + }, + IcoPath = @"Images\copylink.png", + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8") + } + }; + } + + internal class BookmarkAttributes + { + internal string Url { get; set; } + } + + public void Dispose() + { + DisposeFileWatchers(); + } + + private static void DisposeFileWatchers() + { + foreach (var watcher in Watchers) + { + watcher.Dispose(); + } + Watchers.Clear(); + } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs index c2fa9d977..c738da389 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs @@ -1,22 +1,21 @@ using System.Collections.Generic; -namespace Flow.Launcher.Plugin.BrowserBookmark.Models +namespace Flow.Launcher.Plugin.BrowserBookmark.Models; + +// Source may be important in the future +public record Bookmark(string Name, string Url, string Source = "") { - // Source may be important in the future - public record Bookmark(string Name, string Url, string Source = "") + public override int GetHashCode() { - public override int GetHashCode() - { - var hashName = Name?.GetHashCode() ?? 0; - var hashUrl = Url?.GetHashCode() ?? 0; - return hashName ^ hashUrl; - } - - public virtual bool Equals(Bookmark other) - { - return other != null && Name == other.Name && Url == other.Url; - } - - public List CustomBrowsers { get; set; }= new(); + var hashName = Name?.GetHashCode() ?? 0; + var hashUrl = Url?.GetHashCode() ?? 0; + return hashName ^ hashUrl; } -} \ No newline at end of file + + public virtual bool Equals(Bookmark other) + { + return other != null && Name == other.Name && Url == other.Url; + } + + public List CustomBrowsers { get; set; } = new(); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs index 69bb56e48..74e0f299a 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs @@ -1,45 +1,44 @@ -namespace Flow.Launcher.Plugin.BrowserBookmark.Models -{ - public class CustomBrowser : BaseModel - { - private string _name; - private string _dataDirectoryPath; - private BrowserType browserType = BrowserType.Chromium; +namespace Flow.Launcher.Plugin.BrowserBookmark.Models; - public string Name +public class CustomBrowser : BaseModel +{ + private string _name; + private string _dataDirectoryPath; + private BrowserType _browserType = BrowserType.Chromium; + + public string Name + { + get => _name; + set { - get => _name; - set - { - _name = value; - OnPropertyChanged(nameof(Name)); - } - } - - public string DataDirectoryPath - { - get => _dataDirectoryPath; - set - { - _dataDirectoryPath = value; - OnPropertyChanged(nameof(DataDirectoryPath)); - } - } - - public BrowserType BrowserType - { - get => browserType; - set - { - browserType = value; - OnPropertyChanged(nameof(BrowserType)); - } + _name = value; + OnPropertyChanged(); } } - public enum BrowserType + public string DataDirectoryPath { - Chromium, - Firefox, + get => _dataDirectoryPath; + set + { + _dataDirectoryPath = value; + OnPropertyChanged(); + } + } + + public BrowserType BrowserType + { + get => _browserType; + set + { + _browserType = value; + OnPropertyChanged(); + } } } + +public enum BrowserType +{ + Chromium, + Firefox, +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs index dc1016b4e..86532d275 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs @@ -1,17 +1,16 @@ using System.Collections.ObjectModel; -namespace Flow.Launcher.Plugin.BrowserBookmark.Models +namespace Flow.Launcher.Plugin.BrowserBookmark.Models; + +public class Settings : BaseModel { - public class Settings : BaseModel - { - public bool OpenInNewBrowserWindow { get; set; } = true; + public bool OpenInNewBrowserWindow { get; set; } = true; - public string BrowserPath { get; set; } + public string BrowserPath { get; set; } - public bool LoadChromeBookmark { get; set; } = true; - public bool LoadFirefoxBookmark { get; set; } = true; - public bool LoadEdgeBookmark { get; set; } = true; + public bool LoadChromeBookmark { get; set; } = true; + public bool LoadFirefoxBookmark { get; set; } = true; + public bool LoadEdgeBookmark { get; set; } = true; - public ObservableCollection CustomChromiumBrowsers { get; set; } = new(); - } -} \ No newline at end of file + public ObservableCollection CustomChromiumBrowsers { get; set; } = new(); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml index 392c9e0a7..f5017ced5 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml @@ -63,7 +63,6 @@ +/// Interaction logic for CustomBrowserSetting.xaml +/// +public partial class CustomBrowserSettingWindow : Window { - /// - /// Interaction logic for CustomBrowserSetting.xaml - /// - public partial class CustomBrowserSettingWindow : Window + private CustomBrowser _currentCustomBrowser; + public CustomBrowserSettingWindow(CustomBrowser browser) { - private CustomBrowser currentCustomBrowser; - public CustomBrowserSettingWindow(CustomBrowser browser) + InitializeComponent(); + _currentCustomBrowser = browser; + DataContext = new CustomBrowser { - InitializeComponent(); - currentCustomBrowser = browser; - DataContext = new CustomBrowser - { - Name = browser.Name, - DataDirectoryPath = browser.DataDirectoryPath, - BrowserType = browser.BrowserType, - }; - } - - private void ConfirmEditCustomBrowser(object sender, RoutedEventArgs e) - { - CustomBrowser editBrowser = (CustomBrowser)DataContext; - currentCustomBrowser.Name = editBrowser.Name; - currentCustomBrowser.DataDirectoryPath = editBrowser.DataDirectoryPath; - currentCustomBrowser.BrowserType = editBrowser.BrowserType; - DialogResult = true; - Close(); - } + Name = browser.Name, + DataDirectoryPath = browser.DataDirectoryPath, + BrowserType = browser.BrowserType, + }; + } - private void CancelEditCustomBrowser(object sender, RoutedEventArgs e) - { - Close(); - } + private void ConfirmEditCustomBrowser(object sender, RoutedEventArgs e) + { + CustomBrowser editBrowser = (CustomBrowser)DataContext; + _currentCustomBrowser.Name = editBrowser.Name; + _currentCustomBrowser.DataDirectoryPath = editBrowser.DataDirectoryPath; + _currentCustomBrowser.BrowserType = editBrowser.BrowserType; + DialogResult = true; + Close(); + } - private void WindowKeyDown(object sender, System.Windows.Input.KeyEventArgs e) - { - if (e.Key == Key.Enter) - { - ConfirmEditCustomBrowser(sender, e); - } - } + private void CancelEditCustomBrowser(object sender, RoutedEventArgs e) + { + Close(); + } - private void OnSelectPathClick(object sender, RoutedEventArgs e) + private void WindowKeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == Key.Enter) { - var dialog = new FolderBrowserDialog(); - dialog.ShowDialog(); - CustomBrowser editBrowser = (CustomBrowser)DataContext; - editBrowser.DataDirectoryPath = dialog.SelectedPath; + ConfirmEditCustomBrowser(sender, e); } } + + private void OnSelectPathClick(object sender, RoutedEventArgs e) + { + var dialog = new FolderBrowserDialog(); + dialog.ShowDialog(); + CustomBrowser editBrowser = (CustomBrowser)DataContext; + editBrowser.DataDirectoryPath = dialog.SelectedPath; + } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs index 2947c2cb5..4bdab89a9 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs @@ -4,119 +4,116 @@ using System.Windows.Input; using System.ComponentModel; using System.Threading.Tasks; -namespace Flow.Launcher.Plugin.BrowserBookmark.Views +namespace Flow.Launcher.Plugin.BrowserBookmark.Views; + +public partial class SettingsControl : INotifyPropertyChanged { - public partial class SettingsControl : INotifyPropertyChanged + public Settings Settings { get; } + + public CustomBrowser SelectedCustomBrowser { get; set; } + + public bool LoadChromeBookmark { - public Settings Settings { get; } - - public CustomBrowser SelectedCustomBrowser { get; set; } - - public bool LoadChromeBookmark + get => Settings.LoadChromeBookmark; + set { - get => Settings.LoadChromeBookmark; - set + Settings.LoadChromeBookmark = value; + _ = Task.Run(() => Main.ReloadAllBookmarks()); + } + } + + public bool LoadFirefoxBookmark + { + get => Settings.LoadFirefoxBookmark; + set + { + Settings.LoadFirefoxBookmark = value; + _ = Task.Run(() => Main.ReloadAllBookmarks()); + } + } + + public bool LoadEdgeBookmark + { + get => Settings.LoadEdgeBookmark; + set + { + Settings.LoadEdgeBookmark = value; + _ = Task.Run(() => Main.ReloadAllBookmarks()); + } + } + + public bool OpenInNewBrowserWindow + { + get => Settings.OpenInNewBrowserWindow; + set + { + Settings.OpenInNewBrowserWindow = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OpenInNewBrowserWindow))); + } + } + + public SettingsControl(Settings settings) + { + Settings = settings; + InitializeComponent(); + } + + public event PropertyChangedEventHandler PropertyChanged; + + private void NewCustomBrowser(object sender, RoutedEventArgs e) + { + var newBrowser = new CustomBrowser(); + var window = new CustomBrowserSettingWindow(newBrowser); + window.ShowDialog(); + if (newBrowser is not { - Settings.LoadChromeBookmark = value; - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - public bool LoadFirefoxBookmark + Name: null, + DataDirectoryPath: null + }) { - get => Settings.LoadFirefoxBookmark; - set - { - Settings.LoadFirefoxBookmark = value; - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } + Settings.CustomChromiumBrowsers.Add(newBrowser); + _ = Task.Run(() => Main.ReloadAllBookmarks()); } + } - public bool LoadEdgeBookmark + private void DeleteCustomBrowser(object sender, RoutedEventArgs e) + { + if (CustomBrowsers.SelectedItem is CustomBrowser selectedCustomBrowser) { - get => Settings.LoadEdgeBookmark; - set - { - Settings.LoadEdgeBookmark = value; - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } + Settings.CustomChromiumBrowsers.Remove(selectedCustomBrowser); + _ = Task.Run(() => Main.ReloadAllBookmarks()); } + } - public bool OpenInNewBrowserWindow + private void MouseDoubleClickOnSelectedCustomBrowser(object sender, MouseButtonEventArgs e) + { + EditSelectedCustomBrowser(); + } + + private void Others_Click(object sender, RoutedEventArgs e) + { + CustomBrowsersList.Visibility = CustomBrowsersList.Visibility switch { - get => Settings.OpenInNewBrowserWindow; - set - { - Settings.OpenInNewBrowserWindow = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OpenInNewBrowserWindow))); - } - } + Visibility.Collapsed => Visibility.Visible, + _ => Visibility.Collapsed + }; + } - public SettingsControl(Settings settings) + private void EditCustomBrowser(object sender, RoutedEventArgs e) + { + EditSelectedCustomBrowser(); + } + + private void EditSelectedCustomBrowser() + { + if (SelectedCustomBrowser is null) + return; + + var window = new CustomBrowserSettingWindow(SelectedCustomBrowser); + var result = window.ShowDialog() ?? false; + if (result) { - Settings = settings; - InitializeComponent(); - } - - public event PropertyChangedEventHandler PropertyChanged; - - private void NewCustomBrowser(object sender, RoutedEventArgs e) - { - var newBrowser = new CustomBrowser(); - var window = new CustomBrowserSettingWindow(newBrowser); - window.ShowDialog(); - if (newBrowser is not - { - Name: null, - DataDirectoryPath: null - }) - { - Settings.CustomChromiumBrowsers.Add(newBrowser); - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - private void DeleteCustomBrowser(object sender, RoutedEventArgs e) - { - if (CustomBrowsers.SelectedItem is CustomBrowser selectedCustomBrowser) - { - Settings.CustomChromiumBrowsers.Remove(selectedCustomBrowser); - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - private void MouseDoubleClickOnSelectedCustomBrowser(object sender, MouseButtonEventArgs e) - { - EditSelectedCustomBrowser(); - } - - private void Others_Click(object sender, RoutedEventArgs e) - { - - if (CustomBrowsersList.Visibility == Visibility.Collapsed) - { - CustomBrowsersList.Visibility = Visibility.Visible; - } - else - CustomBrowsersList.Visibility = Visibility.Collapsed; - } - - private void EditCustomBrowser(object sender, RoutedEventArgs e) - { - EditSelectedCustomBrowser(); - } - - private void EditSelectedCustomBrowser() - { - if (SelectedCustomBrowser is null) - return; - - var window = new CustomBrowserSettingWindow(SelectedCustomBrowser); - var result = window.ShowDialog() ?? false; - if (result) - { - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } + _ = Task.Run(() => Main.ReloadAllBookmarks()); } } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json index d6dca1b88..c4a24a57b 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser Bookmarks", "Description": "Search your browser bookmarks", "Author": "qianlifeng, Ioannis G.", - "Version": "3.1.4", + "Version": "3.2.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs b/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs index 27bdf94ee..81a68739b 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using Flow.Launcher.Core.Resource; -namespace Flow.Launcher.Plugin.Caculator +namespace Flow.Launcher.Plugin.Calculator { [TypeConverter(typeof(LocalizationConverter))] public enum DecimalSeparator diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj index db731fb2c..69c03b877 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj @@ -5,8 +5,8 @@ net7.0-windows {59BD9891-3837-438A-958D-ADC7F91F6F7E} Properties - Flow.Launcher.Plugin.Caculator - Flow.Launcher.Plugin.Caculator + Flow.Launcher.Plugin.Calculator + Flow.Launcher.Plugin.Calculator true true false @@ -18,7 +18,7 @@ true portable false - ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Caculator\ + ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Calculator\ DEBUG;TRACE prompt 4 @@ -28,7 +28,7 @@ pdbonly true - ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Caculator\ + ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Calculator\ TRACE prompt 4 @@ -62,7 +62,7 @@ - + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/uk-UA.xaml index 15598118c..014755929 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/uk-UA.xaml @@ -1,15 +1,15 @@  - Calculator - Allows to do mathematical calculations.(Try 5*3-2 in Flow Launcher) - Not a number (NaN) - Expression wrong or incomplete (Did you forget some parentheses?) - Copy this number to the clipboard - Decimal separator - The decimal separator to be used in the output. - Use system locale - Comma (,) - Dot (.) - Max. decimal places + Калькулятор + Дозволяє виконувати математичні обчислення (спробуйте 5*3-2 у Flow Launcher) + Не є числом (NaN) + Вираз неправильний або неповний (Ви забули якісь дужки?) + Скопіюйте це число в буфер обміну + Десятковий роздільник + Десятковий роздільник, який буде використовуватися у виведенні. + Використовувати системну локаль + Кома (,) + Крапка (.) + Макс. кількість знаків після коми diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/vi.xaml new file mode 100644 index 000000000..82ebbbe93 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/vi.xaml @@ -0,0 +1,15 @@ + + + + Máy tính + Cho phép thực hiện các phép tính toán học. (Thử 5*3-2 trong Flow Launcher) + Không phải là số (NaN) + Biểu thức sai hoặc không đầy đủ (Bạn có quên một số dấu ngoặc đơn không?) + Sao chép số này vào clipboard + Dấu tách thập phân + Dấu phân cách thập phân được sử dụng ở đầu ra. + Sử dụng ngôn ngữ hệ thống + Dấu phẩy (,) + dấu chấm (.) + Tối đa. chữ số thập phân + diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index e2aa5860c..5077e6061 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -6,10 +6,10 @@ using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using Mages.Core; -using Flow.Launcher.Plugin.Caculator.ViewModels; -using Flow.Launcher.Plugin.Caculator.Views; +using Flow.Launcher.Plugin.Calculator.ViewModels; +using Flow.Launcher.Plugin.Calculator.Views; -namespace Flow.Launcher.Plugin.Caculator +namespace Flow.Launcher.Plugin.Calculator { public class Main : IPlugin, IPluginI18n, ISettingProvider { @@ -19,8 +19,9 @@ namespace Flow.Launcher.Plugin.Caculator @"sin|cos|tan|arcsin|arccos|arctan|" + @"eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|" + @"bin2dec|hex2dec|oct2dec|" + - @"==|~=|&&|\|\||" + - @"[ei]|[0-9]|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" + + @"factorial|sign|isprime|isinfty|" + + @"==|~=|&&|\|\||(?:\<|\>)=?|" + + @"[ei]|[0-9]|[\+\%\-\*\/\^\., ""]|[\(\)\|\!\[\]]" + @")+$", RegexOptions.Compiled); private static readonly Regex RegBrackets = new Regex(@"[\(\)\[\]]", RegexOptions.Compiled); private static Engine MagesEngine; @@ -61,7 +62,7 @@ namespace Flow.Launcher.Plugin.Caculator switch (_settings.DecimalSeparator) { case DecimalSeparator.Comma: - case DecimalSeparator.UseSystemLocale when CultureInfo.DefaultThreadCurrentCulture.NumberFormat.NumberDecimalSeparator == ",": + case DecimalSeparator.UseSystemLocale when CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator == ",": expression = query.Search.Replace(",", "."); break; default: @@ -157,7 +158,7 @@ namespace Flow.Launcher.Plugin.Caculator private string GetDecimalSeparator() { - string systemDecimalSeperator = CultureInfo.DefaultThreadCurrentCulture.NumberFormat.NumberDecimalSeparator; + string systemDecimalSeperator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; switch (_settings.DecimalSeparator) { case DecimalSeparator.UseSystemLocale: return systemDecimalSeperator; diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/NumberTranslator.cs b/Plugins/Flow.Launcher.Plugin.Calculator/NumberTranslator.cs index ed4aed75b..4eacb9d34 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/NumberTranslator.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/NumberTranslator.cs @@ -2,7 +2,7 @@ using System.Text; using System.Text.RegularExpressions; -namespace Flow.Launcher.Plugin.Caculator +namespace Flow.Launcher.Plugin.Calculator { /// /// Tries to convert all numbers in a text from one culture format to another. diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs index 615514873..8354863b8 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs @@ -1,5 +1,5 @@  -namespace Flow.Launcher.Plugin.Caculator +namespace Flow.Launcher.Plugin.Calculator { public class Settings { diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs index afe4d1c0c..09f745669 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace Flow.Launcher.Plugin.Caculator.ViewModels +namespace Flow.Launcher.Plugin.Calculator.ViewModels { public class SettingsViewModel : BaseModel { diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml index 9fd4bb17c..d6237c6da 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml @@ -1,13 +1,13 @@ /// Interaction logic for CalculatorSettings.xaml diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json index 2bba6341c..6abc41668 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json @@ -4,9 +4,9 @@ "Name": "Calculator", "Description": "Provide mathematical calculations.(Try 5*3-2 in Flow Launcher)", "Author": "cxfksword", - "Version": "3.0.3", + "Version": "3.1.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", - "ExecuteFileName": "Flow.Launcher.Plugin.Caculator.dll", + "ExecuteFileName": "Flow.Launcher.Plugin.Calculator.dll", "IcoPath": "Images\\calculator.png" } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index b4924a0fc..2297e5f96 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -54,6 +54,11 @@ namespace Flow.Launcher.Plugin.Explorer contextMenus.Add(CreateOpenContainingFolderResult(record)); + if (record.Type == ResultType.File) + { + contextMenus.Add(CreateOpenWithMenu(record)); + } + if (record.WindowsIndexed) { contextMenus.Add(CreateOpenWindowsIndexingOptions()); @@ -434,6 +439,22 @@ namespace Flow.Launcher.Plugin.Explorer }; } + private Result CreateOpenWithMenu(SearchResult record) + { + return new Result + { + Title = Context.API.GetTranslation("plugin_explorer_openwith"), + SubTitle = Context.API.GetTranslation("plugin_explorer_openwith_subtitle"), + Action = _ => + { + Process.Start("rundll32.exe", $"{Path.Combine(Environment.SystemDirectory, "shell32.dll")},OpenAs_RunDLL {record.FullPath}"); + return true; + }, + IcoPath = Constants.ShowContextMenuImagePath, + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue7ac"), + }; + } + private void LogException(string message, Exception e) { Log.Exception($"|Flow.Launcher.Plugin.Folder.ContextMenu|{message}", e); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index 1c0bdaad7..6d1497327 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -45,7 +45,7 @@ - + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ar.xaml index 52aed4b9a..ad83e6e05 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ar.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ar.xaml @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/cs.xaml index 1ab9da4b7..83334b9b1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/cs.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/cs.xaml @@ -100,7 +100,9 @@ Odstranit z Rychlého přístupu Odstranit aktuální položku z rychlého přístupu Zobrazit kontextové menu Windows - + Open With + Select a program to open with + Volných {0} z {1} Otevřít ve výchozím správci souborů diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/da.xaml index ba2152e61..9dfbb94a1 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/da.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/da.xaml @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/de.xaml index dc97980fd..1bae42c77 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/de.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/de.xaml @@ -2,18 +2,18 @@ - Please make a selection first + Zuerst eine Auswahl treffen Bitte wähle eine Ordnerverknüpfung Bist du sicher {0} zu löschen? Sind Sie sicher, dass Sie diese Datei dauerhaft löschen möchten? Are you sure you want to permanently delete this file/folder? - Deletion successful + Löschen erfolgreich Successfully deleted {0} - Assigning the global action keyword could bring up too many results during search. Please choose a specific action keyword - Quick Access can not be set to the global action keyword when enabled. Please choose a specific action keyword - The required service for Windows Index Search does not appear to be running - To fix this, start the Windows Search service. Select here to remove this warning - The warning message has been switched off. As an alternative for searching files and folders, would you like to install Everything plugin?{0}{0}Select 'Yes' to install Everything plugin, or 'No' to return + Die Zuweisung des globalen Aktions-Schlüsselwortes könnte zu viele Ergebnisse bei der Suche hervorrufen. Bitte wählen Sie ein spezielles Aktionswort + Schnellzugriff kann nicht auf das globale Aktionswort gesetzt werden, wenn aktiviert. Bitte wählen Sie ein spezielles Aktionswort + Der benötigte Dienst für Windows Indexsuche scheint nicht ausgeführt zu werden + Um dies zu beheben, starten Sie den Windows-Suchdienst. Klicken Sie hier, um diese Warnung zu entfernen + Die Warnmeldung wurde ausgeschaltet. Möchten Sie als Alternative für die Suche nach Dateien und Ordnern das Plugin Everything installieren?{0}{0}Wählen Sie 'Ja', um das Plugin Everything zu installieren, oder 'Nein', um zurückzukehren Explorer Alternative Error occurred during search: {0} Could not open folder @@ -24,28 +24,28 @@ Bearbeiten Hinzufügen General Setting - Customise Action Keywords - Quick Access Links + Aktions-Schlüsselwörter ändern + Schnellzugriff-Links Everything Setting Sort Option: Everything Path: Launch Hidden Editor pad Shell Path - Index Search Excluded Paths + Indexsuche ausgeschlossen Pfade Verwenden Suchergebnis Standort als ausführbare Arbeitsverzeichnis Hit Enter to open folder in Default File Manager Use Index Search For Path Search - Indexing Options + Indexierungsoptionen Suche: Pfad-Suche: Suche nach Dateiinhalten: Index-Suche: Schnellzugriff: - Current Action Keyword + Aktuelles Aktions-Schlüsselwort Fertig Aktiviert - When disabled Flow will not execute this search option, and will additionally revert back to '*' to free up the action keyword + Wenn diese Funktion deaktiviert ist, führt Flow diese Suchoption nicht aus und kehrt zusätzlich zu '*' zurück, um das Aktionsschlüsselwort freizugeben Everything Windows Index Direct Enumeration @@ -78,18 +78,18 @@ Ausgewählte löschen Als anderer Benutzer ausführen Ausgewählte mit einem anderen Benutzerkonto ausführen - Open containing folder + Enthaltenden Ordner öffnen Open the location that contains current item - Open With Editor: + Öffnen mit Editor: Failed to open file at {0} with Editor {1} at {2} Open With Shell: Failed to open folder {0} with Shell {1} at {2} - Exclude current and sub-directories from Index Search - Excluded from Index Search - Open Windows Indexing Options - Manage indexed files and folders - Failed to open Windows Indexing Options - Add to Quick Access + Aktuelles und Unterverzeichnisse von der Indexsuche ausschließen + Von der Indexsuche ausgeschlossen + Windows Indexoptionen öffnen + Indexierte Dateien und Ordner verwalten + Fehler beim Öffnen der Windows-Indexierungsoptionen + Zu Schnellzugriff hinzufügen Add current item to Quick Access Successfully Added Successfully added to Quick Access @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Öffnen mit + Programm zum Öffnen auswählen + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index d5352ef1e..cd4a1dc84 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -29,6 +29,12 @@ Customise Action Keywords Quick Access Links Everything Setting + Preview Panel + Size + Date Created + Date Modified + Display File Info + Date and time format Sort Option: Everything Path: Launch Hidden @@ -102,11 +108,15 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - - + Open With + Select a program to open with + + {0} free of {1} Open in Default File Manager - Use '>' to search in this directory, '*' to search for file extensions or '>*' to combine both searches. + + Use '>' to search in this directory, '*' to search for file extensions or '>*' to combine both searches. + Failed to load Everything SDK @@ -131,7 +141,7 @@ Warning: This is not a Fast Sort option, searches may be slow Search Full Path - + Click to launch or install Everything Everything Installation Installing Everything service. Please wait... diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/es-419.xaml index ff1773c71..2d23b315e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/es-419.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/es-419.xaml @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/es.xaml index 51b3e831b..ad5d7c31a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/es.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/es.xaml @@ -4,7 +4,7 @@ Por favor haga una selección primero Por favor, seleccione un enlace de carpeta - ¿Está seguro que desea eliminar {0}? + ¿Está seguro de que desea eliminar {0}? ¿Está seguro que desea eliminar permanentemente este archivo? ¿Está seguro de que desea eliminar permanentemente este/esta archivo/carpeta? Eliminación correcta @@ -100,7 +100,9 @@ Eliminar del acceso rápido Elimina elemento actual del acceso rápido Mostrar menú contextual de Windows - + Abrir con + Seleccione un programa para abrir con + {0} libre de {1} Abrir en administrador de archivos predeterminado diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/fr.xaml index e4e1f9cb2..adad69af4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/fr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/fr.xaml @@ -100,7 +100,9 @@ Supprimer de l'accès rapide Supprimer l'élément de l'accès rapide Afficher le menu contextuel de Windows - + Ouvrir avec + Sélectionnez un programme à ouvrir avec + {0} libres sur {1} Ouvrir dans le gestionnaire de fichiers par défaut diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/it.xaml index 79143d27d..32159af96 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/it.xaml @@ -5,37 +5,37 @@ Effettua prima una selezione Si prega di selezionare un collegamento alla cartella Sei sicuro di voler eliminare {0}? - Are you sure you want to permanently delete this file? - Are you sure you want to permanently delete this file/folder? + Sei sicuro di voler eliminare definitivamente questo file? + Sei sicuro di voler eliminare definitivamente questo/a file/cartella? Eliminato con successo - Successfully deleted {0} + {0} eliminato correttamente L'assegnazione della parola chiave globale potrebbe portare a troppi risultati durante la ricerca. Scegli una parola chiave specifica per l'azione L'accesso rapido non può essere impostato sulla parola chiave globale quando abilitata. Si prega di scegliere una parola chiave specifica Il servizio richiesto per Windows Index Search non sembra essere in esecuzione Per risolvere il problema, avvia il servizio Ricerca Windows. Seleziona qui per rimuovere questo avviso Il messaggio di avviso è stato spento. In alternativa per la ricerca di file e cartelle, vuoi installare il plugin Everything?{0}{0} Seleziona 'Sì' per installare il plugin Everything, o 'No' per tornare Alternativa all'Esplora Risorse - Error occurred during search: {0} - Could not open folder - Could not open file + Errore durante la ricerca: {0} + Impossibile aprire la cartella + Impossibile aprire il file Cancella Modifica Aggiungi - General Setting + Impostazione Generale Personalizza Parola Chiave Collegamenti ad Accesso Rapido - Everything Setting - Sort Option: - Everything Path: - Launch Hidden + Impostazioni di Everything + Opzioni di ordinamento: + Percorso di Everything: + Avvia nascosto Tasto di accesso rapido alla finestra - Shell Path + Percorso Shell Percorsi Esclusi dall'Indice di Ricerca Utilizza il percorso ottenuto dalla ricerca come cartella di lavoro - Hit Enter to open folder in Default File Manager - Use Index Search For Path Search + Premi Invio per aprire la cartella nel gestore file predefinito + Usa Indice di Ricerca per la Ricerca Percorso Opzioni di Indicizzazione Cerca: Ricerca Percorso: @@ -47,50 +47,50 @@ Abilitato Quando disabilitato Flow non eseguirà questa opzione di ricerca, e ripristinerà a "*" per liberare la parola chiave Tutto - Windows Index - Direct Enumeration + Indice Di Windows + Enumerazione Diretta Percorso dell'editor di file Percorso dell'editor delle cartelle - Content Search Engine - Directory Recursive Search Engine - Index Search Engine - Open Windows Index Option + Motore di Ricerca dei Contenuti + Motore di Ricerca Ricorsivo sulle Catelle + Indice del Motore di Ricerca + Apri Opzioni di Indicizzazione di Windows Esplora Risorse - Find and manage files and folders via Windows Search or Everything + Trova e gestisci file e cartelle tramite Ricerca di Windows o Everything - Ctrl + Enter to open the directory - Ctrl + Enter to open the containing folder + Ctrl + Invio per aprire la cartella + Ctrl + Invio per aprire la cartella che lo contiene Copia percorso - Copy path of current item to clipboard + Copia negli appunti il percorso dell'elemento corrente Copia - Copy current file to clipboard - Copy current folder to clipboard + Copia file corrente negli appunti + Copia cartella corrente negli appunti Cancella - Permanently delete current file - Permanently delete current folder + Elimina permanentemente il file corrente + Elimina definitivamente la cartella corrente Percorso: Elimina il selezionato Esegui come utente differente Esegui la selezione utilizzando un altro account utente Apri percorso file - Open the location that contains current item + Apri la posizione che contiene l'elemento corrente Apri nell'Editor: - Failed to open file at {0} with Editor {1} at {2} - Open With Shell: - Failed to open folder {0} with Shell {1} at {2} + Apertura del file in {0} con l'editor {1} in {2} non riuscita + Apri Con Shell: + Apertura della cartella {0} con Shell {1} in {2} non riuscita Escludi cartelle e sottocartelle dall'Indice di Ricerca Escludi dall'Indice di Ricerca Apri Opzioni di Indicizzazione di Windows Gestisci file e cartelle indicizzati Impossibile aprire le Opzioni di Indicizzazione di Windows Aggiungi ad Accesso Rapido - Add current item to Quick Access + Aggiungi elemento corrente all'Accesso Rapido Aggiunto con successo Aggiunto con successo ad Accesso Rapido Rimosso con Successo @@ -98,20 +98,22 @@ Aggiungi ad Accesso Rapido in modo che possa essere aperto con la parola chiave di ricerca dell'Esplora Risorse Rimuovi da Accesso Rapido Rimuovi da Accesso Rapido - Remove current item from Quick Access - Show Windows Context Menu - + Rimuovi elemento corrente dall'Accesso Rapido + Mostra Menu Contestuale di Windows + Apri Con + Seleziona un programma con cui aprire + - {0} free of {1} - Open in Default File Manager - Use '>' to search in this directory, '*' to search for file extensions or '>*' to combine both searches. + {0} disponibili su {1} + Aprire con il Gestore File Predefinito + Usa '>' per cercare in questa directory, '*' per cercare estensioni file o '>*' per combinare entrambe le ricerche. - Failed to load Everything SDK + Impossibile caricare l'SDK di Everything Attenzione: Il servizio "Everything" non è in esecuzione Errore nell'interrogazione di Everything Ordina per - Name + Nome Percorso Dimensioni Estensione @@ -128,16 +130,16 @@ Attenzione: Questa non è un'opzione di ordinamento rapido, le ricerche potrebbero essere lente - Search Full Path + Cerca Percorso Completo - Click to launch or install Everything + Clicca per avviare o installare Everything Installazione di Everything Installazione di everything. Si prega di attendere... Everything è stato installato con successo Impossibile installare automaticamente il servizio Everything. Si prega di installarlo manualmente da https://www.voidtools.com Premi per avviare Impossibile trovare l'installazione di Everything, vuoi inserire manualmente un percorso? {0} {0} Premi no per installare automaticamente Everything - Do you want to enable content search for Everything? - It can be very slow without index (which is only supported in Everything v1.5+) + Vuoi abilitare la ricerca di contenuti per Everything? + Può essere molto lento senza indice (che è supportato solo in Everything v1.5+) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ja.xaml index bb831b003..d582424ed 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ja.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ja.xaml @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ko.xaml index 627338473..37a096f09 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ko.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ko.xaml @@ -100,7 +100,9 @@ 빠른 접근에서 제거 이 항목을 빠른 접근에서 제거 우클릭 메뉴 보기 - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/nb.xaml index 0fe4db467..a5fd6c0c4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/nb.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/nb.xaml @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/nl.xaml index 3649a2308..a7a993b37 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/nl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/nl.xaml @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pl.xaml index d42f1067e..13d9cf731 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pl.xaml @@ -2,10 +2,10 @@ - Please make a selection first + Pierw dokonaj wyboru Musisz wybrać któryś folder z listy Czy jesteś pewien że chcesz usunąć {0}? - Are you sure you want to permanently delete this file? + Jesteś pewny, że chcesz usunąć ten plik trwale? Are you sure you want to permanently delete this file/folder? Deletion successful Successfully deleted {0} @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pt-br.xaml index 4b8865b84..8e5761345 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pt-br.xaml @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pt-pt.xaml index b3d772498..193ae0dd9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pt-pt.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/pt-pt.xaml @@ -100,7 +100,9 @@ Remover do acesso rápido Remover item do Acesso rápido Mostrar menu de contexto do Windows - + Abrir com + Selecione o programa a utilizar + {0} livre de {1} no total Abrir no gestor de ficheiros padrão diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ru.xaml index 73bd620e9..ccdba5da9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ru.xaml @@ -3,21 +3,21 @@ Сначала отметьте что-нибудь - Please select a folder link + Пожалуйста, выберите ссылку на папку Вы уверены, что хотите удалить {0}? Вы действительно хотите безвозвратно удалить этот файл? Вы действительно хотите безвозвратно удалить этот файл/папку? Удаление завершено Успешно удалено {0} - Assigning the global action keyword could bring up too many results during search. Please choose a specific action keyword + Назначение ключевого слова глобальных действий может привести к слишком большому количеству результатов поиска. Пожалуйста, выберите конкретное ключевое слово действий Quick Access can not be set to the global action keyword when enabled. Please choose a specific action keyword The required service for Windows Index Search does not appear to be running To fix this, start the Windows Search service. Select here to remove this warning The warning message has been switched off. As an alternative for searching files and folders, would you like to install Everything plugin?{0}{0}Select 'Yes' to install Everything plugin, or 'No' to return Explorer Alternative - Error occurred during search: {0} - Could not open folder - Could not open file + При поиске произошла ошибка: {0} + Не удалось открыть папку + Не удалось открыть файл Удалить @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/sk.xaml index 47aa83ffd..e926735b8 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/sk.xaml @@ -100,7 +100,9 @@ Odstráni z Rýchleho prístupu Odstrániť aktuálnu položku z Rýchleho prístupu Zobraziť kontextovú ponuku Windowsu - + Otvoriť s + Vyberte program, ktorým chcete otvoriť súbor + Voľných {0} z {1} Otvoriť v predvolenom správcovi súborov diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/sr.xaml index 38aa39f94..2b6f087af 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/sr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/sr.xaml @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/tr.xaml index b91297563..2c90a7720 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/tr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/tr.xaml @@ -100,7 +100,9 @@ Remove from Quick Access Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/uk-UA.xaml index 6d5ca1bb4..9909bf9c5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/uk-UA.xaml @@ -2,142 +2,144 @@ - Please make a selection first - Please select a folder link - Are you sure you want to delete {0}? - Are you sure you want to permanently delete this file? - Are you sure you want to permanently delete this file/folder? - Deletion successful - Successfully deleted {0} - Assigning the global action keyword could bring up too many results during search. Please choose a specific action keyword - Quick Access can not be set to the global action keyword when enabled. Please choose a specific action keyword - The required service for Windows Index Search does not appear to be running - To fix this, start the Windows Search service. Select here to remove this warning - The warning message has been switched off. As an alternative for searching files and folders, would you like to install Everything plugin?{0}{0}Select 'Yes' to install Everything plugin, or 'No' to return - Explorer Alternative - Error occurred during search: {0} - Could not open folder - Could not open file + Будь ласка, спочатку зробіть вибір + Будь ласка, оберіть посилання на теку + Ви впевнені, що хочете видалити {0}? + Ви впевнені, що хочете назавжди видалити цей файл? + Ви впевнені, що хочете назавжди видалити цей файл/теку? + Видалення виконано успішно + Успішно видалено {0} + Використання глобального ключового слова дії може призвести до надмірної кількості результатів під час пошуку. Будь ласка, виберіть конкретне ключове слово дії + Швидкий доступ не може бути встановлений на глобальне ключове слово дії, коли він увімкнений. Будь ласка, виберіть конкретне ключове слово дії + Здається, не запущено потрібну службу для індексного пошуку Windows + Щоб виправити це, запустіть службу пошуку Windows. Натисніть тут, щоб прибрати це попередження + Попередження вимкнено. Як альтернативу для пошуку файлів і папок, чи бажаєте ви встановити плагін Everything? {0}{0}Виберіть "Так", щоб встановити плагін Everything, або "Ні", щоб повернутися + Альтернативний провідник + Виникла помилка під час пошуку: {0} + Не вдалося відкрити папку + Не вдалося відкрити файл Видалити Редагувати Додати - General Setting - Customise Action Keywords - Quick Access Links - Everything Setting - Sort Option: - Everything Path: - Launch Hidden - Editor Path - Shell Path - Index Search Excluded Paths - Use search result's location as the working directory of the executable - Hit Enter to open folder in Default File Manager - Use Index Search For Path Search - Indexing Options - Search: - Path Search: - File Content Search: - Index Search: - Quick Access: - Current Action Keyword + Загальні налаштування + Налаштувати ключові слова дії + Посилання швидкого доступу + Налаштування Everything + Варіант сортування: + Шлях до Everything: + Запустити приховано + Шлях до редактора + Шлях до оболонки Shell + Виключені шляхи індексного пошуку + Використовувати розташування результату пошуку як робочу директорію виконуваного файлу + Натисніть Enter, щоб відкрити папку у файловому менеджері за замовчуванням + Використовуйте індексний пошук для пошуку шляху + Параметри індексації + Пошук: + Пошук шляху: + Пошук вмісту файлу: + Індексний пошук: + Швидкий доступ: + Поточне ключове слово дії Готово - Enabled - When disabled Flow will not execute this search option, and will additionally revert back to '*' to free up the action keyword + Увімкнено + Якщо вимкнено, Flow не виконуватиме цю опцію пошуку і додатково повернеться до '*', щоб звільнити ключове слово дії Everything - Windows Index - Direct Enumeration + Індекс Windows + Пряме перерахування Шлях редактора файлів Шлях редактора папок - Content Search Engine - Directory Recursive Search Engine - Index Search Engine - Open Windows Index Option + Пошукова система контенту + Рекурсивна пошукова система за каталогом + Пошукова система індексів + Відкрити параметри індексування Windows - Explorer - Find and manage files and folders via Windows Search or Everything + Провідник + Пошук і керування файлами та папками за допомогою програми "Пошук" або "Everything" - Ctrl + Enter to open the directory - Ctrl + Enter to open the containing folder + Ctrl + Enter, щоб відкрити каталог + Ctrl + Enter, щоб відкрити відповідну папку - Copy path - Copy path of current item to clipboard - Copy - Copy current file to clipboard - Copy current folder to clipboard + Копіювати шлях + Копіювати шлях до поточного елемента в буфер обміну + Копіювати + Копіювання поточного файлу в буфер обміну + Копіювати поточну папку в буфер обміну Видалити - Permanently delete current file - Permanently delete current folder - Path: - Delete the selected - Run as different user - Run the selected using a different user account - Open containing folder - Open the location that contains current item - Open With Editor: - Failed to open file at {0} with Editor {1} at {2} - Open With Shell: - Failed to open folder {0} with Shell {1} at {2} - Exclude current and sub-directories from Index Search - Excluded from Index Search - Open Windows Indexing Options - Manage indexed files and folders - Failed to open Windows Indexing Options - Add to Quick Access - Add current item to Quick Access - Successfully Added - Successfully added to Quick Access - Successfully Removed - Successfully removed from Quick Access - Add to Quick Access so it can be opened with Explorer's Search Activation action keyword - Remove from Quick Access - Remove from Quick Access - Remove current item from Quick Access - Show Windows Context Menu - + Безповоротно видалити поточний файл + Назавжди видалити поточну папку + Шлях: + Видалити вибране + Запустити від імені іншого користувача + Запустити вибране під обліковим записом іншого користувача + Відкрити папку + Відкрийте місце, яке містить поточний елемент + Відкрити за допомогою редактора: + Не вдалося відкрити файл за адресою {0} за допомогою редактора {1} на {2} + Відкрий за допомогою Shell: + Не вдалося відкрити папку {0} за допомогою Shell {1} за адресою {2} + Виключити поточні та вкладені каталоги з індексного пошуку + Виключено з індексного пошуку + Відкрити параметри індексування Windows + Керування проіндексованими файлами та папками + Не вдалося відкрити параметри індексування Windows + Додати до Швидкого доступу + Додати поточний елемент до швидкого доступу + Успішно додано + Успішно додано до швидкого доступу + Успішно вилучено + Успішно видалено з швидкого доступу + Додати до швидкого доступу, щоб його можна було відкрити за допомогою ключового слова дії "Активація пошуку" у Провіднику + Видалити зі швидкого доступу + Видалити зі швидкого доступу + Видалити поточний елемент зі швидкого доступу + Показати контекстне меню Windows + Відкрити за допомогою + Виберіть програму для відкриття + - {0} free of {1} - Open in Default File Manager - Use '>' to search in this directory, '*' to search for file extensions or '>*' to combine both searches. + {0} не містить {1} + Відкрити у файловому менеджері за замовчуванням + Використовуйте '>' для пошуку в цьому каталозі, '*' для пошуку за розширеннями файлів або '>*' для поєднання обох варіантів пошуку. - Failed to load Everything SDK - Warning: Everything service is not running - Error while querying Everything - Sort By - Name - Path - Size - Extension - Type Name - Date Created - Date Modified - Attributes - File List FileName - Run Count - Date Recently Changed - Date Accessed - Date Run + Не вдалося завантажити Everything SDK + Попередження: Служба Everything не запущена + Помилка при виконанні запиту Everything + Сортувати за + Назва + Шлях + Розмір + Розширення + Назва типу + Дата створення + Дата останньої зміни + Атрибути + Список файлів FileName + Кількість запусків + Дата нещодавно змінена + Дата доступу + Дата запуску - Warning: This is not a Fast Sort option, searches may be slow + Попередження: Це не швидке сортування, пошук може бути повільним - Search Full Path + Шукати повний шлях - Click to launch or install Everything - Everything Installation - Installing Everything service. Please wait... - Successfully installed Everything service - Failed to automatically install Everything service. Please manually install it from https://www.voidtools.com - Click here to start it - Unable to find an Everything installation, would you like to manually select a location?{0}{0}Click no and Everything will be automatically installed for you - Do you want to enable content search for Everything? - It can be very slow without index (which is only supported in Everything v1.5+) + Натисніть, щоб запустити або встановити Everything + Встановлення програми Everything + Встановлення служби Everything. Будь ласка, зачекайте... + Успішно встановлено сервіс Everything + Не вдалося автоматично встановити службу Everything. Будь ласка, встановіть її вручну з https://www.voidtools.com + Натисніть тут, щоб розпочати + Не вдалося знайти інсталяцію Everything, бажаєте вручну вибрати розташування? {0}{0}Натисніть "ні", і Everything буде автоматично інстальовано для вас + Бажаєте увімкнути пошук контенту для Everything? + Без індексу (який підтримується лише у версії Everything v1.5+) воно може працювати дуже повільно diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/vi.xaml new file mode 100644 index 000000000..ee69ba9a3 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/vi.xaml @@ -0,0 +1,145 @@ + + + + + Vui lòng lựa chọn trước + Hãy chọn một thư mục. + Bạn có chắc chắn muốn xóa {0} không? + Bạn có chắc là muốn xóa vĩnh viễn các ảnh này? + Bạn có chắc là muốn xóa vĩnh viễn các ảnh này? + Xóa thành công + Thành công trong việc xóa + Việc chỉ định từ khóa hành động chung có thể mang lại quá nhiều kết quả trong quá trình tìm kiếm. Vui lòng chọn từ khóa hành động cụ thể + Không thể đặt Truy cập nhanh thành từ khóa hành động chung khi được bật. Vui lòng chọn từ khóa hành động cụ thể + Dịch vụ cần thiết cho Windows Index Search dường như không chạy + Để khắc phục điều này, hãy khởi động dịch vụ Windows Search. Chọn vào đây để xóa cảnh báo này + Thông báo cảnh báo đã bị tắt. Là một giải pháp thay thế cho việc tìm kiếm tệp và thư mục, bạn có muốn cài đặt plugin Mọi thứ không?{0}{0}Chọn 'Có' để cài đặt plugin Mọi thứ hoặc 'Không' để quay lại + Nhà thám hiểm thay thế + Đã xảy ra lỗi trong quá trình tìm kiếm: {0} + Không thể mở thư mục + Không thể mở file + + + Xóa + Sửa + Thêm + Cài đặt chung + Tùy chỉnh từ khóa hành động + Liên kết truy cập nhanh + Cài đặt mọi thứ + Tùy Chọn Sắp Xếp + Đường dẫn mọi thứ: + Khởi chạy ẩn + Đường dẫn soạn thảo + Đường dẫn Shell + Đường dẫn bị loại trừ tìm kiếm chỉ mục + Sử dụng vị trí của kết quả tìm kiếm làm thư mục làm việc của tệp thực thi + Chạy với tư cách quản trị viên / mở thư mục trong trình quản lý tệp tiêu chuẩn + Sử dụng Tìm kiếm Chỉ mục để Tìm kiếm Đường dẫn + Tùy chọn lập chỉ mục + Tìm kiếm trên web với sự hỗ trợ của công cụ tìm kiếm khác + Tìm kiếm đường dẫn: + Tìm kiếm nội dung tệp: + Tìm kiếm chỉ mục: + Truy cập nhanh + Từ hành động hiện tại + Xong + Đã bật + Khi bị tắt, Flow sẽ không thực thi tùy chọn tìm kiếm này và sẽ hoàn nguyên về '*' để giải phóng từ khóa hành động + Tất cả mọi thứ + Windows Index + Direct Enumeration + File Editor Path + Folder Editor Path + + Content Search Engine + Directory Recursive Search Engine + Index Search Engine + Mở tùy chọn lập chỉ mục Windows + + + Thư mục + Find and manage files and folders via Windows Search or Everything + + + Ctrl + Enter to open the directory + Ctrl + Enter to open the containing folder + + + Copy đường dẫn + Copy path of current item to clipboard + Sao chép + Copy current file to clipboard + Copy current folder to clipboard + Xóa + Permanently delete current file + Permanently delete current folder + Đường dẫn: + Xóa đã chọn + Xóa lựa chọn đã chọn + Chạy phần đã chọn bằng tài khoản người dùng khác + Mở thư mục chứa + Open the location that contains current item + Mở bằng trình chỉnh sửa: + Failed to open file at {0} with Editor {1} at {2} + Open With Shell: + Failed to open folder {0} with Shell {1} at {2} + Loại trừ các thư mục hiện tại và thư mục con khỏi Tìm kiếm chỉ mục + Bị loại trừ khỏi Tìm kiếm chỉ mục + Mở tùy chọn lập chỉ mục Windows + Quản lý các tập tin và thư mục được lập chỉ mục + Quản lý các tập tin và thư mục được lập chỉ mục + Thêm vào Bảng truy cập nhanh + Thêm {0} hiện tại vào Truy cập nhanh + Đã thêm thành công + Đã thêm thành công vào Truy cập nhanh + Đã được gỡ bỏ thành công + Đã xóa thành công khỏi Truy cập nhanh + Thêm vào Truy cập nhanh để có thể mở nó bằng từ khóa hành động Kích hoạt tìm kiếm của Explorer + Loại bỏ khỏi bảng truy cập nhanh + Loại bỏ khỏi bảng truy cập nhanh + Xóa {0} hiện tại khỏi Truy cập nhanh + Show Windows Context Menu + Mở bằng + Select a program to open with + + + {0} phần {1} + Trình quản lý tệp mặc định + Use '>' to search in this directory, '*' to search for file extensions or '>*' to combine both searches. + + + Failed to load Everything SDK + Warning: Everything service is not running + Error while querying Everything + Sắp xếp theo + Tên + Đường dẫn + Kích thước + Mở rộng + Loại tên + Ngày Tạo + ngày sửa đổi + Thuộc tính + File List FileName + Số lần chạy + Date Recently Changed + Ngày truy cập + Date Run + + + Warning: This is not a Fast Sort option, searches may be slow + + Search Full Path + + Click to launch or install Everything + Everything Installation + Installing Everything service. Please wait... + Successfully installed Everything service + Failed to automatically install Everything service. Please manually install it from https://www.voidtools.com + Click here to start it + Không thể tìm thấy bản cài đặt Mọi thứ, bạn có muốn chọn vị trí theo cách thủ công không?{0}{0}Nhấp vào không và Mọi thứ sẽ được cài đặt tự động cho bạn + Do you want to enable content search for Everything? + It can be very slow without index (which is only supported in Everything v1.5+) + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-cn.xaml index 15dc9cea4..2899c77c6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-cn.xaml @@ -100,7 +100,9 @@ 从快速访问中删除 从快速访问中移除当前结果 显示 Windows 上下文菜单 - + Open With + Select a program to open with + {0} 可用,共 {1} 在默认文件管理器中打开 diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-tw.xaml index 8f63eb659..d60f1f09d 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-tw.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/zh-tw.xaml @@ -100,7 +100,9 @@ 從快速訪問中移除 Remove current item from Quick Access Show Windows Context Menu - + Open With + Select a program to open with + {0} free of {1} Open in Default File Manager diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs index cdd2c93e6..85b595390 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs @@ -13,20 +13,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks if (string.IsNullOrEmpty(query.Search)) return new List(); - string search = query.Search.ToLower(); - - var queriedAccessLinks = - accessLinks - .Where(x => x.Name.Contains(search, StringComparison.OrdinalIgnoreCase) || x.Path.Contains(search, StringComparison.OrdinalIgnoreCase)) + return accessLinks + .Where(x => Main.Context.API.FuzzySearch(query.Search, x.Name).IsSearchPrecisionScoreMet() || Main.Context.API.FuzzySearch(query.Search, x.Path).IsSearchPrecisionScoreMet()) .OrderBy(x => x.Type) - .ThenBy(x => x.Name); - - return queriedAccessLinks.Select(l => l.Type switch - { - ResultType.Folder => ResultManager.CreateFolderResult(l.Name, l.Path, l.Path, query, quickAccessResultScore), - ResultType.File => ResultManager.CreateFileResult(l.Path, query, quickAccessResultScore), - _ => throw new ArgumentOutOfRangeException() - }).ToList(); + .ThenBy(x => x.Name) + .Select(l => l.Type switch + { + ResultType.Folder => ResultManager.CreateFolderResult(l.Name, l.Path, l.Path, query, quickAccessResultScore), + ResultType.File => ResultManager.CreateFileResult(l.Path, query, quickAccessResultScore), + _ => throw new ArgumentOutOfRangeException() + }) + .ToList(); } internal static List AccessLinkListAll(Query query, IEnumerable accessLinks) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index b55c32fee..d5b731cc4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -8,11 +8,15 @@ using System.Threading.Tasks; using System.Windows; using Flow.Launcher.Plugin.Explorer.Search.Everything; using System.Windows.Input; +using Path = System.IO.Path; +using System.Windows.Controls; +using Flow.Launcher.Plugin.Explorer.Views; namespace Flow.Launcher.Plugin.Explorer.Search { public static class ResultManager { + private static readonly string[] SizeUnits = { "B", "KB", "MB", "GB", "TB" }; private static PluginInitContext Context; private static Settings Settings { get; set; } @@ -180,36 +184,28 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } - private static string ToReadableSize(long pDrvSize, int pi) + internal static string ToReadableSize(long sizeOnDrive, int pi) { - int mok = 0; - double drvSize = pDrvSize; - string uom = "Byte"; // Unit Of Measurement + var unitIndex = 0; + double readableSize = sizeOnDrive; - while (drvSize > 1024.0) + while (readableSize > 1024.0 && unitIndex < SizeUnits.Length - 1) { - drvSize /= 1024.0; - mok++; + readableSize /= 1024.0; + unitIndex++; } - if (mok == 1) - uom = "KB"; - else if (mok == 2) - uom = " MB"; - else if (mok == 3) - uom = " GB"; - else if (mok == 4) - uom = " TB"; + var unit = SizeUnits[unitIndex] ?? ""; - var returnStr = $"{Convert.ToInt32(drvSize)}{uom}"; - if (mok != 0) + var returnStr = $"{Convert.ToInt32(readableSize)} {unit}"; + if (unitIndex != 0) { returnStr = pi switch { - 1 => $"{drvSize:F1}{uom}", - 2 => $"{drvSize:F2}{uom}", - 3 => $"{drvSize:F3}{uom}", - _ => $"{Convert.ToInt32(drvSize)}{uom}" + 1 => $"{readableSize:F1} {unit}", + 2 => $"{readableSize:F2} {unit}", + 3 => $"{readableSize:F3} {unit}", + _ => $"{Convert.ToInt32(readableSize)} {unit}" }; } @@ -244,6 +240,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search bool isMedia = IsMedia(Path.GetExtension(filePath)); var title = Path.GetFileName(filePath); + + /* Preview Detail */ + var result = new Result { Title = title, @@ -259,6 +258,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData, Score = score, CopyText = filePath, + PreviewPanel = new Lazy(() => new PreviewPanel(Settings, filePath)), Action = c => { try diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 137ba2f19..088bcf5d6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -55,6 +55,16 @@ namespace Flow.Launcher.Plugin.Explorer public bool WarnWindowsSearchServiceOff { get; set; } = true; + public bool ShowFileSizeInPreviewPanel { get; set; } = true; + + public bool ShowCreatedDateInPreviewPanel { get; set; } = true; + + public bool ShowModifiedDateInPreviewPanel { get; set; } = true; + + public string PreviewPanelDateFormat { get; set; } = "yyyy-MM-dd"; + + public string PreviewPanelTimeFormat { get; set; } = "HH:mm"; + private EverythingSearchManager _everythingManagerInstance; private WindowsIndexSearchManager _windowsIndexSearchManager; @@ -137,7 +147,7 @@ namespace Flow.Launcher.Plugin.Explorer ContentSearchEngine == ContentIndexSearchEngineOption.Everything; public bool EverythingSearchFullPath { get; set; } = false; - + #endregion internal enum ActionKeyword diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 02199fb5a..064795b43 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Linq; using System.Windows; @@ -101,7 +102,107 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels #endregion + #region Preview Panel + public bool ShowFileSizeInPreviewPanel + { + get => Settings.ShowFileSizeInPreviewPanel; + set + { + Settings.ShowFileSizeInPreviewPanel = value; + OnPropertyChanged(); + } + } + + public bool ShowCreatedDateInPreviewPanel + { + get => Settings.ShowCreatedDateInPreviewPanel; + set + { + Settings.ShowCreatedDateInPreviewPanel = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ShowPreviewPanelDateTimeChoices)); + OnPropertyChanged(nameof(PreviewPanelDateTimeChoicesVisibility)); + } + } + + public bool ShowModifiedDateInPreviewPanel + { + get => Settings.ShowModifiedDateInPreviewPanel; + set + { + Settings.ShowModifiedDateInPreviewPanel = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ShowPreviewPanelDateTimeChoices)); + OnPropertyChanged(nameof(PreviewPanelDateTimeChoicesVisibility)); + } + } + + public string PreviewPanelDateFormat + { + get => Settings.PreviewPanelDateFormat; + set + { + Settings.PreviewPanelDateFormat = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(PreviewPanelDateFormatDemo)); + } + } + + public string PreviewPanelTimeFormat + { + get => Settings.PreviewPanelTimeFormat; + set + { + Settings.PreviewPanelTimeFormat = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(PreviewPanelTimeFormatDemo)); + } + } + + public string PreviewPanelDateFormatDemo => DateTime.Now.ToString(PreviewPanelDateFormat, CultureInfo.CurrentCulture); + public string PreviewPanelTimeFormatDemo => DateTime.Now.ToString(PreviewPanelTimeFormat, CultureInfo.CurrentCulture); + + public bool ShowPreviewPanelDateTimeChoices => ShowCreatedDateInPreviewPanel || ShowModifiedDateInPreviewPanel; + + public Visibility PreviewPanelDateTimeChoicesVisibility => ShowCreatedDateInPreviewPanel || ShowModifiedDateInPreviewPanel ? Visibility.Visible : Visibility.Collapsed; + + + public List TimeFormatList { get; } = new() + { + "h:mm", + "hh:mm", + "H:mm", + "HH:mm", + "tt h:mm", + "tt hh:mm", + "h:mm tt", + "hh:mm tt", + "hh:mm:ss tt", + "HH:mm:ss" + }; + + + public List DateFormatList { get; } = new() + { + "dd/MM/yyyy", + "dd/MM/yyyy ddd", + "dd/MM/yyyy, dddd", + "dd-MM-yyyy", + "dd-MM-yyyy ddd", + "dd-MM-yyyy, dddd", + "dd.MM.yyyy", + "dd.MM.yyyy ddd", + "dd.MM.yyyy, dddd", + "MM/dd/yyyy", + "MM/dd/yyyy ddd", + "MM/dd/yyyy, dddd", + "yyyy-MM-dd", + "yyyy-MM-dd ddd", + "yyyy-MM-dd, dddd", + }; + + #endregion #region ActionKeyword diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index b0708b793..5c92fc271 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -348,6 +348,57 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml new file mode 100644 index 000000000..b5cfd1a5d --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs new file mode 100644 index 000000000..878832e4f --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs @@ -0,0 +1,101 @@ +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Flow.Launcher.Infrastructure.Image; +using Flow.Launcher.Plugin.Explorer.Search; + +namespace Flow.Launcher.Plugin.Explorer.Views; + +#nullable enable + +public partial class PreviewPanel : UserControl, INotifyPropertyChanged +{ + private string FilePath { get; } + public string FileSize { get; } = ""; + public string CreatedAt { get; } = ""; + public string LastModifiedAt { get; } = ""; + private ImageSource _previewImage = new BitmapImage(); + private Settings Settings { get; } + + public ImageSource PreviewImage + { + get => _previewImage; + private set + { + _previewImage = value; + OnPropertyChanged(); + } + } + + public Visibility FileSizeVisibility => Settings.ShowFileSizeInPreviewPanel + ? Visibility.Visible + : Visibility.Collapsed; + public Visibility CreatedAtVisibility => Settings.ShowCreatedDateInPreviewPanel + ? Visibility.Visible + : Visibility.Collapsed; + public Visibility LastModifiedAtVisibility => Settings.ShowModifiedDateInPreviewPanel + ? Visibility.Visible + : Visibility.Collapsed; + + public Visibility FileInfoVisibility => + Settings.ShowFileSizeInPreviewPanel || + Settings.ShowCreatedDateInPreviewPanel || + Settings.ShowModifiedDateInPreviewPanel + ? Visibility.Visible + : Visibility.Collapsed; + + public PreviewPanel(Settings settings, string filePath) + { + InitializeComponent(); + + Settings = settings; + + FilePath = filePath; + + if (Settings.ShowFileSizeInPreviewPanel) + { + var fileSize = new FileInfo(filePath).Length; + FileSize = ResultManager.ToReadableSize(fileSize, 2); + } + + if (Settings.ShowCreatedDateInPreviewPanel) + { + CreatedAt = File + .GetCreationTime(filePath) + .ToString( + $"{Settings.PreviewPanelDateFormat} {Settings.PreviewPanelTimeFormat}", + CultureInfo.CurrentCulture + ); + } + + if (Settings.ShowModifiedDateInPreviewPanel) + { + LastModifiedAt = File + .GetLastWriteTime(filePath) + .ToString( + $"{Settings.PreviewPanelDateFormat} {Settings.PreviewPanelTimeFormat}", + CultureInfo.CurrentCulture + ); + } + + _ = LoadImageAsync(); + } + + private async Task LoadImageAsync() + { + PreviewImage = await ImageLoader.LoadAsync(FilePath, true).ConfigureAwait(false); + } + + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json index 7dfa1656c..60c1b11b5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json @@ -10,7 +10,7 @@ "Name": "Explorer", "Description": "Find and manage files and folders via Windows Search or Everything", "Author": "Jeremy Wu", - "Version": "3.1.3", + "Version": "3.1.6", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/it.xaml index 9590cdf33..6cea2bb55 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/it.xaml @@ -1,7 +1,7 @@  - Activate {0} plugin action keyword + Attiva la parola chiave del plugin {0} Indicatore Plugin Fornisce suggerimenti sulle parole d'azione dei plugin diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/uk-UA.xaml index 893948d3d..e5398e6e0 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/uk-UA.xaml @@ -1,9 +1,9 @@  - Activate {0} plugin action keyword + Активувати {0} ключове слово дії плагіна - Plugin Indicator - Provides plugins action words suggestions + Індикатор плагіну + Надає плагінам пропозиції щодо слів дії diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/vi.xaml new file mode 100644 index 000000000..58fcba3eb --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/vi.xaml @@ -0,0 +1,9 @@ + + + + Activate {0} plugin action keyword + + Chỉ báo plugin + Cung cấp plugin gợi ý từ hành động + + diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json index 98aac405a..d682ee7c8 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json @@ -4,7 +4,7 @@ "Name": "Plugin Indicator", "Description": "Provides plugin action keyword suggestions", "Author": "qianlifeng", - "Version": "3.0.2", + "Version": "3.0.3", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginIndicator.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs index 580954f3c..17e9fe2bc 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ContextMenu.cs @@ -13,6 +13,10 @@ namespace Flow.Launcher.Plugin.PluginsManager Context = context; } + private readonly GlyphInfo sourcecodeGlyph = new("/Resources/#Segoe Fluent Icons","\uE943"); + private readonly GlyphInfo issueGlyph = new("/Resources/#Segoe Fluent Icons", "\ued15"); + private readonly GlyphInfo manifestGlyph = new("/Resources/#Segoe Fluent Icons", "\uea37"); + public List LoadContextMenus(Result selectedResult) { if(selectedResult.ContextData is not UserPlugin pluginManifestInfo) @@ -36,6 +40,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_title"), SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_gotosourcecode_subtitle"), IcoPath = "Images\\sourcecode.png", + Glyph = sourcecodeGlyph, Action = _ => { Context.API.OpenUrl(pluginManifestInfo.UrlSourceCode); @@ -47,6 +52,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_title"), SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_newissue_subtitle"), IcoPath = "Images\\request.png", + Glyph = issueGlyph, Action = _ => { // standard UrlSourceCode format in PluginsManifest's plugins.json file: https://github.com/jjw24/Flow.Launcher.Plugin.Putty/tree/master @@ -63,6 +69,7 @@ namespace Flow.Launcher.Plugin.PluginsManager Title = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_title"), SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_plugin_contextmenu_pluginsmanifest_subtitle"), IcoPath = "Images\\manifestsite.png", + Glyph = manifestGlyph, Action = _ => { Context.API.OpenUrl("https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest"); diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index 51882a20e..b438305d6 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -6,6 +6,7 @@ true true false + en @@ -27,7 +28,7 @@ PreserveNewest - + PreserveNewest @@ -36,8 +37,4 @@ PreserveNewest - - - - - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png new file mode 100644 index 000000000..5d3ed0ace Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png differ diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ar.xaml index 2f31b62cd..e13a857a1 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ar.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ar.xaml @@ -6,7 +6,9 @@ Successfully downloaded {0} Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/cs.xaml index e2303c925..3c377c27f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/cs.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/cs.xaml @@ -6,7 +6,9 @@ Úspěšně staženo {0} Chyba: Nepodařilo se stáhnout plugin {0} z {1} {2}{3}Chcete odinstalovat tento plugin? Flow se po odinstalování automaticky restartuje. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} z {1} {2}{3}Chcete nainstalovat tento plugin? Po instalaci se Flow automaticky restartuje. + {0} by {1} {2}{2}Would you like to install this plugin? Instalovat plugin Instaluje se plugin Stáhnout a nainstalovat {0} @@ -16,19 +18,30 @@ Chyba: Zásuvný modul se stejnou nebo vyšší verzí než {0} již existuje. Chyba instalace pluginu Došlo k chybě při pokusu o instalaci {0} + Error uninstalling plugin Nejsou dostupné žádné aktualizace Všechny pluginy jsou aktuální {0} z {1} {2}{3}Chcete tento zásuvný modul aktualizovat? Flow se po aktualizaci automaticky restartuje. + {0} by {1} {2}{2}Would you like to update this plugin? Aktualizace Pluginu - Aktualizace tohoto pluginu je k dispozici, chcete ji zobrazit? Tento plugin je již nainstalován Stahování manifestu pluginu se nezdařilo Zkontrolujte, zda se můžete připojit k webu github.com. Tato chyba pravděpodobně znamená, že nemůžete instalovat nebo aktualizovat zásuvné moduly. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Instalace z neznámého zdroje Tento plugin instalujete z neznámého zdroje a může obsahovat potenciální rizika!{0}{0}Ujistěte se, že víte, odkud tento plugin pochází a že je bezpečný.{0}{0}Chcete pokračovat?{0}{0}(Toto varování můžete vypnout v nastavení) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Správce pluginů Správa instalace, odinstalace nebo aktualizace pluginů Flow Launcheru @@ -46,4 +59,5 @@ Upozornění na instalaci z neznámého zdroje + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/da.xaml index e1aa21eca..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/da.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/da.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/de.xaml index e1aa21eca..c69c3a2b5 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/de.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/de.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -37,8 +50,8 @@ Open website Visit the plugin's website - See source code - See the plugin's source code + Quellcode anzeigen + Quellcode des Plugins ansehen Suggest an enhancement or submit an issue Suggest an enhancement or submit an issue to the plugin developer Go to Flow's plugins repository @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index 1f74a49a2..de6a3a2fb 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -8,7 +8,9 @@ Successfully downloaded {0} Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -18,19 +20,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -48,4 +61,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es-419.xaml index e1aa21eca..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es-419.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es-419.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es.xaml index 7f37d2765..75126dea6 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es.xaml @@ -6,7 +6,9 @@ {0} se ha descargado correctamente Error: No se puede descargar el complemento {0} por {1} {2}{3}¿Desea desinstalar este complemento? Después de la desinstalación Flow se reiniciará automáticamente. + {0} por {1} {2}{2}¿Desea desinstalar este complemento? {0} por {1} {2}{3}¿Desea instalar este complemento? Después de la instalación Flow se reiniciará automáticamente. + {0} por {1} {2}{2}¿Desea instalar este complemento? Instalar complemento Instalando complemento Descargar e instalar {0} @@ -16,19 +18,30 @@ Error: Ya existe un complemento que tiene la misma o mayor versión con {0}. Error al instalar el complemento Se ha producido un error al intentar instalar {0} + Error al desinstalar el complemento No hay actualizaciones disponibles Todos los complementos están actualizados {0} por {1} {2}{3}¿Desea actualizar este complemento? Después de la actualización Flow se reiniciará automáticamente. + {0} por {1} {2}{2}¿Desea actualizar este complemento? Actualizar complemento - Este complemento tiene una actualización, ¿desea verla? Este complemento ya está instalado La descarga del manifiesto del complemento ha fallado Por favor, compruebe que puede establecer conexión con github.com. Este error significa que es posible que no pueda instalar o actualizar complementos. + Actualizar todos los complementos + ¿Desea actualizar todos los complementos? + ¿Desea actualizar {0} complementos?{1}Flow Launcher se reiniciará después de actualizar todos los complementos. + ¿Desea actualizar {0} complementos? + {0} complementos se han actualizado correctamente. Reiniciando Flow, por favor espere... + Complemento {0} actualizado correctamente. Reiniciando Flow, por favor espere... Instalando desde una fuente desconocida ¡Está instalando este complemento desde una fuente desconocida y puede contener riesgos potenciales!{0}{0}Por favor, asegúrese de saber de dónde procede este complemento y de que es seguro.{0}{0}¿Aún así desea continuar?{0}{0}(Puede desactivar esta advertencia en la configuración) - - - + + Complemento {0} instalado correctamente. Por favor, reinicie Flow. + Complemento {0} desinstalado correctamente. Por favor, reinicie Flow. + Complemento {0} actualizado correctamente. Por favor, reinicie Flow. + {0} complementos se han actualizado correctamente. Por favor, reinicie Flow. + El complemento {0} ya ha sido modificado. Por favor, reinicie Flow antes de realizar más cambios. + Administrador de complementos Administración de instalación, desinstalación o actualización de los complementos de Flow Launcher @@ -46,4 +59,5 @@ Aviso de instalación desde fuentes desconocidas + Reiniciar automáticamente Flow Launcher después de instalar/desinstalar/actualizar complementos diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/fr.xaml index e1aa21eca..d40f2f9da 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/fr.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/fr.xaml @@ -2,48 +2,62 @@ - Downloading plugin + Téléchargement du plugin Successfully downloaded - Error: Unable to download the plugin - {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. - {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. - Plugin Install - Installing Plugin - Download and install {0} - Plugin Uninstall + Erreur : Impossible de télécharger le plugin + {0} par {1} {2}{3}Voulez-vous désinstaller ce plugin ? Après la désinstallation Flow redémarrera automatiquement. + {0} par {1} {2}{2}Voulez-vous désinstaller ce plugin ? + {0} par {1} {2}{3}Voulez-vous installer ce plugin ? Après l'installation Flow redémarrera automatiquement. + {0} par {1} {2}{2}Voulez-vous installer ce plugin ? + Installation du plugin + Installation du plugin + Télécharger et installer {0} + Désinstallation du plugin Plugin successfully installed. Restarting Flow, please wait... - Unable to find the plugin.json metadata file from the extracted zip file. - Error: A plugin which has the same or greater version with {0} already exists. - Error installing plugin - Error occurred while trying to install {0} - No update available - All plugins are up to date - {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. - Plugin Update - This plugin has an update, would you like to see it? - This plugin is already installed - Plugin Manifest Download Failed - Please check if you can connect to github.com. This error means you may not be able to install or update plugins. - Installing from an unknown source - You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + Impossible de trouver le fichier de métadonnées plugin.json à partir du fichier zip extrait. + Erreur : Un plugin ayant une version identique ou supérieure à {0} existe déjà. + Erreur lors de l'installation du plugin + Une erreur s'est produite lors de l'installation de {0} + Erreur lors de la désinstallation du plugin + Aucune mise à jour disponible + Tous les plugins sont à jour + {0} par {1} {2}{3}Voulez-vous mettre à jour ce plugin ? Après la mise à jour Flow redémarrera automatiquement. + {0} par {1} {2}{2}Voulez-vous mettre à jour ce plugin ? + Mise à jour du Plugin + Ce plugin est déjà installé + Échec du téléchargement du Plugin Manifest + Veuillez vérifier si vous pouvez vous connecter à github.com. Cette erreur signifie que vous ne serez pas en mesure d'installer ou de mettre à jour des plugins. + Mettre à jour tous les plugins + Voulez-vous mettre à jour tous les plugins ? + Voulez-vous mettre à jour {0} plugins ?{1}Flow Launcher redémarrera après la mise à jour de tous les plugins. + Voulez-vous mettre à jour {0} plugins ? + {0} plugins mis à jour avec succès. Redémarrage de Flow, veuillez patienter... + Plugin {0} mis à jour avec succès. Redémarrage de Flow, veuillez patienter... + Installation depuis une source inconnue + Vous installez ce plugin à partir d'une source inconnue et il peut contenir des risques !{0}{0}Veuillez vous assurer que vous comprenez d'où provient ce plugin et qu'il est sûr.{0}{0}Voulez-vous continuer ?{0}{0}(Vous pouvez désactiver cet avertissement via les paramètres) + + Plugin {0} installé avec succès. Veuillez redémarrer Flow. + Plugin {0} désinstallé avec succès. Veuillez redémarrer Flow. + Plugin {0} mis à jour avec succès. Veuillez redémarrer Flow. + {0} plugins mis à jour avec succès. Veuillez redémarrer Flow. + Le plugin {0} a déjà été modifié. Veuillez redémarrer Flow avant de faire d'autres modifications. + - Plugins Manager - Management of installing, uninstalling or updating Flow Launcher plugins - Unknown Author + Gestionnaire de plugins + Gestion de l'installation, de la désinstallation ou de la mise à jour des plugins Flow Launcher + Auteur inconnu - Open website - Visit the plugin's website - See source code - See the plugin's source code - Suggest an enhancement or submit an issue - Suggest an enhancement or submit an issue to the plugin developer - Go to Flow's plugins repository - Visit the PluginsManifest repository to see community-made plugin submissions + Ouvrir le site web + Visitez le site web du plugin + Voir le code source + Voir le code source du plugin + Suggérer une amélioration ou soumettre un problème + Suggérer une amélioration ou soumettre un problème au développeur du plugin + Aller au dépôt de plugins Flow + Visitez le dépôt de PluginsManifest pour voir les soumissions de plugin créées par la communauté - Install from unknown source warning + Avertissement d'installation à partir d'une source inconnue + Redémarrer automatiquement Flow Launcher après l'installation/désinstallation/mise à jour des plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/it.xaml index ec7285142..9b6cf3068 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/it.xaml @@ -6,7 +6,9 @@ Download completato Errore: non è possibile scaricare il plugin {0} da {1} {2}{3}Vuoi disinstallare questo plugin? Dopo la disinstallazione, Flow si riavvierà automaticamente. + {0} di {1} {2}{2}Vuoi disinstallare questo plugin? {0} da {1} {2}{3}Vuoi installare questo plugin? Dopo l'installazione, Flow si riavvierà automaticamente. + {0} di {1} {2}{2}Vuoi installare questo plugin? Installazione del plugin Installazione del Plugin Scarica e installa {0} @@ -16,19 +18,30 @@ Errore: esiste già un plugin che ha la stessa o maggiore versione con {0}. Errore durante l'installazione del plugin Errore durante il tentativo di installare {0} + Errore durante la disinstallazione del plugin Nessun aggiornamento disponibile Tutti i plugin sono aggiornati {0} da {1} {2}{3}Vuoi aggiornare questo plugin? Dopo l'aggiornamento, Flow si riavvierà automaticamente. + {0} di {1} {2}{2}Vuoi aggiornare questo plugin? Aggiornamento del plugin - Questo plugin ha un aggiornamento, vuoi vederlo? Questo plugin è già stato installato Download del manifesto del plugin fallito Controlla se puoi connetterti a github.com. Questo errore significa che potresti non essere in grado di installare o aggiornare i plugin. + Aggiorna tutti i plugin + Vuoi aggiornare tutti i plugin? + Vuoi aggiornare {0} plugin?{1}Flow Launcher verrà riavviato dopo aver aggiornato tutti i plugin. + Vuoi aggiornare {0} plugin? + {0} plugin aggiornati con successo. Riavviando Flow, attendere... + Il plugin {0} aggiornato con successo. Riavviando Flow, attendere... Installazione da una fonte sconosciuta Stai installando questo plugin da una fonte sconosciuta e potrebbe contenere potenziali rischi!{0}{0}Si prega di assicurarsi di capire la provenienza di questo plugin e se sia sicuro.{0}{0}Vuoi comunque continuare?{0}{0}(Puoi disattivare questo avviso dalle impostazioni) - - - + + Il plugin {0} installato con successo. Riavviare Flow. + Il plugin {0} disinstallato con successo. Riavviare Flow. + Il plugin {0} aggiornato con successo. Riavviare Flow. + {0} plugin aggiornato con successo. Riavviare Flow. + Il plugin {0} è già stato modificato. Riavviare Flow prima di fare altre modifiche. + Gestore dei plugin Gestione dell'installazione, disinstallazione o aggiornamento dei plugin di Flow Launcher @@ -46,4 +59,5 @@ Avviso di installazione da sorgenti sconosciute + Riavvia automaticamente Flow Launcher dopo l'installazione/disinstallazione/aggiornamento dei plugin diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ja.xaml index e1aa21eca..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ja.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ja.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ko.xaml index 7fb07a6db..9bcf0a74b 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ko.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ko.xaml @@ -6,7 +6,9 @@ 다운로드 성공 오류: 플러그인을 받을 수 없습니다 {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? 플러그인 설치 Installing Plugin 다운로드 및 설치 {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin 사용 가능한 업데이트가 없습니다 All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? 플러그인 업데이트 - This plugin has an update, would you like to see it? 이미 설치된 플러그인입니다 플러그인 목록 다운로드 실패 Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + 플러그인 관리자 플러그인의 설치/삭제/업데이트를 관리하는 플러그인 @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nb.xaml index e1aa21eca..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nb.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nb.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nl.xaml index e1aa21eca..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nl.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nl.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pl.xaml index e1aa21eca..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pl.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pl.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-br.xaml index 442079498..8640625c2 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-br.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? Este plugin já está instalado Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-pt.xaml index c7f3f2e9e..c69317276 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-pt.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-pt.xaml @@ -6,7 +6,9 @@ Descarregado com sucesso {0} Não foi possível descarregar o plugin {0} de {1} {2}{3}Tem a certeza de que pretende desinstalar este plugin? Após a desinstalação, Flow Launcher será reiniciado. + {0} de {1} {2}{2}Gostaria de desinstalar este plugin? {0} de {1} {2}{3}Tem a certeza de que pretende instalar este plugin? Após a instalação, Flow Launcher será reiniciado. + {0} de {1} {2}{2}Gostaria de instalar este plugin? Instalador de plugins Instalando plugin... Descarregar e instalar {0} @@ -16,19 +18,30 @@ Erro: já está instalado um plugin com uma versão igual ou superior a {0}. Erro ao instalar o plugin Ocorreu um erro ao tentar instalar {0} + Erro ao desinstalar o plugin Não existem atualizações Todos os plugins estão instalados {0} de {1} {2}{3}Tem a certeza de que pretende atualizar este plugin? Após a atualização, Flow Launcher será reiniciado. + {0} de {1} {2}{2}Gostaria de atualizar este plugin? Atualização de plugin - Existe uma atualização para este plugin. Deseja ver? Este plugin já está instalado Erro ao descarregar o manifesto do plugin Verifique se consegue estabelecer ligação a github.com. Este erro significa que pode não ser possível instalar ou atualizar os plugins. + Atualizar todos os plugins + Gostaria de atualizar todos os plugins? + Gostaria de atualizar {0} plugins ?{1}Flow Launcher será reiniciado após a atualização. + Gostaria de atualizar {0} plugins? + {0} plugins atualizados com sucesso. Estamos a reiniciar Flow Launcher... + Plugin {0} atualizado com sucesso. Estamos a reiniciar Flow Launcher, aguarde... Instalar a partir de fontes desconhecidas Está a instalar este plugin a partir de uma fonte desconhecida o que pode ser perigoso!{0}{0}Certifique-se de que este plugin é seguro.{0}{0}Ainda assim, pretende continuar com a instalação?{0}{0}(Pode desativar este aviso nas definições da aplicação) - - - + + Plugin {0} instalado com sucesso. Por favor, reinicie o Flow Launcher. + Plugin {0} desinstalado com sucesso. Por favor, reinicie o Flow Launcher. + Plugin {0} atualizado com sucesso. Por favor, reinicie o Flow Launcher. + {0} plugins atualizados com sucesso. Deve reiniciar Flow Launcher. + O plugin {0} foi modificado. Por favor, reinicie o Flow Launcher antes de fazer mais alterações. + Gestor de plugins Módulo para instalar, desinstalar e atualizar os plugins do Flow Launcher @@ -46,4 +59,5 @@ Aviso ao instalar de fontes desconhecidas + Reiniciar automaticamente após instalar/desinstalar/atualizar plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ru.xaml index e1aa21eca..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ru.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml index ea43b3c62..1982dd5cf 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml @@ -6,7 +6,9 @@ Úspešne stiahnuté {0} Chyba: Nepodarilo sa stiahnuť plugin {0} od {1} {2}{3}Chcete odinštalovať tento plugin? Po odinštalovaní sa Flow automaticky reštartuje. + {0} od {1} {2}{2}Chcete odinštalovať tento plugin? {0} od {1} {2}{3}Chcete nainštalovať tento plugin? Po nainštalovaní sa Flow automaticky reštartuje. + {0} od {1} {2}{2}Chcete nainštalovať tento plugin? Inštalovať plugin Inštaluje sa plugin Stiahnuť a nainštalovať {0} @@ -16,19 +18,30 @@ Chyba: Plugin s rovnakou alebo vyššou verziou ako {0} už existuje. Chyba inštalácie pluginu Nastala chyba počas inštalácie pluginu {0} + Chyba odinštalácie pluginu Nie je k dispozícii žiadna aktualizácia Všetky pluginy sú aktuálne {0} od {1} {2}{3}Chcete aktualizovať tento plugin? Po aktualizácii sa Flow automaticky reštartuje. + {0} od {1} {2}{2}Chcete aktualizovať tento plugin? Aktualizácia pluginu - Aktualizácia pre tento plugin je k dispozícii, chcete ju zobraziť? Tento plugin je už nainštalovaný Stiahnutie manifestu pluginu zlyhalo Skontrolujte, či sa môžete pripojiť ku github.com. Táto chyba znamená, že pravdepodobne nemôžete pluginy inštalovať alebo aktualizovať. + Aktualizovať všetky pluginy + Chcete aktualizovať všetky pluginy? + Chcete aktualizovať pluginy ({0})?{1}Po aktualizácii všetkých pluginov sa Flow Launcher reštartuje. + Chcete aktualizovať pluginy ({0})? + Pluginy úspešne aktualizované ({0}). Flow sa reštartuje, čakajte… + Plugin {0} bol úspešne aktualizovaný. Reštartuje sa Flow, čakajte, prosím... Inštalácia z neznámeho zdroja Tento plugin inštalujete z neznámeho zdroja a môže obsahovať potenciálne riziká!{0}{0}Uistite sa, že rozumiete, odkiaľ tento plugin pochádza, a že je bezpečný.{0}{0}Chcete pokračovať?{0}{0}(Toto upozornenie môžete vypnúť v nastaveniach) - - - + + Plugin {0} bol úspešne nainštalovaný. Prosím, reštartuje Flow. + Plugin {0} bol úspešne odinštalovaný. Prosím, reštartuje Flow. + Plugin {0} bol úspešne aktualizovaný. Prosím, reštartuje Flow. + Pluginy úspešne aktualizované ({0}). Reštartuje Flow. + Plugin {0} už bol upravený. Prosím, reštartuje Flow pred ďalšími zmenami. + Správca pluginov Správa inštalácie, odinštalácie alebo aktualizácie pluginov programu Flow Launcher @@ -46,4 +59,5 @@ Upozornenie na inštaláciu z neznámeho zdroja + Automaticky reštartovať Flow Launcher po inštalácií/odinštalácii/aktualizáciu pluginov diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sr.xaml index e1aa21eca..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sr.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sr.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/tr.xaml index e1aa21eca..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/tr.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/tr.xaml @@ -6,7 +6,9 @@ Successfully downloaded Error: Unable to download the plugin {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? Plugin Install Installing Plugin Download and install {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. Error installing plugin Error occurred while trying to install {0} + Error uninstalling plugin No update available All plugins are up to date {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + Plugins Manager Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/uk-UA.xaml index e1aa21eca..db863b70f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/uk-UA.xaml @@ -2,48 +2,62 @@ - Downloading plugin + Завантаження плагіну Successfully downloaded - Error: Unable to download the plugin - {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. - {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. - Plugin Install - Installing Plugin - Download and install {0} - Plugin Uninstall + Помилка: Не вдалося завантажити плагін + {0} від {1} {2}{3}Бажаєте видалити цей плагін? Після видалення Flow автоматично перезапуститься. + {0} від {1} {2}{2}Бажаєте видалити цей плагін? + {0} від {1} {2}{3}Бажаєте встановити цей плагін? Після встановлення Flow автоматично перезапуститься. + {0} від {1} {2}{2}Бажаєте встановити цей плагін? + Встановлення плагіна + Встановлення плагіна + Завантажити та встановити {0} + Видалення плагіна Plugin successfully installed. Restarting Flow, please wait... - Unable to find the plugin.json metadata file from the extracted zip file. - Error: A plugin which has the same or greater version with {0} already exists. - Error installing plugin - Error occurred while trying to install {0} - No update available - All plugins are up to date - {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. - Plugin Update - This plugin has an update, would you like to see it? - This plugin is already installed - Plugin Manifest Download Failed - Please check if you can connect to github.com. This error means you may not be able to install or update plugins. - Installing from an unknown source - You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + Не вдалося знайти файл метаданих plugin.json у розпакованому zip-архіві. + Помилка: Плагін, який має ідентичну або новішу версію з {0}, вже існує. + Помилка під час встановлення плагіна + Виникла помилка при спробі встановити {0} + Помилка видалення плагіну + Оновлень не знайдено + Всі плагіни оновлено + {0} від {1} {2}{3}Бажаєте оновити цей плагін? Після оновлення Flow буде автоматично перезапущено. + {0} від {1} {2}{2}Бажаєте оновити цей плагін? + Оновлення плагіна + Цей плагін вже встановлено + Не вдалося завантажити маніфест плагіна + Будь ласка, перевірте, чи можете ви підключитися до github.com. Ця помилка означає, що ви не можете встановлювати або оновлювати плагіни. + Оновити всі плагіни + Ви хочете оновити всі плагіни? + Бажаєте оновити плагіни {0}?{1}Flow Launcher перезапуститься після оновлення всіх плагінів. + Бажаєте оновити плагіни {0}? + {0} плагіни успішно оновлено. Перезапуск Flow, будь ласка, зачекайте... + Плагін {0} успішно оновлено. Перезапускаємо Flow, будь ласка, зачекайте... + Встановлення з невідомого джерела + Ви встановлюєте цей плагін з невідомого джерела, тому він може бути потенційно небезпечним!{0}{0}Переконайтеся, що ви розумієте, звідки цей плагін, і що він є безпечним.{0}{0}Бажаєте продовжити?{0}{0}(Ви можете вимкнути це попередження через налаштування) + + Плагін {0} успішно встановлено. Будь ласка, перезапустіть Flow. + Плагін {0} успішно видалено. Будь ласка, перезапустіть Flow. + Плагін {0} успішно оновлено. Будь ласка, перезапустіть Flow. + {0} плагіни успішно оновлено. Будь ласка, перезапустіть Flow. + Плагін {0} вже було змінено. Будь ласка, перезапустіть Flow, перш ніж вносити будь-які подальші зміни. + - Plugins Manager - Management of installing, uninstalling or updating Flow Launcher plugins - Unknown Author + Менеджер плагінів + Керування встановленням, видаленням або оновленням плагінів Flow Launcher + Невідомий автор - Open website - Visit the plugin's website - See source code - See the plugin's source code - Suggest an enhancement or submit an issue - Suggest an enhancement or submit an issue to the plugin developer - Go to Flow's plugins repository - Visit the PluginsManifest repository to see community-made plugin submissions + Відкрити сайт + Відвідати веб-сайт плагіна + Переглянути вихідний код + Переглянути вихідний код плагіну + Запропонувати покращення або надіслати повідомлення про проблему + Запропонувати покращення або повідомити про проблему розробнику плагіна + Перейти до репозиторію плагінів Flow + Відвідати репозиторій PluginsManifest, щоб переглянути подані спільнотою плагіни - Install from unknown source warning + Попередження про встановлення з невідомого джерела + Автоматичний перезапуск Flow Launcher після встановлення/видалення/оновлення плагінів diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/vi.xaml new file mode 100644 index 000000000..b41ad44e9 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/vi.xaml @@ -0,0 +1,63 @@ + + + + + Plugin đang được tải + Đã tải xuống thành công! + Lỗi: Không thể tải plugin xuống + {0} bởi {1} {2}{3}Bạn có muốn gỡ cài đặt plugin này không? Sau khi gỡ cài đặt Flow sẽ tự động khởi động lại. + {0} by {1} {2}{2}Would you like to uninstall this plugin? + {0} bởi {1} {2}{3}Bạn có muốn gỡ cài đặt plugin này không? Sau khi gỡ cài đặt Flow sẽ tự động khởi động lại. + {0} by {1} {2}{2}Would you like to install this plugin? + Đã cài đặt plugin + Cài đặt Plugin + Tải về và cài đặt + Đã gỡ cài đặt plugin + Đã cài đặt thành công plugin. Đang khởi động lại Flow, vui lòng đợi... + Không thể tìm thấy tệp siêu dữ liệu plugin.json từ tệp zip được giải nén. + Lỗi: Đã tồn tại một plugin có phiên bản tương tự hoặc cao hơn với {0}. + Lỗi cài đặt plugin + Đã xảy ra lỗi khi cố gắng cài đặt {0} + Lỗi cài đặt plugin + Không có cập nhật + Tất cả các plugin đều được cập nhật + {0} bởi {1} {2}{3}Bạn có muốn gỡ cài đặt plugin này không? Sau khi gỡ cài đặt Flow sẽ tự động khởi động lại. + {0} by {1} {2}{2}Would you like to update this plugin? + Cập nhật plugin + Đã cài đặt plugin + Tải xuống bản kê khai plugin không thành công + Vui lòng kiểm tra xem bạn có thể kết nối với github.com không. Lỗi này có nghĩa là bạn không thể cài đặt hoặc cập nhật plugin. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... + Cài đặt từ một nguồn không xác định + Bạn đang cài đặt plugin này từ một nguồn không xác định và nó có thể chứa những rủi ro tiềm ẩn!{0}{0}Hãy đảm bảo rằng bạn hiểu plugin này đến từ đâu và nó an toàn.{0}{0}Bạn vẫn muốn tiếp tục chứ? {0}{0}(Bạn có thể tắt cảnh báo này thông qua cài đặt) + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + + + Trình quản lý plugin + Quản lý cài đặt, gỡ cài đặt hoặc cập nhật plugin Flow Launcher + Không rõ tác giả + + + Mở website + Truy cập trang web của plugin + Xem mã nguồn + Xem mã nguồn của plugin + Đề xuất cải tiến hoặc gửi vấn đề + Đề xuất cải tiến hoặc gửi vấn đề cho nhà phát triển plugin + Đi tới kho plugin của Flow + Truy cập kho lưu trữ PluginsManifest để xem các bản gửi plugin do cộng đồng gửi + + + Cảnh báo cài đặt từ nguồn không xác định + Automatically restart Flow Launcher after installing/uninstalling/updating plugins + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml index 58d3fdf1f..676c20a73 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml @@ -3,10 +3,12 @@ 正在下载插件... - 下载完成 + 成功下载 {0} 错误:无法下载该插件 {0} by {1} {2}{3} 您要卸载此插件吗? 卸载后,Flow Launcher 将自动重启。 + {0} 作者: {1} {2}{2}您想要卸载这个插件吗? {0} by {1} {2}{3} 您要安装此插件吗? 安装后,Flow Launcher 将自动重启 + {0} 作者: {1} {2}{2}您想要安装这个插件吗? 插件安装 正在安装插件 下载与安装 {0} @@ -16,19 +18,30 @@ 错误:具有相同或更高版本的 {0} 的插件已经存在。 安装插件时出错 尝试安装 {0} 时发生错误 + 卸载插件时出错 无可用更新 所有插件都是最新的 {0} by {1} {2}{3}您要更新此插件吗? 更新后,Flow Launcher 将自动重启。 + {0} 作者: {1} {2}{2}您想要更新这个插件吗? 插件更新 - 该插件有更新,您想看看吗? 此插件已安装 插件列表下载失败 请检查您是否可以连接到 github.com。这个错误意味着您可能无法安装或更新插件。 + 更新所有插件 + 您想要更新所有插件吗? + 您想要更新 {0} 插件吗?{1}更新所有插件后 Flow Launcher 将重启。 + 您想要更新 {0} 插件吗? + 插件 {0} 更新成功。正在重新启动 Flow Launcher,请稍候..…… + 插件{0}更新成功。正在重新启动 Flow Launcher,请稍候... 从未知源安装 您正在从未知源安装此插件,它可能包含潜在风险!{0}{0}请确保您了解来源以及安全性。{0}{0}您想要继续吗?{0}{0}(您可以通过设置关闭此警告) - - - + + 成功安装插件{0}。请重新启动 Flow Launcher。 + 成功卸载插件{0}。请重新启动 Flow Launcher。 + 成功更新插件{0}。请重新启动 Flow Launcher。 + 插件 {0} 更新成功。请重新启动 Flow Launcher。 + 插件 {0} 已被修改。请在进行任何进一步更改之前重新启动Flow。 + 插件管理 安装,卸载或更新 Flow Launcher 插件 @@ -46,4 +59,5 @@ 未知源安装警告 + 安装/卸载/更新插件后自动重启 Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-tw.xaml index 8ed16fe90..ae37579dc 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-tw.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-tw.xaml @@ -6,7 +6,9 @@ 下載完成 錯誤:無法下載擴充功能 {0} by {1} {2}{3}Would you like to uninstall this plugin? After the uninstallation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to uninstall this plugin? {0} by {1} {2}{3}Would you like to install this plugin? After the installation Flow will automatically restart. + {0} by {1} {2}{2}Would you like to install this plugin? 安裝擴充功能 Installing Plugin 下載並安裝 {0} @@ -16,19 +18,30 @@ Error: A plugin which has the same or greater version with {0} already exists. 安裝擴充功能時發生錯誤 嘗試安裝 {0} 時發生錯誤 + Error uninstalling plugin 無可用更新 所有插件均為最新版本 {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. + {0} by {1} {2}{2}Would you like to update this plugin? 擴充功能更新 - This plugin has an update, would you like to see it? 已安裝此擴充功能 Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... Installing from an unknown source You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings) - - - + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + 擴充功能管理 Management of installing, uninstalling or updating Flow Launcher plugins @@ -46,4 +59,5 @@ Install from unknown source warning + Automatically restart Flow Launcher after installing/uninstalling/updating plugins diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 683904ea0..15cbda7f2 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -3,14 +3,12 @@ using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -60,7 +58,8 @@ namespace Flow.Launcher.Plugin.PluginsManager AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.InstallCommand} ", Action = _ => { - Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.InstallCommand} "); + Context.API.ChangeQuery( + $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.InstallCommand} "); return false; } }, @@ -71,7 +70,8 @@ namespace Flow.Launcher.Plugin.PluginsManager AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UninstallCommand} ", Action = _ => { - Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UninstallCommand} "); + Context.API.ChangeQuery( + $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UninstallCommand} "); return false; } }, @@ -82,7 +82,8 @@ namespace Flow.Launcher.Plugin.PluginsManager AutoCompleteText = $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UpdateCommand} ", Action = _ => { - Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UpdateCommand} "); + Context.API.ChangeQuery( + $"{Context.CurrentPluginMetadata.ActionKeyword} {Settings.UpdateCommand} "); return false; } } @@ -96,13 +97,12 @@ namespace Flow.Launcher.Plugin.PluginsManager if (Context.API.GetAllPlugins() .Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0)) { - if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"), - Context.API.GetTranslation("plugin_pluginsmanager_update_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - Context - .API - .ChangeQuery( - $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {plugin.Name}"); + var updateDetail = !plugin.IsFromLocalInstallPath ? plugin.Name : plugin.LocalInstallPath; + + Context + .API + .ChangeQuery( + $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {updateDetail}"); var mainWindow = Application.Current.MainWindow; mainWindow.Show(); @@ -117,9 +117,19 @@ namespace Flow.Launcher.Plugin.PluginsManager return; } - var message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"), - plugin.Name, plugin.Author, - Environment.NewLine, Environment.NewLine); + string message; + if (Settings.AutoRestartAfterChanging) + { + message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt"), + plugin.Name, plugin.Author, + Environment.NewLine, Environment.NewLine); + } + else + { + message = string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_prompt_no_restart"), + plugin.Name, plugin.Author, + Environment.NewLine); + } if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"), MessageBoxButton.YesNo) == MessageBoxResult.No) @@ -130,59 +140,94 @@ namespace Flow.Launcher.Plugin.PluginsManager ? $"{plugin.Name}-{Guid.NewGuid()}.zip" : $"{plugin.Name}-{plugin.Version}.zip"; - var filePath = Path.Combine(DataLocation.PluginsDirectory, downloadFilename); + var filePath = Path.Combine(Path.GetTempPath(), downloadFilename); try { - await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); + if (!plugin.IsFromLocalInstallPath) + { + if (File.Exists(filePath)) + File.Delete(filePath); - Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), - string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), plugin.Name)); + await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); + } + else + { + filePath = plugin.LocalInstallPath; + } Install(plugin, filePath); } + catch (HttpRequestException e) + { + Context.API.ShowMsgError( + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), plugin.Name), + Context.API.GetTranslation("plugin_pluginsmanager_download_error")); + Log.Exception("PluginsManager", "An error occurred while downloading plugin", e); + return; + } catch (Exception e) { - if (e is HttpRequestException) - MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_download_error"), - Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin")); - Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), plugin.Name)); - - Log.Exception("PluginsManager", "An error occurred while downloading plugin", e, "InstallOrUpdate"); - + Log.Exception("PluginsManager", "An error occurred while downloading plugin", e); return; } - Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"), - string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"), plugin.Name)); - - Context.API.RestartApp(); + if (Settings.AutoRestartAfterChanging) + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_restart"), + plugin.Name)); + Context.API.RestartApp(); + } + else + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_installing_plugin"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_success_no_restart"), + plugin.Name)); + } } - internal async ValueTask> RequestUpdateAsync(string search, CancellationToken token, bool usePrimaryUrlOnly = false) + internal async ValueTask> RequestUpdateAsync(string search, CancellationToken token, + bool usePrimaryUrlOnly = false) { await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly); - var resultsForUpdate = + var pluginFromLocalPath = null as UserPlugin; + var updateFromLocalPath = false; + + if (FilesFolders.IsZipFilePath(search, checkFileExists: true)) + { + pluginFromLocalPath = Utilities.GetPluginInfoFromZip(search); + pluginFromLocalPath.LocalInstallPath = search; + updateFromLocalPath = true; + } + + var updateSource = !updateFromLocalPath + ? PluginsManifest.UserPlugins + : new List { pluginFromLocalPath }; + + var resultsForUpdate = ( from existingPlugin in Context.API.GetAllPlugins() - join pluginFromManifest in PluginsManifest.UserPlugins - on existingPlugin.Metadata.ID equals pluginFromManifest.ID - where existingPlugin.Metadata.Version.CompareTo(pluginFromManifest.Version) < - 0 // if current version precedes manifest version + join pluginUpdateSource in updateSource + 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) + && !PluginManager.PluginModified(existingPlugin.Metadata.ID) select new { - pluginFromManifest.Name, - pluginFromManifest.Author, + pluginUpdateSource.Name, + pluginUpdateSource.Author, CurrentVersion = existingPlugin.Metadata.Version, - NewVersion = pluginFromManifest.Version, + NewVersion = pluginUpdateSource.Version, existingPlugin.Metadata.IcoPath, PluginExistingMetadata = existingPlugin.Metadata, - PluginNewUserPlugin = pluginFromManifest - }; + PluginNewUserPlugin = pluginUpdateSource + }).ToList(); if (!resultsForUpdate.Any()) return new List @@ -204,56 +249,178 @@ namespace Flow.Launcher.Plugin.PluginsManager IcoPath = x.IcoPath, Action = e => { - string message = string.Format( - Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), - x.Name, x.Author, - Environment.NewLine, Environment.NewLine); + string message; + if (Settings.AutoRestartAfterChanging) + { + message = string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_update_prompt"), + x.Name, x.Author, + Environment.NewLine, Environment.NewLine); + } + else + { + message = string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_update_prompt_no_restart"), + x.Name, x.Author, + Environment.NewLine); + } if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_update_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) + MessageBoxButton.YesNo) != MessageBoxResult.Yes) { - Uninstall(x.PluginExistingMetadata, false); - - var downloadToFilePath = Path.Combine(DataLocation.PluginsDirectory, - $"{x.Name}-{x.NewVersion}.zip"); - - Task.Run(async delegate - { - await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath) - .ConfigureAwait(false); - - Context.API.ShowMsg( - Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), - string.Format(Context.API.GetTranslation("plugin_pluginsmanager_download_success"), x.Name)); - - Install(x.PluginNewUserPlugin, downloadToFilePath); - - Context.API.RestartApp(); - }).ContinueWith(t => - { - Log.Exception("PluginsManager", $"Update failed for {x.Name}", - t.Exception.InnerException, "RequestUpdate"); - Context.API.ShowMsg( - Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), - string.Format( - Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), - x.Name)); - }, TaskContinuationOptions.OnlyOnFaulted); - - return true; + return false; } - return false; + var downloadToFilePath = Path.Combine(Path.GetTempPath(), + $"{x.Name}-{x.NewVersion}.zip"); + + _ = Task.Run(async delegate + { + if (!x.PluginNewUserPlugin.IsFromLocalInstallPath) + { + if (File.Exists(downloadToFilePath)) + { + File.Delete(downloadToFilePath); + } + + await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath) + .ConfigureAwait(false); + } + else + { + downloadToFilePath = x.PluginNewUserPlugin.LocalInstallPath; + } + + + PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, + downloadToFilePath); + + if (Settings.AutoRestartAfterChanging) + { + Context.API.ShowMsg( + Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + string.Format( + Context.API.GetTranslation( + "plugin_pluginsmanager_update_success_restart"), + x.Name)); + Context.API.RestartApp(); + } + else + { + Context.API.ShowMsg( + Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + string.Format( + Context.API.GetTranslation( + "plugin_pluginsmanager_update_success_no_restart"), + x.Name)); + } + }).ContinueWith(t => + { + Log.Exception("PluginsManager", $"Update failed for {x.Name}", + t.Exception.InnerException); + Context.API.ShowMsg( + Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), + x.Name)); + }, TaskContinuationOptions.OnlyOnFaulted); + + return true; + }, ContextData = new UserPlugin { - Website = x.PluginNewUserPlugin.Website, UrlSourceCode = x.PluginNewUserPlugin.UrlSourceCode + Website = x.PluginNewUserPlugin.Website, + UrlSourceCode = x.PluginNewUserPlugin.UrlSourceCode } }); - return Search(results, search); + // Update all result + if (resultsForUpdate.Count() > 1) + { + var updateAllResult = new Result + { + Title = Context.API.GetTranslation("plugin_pluginsmanager_update_all_title"), + SubTitle = Context.API.GetTranslation("plugin_pluginsmanager_update_all_subtitle"), + IcoPath = icoPath, + AsyncAction = async e => + { + string message; + if (Settings.AutoRestartAfterChanging) + { + message = string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_update_all_prompt"), + resultsForUpdate.Count(), Environment.NewLine); + } + else + { + message = string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_update_all_prompt_no_restart"), + resultsForUpdate.Count()); + } + + if (MessageBox.Show(message, + Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + MessageBoxButton.YesNo) == MessageBoxResult.No) + { + return false; + } + + await Task.WhenAll(resultsForUpdate.Select(async plugin => + { + var downloadToFilePath = Path.Combine(Path.GetTempPath(), + $"{plugin.Name}-{plugin.NewVersion}.zip"); + + try + { + if (File.Exists(downloadToFilePath)) + { + File.Delete(downloadToFilePath); + } + + await Http.DownloadAsync(plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath) + .ConfigureAwait(false); + + PluginManager.UpdatePlugin(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, + downloadToFilePath); + } + catch (Exception ex) + { + Log.Exception("PluginsManager", $"Update failed for {plugin.Name}", ex.InnerException); + Context.API.ShowMsg( + Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"), + plugin.Name)); + } + })); + + if (Settings.AutoRestartAfterChanging) + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_restart"), + resultsForUpdate.Count())); + Context.API.RestartApp(); + } + else + { + Context.API.ShowMsg(Context.API.GetTranslation("plugin_pluginsmanager_update_title"), + string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_update_all_success_no_restart"), + resultsForUpdate.Count())); + } + + return true; + }, + ContextData = new UserPlugin() + }; + results = results.Prepend(updateAllResult); + } + + return !updateFromLocalPath ? Search(results, search) : results.ToList(); } internal bool PluginExists(string id) @@ -308,9 +475,11 @@ namespace Flow.Launcher.Plugin.PluginsManager if (Settings.WarnFromUnknownSource) { if (!InstallSourceKnown(plugin.UrlDownload) - && MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"), + && MessageBox.Show(string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"), Environment.NewLine), - Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning_title"), + Context.API.GetTranslation( + "plugin_pluginsmanager_install_unknown_source_warning_title"), MessageBoxButton.YesNo) == MessageBoxResult.No) return false; } @@ -322,9 +491,42 @@ namespace Flow.Launcher.Plugin.PluginsManager } }; + return new List { result }; + } + + internal List InstallFromLocalPath(string localPath) + { + var plugin = Utilities.GetPluginInfoFromZip(localPath); + + plugin.LocalInstallPath = localPath; + return new List { - result + new Result + { + Title = $"{plugin.Name} by {plugin.Author}", + SubTitle = plugin.Description, + IcoPath = plugin.IcoPath, + Action = e => + { + if (Settings.WarnFromUnknownSource) + { + if (!InstallSourceKnown(plugin.Website) + && MessageBox.Show(string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"), + Environment.NewLine), + Context.API.GetTranslation( + "plugin_pluginsmanager_install_unknown_source_warning_title"), + MessageBoxButton.YesNo) == MessageBoxResult.No) + return false; + } + + Application.Current.MainWindow.Hide(); + _ = InstallOrUpdateAsync(plugin); + + return ShouldHideWindow; + } + } }; } @@ -334,10 +536,12 @@ namespace Flow.Launcher.Plugin.PluginsManager var acceptedSource = "https://github.com"; var constructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author); - return url.StartsWith(acceptedSource) && Context.API.GetAllPlugins().Any(x => x.Metadata.Website.StartsWith(constructedUrlPart)); + return url.StartsWith(acceptedSource) && + Context.API.GetAllPlugins().Any(x => x.Metadata.Website.StartsWith(constructedUrlPart)); } - internal async ValueTask> RequestInstallOrUpdate(string search, CancellationToken token, bool usePrimaryUrlOnly = false) + internal async ValueTask> RequestInstallOrUpdate(string search, CancellationToken token, + bool usePrimaryUrlOnly = false) { await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly); @@ -345,15 +549,19 @@ namespace Flow.Launcher.Plugin.PluginsManager && search.Split('.').Last() == zip) return InstallFromWeb(search); + if (FilesFolders.IsZipFilePath(search, checkFileExists: true)) + return InstallFromLocalPath(search); + var results = PluginsManifest .UserPlugins + .Where(x => !PluginExists(x.ID) && !PluginManager.PluginModified(x.ID)) .Select(x => new Result { Title = $"{x.Name} by {x.Author}", SubTitle = x.Description, - IcoPath = icoPath, + IcoPath = x.IcoPath, Action = e => { if (e.SpecialKeyState.CtrlPressed) @@ -375,78 +583,36 @@ namespace Flow.Launcher.Plugin.PluginsManager private void Install(UserPlugin plugin, string downloadedFilePath) { if (!File.Exists(downloadedFilePath)) - return; + throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", + downloadedFilePath); - var tempFolderPath = Path.Combine(Path.GetTempPath(), "flowlauncher"); - var tempFolderPluginPath = Path.Combine(tempFolderPath, "plugin"); - - if (Directory.Exists(tempFolderPath)) - Directory.Delete(tempFolderPath, true); - - Directory.CreateDirectory(tempFolderPath); - - var zipFilePath = Path.Combine(tempFolderPath, Path.GetFileName(downloadedFilePath)); - - File.Copy(downloadedFilePath, zipFilePath); - - File.Delete(downloadedFilePath); - - Utilities.UnZip(zipFilePath, tempFolderPluginPath, true); - - var pluginFolderPath = Utilities.GetContainingFolderPathAfterUnzip(tempFolderPluginPath); - - var metadataJsonFilePath = string.Empty; - if (File.Exists(Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName))) - metadataJsonFilePath = Path.Combine(pluginFolderPath, Constant.PluginMetadataFileName); - - if (string.IsNullOrEmpty(metadataJsonFilePath) || string.IsNullOrEmpty(pluginFolderPath)) + try { - MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile"), - Context.API.GetTranslation("plugin_pluginsmanager_install_error_title")); - - throw new FileNotFoundException( - string.Format("Unable to find plugin.json from the extracted zip file, or this path {0} does not exist", pluginFolderPath)); + PluginManager.InstallPlugin(plugin, downloadedFilePath); + + if (!plugin.IsFromLocalInstallPath) + File.Delete(downloadedFilePath); } - - if (SameOrLesserPluginVersionExists(metadataJsonFilePath)) + catch (FileNotFoundException e) { - MessageBox.Show(string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name), - Context.API.GetTranslation("plugin_pluginsmanager_install_error_title")); - - throw new InvalidOperationException( - string.Format("A plugin with the same ID and version already exists, " + - "or the version is greater than this downloaded plugin {0}", + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + Context.API.GetTranslation("plugin_pluginsmanager_install_errormetadatafile")); + Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); + } + catch (InvalidOperationException e) + { + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_install_error_duplicate"), plugin.Name)); + Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); + } + catch (ArgumentException e) + { + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"), + string.Format(Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error"), + plugin.Name)); + Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); } - - var folderName = string.IsNullOrEmpty(plugin.Version) ? $"{plugin.Name}-{Guid.NewGuid()}" : $"{plugin.Name}-{plugin.Version}"; - - var defaultPluginIDs = new List - { - "0ECADE17459B49F587BF81DC3A125110", // BrowserBookmark - "CEA0FDFC6D3B4085823D60DC76F28855", // Calculator - "572be03c74c642baae319fc283e561a8", // Explorer - "6A122269676E40EB86EB543B945932B9", // PluginIndicator - "9f8f9b14-2518-4907-b211-35ab6290dee7", // PluginsManager - "b64d0a79-329a-48b0-b53f-d658318a1bf6", // ProcessKiller - "791FC278BA414111B8D1886DFE447410", // Program - "D409510CD0D2481F853690A07E6DC426", // Shell - "CEA08895D2544B019B2E9C5009600DF4", // Sys - "0308FD86DE0A4DEE8D62B9B535370992", // URL - "565B73353DBF4806919830B9202EE3BF", // WebSearch - "5043CETYU6A748679OPA02D27D99677A" // WindowsSettings - }; - - // Treat default plugin differently, it needs to be removable along with each flow release - var installDirectory = !defaultPluginIDs.Any(x => x == plugin.ID) - ? DataLocation.PluginsDirectory - : Constant.PreinstalledDirectory; - - var newPluginPath = Path.Combine(installDirectory, folderName); - - FilesFolders.CopyAll(pluginFolderPath, newPluginPath); - - Directory.Delete(pluginFolderPath, true); } internal List RequestUninstall(string search) @@ -461,10 +627,21 @@ namespace Flow.Launcher.Plugin.PluginsManager IcoPath = x.Metadata.IcoPath, Action = e => { - string message = string.Format( - Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), - x.Metadata.Name, x.Metadata.Author, - Environment.NewLine, Environment.NewLine); + string message; + if (Settings.AutoRestartAfterChanging) + { + message = string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt"), + x.Metadata.Name, x.Metadata.Author, + Environment.NewLine, Environment.NewLine); + } + else + { + message = string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_uninstall_prompt_no_restart"), + x.Metadata.Name, x.Metadata.Author, + Environment.NewLine); + } if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), @@ -472,7 +649,19 @@ namespace Flow.Launcher.Plugin.PluginsManager { Application.Current.MainWindow.Hide(); Uninstall(x.Metadata); - Context.API.RestartApp(); + if (Settings.AutoRestartAfterChanging) + { + Context.API.RestartApp(); + } + else + { + Context.API.ShowMsg( + Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"), + string.Format( + Context.API.GetTranslation( + "plugin_pluginsmanager_uninstall_success_no_restart"), + x.Metadata.Name)); + } return true; } @@ -484,24 +673,18 @@ namespace Flow.Launcher.Plugin.PluginsManager return Search(results, search); } - private void Uninstall(PluginMetadata plugin, bool removedSetting = true) + private void Uninstall(PluginMetadata plugin) { - if (removedSetting) + try { - PluginManager.Settings.Plugins.Remove(plugin.ID); - PluginManager.AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); + PluginManager.UninstallPlugin(plugin, removeSettings: true); + } + catch (ArgumentException e) + { + Log.Exception("Flow.Launcher.Plugin.PluginsManager", e.Message, e); + Context.API.ShowMsgError(Context.API.GetTranslation("plugin_pluginsmanager_uninstall_error_title"), + Context.API.GetTranslation("plugin_pluginsmanager_plugin_modified_error")); } - - // Marked for deletion. Will be deleted on next start up - using var _ = File.CreateText(Path.Combine(plugin.PluginDirectory, "NeedDelete.txt")); - } - - private bool SameOrLesserPluginVersionExists(string metadataPath) - { - var newMetadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath)); - return Context.API.GetAllPlugins() - .Any(x => x.Metadata.ID == newMetadata.ID - && newMetadata.Version.CompareTo(x.Metadata.Version) <= 0); } } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs index aa35f02b5..811bec50c 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs @@ -9,5 +9,7 @@ internal const string UpdateCommand = "update"; public bool WarnFromUnknownSource { get; set; } = true; + + public bool AutoRestartAfterChanging { get; set; } = true; } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs index 792891ad1..9800fa020 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs @@ -1,5 +1,10 @@ -using ICSharpCode.SharpZipLib.Zip; +using Flow.Launcher.Core.ExternalPlugins; +using Flow.Launcher.Infrastructure.UserSettings; +using ICSharpCode.SharpZipLib.Zip; +using Newtonsoft.Json; using System.IO; +using System.IO.Compression; +using System.Linq; namespace Flow.Launcher.Plugin.PluginsManager { @@ -55,5 +60,28 @@ namespace Flow.Launcher.Plugin.PluginsManager return string.Empty; } + + internal static UserPlugin GetPluginInfoFromZip(string filePath) + { + var plugin = null as UserPlugin; + + using (ZipArchive archive = System.IO.Compression.ZipFile.OpenRead(filePath)) + { + var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString(); + ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath); + + if (pluginJsonEntry != null) + { + using (StreamReader reader = new StreamReader(pluginJsonEntry.Open())) + { + string pluginJsonContent = reader.ReadToEnd(); + plugin = JsonConvert.DeserializeObject(pluginJsonContent); + plugin.IcoPath = "Images\\zipfolder.png"; + } + } + } + + return plugin; + } } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs index 672884f80..1c71507fc 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/ViewModels/SettingsViewModel.cs @@ -17,5 +17,11 @@ get => Settings.WarnFromUnknownSource; set => Settings.WarnFromUnknownSource = value; } + + public bool AutoRestartAfterChanging + { + get => Settings.AutoRestartAfterChanging; + set => Settings.AutoRestartAfterChanging = value; + } } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml index a62a032f7..18be0d2ca 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml @@ -12,10 +12,22 @@ + + + + + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index e0d4fed81..438a407e2 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "3.0.5", + "Version": "3.1.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index 861fc3197..876bac1e7 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -12,6 +12,7 @@ true false false + en @@ -54,4 +55,4 @@ - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/de.xaml index c4cc85463..c27892d51 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/de.xaml +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/de.xaml @@ -1,7 +1,7 @@  - Process Killer + Prozesskiller Kill running processes from Flow Launcher kill all instances of "{0}" diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/it.xaml index c4cc85463..1ea52e741 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/it.xaml @@ -2,10 +2,10 @@ Process Killer - Kill running processes from Flow Launcher + Termina i processi in esecuzione da Flow Launcher - kill all instances of "{0}" - kill {0} processes - kill all instances + termina tutte le istanze di "{0}" + termina {0} processi + termina tutte le istanze diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/uk-UA.xaml index c4cc85463..e23f43875 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/uk-UA.xaml @@ -1,11 +1,11 @@  - Process Killer - Kill running processes from Flow Launcher + Вбивця процесів + Завершення запущених процесів через Flow Launcher - kill all instances of "{0}" - kill {0} processes - kill all instances + вбити всі екземпляри "{0}" + вбити {0} процесів + вбити всі екземпляри diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/vi.xaml new file mode 100644 index 000000000..0bf065ee1 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/vi.xaml @@ -0,0 +1,11 @@ + + + + Buộc Tắt tiến trình + Tắt các tiến trình đang chạy từ Flow Launcher + + tiêu diệt tất cả các phiên bản của "{0}" + Buộc tắt các tiến trình {0} + Tắt tất cả phên bản + + diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json b/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json index 5a74d8ebb..c1760d1cd 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json @@ -4,7 +4,7 @@ "Name":"Process Killer", "Description":"Kill running processes from Flow", "Author":"Flow-Launcher", - "Version":"3.0.3", + "Version":"3.0.4", "Language":"csharp", "Website":"https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller", "IcoPath":"Images\\app.png", diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index e9c680824..132c0c705 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -12,6 +12,7 @@ true false false + en @@ -59,6 +60,7 @@ + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml index e62854305..dda427996 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml index 7be93ad14..9a703d3de 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml @@ -71,6 +71,7 @@ Spustit jako správce Otevřít umístění složky Zakázat zobrazování tohoto programu + Open target folder Program  Vyhledávání programů ve Flow Launcheru diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml index 905f7e787..676eb76cb 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml index bf844188e..293dbc9c8 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml @@ -9,12 +9,12 @@ Name Aktivieren Aktiviert - Disable + Deaktivieren Status Aktiviert Disabled Speicherort - All Programs + Alle Programme File Type erneut Indexieren Indexieren @@ -28,7 +28,7 @@ When enabled, Flow will load programs from the registry PATH When enabled, Flow will load programs from the PATH environment variable - Hide app path + App-Pfad verstecken For executable files such as UWP or lnk, hide the file path from being visible Search in Program Description Flow will search program's description @@ -69,16 +69,17 @@ Run As Different User Als Administrator ausführen - Open containing folder + Enthaltenden Ordner öffnen Disable this program from displaying + Open target folder Programm Suche Programme mit Flow Launcher - Invalid Path + Ungültiger Pfad Customized Explorer - Args + Argumente You can customized the explorer used for opening the container folder by inputing the Environmental Variable of the explorer you want to use. It will be useful to use CMD to test whether the Environmental Variable is available. Enter the customized args you want to add for your customized explorer. %s for parent directory, %f for full path (which only works for win32). Check the explorer's website for details. diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml index 39807db2c..25ceac3bb 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml @@ -32,6 +32,8 @@ When enabled, Flow will load programs from the PATH environment variable Hide app path For executable files such as UWP or lnk, hide the file path from being visible + Hide uninstallers + Hides programs with common uninstaller names, such as unins000.exe Search in Program Description Flow will search program's description Suffixes @@ -73,6 +75,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher @@ -91,4 +94,4 @@ This app is not intended to be run as administrator Unable to run {0} - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml index 4a18143bf..375dcd041 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml index 4955c4ea2..d4eccc8ac 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml @@ -71,6 +71,7 @@ Ejecutar como administrador Abrir carpeta contenedora Desactivar la visualización de este programa + Abrir carpeta de destino Programa Busca programas en Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml index 91f5e3ee5..d8b3dc5dd 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml @@ -71,6 +71,7 @@ Exécuter en tant qu'administrateur Ouvrir le répertoire Désactiver l'affichage de ce programme + Ouvrir le répertoire cible Programme Rechercher des programmes dans Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml index d13f926d6..e73c8d24e 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml @@ -6,42 +6,42 @@ Cancella Modifica Aggiungi - Name - Enable + Nome + Abilita Abilitato - Disable + Disabilita Status Abilitato Disabled - Location - All Programs + Posizione + Tutti i programmi File Type - Reindex - Indexing + Reindicizza + Indicizzando Index Sources Options UWP Apps When enabled, Flow will load UWP Applications Start Menu - When enabled, Flow will load programs from the start menu + Se abilitato, Flow caricherà i programmi dal Menu Start Registry - When enabled, Flow will load programs from the registry + Se abilitato, Flow caricherà i programmi dal Registro PATH When enabled, Flow will load programs from the PATH environment variable - Hide app path - For executable files such as UWP or lnk, hide the file path from being visible - Search in Program Description + Nascondi percorso app + Per i file eseguibili come UWP o lnk, nascondere il percorso del file + Cerca nella Descrizione del Programma Flow will search program's description - Suffixes - Max Depth + Suffissi + Profondità max - Directory - Browse - File Suffixes: - Maximum Search Depth (-1 is unlimited): + Cartella + Sfoglia + Suffissi dei file: + Profondità massima di ricerca (-1 è illimitata): - Please select a program source - Are you sure you want to delete the selected program sources? + Seleziona la sorgente del programma + Sei sicuro di voler cancellare le sorgenti dei programmi selezionate? Another program source with the same location already exists. Program Source @@ -49,11 +49,11 @@ Aggiorna Program Plugin will only index files with selected suffixes and .url files with selected protocols. - Successfully updated file suffixes - File suffixes can't be empty + Suffissi dei file aggiornati con successo + I suffissi del file non possono essere vuoti Protocols can't be empty - File Suffixes + Suffissi dei file URL Protocols Steam Games Epic Games @@ -67,17 +67,18 @@ Insert protocols of .url files you want to index. Protocols should be separated by ';', and should end with "://". (ex>ftp://;mailto://) - Run As Different User - Run As Administrator + Esegui Come Utente Differente + Esegui Come Amministratore Apri percorso file - Disable this program from displaying + Disabilita questo programma dalla visualizzazione + Open target folder - Program - Search programs in Flow Launcher + Programma + Cerca programmi in Flow Launcher - Invalid Path + Percorso non valido - Customized Explorer + Esplora Risorse personalizzato Parametri You can customized the explorer used for opening the container folder by inputing the Environmental Variable of the explorer you want to use. It will be useful to use CMD to test whether the Environmental Variable is available. Inserisci i parametri personalizzati che vuoi aggiungere per il tuo Esplora Risorse personalizzato. %s per la cartella superiore, %f per il percorso completo (che funziona solo per win32). Controlla il sito dell'Esplora Risorse per i dettagli. diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml index 863735769..6c35d7df1 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml index 9741a7fe2..2857194c7 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml @@ -71,6 +71,7 @@ 관리자 권한으로 실행 포함된 폴더 열기 이 프로그램 표시 비활성화 + Open target folder 프로그램 Flow Launcher에서 프로그램을 검색합니다 diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml index e62854305..dda427996 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml index 3c3b20afd..972cbe8d6 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml index c46a92006..d651fb0b9 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml @@ -71,6 +71,7 @@ Uruchom jako administrator Open containing folder Disable this program from displaying + Open target folder Programy Szukaj i uruchamiaj programy z poziomu Flow Launchera diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml index b943ffe97..f1cb7d7a2 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml index 669fe3182..0832c2150 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml @@ -71,6 +71,7 @@ Executar como administrador Abrir pasta de destino Desativar exibição deste programa + Abrir pasta de destino Programas Pesquisa de programas com o Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml index 9fdb70d4f..ebc30e161 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml @@ -71,6 +71,7 @@ Запустить от имени администратора Открыть содержащую папку Отключить отображение этой программы + Open target folder Программа Поиск программ в Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml index ee62a823e..a669d256b 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml @@ -71,6 +71,7 @@ Spustiť ako správca Otvoriť umiestnenie priečinka Zakázať zobrazovanie tohto programu + Otvoriť cieľový pričinok Program Vyhľadávanie programov vo Flow Launcheri diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml index f77bce039..6d995e1ab 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml index 3d1f087f2..675c9f132 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml @@ -71,6 +71,7 @@ Yönetici Olarak Çalıştır Open containing folder Disable this program from displaying + Open target folder Program Programları Flow Launcher'tan arayın diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml index 83062ce6e..344695d1e 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml @@ -2,91 +2,92 @@ - Reset Default + Скинути до значення за замовчуванням Видалити Редагувати Додати - Name - Enable - Enabled - Disable - Status - Enabled - Disabled - Location - All Programs - File Type - Reindex - Indexing - Index Sources - Options - UWP Apps - When enabled, Flow will load UWP Applications - Start Menu - When enabled, Flow will load programs from the start menu - Registry - When enabled, Flow will load programs from the registry + Назва + Увімкнути + Увімкнено + Вимкнути + Статус + Увімкнено + Вимкнено + Місцезнаходження + Всі програми + Тип файлу + Переіндексувати + Індексація + Джерела індексу + Параметри + Додатки UWP + Якщо увімкнено, Flow буде завантажувати UWP-додатки + Меню Пуск + Якщо увімкнено, Flow завантажуватиме програми зі стартового меню + Реєстр + Якщо увімкнено, Flow буде завантажувати програми з реєстру PATH - When enabled, Flow will load programs from the PATH environment variable - Hide app path - For executable files such as UWP or lnk, hide the file path from being visible - Search in Program Description - Flow will search program's description - Suffixes - Max Depth + Якщо увімкнено, Flow буде завантажувати програми зі змінної середовища PATH + Приховати шлях до програми + Для виконуваних файлів, таких як UWP або lnk, приховати шлях до файлу, щоб його не було видно + Пошук в описі програми + Flow буде шукати опис програми + Суфікси + Максимальна глибина - Directory - Browse - File Suffixes: - Maximum Search Depth (-1 is unlimited): + Каталог + Перегляд + Суфікси файлів: + Максимальна глибина пошуку (-1 - необмежена): - Please select a program source - Are you sure you want to delete the selected program sources? - Another program source with the same location already exists. + Будь ласка, виберіть джерело програми + Ви впевнені, що хочете видалити вибрані джерела програм? + Інше програмне джерело з тим самим розташуванням вже існує. - Program Source - Edit directory and status of this program source. + Вихідний код програми + Редагування каталогу і статусу вихідного коду програми. Оновити - Program Plugin will only index files with selected suffixes and .url files with selected protocols. - Successfully updated file suffixes - File suffixes can't be empty - Protocols can't be empty + Плагін проіндексує лише файли з вибраними суфіксами та .url-файли з вибраними протоколами. + Успішно оновлено суфікси файлів + Суфікси файлів не можуть бути порожніми + Протоколи не можуть бути порожніми - File Suffixes - URL Protocols - Steam Games + Суфікси файлів + Протоколи URL-адрес + Ігри в Steam Epic Games Http/Https - Custom URL Protocols - Custom File Suffixes + Користувацькі протоколи URL-адрес + Користувацькі суфікси файлів - Insert file suffixes you want to index. Suffixes should be separated by ';'. (ex>bat;py) + Вставте суфікси файлів, які потрібно проіндексувати. Суфікси слід розділяти символом ';'. (ex>bat;py)a - Insert protocols of .url files you want to index. Protocols should be separated by ';', and should end with "://". (ex>ftp://;mailto://) + Вставте протоколи .url файлів, які потрібно проіндексувати. Протоколи слід розділяти символом ';' і закінчувати "://". (ex>ftp://; mailto://) - Run As Different User - Run As Administrator - Open containing folder - Disable this program from displaying + Запустити від імені іншого користувача + Запустити від імені адміністратора + Відкрити папку + Вимкнути відображення цієї програми + Відкрити цільову папку - Program - Search programs in Flow Launcher + Програма + Пошук програм у Flow Launcher - Invalid Path + Неправильний шлях - Customized Explorer - Args - You can customized the explorer used for opening the container folder by inputing the Environmental Variable of the explorer you want to use. It will be useful to use CMD to test whether the Environmental Variable is available. - Enter the customized args you want to add for your customized explorer. %s for parent directory, %f for full path (which only works for win32). Check the explorer's website for details. + Кастомізований провідник + Аргументи + Ви можете налаштувати провідник, який використовується для відкриття теки контейнера, ввівши змінну середовища провідника, який ви хочете використовувати. Буде корисно скористатися командою CMD для перевірки доступності змінної середовища. + Введіть спеціальні аргументи, які ви хочете додати до вашого провідника. %s для батьківського каталогу, %f для повного шляху (працює лише для win32). Докладнішу інформацію можна знайти на веб-сайті провідника. Успішно - Error - Successfully disabled this program from displaying in your query - This app is not intended to be run as administrator - Unable to run {0} + Помилка + Успішно вимкнено відображення цієї програми у вашому запиті + Ця програма не призначена для запуску від імені адміністратора + Неможливо запустити {0} diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/vi.xaml new file mode 100644 index 000000000..7ec467566 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/vi.xaml @@ -0,0 +1,93 @@ + + + + + Khôi phục về mặc định + Xóa + Sửa + Thêm + Tên + Kích hoạt + Đã bật + Vô hiệu + Trạng thái + Đã bật + Vô hiệu hóa + Vị trí + Tất cả ứng dụng + Loại tệp + Chỉ số hoá + Nhập dữ liệu + Nguồn chỉ mục + Tùy chỉnh + UWP Apps + When enabled, Flow will load UWP Applications + Menu Khởi động + Khi được bật, Flow sẽ tải các chương trình từ menu bắt đầu + Đăng ký + Khi được bật, Flow sẽ tải các chương trình từ menu bắt đầu + ĐƯỜNG DẪN + Khi được bật, Flow sẽ tải các chương trình từ menu bắt đầu + Ẩn đường dẫn ứng dụng + Đối với các tệp thực thi như UWP hoặc lnk, hãy ẩn đường dẫn tệp để không hiển thị + Tìm kiếm trong Mô tả chương trình + Flow will search program's description + Hậu tố + Độ sâu tối đa: + + Danh bạ + Duyệt + Hậu tố tệp: + Độ sâu tìm kiếm tối đa (-1 là không giới hạn): + + Hãy chọn một nguồn dữ liệu + Bạn có chắc chắn là muốn xóa các đặt hàng đã chọn? + Another program source with the same location already exists. + + Program Source + Edit directory and status of this program source. + + Cập nhật + Program Plugin will only index files with selected suffixes and .url files with selected protocols. + Đã cập nhật thành công hậu tố tệp + Hậu tố tệp không được để trống + Trường cổng không được trống + + Hậu tố tệp + URL Protocols + Steam Games + Epic Games + Http/Https + Custom URL Protocols + Custom File Suffixes + + Insert file suffixes you want to index. Suffixes should be separated by ';'. (ex>bat;py) + + + Insert protocols of .url files you want to index. Protocols should be separated by ';', and should end with "://". (ex>ftp://;mailto://) + + + Xóa lựa chọn đã chọn + Chạy với quyền quản trị + Mở thư mục chứa + Vô hiệu hóa chương trình này hiển thị + Open target folder + + Chương trình + Tìm kiếm chương trình trong Flow Launcher + + Đường dẫn không hợp lệ + + Trình khám phá tùy chỉnh + Đối số + Bạn có thể tùy chỉnh trình thám hiểm được sử dụng để mở thư mục vùng chứa bằng cách nhập Biến môi trường của trình khám phá mà bạn muốn sử dụng. Sẽ rất hữu ích khi sử dụng CMD để kiểm tra xem Biến môi trường có sẵn hay không. + Nhập các đối số tùy chỉnh mà bạn muốn thêm cho trình khám phá tùy chỉnh của mình. %s cho thư mục mẹ, %f cho đường dẫn đầy đủ (chỉ hoạt động với win32). Kiểm tra trang web của nhà thám hiểm để biết chi tiết. + + + Thành công + Lỗi + Đã vô hiệu hóa thành công chương trình này khỏi hiển thị trong truy vấn của bạn + Ứng dụng này không nhằm mục đích chạy với tư cách quản trị viên + Không thể đọc {0} + + diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml index 3a6fbda5d..324bd3057 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml @@ -71,6 +71,7 @@ 以管理员身份运行 打开文件所在文件夹 禁止显示该程序 + Open target folder 程序 在 Flow Launcher 中搜索程序 diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml index d27b5f78f..7c72b1aa3 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml @@ -71,6 +71,7 @@ 以系統管理員身分執行 開啟檔案位置 Disable this program from displaying + Open target folder 程式 在 Flow Launcher 中搜尋程式 diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index ac23534b1..8bf1830e3 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -11,38 +11,48 @@ using Flow.Launcher.Plugin.Program.Programs; using Flow.Launcher.Plugin.Program.Views; using Flow.Launcher.Plugin.Program.Views.Models; using Microsoft.Extensions.Caching.Memory; +using Path = System.IO.Path; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Plugin.Program { - public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable, IDisposable + public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable, + IDisposable { internal static Win32[] _win32s { get; set; } - internal static UWP.Application[] _uwps { get; set; } + internal static UWPApp[] _uwps { get; set; } internal static Settings _settings { get; set; } internal static PluginInitContext Context { get; private set; } private static BinaryStorage _win32Storage; - private static BinaryStorage _uwpStorage; + private static BinaryStorage _uwpStorage; private static readonly List emptyResults = new(); - private static readonly MemoryCacheOptions cacheOptions = new() - { - SizeLimit = 1560 - }; + private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 }; private static MemoryCache cache = new(cacheOptions); + private static readonly string[] commonUninstallerNames = + { + "uninst.exe", + "unins000.exe", + "uninst000.exe", + "uninstall.exe" + }; + // For cases when the uninstaller is named like "Uninstall Program Name.exe" + private const string CommonUninstallerPrefix = "uninstall"; + private const string CommonUninstallerSuffix = ".exe"; + static Main() { } public void Save() { - _win32Storage.Save(_win32s); - _uwpStorage.Save(_uwps); + _win32Storage.SaveAsync(_win32s); + _uwpStorage.SaveAsync(_uwps); } public async Task> QueryAsync(Query query, CancellationToken token) @@ -54,6 +64,7 @@ namespace Flow.Launcher.Plugin.Program .Concat(_uwps) .AsParallel() .WithCancellation(token) + .Where(HideUninstallersFilter) .Where(p => p.Enabled) .Select(p => p.Result(query.Search, Context.API)) .Where(r => r?.Score > 0) @@ -70,23 +81,33 @@ namespace Flow.Launcher.Plugin.Program return result; } + private bool HideUninstallersFilter(IProgram program) + { + if (!_settings.HideUninstallers) return true; + if (program is not Win32 win32) return true; + var fileName = Path.GetFileName(win32.ExecutablePath); + return !commonUninstallerNames.Contains(fileName, StringComparer.OrdinalIgnoreCase) && + !(fileName.StartsWith(CommonUninstallerPrefix, StringComparison.OrdinalIgnoreCase) && + fileName.EndsWith(CommonUninstallerSuffix, StringComparison.OrdinalIgnoreCase)); + } + public async Task InitAsync(PluginInitContext context) { Context = context; _settings = context.API.LoadSettingJsonStorage(); - Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () => + await Stopwatch.NormalAsync("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", async () => { _win32Storage = new BinaryStorage("Win32"); - _win32s = _win32Storage.TryLoad(Array.Empty()); - _uwpStorage = new BinaryStorage("UWP"); - _uwps = _uwpStorage.TryLoad(Array.Empty()); + _win32s = await _win32Storage.TryLoadAsync(Array.Empty()); + _uwpStorage = new BinaryStorage("UWP"); + _uwps = await _uwpStorage.TryLoadAsync(Array.Empty()); }); Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>"); Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>"); - bool cacheEmpty = !_win32s.Any() && !_uwps.Any(); + bool cacheEmpty = !_win32s.Any() || !_uwps.Any(); if (cacheEmpty || _settings.LastIndexTime.AddHours(30) < DateTime.Now) { @@ -104,7 +125,7 @@ namespace Flow.Launcher.Plugin.Program static void WatchProgramUpdate() { Win32.WatchProgramUpdate(_settings); - _ = UWP.WatchPackageChange(); + _ = UWPPackage.WatchPackageChange(); } } @@ -113,16 +134,16 @@ namespace Flow.Launcher.Plugin.Program var win32S = Win32.All(_settings); _win32s = win32S; ResetCache(); - _win32Storage.Save(_win32s); + _win32Storage.SaveAsync(_win32s); _settings.LastIndexTime = DateTime.Now; } public static void IndexUwpPrograms() { - var applications = UWP.All(_settings); + var applications = UWPPackage.All(_settings); _uwps = applications; ResetCache(); - _uwpStorage.Save(_uwps); + _uwpStorage.SaveAsync(_uwps); _settings.LastIndexTime = DateTime.Now; } @@ -228,7 +249,8 @@ namespace Flow.Launcher.Plugin.Program catch (Exception) { 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); + 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); } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs deleted file mode 100644 index d5924ba28..000000000 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ /dev/null @@ -1,737 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Security.Principal; -using System.Threading.Tasks; -using System.Windows.Media.Imaging; -using Windows.ApplicationModel; -using Windows.Management.Deployment; -using Flow.Launcher.Infrastructure; -using Flow.Launcher.Plugin.Program.Logger; -using Flow.Launcher.Plugin.SharedModels; -using System.Threading.Channels; -using System.Xml; -using Windows.ApplicationModel.Core; -using System.Windows.Input; - -namespace Flow.Launcher.Plugin.Program.Programs -{ - [Serializable] - public class UWP - { - public string Name { get; } - public string FullName { get; } - public string FamilyName { get; } - public string Location { get; set; } - - public Application[] Apps { get; set; } = Array.Empty(); - - - public UWP(Package package) - { - Location = package.InstalledLocation.Path; - Name = package.Id.Name; - FullName = package.Id.FullName; - FamilyName = package.Id.FamilyName; - } - - public void InitAppsInPackage(Package package) - { - var apps = new List(); - // WinRT - var appListEntries = package.GetAppListEntries(); - foreach (var app in appListEntries) - { - try - { - var tmp = new Application(app, this); - apps.Add(tmp); - } - catch (Exception e) - { - ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" + - "|Unexpected exception occurs when trying to construct a Application from package" - + $"{FullName} from location {Location}", e); - } - } - Apps = apps.ToArray(); - - try - { - var xmlDoc = GetManifestXml(); - if (xmlDoc == null) - { - return; - } - - var xmlRoot = xmlDoc.DocumentElement; - var packageVersion = GetPackageVersionFromManifest(xmlRoot); - if (!smallLogoNameFromVersion.TryGetValue(packageVersion, out string logoName) || - !bigLogoNameFromVersion.TryGetValue(packageVersion, out string bigLogoName)) - { - return; - } - - var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable); - namespaceManager.AddNamespace("d", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); // still need a name - namespaceManager.AddNamespace("rescap", "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"); - namespaceManager.AddNamespace("uap10", "http://schemas.microsoft.com/appx/manifest/uap/windows10/10"); - - var allowElevationNode = xmlRoot.SelectSingleNode("//rescap:Capability[@Name='allowElevation']", namespaceManager); - bool packageCanElevate = allowElevationNode != null; - - var appsNode = xmlRoot.SelectSingleNode("d:Applications", namespaceManager); - foreach (var app in Apps) - { - // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package - // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes - var id = app.UserModelId.Split('!')[1]; - var appNode = appsNode?.SelectSingleNode($"d:Application[@Id='{id}']", namespaceManager); - if (appNode != null) - { - app.CanRunElevated = packageCanElevate || Application.IfAppCanRunElevated(appNode); - - // local name to fit all versions - var visualElement = appNode.SelectSingleNode($"*[local-name()='VisualElements']", namespaceManager); - var logoUri = visualElement?.Attributes[logoName]?.Value; - app.LogoPath = app.LogoPathFromUri(logoUri, (64, 64)); - // use small logo or may have a big margin - var previewUri = visualElement?.Attributes[logoName]?.Value; - app.PreviewImagePath = app.LogoPathFromUri(previewUri, (256, 256)); - } - } - } - catch (Exception e) - { - ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" + - "|Unexpected exception occurs when trying to construct a Application from package" - + $"{FullName} from location {Location}", e); - } - } - - private XmlDocument GetManifestXml() - { - var manifest = Path.Combine(Location, "AppxManifest.xml"); - try - { - var file = File.ReadAllText(manifest); - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(file); - return xmlDoc; - } - catch (FileNotFoundException e) - { - ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "AppxManifest.xml not found.", e); - return null; - } - catch (Exception e) - { - ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "An unexpected error occurred and unable to parse AppxManifest.xml", e); - return null; - } - } - - private PackageVersion GetPackageVersionFromManifest(XmlNode xmlRoot) - { - if (xmlRoot != null) - { - - var namespaces = xmlRoot.Attributes; - foreach (XmlAttribute ns in namespaces) - { - if (versionFromNamespace.TryGetValue(ns.Value, out var packageVersion)) - { - return packageVersion; - } - } - - ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" + - "|Trying to get the package version of the UWP program, but an unknown UWP app-manifest version in package " - + $"{FullName} from location {Location}", new FormatException()); - return PackageVersion.Unknown; - } - else - { - ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" + - "|Can't parse AppManifest.xml of package " - + $"{FullName} from location {Location}", new ArgumentNullException(nameof(xmlRoot))); - return PackageVersion.Unknown; - } - } - - private static readonly Dictionary versionFromNamespace = new() - { - { - "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10 - }, - { - "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81 - }, - { - "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8 - }, - }; - - private static readonly Dictionary smallLogoNameFromVersion = new() - { - { - PackageVersion.Windows10, "Square44x44Logo" - }, - { - PackageVersion.Windows81, "Square30x30Logo" - }, - { - PackageVersion.Windows8, "SmallLogo" - }, - }; - - private static readonly Dictionary bigLogoNameFromVersion = new() - { - { - PackageVersion.Windows10, "Square150x150Logo" - }, - { - PackageVersion.Windows81, "Square150x150Logo" - }, - { - PackageVersion.Windows8, "Logo" - }, - }; - - public static Application[] All(Settings settings) - { - var support = SupportUWP(); - if (support && settings.EnableUWP) - { - var applications = CurrentUserPackages().AsParallel().SelectMany(p => - { - UWP u; - try - { - u = new UWP(p); - u.InitAppsInPackage(p); - } -#if !DEBUG - catch (Exception e) - { - ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and unable to convert Package to UWP for {p.Id.FullName}", e); - return Array.Empty(); - } -#endif -#if DEBUG //make developer aware and implement handling - catch - { - throw; - } -#endif - return u.Apps; - }).ToArray(); - - var updatedListWithoutDisabledApps = applications - .Where(t1 => !Main._settings.DisabledProgramSources - .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)); - - return updatedListWithoutDisabledApps.ToArray(); - } - else - { - return Array.Empty(); - } - } - - public static bool SupportUWP() - { - var windows10 = new Version(10, 0); - var support = Environment.OSVersion.Version.Major >= windows10.Major; - return support; - } - - private static IEnumerable CurrentUserPackages() - { - var user = WindowsIdentity.GetCurrent().User; - - if (user != null) - { - var userId = user.Value; - PackageManager packageManager; - try - { - packageManager = new PackageManager(); - } - catch - { - // Bug from https://github.com/microsoft/CsWinRT, using Microsoft.Windows.SDK.NET.Ref 10.0.19041.0. - // Only happens on the first time, so a try catch can fix it. - packageManager = new PackageManager(); - } - var packages = packageManager.FindPackagesForUser(userId); - packages = packages.Where(p => - { - try - { - var f = p.IsFramework; - var d = p.IsDevelopmentMode; - var path = p.InstalledLocation.Path; - return !f && !d && !string.IsNullOrEmpty(path); - } - catch (Exception e) - { - ProgramLogger.LogException("UWP", "CurrentUserPackages", $"{p.Id.FullName}", "An unexpected error occurred and " - + $"unable to verify if package is valid", e); - return false; - } - }); - return packages; - } - else - { - return Array.Empty(); - } - } - - private static Channel PackageChangeChannel = Channel.CreateBounded(1); - - public static async Task WatchPackageChange() - { - if (Environment.OSVersion.Version.Major >= 10) - { - var catalog = PackageCatalog.OpenForCurrentUser(); - catalog.PackageInstalling += (_, args) => - { - if (args.IsComplete) - PackageChangeChannel.Writer.TryWrite(default); - }; - catalog.PackageUninstalling += (_, args) => - { - if (args.IsComplete) - PackageChangeChannel.Writer.TryWrite(default); - }; - catalog.PackageUpdating += (_, args) => - { - if (args.IsComplete) - PackageChangeChannel.Writer.TryWrite(default); - }; - - while (await PackageChangeChannel.Reader.WaitToReadAsync().ConfigureAwait(false)) - { - await Task.Delay(3000).ConfigureAwait(false); - PackageChangeChannel.Reader.TryRead(out _); - await Task.Run(Main.IndexUwpPrograms); - } - - } - } - - public override string ToString() - { - return FamilyName; - } - - public override bool Equals(object obj) - { - if (obj is UWP uwp) - { - return FamilyName.Equals(uwp.FamilyName); - } - else - { - return false; - } - } - - public override int GetHashCode() - { - return FamilyName.GetHashCode(); - } - - [Serializable] - public class Application : IProgram - { - private string _uid = string.Empty; - public string UniqueIdentifier { get => _uid; set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); } - public string DisplayName { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public string UserModelId { get; set; } = string.Empty; - //public string BackgroundColor { get; set; } = string.Empty; // preserve for future use - public string Name => DisplayName; - public string Location { get; set; } = string.Empty; - - public bool Enabled { get; set; } = false; - public bool CanRunElevated { get; set; } = false; - public string LogoPath { get; set; } = string.Empty; - public string PreviewImagePath { get; set; } = string.Empty; - - public Application(AppListEntry appListEntry, UWP package) - { - UserModelId = appListEntry.AppUserModelId; - UniqueIdentifier = appListEntry.AppUserModelId; - DisplayName = appListEntry.DisplayInfo.DisplayName; - Description = appListEntry.DisplayInfo.Description; - Location = package.Location; - Enabled = true; - } - - public Result Result(string query, IPublicAPI api) - { - string title; - MatchResult matchResult; - - // We suppose Name won't be null - if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || Name.Equals(Description)) - { - title = Name; - matchResult = StringMatcher.FuzzySearch(query, Name); - } - else - { - title = $"{Name}: {Description}"; - var nameMatch = StringMatcher.FuzzySearch(query, Name); - var descriptionMatch = StringMatcher.FuzzySearch(query, Description); - if (descriptionMatch.Score > nameMatch.Score) - { - for (int i = 0; i < descriptionMatch.MatchData.Count; i++) - { - descriptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": " - } - matchResult = descriptionMatch; - } - else - { - matchResult = nameMatch; - } - } - - if (!matchResult.IsSearchPrecisionScoreMet()) - return null; - - var result = new Result - { - Title = title, - AutoCompleteText = Name, - SubTitle = Main._settings.HideAppsPath ? string.Empty : Location, - IcoPath = LogoPath, - Preview = new Result.PreviewInfo - { - IsMedia = false, - PreviewImagePath = PreviewImagePath, - Description = Description - }, - Score = matchResult.Score, - TitleHighlightData = matchResult.MatchData, - ContextData = this, - Action = e => - { - // Ctrl + Enter to open containing folder - bool openFolder = e.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control; - if (openFolder) - { - Main.Context.API.OpenDirectory(Location); - return true; - } - - // Ctrl + Shift + Enter to run elevated - bool elevated = e.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift); - - bool shouldRunElevated = elevated && CanRunElevated; - _ = Task.Run(() => Launch(shouldRunElevated)).ConfigureAwait(false); - if (elevated && !shouldRunElevated) - { - var title = api.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error"); - var message = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator_not_supported_message"); - api.ShowMsg(title, message, string.Empty); - } - - return true; - } - }; - - - return result; - } - - public List ContextMenus(IPublicAPI api) - { - var contextMenus = new List - { - new Result - { - Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"), - Action = _ => - { - Main.Context.API.OpenDirectory(Location); - - return true; - }, - IcoPath = "Images/folder.png", - Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe838"), - } - }; - - if (CanRunElevated) - { - contextMenus.Add(new Result - { - Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"), - Action = _ => - { - Task.Run(() => Launch(true)).ConfigureAwait(false); - return true; - }, - IcoPath = "Images/cmd.png", - Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe7ef") - }); - } - - return contextMenus; - } - - private void Launch(bool elevated = false) - { - string command = "shell:AppsFolder\\" + UserModelId; - command = Environment.ExpandEnvironmentVariables(command.Trim()); - - var info = new ProcessStartInfo(command) - { - UseShellExecute = true, - Verb = elevated ? "runas" : "" - }; - - Main.StartProcess(Process.Start, info); - } - - internal static bool IfAppCanRunElevated(XmlNode appNode) - { - // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package - // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes - - return appNode?.Attributes["EntryPoint"]?.Value == "Windows.FullTrustApplication" || - appNode?.Attributes["uap10:TrustLevel"]?.Value == "mediumIL"; - } - - internal string LogoPathFromUri(string uri, (int, int) desiredSize) - { - // all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets - // windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx - // windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size - // windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx - - if (string.IsNullOrWhiteSpace(uri)) - { - ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" + - $"|{UserModelId} 's logo uri is null or empty: {Location}", new ArgumentException("uri")); - return string.Empty; - } - - string path = Path.Combine(Location, uri); - - var pxCount = desiredSize.Item1 * desiredSize.Item2; - var logoPath = TryToFindLogo(uri, path, pxCount); - if (logoPath == string.Empty) - { - var tmp = Path.Combine(Location, "Assets", uri); - if (!path.Equals(tmp, StringComparison.OrdinalIgnoreCase)) - { - // TODO: Don't know why, just keep it at the moment - // Maybe on older version of Windows 10? - // for C:\Windows\MiracastView etc - return TryToFindLogo(uri, tmp, pxCount); - } - } - return logoPath; - - string TryToFindLogo(string uri, string path, int px) - { - var extension = Path.GetExtension(path); - if (extension != null) - { - //if (File.Exists(path)) - //{ - // return path; // shortcut, avoid enumerating files - //} - - var logoNamePrefix = Path.GetFileNameWithoutExtension(uri); // e.g Square44x44 - var logoDir = Path.GetDirectoryName(path); // e.g ..\..\Assets - if (String.IsNullOrEmpty(logoNamePrefix) || !Directory.Exists(logoDir)) - { - // Known issue: Edge always triggers it since logo is not at uri - ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" + - $"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Location}", new FileNotFoundException()); - return string.Empty; - } - - var logos = Directory.EnumerateFiles(logoDir, $"{logoNamePrefix}*{extension}"); - - // Currently we don't care which one to choose - // Just ignore all qualifiers - // select like logo.[xxx_yyy].png - // https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast - - // todo select from file name like pt run - var selected = logos.FirstOrDefault(); - var closest = selected; - int min = int.MaxValue; - foreach (var logo in logos) - { - - var imageStream = File.OpenRead(logo); - var decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.None); - var height = decoder.Frames[0].PixelHeight; - var width = decoder.Frames[0].PixelWidth; - int pixelCountDiff = Math.Abs(height * width - px); - if (pixelCountDiff < min) - { - // try to find the closest to desired size - closest = logo; - if (pixelCountDiff == 0) - break; // found - min = pixelCountDiff; - } - } - - selected = closest; - if (!string.IsNullOrEmpty(selected)) - { - return selected; - } - else - { - ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" + - $"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Location}", new FileNotFoundException()); - return string.Empty; - } - } - else - { - ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" + - $"|Unable to find extension from {uri} for {UserModelId} " + - $"in package location {Location}", new FileNotFoundException()); - return string.Empty; - } - } - } - - - #region logo legacy - // preserve for potential future use - - //public ImageSource Logo() - //{ - // var logo = ImageFromPath(LogoPath); - // var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package? - - // // todo magic! temp fix for cross thread object - // plated.Freeze(); - // return plated; - //} - //private BitmapImage ImageFromPath(string path) - //{ - // if (File.Exists(path)) - // { - // var image = new BitmapImage(); - // image.BeginInit(); - // image.UriSource = new Uri(path); - // image.CacheOption = BitmapCacheOption.OnLoad; - // image.EndInit(); - // image.Freeze(); - // return image; - // } - // else - // { - // ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Available" : path)}" + - // $"|Unable to get logo for {UserModelId} from {path} and" + - // $" located in {Location}", new FileNotFoundException()); - // return new BitmapImage(new Uri(Constant.MissingImgIcon)); - // } - //} - - //private ImageSource PlatedImage(BitmapImage image) - //{ - // if (!string.IsNullOrEmpty(BackgroundColor) && BackgroundColor != "transparent") - // { - // var width = image.Width; - // var height = image.Height; - // var x = 0; - // var y = 0; - - // var group = new DrawingGroup(); - - // var converted = ColorConverter.ConvertFromString(BackgroundColor); - // if (converted != null) - // { - // var color = (Color)converted; - // var brush = new SolidColorBrush(color); - // var pen = new Pen(brush, 1); - // var backgroundArea = new Rect(0, 0, width, width); - // var rectangle = new RectangleGeometry(backgroundArea); - // var rectDrawing = new GeometryDrawing(brush, pen, rectangle); - // group.Children.Add(rectDrawing); - - // var imageArea = new Rect(x, y, image.Width, image.Height); - // var imageDrawing = new ImageDrawing(image, imageArea); - // group.Children.Add(imageDrawing); - - // // http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush - // var visual = new DrawingVisual(); - // var context = visual.RenderOpen(); - // context.DrawDrawing(group); - // context.Close(); - // const int dpiScale100 = 96; - // var bitmap = new RenderTargetBitmap( - // Convert.ToInt32(width), Convert.ToInt32(height), - // dpiScale100, dpiScale100, - // PixelFormats.Pbgra32 - // ); - // bitmap.Render(visual); - // return bitmap; - // } - // else - // { - // ProgramLogger.LogException($"|UWP|PlatedImage|{Location}" + - // $"|Unable to convert background string {BackgroundColor} " + - // $"to color for {Location}", new InvalidOperationException()); - - // return new BitmapImage(new Uri(Constant.MissingImgIcon)); - // } - // } - // else - // { - // // todo use windows theme as background - // return image; - // } - //} - - #endregion - public override string ToString() - { - return $"{DisplayName}: {Description}"; - } - - public override bool Equals(object obj) - { - if (obj is Application other) - { - return UniqueIdentifier == other.UniqueIdentifier; - } - else - { - return false; - } - } - - public override int GetHashCode() - { - return UniqueIdentifier.GetHashCode(); - } - } - - public enum PackageVersion - { - Windows10, - Windows81, - Windows8, - Unknown - } - } -} diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs new file mode 100644 index 000000000..654897cc5 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWPPackage.cs @@ -0,0 +1,752 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Windows.ApplicationModel; +using Windows.Management.Deployment; +using Flow.Launcher.Infrastructure; +using Flow.Launcher.Plugin.Program.Logger; +using Flow.Launcher.Plugin.SharedModels; +using System.Threading.Channels; +using System.Xml; +using Windows.ApplicationModel.Core; +using System.Windows.Input; +using MemoryPack; + +namespace Flow.Launcher.Plugin.Program.Programs +{ + [MemoryPackable] + public partial class UWPPackage + { + public string Name { get; } + public string FullName { get; } + public string FamilyName { get; } + public string Location { get; set; } + + public UWPApp[] Apps { get; set; } = Array.Empty(); + + + /// + /// For serialization + /// + [MemoryPackConstructor] + private UWPPackage() + { + } + + public UWPPackage(Package package) + { + Location = package.InstalledLocation.Path; + Name = package.Id.Name; + FullName = package.Id.FullName; + FamilyName = package.Id.FamilyName; + } + + public void InitAppsInPackage(Package package) + { + var apps = new List(); + // WinRT + var appListEntries = package.GetAppListEntries(); + foreach (var app in appListEntries) + { + try + { + var tmp = new UWPApp(app, this); + apps.Add(tmp); + } + catch (Exception e) + { + ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" + + "|Unexpected exception occurs when trying to construct a Application from package" + + $"{FullName} from location {Location}", e); + } + } + + Apps = apps.ToArray(); + + try + { + var xmlDoc = GetManifestXml(); + if (xmlDoc == null) + { + return; + } + + var xmlRoot = xmlDoc.DocumentElement; + var packageVersion = GetPackageVersionFromManifest(xmlRoot); + if (!smallLogoNameFromVersion.TryGetValue(packageVersion, out string logoName) || + !bigLogoNameFromVersion.TryGetValue(packageVersion, out string bigLogoName)) + { + return; + } + + var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable); + namespaceManager.AddNamespace("d", + "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); // still need a name + namespaceManager.AddNamespace("rescap", + "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"); + namespaceManager.AddNamespace("uap10", "http://schemas.microsoft.com/appx/manifest/uap/windows10/10"); + + var allowElevationNode = + xmlRoot.SelectSingleNode("//rescap:Capability[@Name='allowElevation']", namespaceManager); + bool packageCanElevate = allowElevationNode != null; + + var appsNode = xmlRoot.SelectSingleNode("d:Applications", namespaceManager); + foreach (var app in Apps) + { + // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package + // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes + var id = app.UserModelId.Split('!')[1]; + var appNode = appsNode?.SelectSingleNode($"d:Application[@Id='{id}']", namespaceManager); + if (appNode != null) + { + app.CanRunElevated = packageCanElevate || UWPApp.IfAppCanRunElevated(appNode); + + // local name to fit all versions + var visualElement = + appNode.SelectSingleNode($"*[local-name()='VisualElements']", namespaceManager); + var logoUri = visualElement?.Attributes[logoName]?.Value; + app.LogoPath = app.LogoPathFromUri(logoUri, (64, 64)); + // use small logo or may have a big margin + var previewUri = visualElement?.Attributes[logoName]?.Value; + app.PreviewImagePath = app.LogoPathFromUri(previewUri, (256, 256)); + } + } + } + catch (Exception e) + { + ProgramLogger.LogException($"|UWP|InitAppsInPackage|{Location}" + + "|Unexpected exception occurs when trying to construct a Application from package" + + $"{FullName} from location {Location}", e); + } + } + + private XmlDocument GetManifestXml() + { + var manifest = Path.Combine(Location, "AppxManifest.xml"); + try + { + var file = File.ReadAllText(manifest); + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(file); + return xmlDoc; + } + catch (FileNotFoundException e) + { + ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", "AppxManifest.xml not found.", e); + return null; + } + catch (Exception e) + { + ProgramLogger.LogException("UWP", "GetManifestXml", $"{Location}", + "An unexpected error occurred and unable to parse AppxManifest.xml", e); + return null; + } + } + + private PackageVersion GetPackageVersionFromManifest(XmlNode xmlRoot) + { + if (xmlRoot != null) + { + var namespaces = xmlRoot.Attributes; + foreach (XmlAttribute ns in namespaces) + { + if (versionFromNamespace.TryGetValue(ns.Value, out var packageVersion)) + { + return packageVersion; + } + } + + ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" + + "|Trying to get the package version of the UWP program, but an unknown UWP app-manifest version in package " + + $"{FullName} from location {Location}", new FormatException()); + return PackageVersion.Unknown; + } + else + { + ProgramLogger.LogException($"|UWP|GetPackageVersionFromManifest|{Location}" + + "|Can't parse AppManifest.xml of package " + + $"{FullName} from location {Location}", + new ArgumentNullException(nameof(xmlRoot))); + return PackageVersion.Unknown; + } + } + + private static readonly Dictionary versionFromNamespace = new() + { + { "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10 }, + { "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81 }, + { "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8 }, + }; + + private static readonly Dictionary smallLogoNameFromVersion = new() + { + { PackageVersion.Windows10, "Square44x44Logo" }, + { PackageVersion.Windows81, "Square30x30Logo" }, + { PackageVersion.Windows8, "SmallLogo" }, + }; + + private static readonly Dictionary bigLogoNameFromVersion = new() + { + { PackageVersion.Windows10, "Square150x150Logo" }, + { PackageVersion.Windows81, "Square150x150Logo" }, + { PackageVersion.Windows8, "Logo" }, + }; + + public static UWPApp[] All(Settings settings) + { + var support = SupportUWP(); + if (support && settings.EnableUWP) + { + var applications = CurrentUserPackages().AsParallel().SelectMany(p => + { + UWPPackage u; + try + { + u = new UWPPackage(p); + u.InitAppsInPackage(p); + } +#if !DEBUG + catch (Exception e) + { + ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and unable to convert Package to UWP for {p.Id.FullName}", e); + return Array.Empty(); + } +#endif +#if DEBUG //make developer aware and implement handling + catch + { + throw; + } +#endif + return u.Apps; + }).ToArray(); + + var updatedListWithoutDisabledApps = applications + .Where(t1 => !Main._settings.DisabledProgramSources + .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)); + + return updatedListWithoutDisabledApps.ToArray(); + } + else + { + return Array.Empty(); + } + } + + public static bool SupportUWP() + { + var windows10 = new Version(10, 0); + var support = Environment.OSVersion.Version.Major >= windows10.Major; + return support; + } + + private static IEnumerable CurrentUserPackages() + { + var user = WindowsIdentity.GetCurrent().User; + + if (user != null) + { + var userId = user.Value; + PackageManager packageManager; + try + { + packageManager = new PackageManager(); + } + catch + { + // Bug from https://github.com/microsoft/CsWinRT, using Microsoft.Windows.SDK.NET.Ref 10.0.19041.0. + // Only happens on the first time, so a try catch can fix it. + packageManager = new PackageManager(); + } + + var packages = packageManager.FindPackagesForUser(userId); + packages = packages.Where(p => + { + try + { + var f = p.IsFramework; + var d = p.IsDevelopmentMode; + var path = p.InstalledLocation.Path; + return !f && !d && !string.IsNullOrEmpty(path); + } + catch (Exception e) + { + ProgramLogger.LogException("UWP", "CurrentUserPackages", $"{p.Id.FullName}", + "An unexpected error occurred and " + + $"unable to verify if package is valid", e); + return false; + } + }); + return packages; + } + else + { + return Array.Empty(); + } + } + + private static Channel PackageChangeChannel = Channel.CreateBounded(1); + + public static async Task WatchPackageChange() + { + if (Environment.OSVersion.Version.Major >= 10) + { + var catalog = PackageCatalog.OpenForCurrentUser(); + catalog.PackageInstalling += (_, args) => + { + if (args.IsComplete) + PackageChangeChannel.Writer.TryWrite(default); + }; + catalog.PackageUninstalling += (_, args) => + { + if (args.IsComplete) + PackageChangeChannel.Writer.TryWrite(default); + }; + catalog.PackageUpdating += (_, args) => + { + if (args.IsComplete) + PackageChangeChannel.Writer.TryWrite(default); + }; + + while (await PackageChangeChannel.Reader.WaitToReadAsync().ConfigureAwait(false)) + { + await Task.Delay(3000).ConfigureAwait(false); + PackageChangeChannel.Reader.TryRead(out _); + await Task.Run(Main.IndexUwpPrograms); + } + } + } + + public override string ToString() + { + return FamilyName; + } + + public override bool Equals(object obj) + { + if (obj is UWPPackage uwp) + { + return FamilyName.Equals(uwp.FamilyName); + } + else + { + return false; + } + } + + public override int GetHashCode() + { + return FamilyName.GetHashCode(); + } + + + public enum PackageVersion + { + Windows10, + Windows81, + Windows8, + Unknown + } + } + + [MemoryPackable] + public partial class UWPApp : IProgram + { + private string _uid = string.Empty; + + public string UniqueIdentifier + { + get => _uid; + set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); + } + + public string DisplayName { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + + public string UserModelId { get; set; } = string.Empty; + + //public string BackgroundColor { get; set; } = string.Empty; // preserve for future use + public string Name => DisplayName; + public string Location { get; set; } = string.Empty; + + public bool Enabled { get; set; } = false; + public bool CanRunElevated { get; set; } = false; + public string LogoPath { get; set; } = string.Empty; + public string PreviewImagePath { get; set; } = string.Empty; + + [MemoryPackConstructor] + private UWPApp() + { + } + + public UWPApp(AppListEntry appListEntry, UWPPackage package) + { + UserModelId = appListEntry.AppUserModelId; + UniqueIdentifier = appListEntry.AppUserModelId; + DisplayName = appListEntry.DisplayInfo.DisplayName; + Description = appListEntry.DisplayInfo.Description; + Location = package.Location; + Enabled = true; + } + + public Result Result(string query, IPublicAPI api) + { + string title; + MatchResult matchResult; + + // We suppose Name won't be null + if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || Name.Equals(Description)) + { + title = Name; + matchResult = StringMatcher.FuzzySearch(query, Name); + } + else + { + title = $"{Name}: {Description}"; + var nameMatch = StringMatcher.FuzzySearch(query, Name); + var descriptionMatch = StringMatcher.FuzzySearch(query, Description); + if (descriptionMatch.Score > nameMatch.Score) + { + for (int i = 0; i < descriptionMatch.MatchData.Count; i++) + { + descriptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": " + } + + matchResult = descriptionMatch; + } + else + { + matchResult = nameMatch; + } + } + + if (!matchResult.IsSearchPrecisionScoreMet()) + return null; + + var result = new Result + { + Title = title, + AutoCompleteText = Name, + SubTitle = Main._settings.HideAppsPath ? string.Empty : Location, + IcoPath = LogoPath, + Preview = new Result.PreviewInfo + { + IsMedia = false, PreviewImagePath = PreviewImagePath, Description = Description + }, + Score = matchResult.Score, + TitleHighlightData = matchResult.MatchData, + ContextData = this, + Action = e => + { + // Ctrl + Enter to open containing folder + bool openFolder = e.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control; + if (openFolder) + { + Main.Context.API.OpenDirectory(Location); + return true; + } + + // Ctrl + Shift + Enter to run elevated + bool elevated = e.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift); + + bool shouldRunElevated = elevated && CanRunElevated; + _ = Task.Run(() => Launch(shouldRunElevated)).ConfigureAwait(false); + if (elevated && !shouldRunElevated) + { + var title = api.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_error"); + var message = + api.GetTranslation( + "flowlauncher_plugin_program_run_as_administrator_not_supported_message"); + api.ShowMsg(title, message, string.Empty); + } + + return true; + } + }; + + + return result; + } + + public List ContextMenus(IPublicAPI api) + { + var contextMenus = new List + { + new Result + { + Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"), + Action = _ => + { + Main.Context.API.OpenDirectory(Location); + + return true; + }, + IcoPath = "Images/folder.png", + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe838"), + } + }; + + if (CanRunElevated) + { + contextMenus.Add(new Result + { + Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"), + Action = _ => + { + Task.Run(() => Launch(true)).ConfigureAwait(false); + return true; + }, + IcoPath = "Images/cmd.png", + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe7ef") + }); + } + + return contextMenus; + } + + private void Launch(bool elevated = false) + { + string command = "shell:AppsFolder\\" + UserModelId; + command = Environment.ExpandEnvironmentVariables(command.Trim()); + + var info = new ProcessStartInfo(command) { UseShellExecute = true, Verb = elevated ? "runas" : "" }; + + Main.StartProcess(Process.Start, info); + } + + internal static bool IfAppCanRunElevated(XmlNode appNode) + { + // According to https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package + // and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes + + return appNode?.Attributes["EntryPoint"]?.Value == "Windows.FullTrustApplication" || + appNode?.Attributes["uap10:TrustLevel"]?.Value == "mediumIL"; + } + + internal string LogoPathFromUri(string uri, (int, int) desiredSize) + { + // all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets + // windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx + // windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size + // windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx + + if (string.IsNullOrWhiteSpace(uri)) + { + ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" + + $"|{UserModelId} 's logo uri is null or empty: {Location}", + new ArgumentException("uri")); + return string.Empty; + } + + string path = Path.Combine(Location, uri); + + var pxCount = desiredSize.Item1 * desiredSize.Item2; + var logoPath = TryToFindLogo(uri, path, pxCount); + if (logoPath == string.Empty) + { + var tmp = Path.Combine(Location, "Assets", uri); + if (!path.Equals(tmp, StringComparison.OrdinalIgnoreCase)) + { + // TODO: Don't know why, just keep it at the moment + // Maybe on older version of Windows 10? + // for C:\Windows\MiracastView etc + return TryToFindLogo(uri, tmp, pxCount); + } + } + + return logoPath; + + string TryToFindLogo(string uri, string path, int px) + { + var extension = Path.GetExtension(path); + if (extension != null) + { + //if (File.Exists(path)) + //{ + // return path; // shortcut, avoid enumerating files + //} + + var logoNamePrefix = Path.GetFileNameWithoutExtension(uri); // e.g Square44x44 + var logoDir = Path.GetDirectoryName(path); // e.g ..\..\Assets + if (String.IsNullOrEmpty(logoNamePrefix) || !Directory.Exists(logoDir)) + { + // Known issue: Edge always triggers it since logo is not at uri + ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" + + $"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Location}", + new FileNotFoundException()); + return string.Empty; + } + + var logos = Directory.EnumerateFiles(logoDir, $"{logoNamePrefix}*{extension}"); + + // Currently we don't care which one to choose + // Just ignore all qualifiers + // select like logo.[xxx_yyy].png + // https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast + + // todo select from file name like pt run + var selected = logos.FirstOrDefault(); + var closest = selected; + int min = int.MaxValue; + foreach (var logo in logos) + { + var imageStream = File.OpenRead(logo); + var decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.IgnoreColorProfile, + BitmapCacheOption.None); + var height = decoder.Frames[0].PixelHeight; + var width = decoder.Frames[0].PixelWidth; + int pixelCountDiff = Math.Abs(height * width - px); + if (pixelCountDiff < min) + { + // try to find the closest to desired size + closest = logo; + if (pixelCountDiff == 0) + break; // found + min = pixelCountDiff; + } + } + + selected = closest; + if (!string.IsNullOrEmpty(selected)) + { + return selected; + } + else + { + ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" + + $"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Location}", + new FileNotFoundException()); + return string.Empty; + } + } + else + { + ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Location}" + + $"|Unable to find extension from {uri} for {UserModelId} " + + $"in package location {Location}", new FileNotFoundException()); + return string.Empty; + } + } + } + + + #region logo legacy + + // preserve for potential future use + + //public ImageSource Logo() + //{ + // var logo = ImageFromPath(LogoPath); + // var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package? + + // // todo magic! temp fix for cross thread object + // plated.Freeze(); + // return plated; + //} + //private BitmapImage ImageFromPath(string path) + //{ + // if (File.Exists(path)) + // { + // var image = new BitmapImage(); + // image.BeginInit(); + // image.UriSource = new Uri(path); + // image.CacheOption = BitmapCacheOption.OnLoad; + // image.EndInit(); + // image.Freeze(); + // return image; + // } + // else + // { + // ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Available" : path)}" + + // $"|Unable to get logo for {UserModelId} from {path} and" + + // $" located in {Location}", new FileNotFoundException()); + // return new BitmapImage(new Uri(Constant.MissingImgIcon)); + // } + //} + + //private ImageSource PlatedImage(BitmapImage image) + //{ + // if (!string.IsNullOrEmpty(BackgroundColor) && BackgroundColor != "transparent") + // { + // var width = image.Width; + // var height = image.Height; + // var x = 0; + // var y = 0; + + // var group = new DrawingGroup(); + + // var converted = ColorConverter.ConvertFromString(BackgroundColor); + // if (converted != null) + // { + // var color = (Color)converted; + // var brush = new SolidColorBrush(color); + // var pen = new Pen(brush, 1); + // var backgroundArea = new Rect(0, 0, width, width); + // var rectangle = new RectangleGeometry(backgroundArea); + // var rectDrawing = new GeometryDrawing(brush, pen, rectangle); + // group.Children.Add(rectDrawing); + + // var imageArea = new Rect(x, y, image.Width, image.Height); + // var imageDrawing = new ImageDrawing(image, imageArea); + // group.Children.Add(imageDrawing); + + // // http://stackoverflow.com/questions/6676072/get-system-drawing-bitmap-of-a-wpf-area-using-visualbrush + // var visual = new DrawingVisual(); + // var context = visual.RenderOpen(); + // context.DrawDrawing(group); + // context.Close(); + // const int dpiScale100 = 96; + // var bitmap = new RenderTargetBitmap( + // Convert.ToInt32(width), Convert.ToInt32(height), + // dpiScale100, dpiScale100, + // PixelFormats.Pbgra32 + // ); + // bitmap.Render(visual); + // return bitmap; + // } + // else + // { + // ProgramLogger.LogException($"|UWP|PlatedImage|{Location}" + + // $"|Unable to convert background string {BackgroundColor} " + + // $"to color for {Location}", new InvalidOperationException()); + + // return new BitmapImage(new Uri(Constant.MissingImgIcon)); + // } + // } + // else + // { + // // todo use windows theme as background + // return image; + // } + //} + + #endregion + + public override string ToString() + { + return $"{DisplayName}: {Description}"; + } + + public override bool Equals(object obj) + { + if (obj is UWPApp other) + { + return UniqueIdentifier == other.UniqueIdentifier; + } + else + { + return false; + } + } + + public override int GetHashCode() + { + return UniqueIdentifier.GetHashCode(); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 20f489c30..a64a708ef 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -16,14 +16,21 @@ using System.Threading.Channels; using Flow.Launcher.Plugin.Program.Views.Models; using IniParser; using System.Windows.Input; +using MemoryPack; namespace Flow.Launcher.Plugin.Program.Programs { - [Serializable] - public class Win32 : IProgram, IEquatable + [MemoryPackable] + public partial class Win32 : IProgram, IEquatable { public string Name { get; set; } - public string UniqueIdentifier { get => _uid; set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); } // For path comparison + + public string UniqueIdentifier + { + get => _uid; + set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); + } // For path comparison + public string IcoPath { get; set; } /// @@ -32,15 +39,20 @@ namespace Flow.Launcher.Plugin.Program.Programs public string FullPath { get; set; } /// - /// Path of the executable for .lnk, or the URL for .url. Arguments are included if any. + /// Path of the executable for .lnk, or the URL for .url /// public string LnkResolvedPath { get; set; } /// - /// Path of the actual executable file. Args are included. + /// Path of the actual executable file /// public string ExecutablePath => LnkResolvedPath ?? FullPath; + /// + /// Arguments for the executable. + /// + public string Args { get; set; } + public string ParentDirectory { get; set; } /// @@ -96,7 +108,8 @@ namespace Flow.Launcher.Plugin.Program.Programs bool useLocalizedName = !string.IsNullOrEmpty(LocalizedName) && !Name.Equals(LocalizedName); string resultName = useLocalizedName ? LocalizedName : Name; - if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || resultName.Equals(Description)) + if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || + resultName.Equals(Description)) { title = resultName; matchResult = StringMatcher.FuzzySearch(query, resultName); @@ -113,6 +126,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { descriptionMatch.MatchData[i] += resultName.Length + 2; // 2 is ": " } + matchResult = descriptionMatch; } else @@ -129,10 +143,12 @@ namespace Flow.Launcher.Plugin.Program.Programs { candidates.Add(ExecutableName); } + if (useLocalizedName) { candidates.Add(Name); } + matchResult = Match(query, candidates); if (matchResult == null) { @@ -167,6 +183,7 @@ namespace Flow.Launcher.Plugin.Program.Programs Score = matchResult.Score, TitleHighlightData = matchResult.MatchData, ContextData = this, + TitleToolTip = $"{title}\n{ExecutablePath}", Action = c => { // Ctrl + Enter to open containing folder @@ -185,7 +202,7 @@ namespace Flow.Launcher.Plugin.Program.Programs FileName = FullPath, WorkingDirectory = ParentDirectory, UseShellExecute = true, - Verb = runAsAdmin ? "runas" : "" + Verb = runAsAdmin ? "runas" : "", }; _ = Task.Run(() => Main.StartProcess(Process.Start, info)); @@ -209,9 +226,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { var info = new ProcessStartInfo { - FileName = FullPath, - WorkingDirectory = ParentDirectory, - UseShellExecute = true + FileName = FullPath, WorkingDirectory = ParentDirectory, UseShellExecute = true }; Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info)); @@ -252,11 +267,29 @@ namespace Flow.Launcher.Plugin.Program.Programs }, IcoPath = "Images/folder.png", Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe838"), - } + }, }; + if (Extension(FullPath) == ShortcutExtension) + { + contextMenus.Add(OpenTargetFolderContextMenuResult(api)); + } return contextMenus; } + private Result OpenTargetFolderContextMenuResult(IPublicAPI api) + { + return new Result + { + Title = api.GetTranslation("flowlauncher_plugin_program_open_target_folder"), + Action = _ => + { + api.OpenDirectory(Path.GetDirectoryName(ExecutablePath), ExecutablePath); + return true; + }, + IcoPath = "Images/folder.png", + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe8de"), + }; + } public override string ToString() { @@ -318,7 +351,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var args = _helper.arguments; if (!string.IsNullOrEmpty(args)) { - program.LnkResolvedPath += " " + args; + program.Args = args; } var description = _helper.description; @@ -424,7 +457,8 @@ namespace Flow.Launcher.Plugin.Program.Programs } } - private static IEnumerable EnumerateProgramsInDir(string directory, string[] suffixes, bool recursive = true) + private static IEnumerable EnumerateProgramsInDir(string directory, string[] suffixes, + bool recursive = true) { if (!Directory.Exists(directory)) return Enumerable.Empty(); @@ -448,7 +482,8 @@ namespace Flow.Launcher.Plugin.Program.Programs } } - private static IEnumerable UnregisteredPrograms(List directories, string[] suffixes, string[] protocols) + private static IEnumerable UnregisteredPrograms(List directories, string[] suffixes, + string[] protocols) { // Disabled custom sources are not in DisabledProgramSources var paths = directories.AsParallel() @@ -466,14 +501,15 @@ namespace Flow.Launcher.Plugin.Program.Programs .Distinct(); var startupPaths = GetStartupPaths(); - + var programs = ExceptDisabledSource(allPrograms) .Where(x => !startupPaths.Any(startup => FilesFolders.PathContains(startup, x))) .Select(x => GetProgramFromPath(x, protocols)); return programs; } - private static IEnumerable PATHPrograms(string[] suffixes, string[] protocols, List commonParents) + private static IEnumerable PATHPrograms(string[] suffixes, string[] protocols, + List commonParents) { var pathEnv = Environment.GetEnvironmentVariable("Path"); if (String.IsNullOrEmpty(pathEnv)) @@ -515,7 +551,8 @@ namespace Flow.Launcher.Plugin.Program.Programs toFilter = toFilter.Distinct().Where(p => suffixes.Contains(Extension(p))); var programs = ExceptDisabledSource(toFilter) - .Select(x => GetProgramFromPath(x, protocols)).Where(x => x.Valid).ToList(); // ToList due to disposing issue + .Select(x => GetProgramFromPath(x, protocols)).Where(x => x.Valid) + .ToList(); // ToList due to disposing issue return programs; } @@ -611,12 +648,15 @@ namespace Flow.Launcher.Plugin.Program.Programs private static IEnumerable ProgramsHasher(IEnumerable programs) { var startMenuPaths = GetStartMenuPaths(); - return programs.GroupBy(p => p.ExecutablePath.ToLowerInvariant()) + return programs.GroupBy(p => (p.ExecutablePath + p.Args).ToLowerInvariant()) .AsParallel() .SelectMany(g => { // is shortcut and in start menu - var startMenu = g.Where(g => g.LnkResolvedPath != null && startMenuPaths.Any(x => FilesFolders.PathContains(x, g.FullPath))).ToList(); + var startMenu = g.Where(g => + g.LnkResolvedPath != null && + startMenuPaths.Any(x => FilesFolders.PathContains(x, g.FullPath))) + .ToList(); if (startMenu.Any()) return startMenu.Take(1); @@ -756,6 +796,7 @@ namespace Flow.Launcher.Plugin.Program.Programs while (reader.TryRead(out _)) { } + await Task.Run(Main.IndexWin32Programs); } } @@ -766,6 +807,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { throw new ArgumentException("Path Not Exist"); } + var watcher = new FileSystemWatcher(directory); watcher.Created += static (_, _) => indexQueue.Writer.TryWrite(default); @@ -804,8 +846,10 @@ namespace Flow.Launcher.Plugin.Program.Programs parents.Remove(source); } } + result.AddRange(parents.Select(x => x.Location)); } + return result.DistinctBy(x => x.ToLowerInvariant()).ToList(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Settings.cs b/Plugins/Flow.Launcher.Plugin.Program/Settings.cs index ca203f803..fb24f64d7 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Settings.cs @@ -102,7 +102,7 @@ namespace Flow.Launcher.Plugin.Program // CustomSuffixes no longer contains custom suffixes // users has tweaked the settings // or this function has been executed once - if (UseCustomSuffixes == true || ProgramSuffixes == null) + if (UseCustomSuffixes == true || ProgramSuffixes == null) return; var suffixes = ProgramSuffixes.ToList(); foreach(var item in BuiltinSuffixesStatus) @@ -117,6 +117,7 @@ namespace Flow.Launcher.Plugin.Program public bool EnableStartMenuSource { get; set; } = true; public bool EnableDescription { get; set; } = false; public bool HideAppsPath { get; set; } = true; + public bool HideUninstallers { get; set; } = false; public bool EnableRegistrySource { get; set; } = true; public bool EnablePathSource { get; set; } = false; public bool EnableUWP { get; set; } = true; diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml index fa97de4f2..e5ca6967e 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml @@ -85,6 +85,11 @@ Content="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath}" IsChecked="{Binding HideAppsPath}" ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath_tooltip}" /> + ProgramSettingDisplayList { get; set; } public bool EnableDescription @@ -47,6 +47,16 @@ namespace Flow.Launcher.Plugin.Program.Views } } + public bool HideUninstallers + { + get => _settings.HideUninstallers; + set + { + Main.ResetCache(); + _settings.HideUninstallers = value; + } + } + public bool EnableRegistrySource { get => _settings.EnableRegistrySource; @@ -87,9 +97,9 @@ namespace Flow.Launcher.Plugin.Program.Views } } - public bool ShowUWPCheckbox => UWP.SupportUWP(); + public bool ShowUWPCheckbox => UWPPackage.SupportUWP(); - public ProgramSetting(PluginInitContext context, Settings settings, Win32[] win32s, UWP.Application[] uwps) + public ProgramSetting(PluginInitContext context, Settings settings, Win32[] win32s, UWPApp[] uwps) { this.context = context; _settings = settings; @@ -149,9 +159,9 @@ namespace Flow.Launcher.Plugin.Program.Views private void DeleteProgramSources(List itemsToDelete) { itemsToDelete.ForEach(t1 => _settings.ProgramSources - .Remove(_settings.ProgramSources - .Where(x => x.UniqueIdentifier == t1.UniqueIdentifier) - .FirstOrDefault())); + .Remove(_settings.ProgramSources + .Where(x => x.UniqueIdentifier == t1.UniqueIdentifier) + .FirstOrDefault())); itemsToDelete.ForEach(x => ProgramSettingDisplayList.Remove(x)); ReIndexing(); @@ -182,16 +192,20 @@ namespace Flow.Launcher.Plugin.Program.Views { if (selectedProgramSource.Enabled) { - ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource }, true); // sync status in win32, uwp and disabled + ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource }, + true); // sync status in win32, uwp and disabled ProgramSettingDisplay.RemoveDisabledFromSettings(); } else { - ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource }, false); + ProgramSettingDisplay.SetProgramSourcesStatus(new List { selectedProgramSource }, + false); ProgramSettingDisplay.StoreDisabledInSettings(); } + ReIndexing(); } + programSourceView.SelectedIndex = selectedIndex; } } @@ -233,7 +247,8 @@ namespace Flow.Launcher.Plugin.Program.Views foreach (string directory in directories) { if (Directory.Exists(directory) - && !ProgramSettingDisplayList.Any(x => x.UniqueIdentifier.Equals(directory, System.StringComparison.OrdinalIgnoreCase))) + && !ProgramSettingDisplayList.Any(x => + x.UniqueIdentifier.Equals(directory, System.StringComparison.OrdinalIgnoreCase))) { var source = new ProgramSource(directory); @@ -262,8 +277,8 @@ namespace Flow.Launcher.Plugin.Program.Views private void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e) { var selectedItems = programSourceView - .SelectedItems.Cast() - .ToList(); + .SelectedItems.Cast() + .ToList(); if (selectedItems.Count == 0) { @@ -274,7 +289,8 @@ namespace Flow.Launcher.Plugin.Program.Views if (IsAllItemsUserAdded(selectedItems)) { - var msg = string.Format(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source")); + var msg = string.Format( + context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source")); if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) { @@ -364,8 +380,8 @@ namespace Flow.Launcher.Plugin.Program.Views private void programSourceView_SelectionChanged(object sender, SelectionChangedEventArgs e) { var selectedItems = programSourceView - .SelectedItems.Cast() - .ToList(); + .SelectedItems.Cast() + .ToList(); if (IsAllItemsUserAdded(selectedItems)) { @@ -400,7 +416,8 @@ namespace Flow.Launcher.Plugin.Program.Views ListView listView = sender as ListView; GridView gView = listView.View as GridView; - var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar + var workingWidth = + listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar var col1 = 0.25; var col2 = 0.15; var col3 = 0.60; diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json index e1d76659b..9ed7edbd9 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json @@ -4,7 +4,7 @@ "Name": "Program", "Description": "Search programs in Flow.Launcher", "Author": "qianlifeng", - "Version": "3.1.3", + "Version": "3.2.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj index dfbf54c3a..8f443214b 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -12,8 +12,9 @@ true false false + en - + true portable @@ -24,7 +25,7 @@ 4 false - + pdbonly true @@ -39,13 +40,13 @@ - + PreserveNewest - + MSBuild:Compile @@ -56,9 +57,9 @@ PreserveNewest - + - - \ No newline at end of file + + diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml index 0ccfd8c9a..b7d02c558 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ar.xaml @@ -2,6 +2,8 @@ Replace Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Do not close Command Prompt after command execution Always run as administrator Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml index 2c764d845..de45c754c 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/cs.xaml @@ -2,6 +2,8 @@ Nahradit Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Po dokončení příkazu příkazový řádek nezavírejte Vždy spustit jako správce Spustit jako jiný uživatel diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml index 0ccfd8c9a..b7d02c558 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/da.xaml @@ -2,6 +2,8 @@ Replace Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Do not close Command Prompt after command execution Always run as administrator Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml index 3fa7c64fa..bd3043d82 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/de.xaml @@ -2,6 +2,8 @@ Ersetzt Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Schließe die Kommandozeilte nicht nachdem der Befehl ausgeführt wurde Immer als Administrator ausführen Als anderer Benutzer ausführen diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml index 9a692cac3..52aaf3c27 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/en.xaml @@ -3,6 +3,8 @@ xmlns:system="clr-namespace:System;assembly=mscorlib"> Replace Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Do not close Command Prompt after command execution Always run as administrator Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml index 284a2a0e6..5ee2c43b4 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es-419.xaml @@ -2,6 +2,8 @@ Reemplazar Win+R + Close Command Prompt after pressing any key + Press any key to close this window... No cerrar Símbolo del Sistema tras ejecutar el comando Siempre ejecutar como administrador Ejecutar como otro usuario diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml index 8bf1a2c11..b43d75690 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/es.xaml @@ -2,6 +2,8 @@ Reemplazar Win+R + Cerrar Símbolo del sistema después de pulsar cualquier tecla + Pulsar cualquier tecla para cerrar esta ventana... No cerrar el símbolo del sistema después de la ejecución del comando Ejecutar siempre como administrador Ejecutar como usuario diferente diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml index d08efb9b8..379f3eda3 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/fr.xaml @@ -2,6 +2,8 @@ Remplacer Win+R + Fermer l'invite de commande après avoir appuyé sur n'importe quelle touche + Appuyez sur n'importe quelle touche pour fermer cette fenêtre... Ne pas fermer l'invite de commandes après l'exécution de la commande Toujours exécuter en tant qu'administrateur Exécuter en tant qu'utilisateur différent diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml index de40b0c47..4e61940d1 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml @@ -2,11 +2,13 @@ Sostituisci Win+R + Chiudi il Prompt dei Comandi dopo aver premuto un tasto + Premi un tasto per chiudere questa finestra... Non chiudere il prompt dei comandi dopo l'esecuzione dei comandi Esegui sempre come amministratore Esegui come utente differente Terminale - Allows to execute system commands from Flow Launcher + Consente di eseguire comandi di sistema da Flow Launcher questo comando è stato eseguito {0} volte esegui il comando attraverso riga di comando Esegui Come Amministratore diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml index 0ccfd8c9a..b7d02c558 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml @@ -2,6 +2,8 @@ Replace Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Do not close Command Prompt after command execution Always run as administrator Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml index 014a46dfc..3faaca429 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml @@ -2,6 +2,8 @@ Win+R 단축키 대체 + Close Command Prompt after pressing any key + Press any key to close this window... 명령 실행 후 명령 프롬프트를 닫지 않음 항상 관리자 권한으로 실행 다른 유저 권한으로 실행 diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml index 0ccfd8c9a..b7d02c558 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nb.xaml @@ -2,6 +2,8 @@ Replace Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Do not close Command Prompt after command execution Always run as administrator Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml index 0ccfd8c9a..b7d02c558 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/nl.xaml @@ -2,6 +2,8 @@ Replace Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Do not close Command Prompt after command execution Always run as administrator Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml index c851be93b..592ff17b7 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pl.xaml @@ -2,6 +2,8 @@ Zastąp Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Nie zamykaj wiersza poleceń po wykonaniu polecenia Always run as administrator Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml index 6a0a3c8fd..729e33ddf 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-br.xaml @@ -2,6 +2,8 @@ Substituir Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Não feche o Prompt de Comando após a execução do comando Sempre executar como administrador Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml index 33d7f35a6..cf4665cce 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/pt-pt.xaml @@ -2,6 +2,8 @@ Substituir Win+R + Fechar linha de comandos ao premir qualquer tecla + Prima uma tecla para fechar esta janela... Não fechar linha de comandos depois de executar o comando Executar sempre como administrador Executar com outro utilizador diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml index 0ccfd8c9a..bf3864c7b 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml @@ -1,15 +1,17 @@  - Replace Win+R - Do not close Command Prompt after command execution - Always run as administrator + Заменить Win+R + Close Command Prompt after pressing any key + Press any key to close this window... + Не закрывать командную строку после выполнения команды + Всегда запускать с правами администратора Run as different user - Shell + Оболочка Allows to execute system commands from Flow Launcher - this command has been executed {0} times + эта команда была выполнена {0} раз execute command through command shell Run As Administrator - Copy the command - Only show number of most used commands: + Скопировать команду + Показывать только самые используемые команды: diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml index 0b76303df..49f5dd796 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sk.xaml @@ -2,6 +2,8 @@ Nahradiť Win+R + Zatvoriť príkazový riadok po stlačení ľubovoľného klávesu + Toto okno zatvoríte stlačením ľubovoľného klávesu… Nezatvárať príkazový riadok po dokončení príkazu Spustiť vždy ako správca Spustiť ako iný používateľ diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml index 0ccfd8c9a..b7d02c558 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/sr.xaml @@ -2,6 +2,8 @@ Replace Win+R + Close Command Prompt after pressing any key + Press any key to close this window... Do not close Command Prompt after command execution Always run as administrator Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml index c6433cef1..7dd1c5b36 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/tr.xaml @@ -2,6 +2,8 @@ Win+R kısayolunu kullan + Close Command Prompt after pressing any key + Press any key to close this window... Çalıştırma sona erdikten sonra komut istemini kapatma Always run as administrator Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml index 0ccfd8c9a..9f51bbcd9 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/uk-UA.xaml @@ -1,15 +1,17 @@  - Replace Win+R - Do not close Command Prompt after command execution - Always run as administrator - Run as different user + Заміна Win+R + Закривати командний рядок після натискання будь-якої клавіші + Натисніть будь-яку клавішу, щоб закрити це вікно... + Не закривати командний рядок після виконання команди + Завжди запускати від імені адміністратора + Запустити від імені іншого користувача Shell - Allows to execute system commands from Flow Launcher - this command has been executed {0} times - execute command through command shell - Run As Administrator - Copy the command - Only show number of most used commands: + Дозволяє виконувати системні команди з Flow Launcher + цю команду було виконано {0} разів + виконати команду через командну оболонку + Запустити від імені адміністратора + Скопіювати команду + Показати лише кількість найчастіше використовуваних команд: diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/vi.xaml new file mode 100644 index 000000000..22d909686 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/vi.xaml @@ -0,0 +1,17 @@ + + + + Thay thế Win + R + Close Command Prompt after pressing any key + Press any key to close this window... + Không đóng dấu nhắc lệnh sau khi thực hiện lệnh + Luôn chạy với tư cách quản trị viên + Xóa lựa chọn đã chọn + Vỏ + Allows to execute system commands from Flow Launcher + lệnh này đã được thực thi {0} lần + thực thi lệnh thông qua lệnh shell + Chạy với quyền quản trị + Sao chép lệnh + Chỉ hiển thị số lượng lệnh được sử dụng nhiều nhất: + diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml index 916542c3a..2db287e39 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml @@ -2,6 +2,8 @@ 替换 Win+R + 按下任意键后关闭命令提示符 + 按下任意键以关闭此窗口... 执行后不关闭命令窗口 始终以管理员身份运行 以其他用户身份运行 diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml index 7ddc58918..21a330e3f 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-tw.xaml @@ -2,6 +2,8 @@ 取代 Win+R + Close Command Prompt after pressing any key + Press any key to close this window... 執行後不關閉命令提示字元視窗 一律以系統管理員身分執行 Run as different user diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index 66917d594..921c6bc21 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -203,7 +203,7 @@ namespace Flow.Launcher.Plugin.Shell case Shell.Cmd: { info.FileName = "cmd.exe"; - info.Arguments = $"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command}"; + info.Arguments = $"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command} {(_settings.CloseShellAfterPress ? $"&& echo {context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")} && pause > nul /c" : "")}"; //// Use info.Arguments instead of info.ArgumentList to enable users better control over the arguments they are writing. //// Previous code using ArgumentList, commands needed to be separated correctly: @@ -232,7 +232,7 @@ namespace Flow.Launcher.Plugin.Shell else { info.ArgumentList.Add("-Command"); - info.ArgumentList.Add(command); + info.ArgumentList.Add($"{command}; {(_settings.CloseShellAfterPress ? $"Write-Host '{context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'; [System.Console]::ReadKey(); exit" : "")}"); } break; } @@ -245,7 +245,7 @@ namespace Flow.Launcher.Plugin.Shell info.ArgumentList.Add("-NoExit"); } info.ArgumentList.Add("-Command"); - info.ArgumentList.Add(command); + info.ArgumentList.Add($"{command}; {(_settings.CloseShellAfterPress ? $"Write-Host '{context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'; [System.Console]::ReadKey(); exit" : "")}"); break; } @@ -275,6 +275,8 @@ namespace Flow.Launcher.Plugin.Shell info.FileName = command; } + info.UseShellExecute = true; + break; } default: diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs index 47b46055c..6f47d5d17 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Settings.cs @@ -7,6 +7,8 @@ namespace Flow.Launcher.Plugin.Shell public Shell Shell { get; set; } = Shell.Cmd; public bool ReplaceWinR { get; set; } = false; + + public bool CloseShellAfterPress { get; set; } = false; public bool LeaveShellOpen { get; set; } diff --git a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml index 240bda953..2f02ef723 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/ShellSetting.xaml @@ -15,6 +15,7 @@ + + CMD @@ -44,7 +51,7 @@ Pwsh RunCommand - + + { + _settings.CloseShellAfterPress = true; + LeaveShellOpen.IsChecked = false; + LeaveShellOpen.IsEnabled = false; + }; + + CloseShellAfterPress.Unchecked += (o, e) => + { + _settings.CloseShellAfterPress = false; + LeaveShellOpen.IsEnabled = true; + }; + LeaveShellOpen.Checked += (o, e) => { _settings.LeaveShellOpen = true; + CloseShellAfterPress.IsChecked = false; + CloseShellAfterPress.IsEnabled = false; }; LeaveShellOpen.Unchecked += (o, e) => { _settings.LeaveShellOpen = false; + CloseShellAfterPress.IsEnabled = true; }; AlwaysRunAsAdministrator.Checked += (o, e) => diff --git a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json index 18c90351b..e06c22c54 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json @@ -4,7 +4,7 @@ "Name": "Shell", "Description": "Provide executing commands from Flow Launcher", "Author": "qianlifeng", - "Version": "3.1.1", + "Version": "3.2.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index c7a722189..b797b3cf4 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -12,8 +12,9 @@ true false false + en - + true portable @@ -24,7 +25,7 @@ 4 false - + pdbonly true @@ -39,7 +40,7 @@ - + MSBuild:Compile @@ -50,10 +51,10 @@ PreserveNewest - + PreserveNewest - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml index 9ada8533b..ddf8a4d71 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml @@ -5,6 +5,28 @@ Command Description + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Exit + Save Settings + Restart Flow Launcher + Settings + Reload Plugin Data + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Shutdown Computer Restart Computer Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + Success All Flow Launcher settings saved Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml index 7ac077c77..b377ad3d2 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml @@ -5,6 +5,28 @@ Příkaz Popis + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Ukončit + Save Settings + Restartovat Flow Launcher + Nastavení + Znovu načíst data pluginů + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Vypnout počítač Restartovat počítač Restartování počítače s pokročilými možnostmi spouštění v nouzovém režimu a režimu ladění a dalšími možnostmi @@ -24,8 +46,9 @@ Zkontrolovat aktualizace Flow Launcheru Další nápovědu a tipy k jeho používání najdete v dokumentaci ke službě Flow Launcher Otevře místo, kde jsou uložena nastavení Flow Launcher + Toggle Game Mode - + Úspěšné Uložení všech nastavení Flow Launcheru Aktualizace všech dat pluginů diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml index 129f40bae..30beff7b5 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml @@ -5,6 +5,28 @@ Command Description + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Afslut + Save Settings + Restart Flow Launcher + Indstillinger + Reload Plugin Data + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Shutdown Computer Restart Computer Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + Fortsæt All Flow Launcher settings saved Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/de.xaml index 052166e28..042384242 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/de.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/de.xaml @@ -5,34 +5,57 @@ Befehl Beschreibung + Herunterfahren + Neu starten + Neustart mit erweiterten Startoptionen + Abmelden + Sperren + Energie sparen + Ruhezustand + Indizierungsoptionen + Papierkorb leeren + Papierkorb öffnen + Schließen + Einstellungen speichern + Flow Launcher neu starten + Einstellungen + Plugin-Daten neu laden + Auf Updates prüfen + Log-Speicherort öffnen + Flow Launcher Tipps + Flow Launcher Benutzerdaten-Ordner + Gott Modus + + Computer herunterfahren Computer neu starten - Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options + Startet den Computer mit erweiterten Startoptionen für den abgesicherten Modus neu Abmelden Computer sperren Flow Launcher schließen Flow Launcher neu starten - Tweak Flow Launcher's settings + Einstellungen des Flow Launchers anpassen Computer in Schlafmodus versetzen Papierkorb leeren - Open recycle bin - Indexing Options - Hibernate computer - Save all Flow Launcher settings - Refreshes plugin data with new content - Open Flow Launcher's log location - Check for new Flow Launcher update - Visit Flow Launcher's documentation for more help and how to use tips - Open the location where Flow Launcher's settings are stored + Papierkorb öffnen + Indizierungsoptionen + Computer in den Ruhezustand versetzen + Alle Flow Launcher Einstellungen speichern + Aktualisiert Plugin-Daten mit neuen Inhalten + Öffnet den Speicherort der Flow Launcher Logs + Prüft auf neue Flow Launcher Updates + Öffnet die Dokumentation von Flow Launcher für Hilfe und Tipps + Öffnet den Ort an dem Flow Laucher Einstellungen speichert + Gott Modus - + Erfolgreich - All Flow Launcher settings saved - Reloaded all applicable plugin data - Are you sure you want to shut the computer down? - Are you sure you want to restart the computer? - Are you sure you want to restart the computer with Advanced Boot Options? - Are you sure you want to log off? + Alle Flow Launcher Einstellungen wurden gespeichert + Alle betroffenen Plugin-Daten wurden neu geladen + Soll der Computer wirklich heruntergefahren werden? + Soll der Computer wirklich neu gestartet werden? + Soll der Computer wirklich mit erweiterten Startoptionen neu gestartet werden? + Soll der aktuelle Benutzer wirklich abgemeldet werden? Systembefehle Stellt Systemrelevante Befehle bereit. z.B. herunterfahren, sperren, Einstellungen, usw. diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml index a9aae930a..91f32a844 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/en.xaml @@ -7,6 +7,28 @@ Command Description + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Exit + Save Settings + Restart Flow Launcher + Settings + Reload Plugin Data + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Shutdown Computer Restart Computer Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -26,8 +48,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + Success All Flow Launcher settings saved Reloaded all applicable plugin data @@ -39,4 +62,4 @@ System Commands Provides System related commands. e.g. shutdown, lock, settings etc. - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml index 9ada8533b..1275f8b74 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml @@ -5,6 +5,28 @@ Command Description + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Salir + Save Settings + Restart Flow Launcher + Ajustes + Reload Plugin Data + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Shutdown Computer Restart Computer Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + Success All Flow Launcher settings saved Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml index 1005e4b8f..c457b160c 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml @@ -5,10 +5,32 @@ Comando Descripción + Apagar + Reiniciar + Reiniciar con opciones de arranque avanzadas + Cerrar sesión/Apagar + Bloquear + Suspender + Hibernar + Opciones de indexación + Vaciar papelera de reciclaje + Abrir papelera de reciclaje + Salir + Guardar configuración + Reinicia Flow Launcher + Configuración + Recargar datos del complemento + Buscar actualizaciones + Abrir ubicación de registro + Consejos Flow Launcher + Carpeta UserData de Flow Launcher + Cambiar a Modo Juego + + Apaga el equipo Reinicia el equipo Reinicia el equipo con opciones avanzadas de arranque para el modo seguro y de depuración, entre otras opciones - Cierra la sesión + Cerrar sesión Bloquea el equipo Cierra Flow Launcher Reinicia Flow Launcher @@ -24,15 +46,16 @@ Busca actualizaciones de Flow Launcher Accede a la documentación de Flow Launcher para más ayuda y consejos de uso Abre la ubicación donde se almacena la configuración de Flow Launcher + Cambiar a Modo Juego - + Correcto Toda la configuración de Flow Launcher ha sido guardada Se recargaron todos los datos del complemento - ¿Está seguro que desea apagar el equipo? - ¿Está seguro que desea reiniciar el equipo? - ¿Está seguro que desea reiniciar el equipo con opciones de arranque avanzadas? - ¿Está seguro que desea cerrar la sesión? + ¿Está seguro de que desea apagar el equipo? + ¿Está seguro de que desea reiniciar el equipo? + ¿Está seguro de que desea reiniciar el equipo con opciones de arranque avanzadas? + ¿Está seguro de que desea cerrar la sesión? Comandos del sistema Proporciona comandos relacionados con el sistema. Por ejemplo, apagar, bloquear, configurar, etc. diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/fr.xaml index cfd2fb832..6aea47df8 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/fr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/fr.xaml @@ -5,6 +5,28 @@ Commande Description + Arrêter + Redémarrer + Redémarrer avec les options de démarrage avancées + Se déconnecter + Verrouiller + Mettre en veille + Veille prolongée + Options d'indexation + Vider la corbeille + Ouvrir la corbeille + Quitter + Sauvegarder les paramètres + Redémarrer Flow Launcher + Paramètres + Recharger les données des plugins + Vérifier les mises à jour + Ouvrir l'emplacement des logs + Astuces pour Flow Launcher + Dossier des données utilisateur de Flow Launcher + Basculer le mode de jeu + + Éteindre l'ordinateur Redémarrer l'ordinateur Redémarrer l'ordinateur avec les options de démarrage avancées @@ -24,8 +46,9 @@ Vérifier de nouvelles mises à jour Flow Launcher Consultez la documentation de Flow Launcher pour plus d'aide et comment utiliser les conseils. Ouvrez l'emplacement où les paramètres de Flow Launcher sont stockés + Basculer le mode de jeu - + Ajouté avec succès Tous les paramètres Flow Launcher ont été sauvés Toutes les données du plugin applicables ont été rechargés diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml index 3451f4aa6..129c6a6fa 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml @@ -5,6 +5,28 @@ Comando Descrizione + Spegni + Riavvia + Riavvia con Opzioni di Avvio Avanzate + Disconnetti + Blocca + Sospendi + Ibernazione + Opzioni di Indice + Svuota Cestino + Apri Cestino + Esci + Salva Impostazioni + Riavvia Flow Launcher + Impostazioni + Ricarica i dati del plugin + Controlla Aggiornamenti + Apri Posizione dei Log + Consigli di Flow Launcher + Cartella UserData di Flow Launcher + Attiva/Disattiva Modalità Di Gioco + + Spegni il computer Riavvia il Computer Riavvia il computer con le Opzioni di Avvio Avanzato per le Modalità di debug e Provvisoria, così come altre opzioni @@ -24,8 +46,9 @@ Controlla il nuovo aggiornamento di Flow Launcher Visita la documentazione di Flow Launcher per maggiori informazioni e suggerimenti su come usarlo Apri la posizione in cui vengono memorizzate le impostazioni di Flow Launcher + Attiva/Disattiva Modalità Di Gioco - + Successo Tutte le impostazioni di Flow Launcher sono state salvate Ricaricato tutti i dati del plugin applicabili diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml index 169135a69..bb00a0df6 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml @@ -5,6 +5,28 @@ コマンド 説明 + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + + Save Settings + Flow Launcherを再起動する + + プラグインデータのリロード + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + コンピュータをシャットダウンする コンピュータを再起動する Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + 成功しまし All Flow Launcher settings saved Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml index dab69b706..e2962ff34 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml @@ -5,6 +5,28 @@ 명령어 설명 + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + 종료 + Save Settings + Flow Launcher 재시작 + 설정 + 플러그인 데이터 새로고 + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + 시스템 종료 시스템 재시작 안전 및 디버깅 모드에 대한 고급 부팅 옵션과 기타 옵션을 사용하여 시스템을 다시 시작 @@ -24,8 +46,9 @@ Flow Launcher 새 업데이트 확인 Flow Launcher의 도움말 및 사용안내 Flow Launcher의 설정이 저장된 위치 열기 + Toggle Game Mode - + 성공 모든 Flow Launcher 설정을 저장했습니다 적용 가능한 모든 플러그인 데이터를 다시 로드했습니다 diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml index 9ada8533b..ddf8a4d71 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml @@ -5,6 +5,28 @@ Command Description + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Exit + Save Settings + Restart Flow Launcher + Settings + Reload Plugin Data + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Shutdown Computer Restart Computer Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + Success All Flow Launcher settings saved Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml index 05e33adef..00201fa0e 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml @@ -2,39 +2,62 @@ - Command - Description + Opdracht + Beschrijving - Shutdown Computer - Restart Computer - Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options - Log off - Lock this computer - Close Flow Launcher - Restart Flow Launcher + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Afsluiten + Save Settings + Flow Launcher herstarten + Instellingen + Reload Plugin Data + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + + Computer afsluiten + Computer opnieuw opstarten + Start de computer opnieuw op met geavanceerde opstartopties voor veilige en foutopsporing modi en andere opties + Afmelden + Deze computer vergrendelen + Sluit Flow Launcher + Flow Launcher herstarten Tweak Flow Launcher's settings - Put computer to sleep - Empty recycle bin + De computer in slaapstand zetten + Prullenbak leegmaken Open recycle bin Indexing Options - Hibernate computer - Save all Flow Launcher settings - Refreshes plugin data with new content - Open Flow Launcher's log location - Check for new Flow Launcher update - Visit Flow Launcher's documentation for more help and how to use tips - Open the location where Flow Launcher's settings are stored + De computer in sluimerstand zetten + Sla alle Flow Launcher instellingen op + Vernieuwt plugin data met nieuwe inhoud + Open de locatie van Flow Launcher's log bestand + Controleer op nieuwe Flow Launcher update + Bezoek Flow Launcher's documentatie voor meer hulp en tips + Open de locatie waar Flow Launcher's instellingen worden opgeslagen + Toggle Game Mode - + Succesvol - All Flow Launcher settings saved - Reloaded all applicable plugin data - Are you sure you want to shut the computer down? - Are you sure you want to restart the computer? - Are you sure you want to restart the computer with Advanced Boot Options? + Alle Flow Launcher instellingen opgeslagen + Alle toepasselijke plugin gegevens zijn herladen + Weet u zeker dat u de computer wilt uitschakelen? + Weet u zeker dat u de computer wilt herstarten? + Weet u zeker dat u de computer wilt herstarten met geavanceerde opstartopties? Are you sure you want to log off? - System Commands - Provides System related commands. e.g. shutdown, lock, settings etc. + Systeemopdrachten + Voorziet in systeem gerelateerde opdrachten. bijv.: afsluiten, vergrendelen, instellingen, enz. diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml index f8e857d1c..4c7592411 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml @@ -5,6 +5,28 @@ Komenda Opis + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Wyjdź + Save Settings + Uruchom ponownie Flow Launchera + Ustawienia + Reload Plugin Data + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Wyłącz komputer Uruchom ponownie komputer Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + Sukces All Flow Launcher settings saved Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml index a78a71c56..a51c751df 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml @@ -5,6 +5,28 @@ Comando Descrição + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Sair + Save Settings + Reiniciar Flow Launcher + Configurações + Recarregar Dados de Plugin + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Desligar o Computador Reiniciar o Computador Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + Sucesso All Flow Launcher settings saved Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-pt.xaml index e53fd601d..4d7609bcb 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-pt.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-pt.xaml @@ -5,6 +5,28 @@ Comando Descrição + Desligar + Reiniciar + Reiniciar com opções avançadas de arranque + Terminar sessão + Bloquear + Suspender + Hibernar + Opções de indexação + Esvaziar reciclagem + Abrir reciclagem + Sair + Guardar definições + Reiniciar Flow Launcher + Definições + Recarregar dados do plugin + Procurar atualizações + Abrir localização de registo + Dicas Flow Launcher + Pasta de dados do utilizador Flow Launcher + Comutar modo de jogo + + Desligar computador Reiniciar computador Reinicia o computador com as opções avançadas de arranque para os modos de segurança e de depuração @@ -24,8 +46,9 @@ Procurar por novas versões do Flow Launcher Aceda à documentação para mais informações e dicas de utilização Abrir localização onde as definições do Flow Launcher estão guardadas + Comutar modo de jogo - + Sucesso Definições guardadas com sucesso Recarregar todos os dados aplicáveis ao plugin diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml index 233754f80..3d3bcfc87 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml @@ -5,6 +5,28 @@ Command Description + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Выйти + Save Settings + Restart Flow Launcher + Настройки + Перезагрузить данные плагинов + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Shutdown Computer Restart Computer Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + Успешно All Flow Launcher settings saved Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml index 516b792c5..c5cee316f 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml @@ -5,6 +5,28 @@ Príkaz Popis + Vypnúť + Reštartovať + Reštartovať s rozšírenými možnosťami spustenia + Odhlásiť sa + Zamknúť + Uspať + Hibernovať + Možnosti indexovania + Vyprázdniť kôš + Otvoriť kôš + Ukončiť + Uložiť nastavenia + Reštartovať Flow Launcher + Nastavenia + Znova načítať údaje pluginov + Skontrolovať aktualizácie + Otvoriť umiestnenie denníka + Tipy pre Flow Launcher + Používateľský priečinok Flow Launchera + Prepnúť herný režim + + Vypnúť počítač Reštartovať počítač Reštartovať počítač s rozšírenými možnosťami spúšťania pre núdzový režim a režim ladenia, ako aj s ďalšími možnosťami @@ -24,8 +46,9 @@ Skontrolovať aktualizácie Flow Launchera V dokumentácii k aplikácii Flow Launcher nájdete ďalšiu pomoc a tipy na používanie Otvoriť umiestnenie, kde sú uložené nastavenia Flow Launchera + Prepnúť herný režim - + Úspešné Všetky nastavenia Flow Launchera uložené Všetky dáta pluginov aktualizované diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml index 561811679..a984954f9 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml @@ -5,6 +5,28 @@ Command Description + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Izlaz + Save Settings + Restart Flow Launcher + Podešavanja + Reload Plugin Data + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Shutdown Computer Restart Computer Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + Uspešno All Flow Launcher settings saved Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml index 3d847d7fa..35850cb26 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml @@ -5,6 +5,28 @@ Komut Açıklama + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Çıkı + Save Settings + Flow Launcher'u Yeniden Başlat + Ayarlar + Reload Plugin Data + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + Bilgisayarı Kapat Yeniden Başlat Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + Başarılı Tüm Flow Launcher ayarları kaydedildi. Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml index cf5a3bed4..8f2883bce 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml @@ -2,39 +2,62 @@ - Command - Description + Команда + Опис - Shutdown Computer - Restart Computer - Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options - Log off - Lock this computer - Close Flow Launcher - Restart Flow Launcher - Tweak Flow Launcher's settings - Put computer to sleep - Empty recycle bin - Open recycle bin - Indexing Options - Hibernate computer - Save all Flow Launcher settings - Refreshes plugin data with new content - Open Flow Launcher's log location - Check for new Flow Launcher update - Visit Flow Launcher's documentation for more help and how to use tips - Open the location where Flow Launcher's settings are stored + Вимкнути + Перезавантажити + Перезавантажити з розширеними параметрами завантаження + Вийти з системи/облікового запису + Заблокувати + Режим сну + Режим глибокого сну + Опція індексу + Очистити кошик + Відкрити кошик + Вийти + Зберегти налаштування + Перезапустити Flow Launcher + Налаштування + Перезавантажити дані плагінів + Перевірити наявність оновлень + Відкрити розташування журналу + Поради щодо Flow Launcher + Тека UserData Flow Launcher + Перемкнути режим гри - + + Вимкнути комп'ютер + Перезавантажити комп'ютер + Перезавантажте комп'ютер за допомогою Розширених параметрів завантаження для безпечного чи налагоджувального режимів, а також інших опцій + Вийти з системи + Заблокувати комп'ютер + Закрити Flow Launcher + Перезапустити Flow Launcher + Налаштування Flow Launcher + Переведення комп'ютера в режим сну + Очистити кошик + Відкрити кошик + Параметри індексації + Переведення комп'ютера в режим гібернації + Зберегти всі налаштування Flow Launcher + Оновлює дані плагіна новим вмістом + Відкрити розташування журналу Flow Launcher + Перевірити наявність оновлень Flow Launcher + Перегляньте документацію Flow Launcher для отримання додаткової допомоги та підказок щодо використання порад + Відкрити каталог, де зберігаються налаштування Flow Launcher + Перемкнути режим гри + + Успішно - All Flow Launcher settings saved - Reloaded all applicable plugin data - Are you sure you want to shut the computer down? - Are you sure you want to restart the computer? - Are you sure you want to restart the computer with Advanced Boot Options? - Are you sure you want to log off? + Усі налаштування Flow Launcher збережено + Перезавантажено всі відповідні дані плагіна + Ви впевнені, що хочете вимкнути комп'ютер? + Ви впевнені, що хочете перезавантажити комп'ютер? + Ви впевнені, що хочете перезавантажити комп'ютер за допомогою додаткових параметрів завантаження? + Ви впевнені, що хочете вийти з системи? - System Commands - Provides System related commands. e.g. shutdown, lock, settings etc. + Системні команди + Надає команди, пов'язані з системою, наприклад, вимкнення, блокування, налаштування тощо. diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/vi.xaml new file mode 100644 index 000000000..bb76debc3 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/vi.xaml @@ -0,0 +1,63 @@ + + + + + Lệnh + Mô Tả + + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Thoát + Save Settings + Bắt đầu Flow-Launcher + Cài đặt + Dữ liệu plugin không tải + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + + shutdown máy tính + Khởi động lại máy tính + Khởi động lại máy tính với Tùy chọn khởi động nâng cao cho chế độ An toàn và Gỡ lỗi, cũng như các tùy chọn khác + Đăng xuất + Khóa máy tính này + Chào mừng bạn đến với Trình khởi chạy luồng + Bắt đầu Flow-Launcher + Tweak Flow Launcher's settings + Đặt máy tính vào chế độ ngủ + Dọn sạch thùng rác + Open recycle bin + Tùy chọn lập chỉ mục + Máy tính ngủ đông + Lưu tất cả cài đặt Flow Launcher + Làm mới dữ liệu plugin với nội dung mới + Mở vị trí nhật ký của Flow Launcher + Kiểm tra bản cập nhật Flow Launcher mới + Truy cập tài liệu của Flow Launcher để được trợ giúp thêm và cách sử dụng các mẹo + Mở vị trí lưu trữ cài đặt của Flow Launcher + Toggle Game Mode + + + Thành công + Đã lưu tất cả cài đặt của Flow Launcher + Đã tải lại tất cả dữ liệu plugin hiện hành + Bạn có chắc chắn muốn tắt máy tính không? + Bạn có chắc muốn khởi động lại cấp này? + Bạn có chắc chắn muốn khởi động lại máy tính bằng Advanced Boot Options không? + Are you sure you want to log off? + + Lệnh hệ thống + Cung cấp các lệnh liên quan đến Hệ thống. ví dụ. tắt máy, khóa, cài đặt, v.v. + + diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml index e9bf86065..cd0b6f166 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml @@ -5,6 +5,28 @@ 命令 描述 + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + 休眠 + 索引选项 + 清空回收站 + 打开回收站 + 退出 + 保存设置 + 重启 Flow Launcher + 设置 + 重新加载插件数据 + 检查更新 + 打开日志文件夹 + Flow Launcher 提示 + Flow Launcher 用户数据文件夹 + 切换游戏模式 + + 关闭电脑 重启这台电脑 以高级启动选项重启计算机,用于安全和调试模式以及其他选项 @@ -24,8 +46,9 @@ 检查新的 Flow Launcher 更新 访问 Flow Launcher 的文档以获取更多帮助以及使用技巧 打开Flow Launcher 设置文件夹 + 切换游戏模式 - + 成功 所有 Flow Launcher 设置已保存 重新加载了所有插件数据 diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml index 09b099bdc..9934acc9f 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml @@ -5,6 +5,28 @@ 命令 描述 + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + 結束 + Save Settings + 重新啟動 Flow Launcher + 設定 + 重新載入插件資料 + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + 電腦關機 電腦重新啟動 Restart the computer with Advanced Boot Options for Safe and Debugging modes, as well as other options @@ -24,8 +46,9 @@ Check for new Flow Launcher update Visit Flow Launcher's documentation for more help and how to use tips Open the location where Flow Launcher's settings are stored + Toggle Game Mode - + All Flow Launcher settings saved Reloaded all applicable plugin data diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 43f293f74..1ec07915d 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Forms; using System.Windows.Interop; using Flow.Launcher.Infrastructure; +using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.SharedCommands; using Application = System.Windows.Application; @@ -19,6 +21,7 @@ namespace Flow.Launcher.Plugin.Sys public class Main : IPlugin, ISettingProvider, IPluginI18n { private PluginInitContext context; + private Dictionary KeywordTitleMappings = new Dictionary(); #region DllImport @@ -59,6 +62,8 @@ namespace Flow.Launcher.Plugin.Sys var results = new List(); foreach (var c in commands) { + c.Title = GetDynamicTitle(query, c); + var titleMatch = StringMatcher.FuzzySearch(query.Search, c.Title); var subTitleMatch = StringMatcher.FuzzySearch(query.Search, c.SubTitle); @@ -77,9 +82,52 @@ namespace Flow.Launcher.Plugin.Sys return results; } + private string GetDynamicTitle(Query query, Result result) + { + if (!KeywordTitleMappings.TryGetValue(result.Title, out var translationKey)) + { + Log.Error("Flow.Launcher.Plugin.Sys.Main", $"Dynamic Title not found for: {result.Title}"); + return "Title Not Found"; + } + + var translatedTitle = context.API.GetTranslation(translationKey); + + if (result.Title == translatedTitle) + { + return result.Title; + } + + var englishTitleMatch = StringMatcher.FuzzySearch(query.Search, result.Title); + var translatedTitleMatch = StringMatcher.FuzzySearch(query.Search, translatedTitle); + + return englishTitleMatch.Score >= translatedTitleMatch.Score ? result.Title : translatedTitle; + } + public void Init(PluginInitContext context) { this.context = context; + KeywordTitleMappings = new Dictionary{ + {"Shutdown", "flowlauncher_plugin_sys_shutdown_computer_cmd"}, + {"Restart", "flowlauncher_plugin_sys_restart_computer_cmd"}, + {"Restart With Advanced Boot Options", "flowlauncher_plugin_sys_restart_advanced_cmd"}, + {"Log Off/Sign Out", "flowlauncher_plugin_sys_log_off_cmd"}, + {"Lock", "flowlauncher_plugin_sys_lock_cmd"}, + {"Sleep", "flowlauncher_plugin_sys_sleep_cmd"}, + {"Hibernate", "flowlauncher_plugin_sys_hibernate_cmd"}, + {"Index Option", "flowlauncher_plugin_sys_indexoption_cmd"}, + {"Empty Recycle Bin", "flowlauncher_plugin_sys_emptyrecyclebin_cmd"}, + {"Open Recycle Bin", "flowlauncher_plugin_sys_openrecyclebin_cmd"}, + {"Exit", "flowlauncher_plugin_sys_exit_cmd"}, + {"Save Settings", "flowlauncher_plugin_sys_save_all_settings_cmd"}, + {"Restart Flow Launcher", "flowlauncher_plugin_sys_restart_cmd"}, + {"Settings", "flowlauncher_plugin_sys_setting_cmd"}, + {"Reload Plugin Data", "flowlauncher_plugin_sys_reload_plugin_data_cmd"}, + {"Check For Update", "flowlauncher_plugin_sys_check_for_update_cmd"}, + {"Open Log Location", "flowlauncher_plugin_sys_open_log_location_cmd"}, + {"Flow Launcher Tips", "flowlauncher_plugin_sys_open_docs_tips_cmd"}, + {"Flow Launcher UserData Folder", "flowlauncher_plugin_sys_open_userdata_location_cmd"}, + {"Toggle Game Mode", "flowlauncher_plugin_sys_toggle_game_mode_cmd"} + }; } private List Commands() @@ -139,7 +187,7 @@ namespace Flow.Launcher.Plugin.Sys context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer_advanced"), context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"), MessageBoxButton.YesNo, MessageBoxImage.Warning); - + if (result == MessageBoxResult.Yes) Process.Start("shutdown", "/r /o /t 0"); @@ -148,7 +196,7 @@ namespace Flow.Launcher.Plugin.Sys }, new Result { - Title = "Log Off", + Title = "Log Off/Sign Out", SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_log_off"), Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe77b"), IcoPath = "Images\\logoff.png", @@ -158,7 +206,7 @@ namespace Flow.Launcher.Plugin.Sys context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_logoff_computer"), context.API.GetTranslation("flowlauncher_plugin_sys_log_off"), MessageBoxButton.YesNo, MessageBoxImage.Warning); - + if (result == MessageBoxResult.Yes) ExitWindowsEx(EWX_LOGOFF, 0); @@ -198,7 +246,7 @@ namespace Flow.Launcher.Plugin.Sys info.UseShellExecute = true; ShellCommand.Execute(info); - + return true; } }, @@ -317,7 +365,7 @@ namespace Flow.Launcher.Plugin.Sys context.API.GetTranslation( "flowlauncher_plugin_sys_dlgtext_all_applicableplugins_reloaded")), System.Threading.Tasks.TaskScheduler.Current); - + return true; } }, @@ -366,6 +414,18 @@ namespace Flow.Launcher.Plugin.Sys context.API.OpenDirectory(DataLocation.DataDirectory()); return true; } + }, + new Result + { + Title = "Toggle Game Mode", + SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_toggle_game_mode"), + IcoPath = "Images\\app.png", + Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\ue7fc"), + Action = c => + { + context.API.ToggleGameMode(); + return true; + } } }); diff --git a/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs index b5f1531c3..feb30821a 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs @@ -22,7 +22,12 @@ namespace Flow.Launcher.Plugin.Sys var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar var col1 = 0.3; - var col2 = 0.7; + var col2 = 0.7; + + if (workingWidth <= 0) + { + return; + } gView.Columns[0].Width = workingWidth * col1; gView.Columns[1].Width = workingWidth * col2; diff --git a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json index a893c0ea2..f7dea266a 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json @@ -4,7 +4,7 @@ "Name": "System Commands", "Description": "Provide System related commands. e.g. shutdown,lock, setting etc.", "Author": "qianlifeng", - "Version": "3.0.3", + "Version": "3.1.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj index c03acefae..3db0cd0cb 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj +++ b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj @@ -1,5 +1,5 @@  - + Library net7.0-windows @@ -11,8 +11,9 @@ true false false + en - + true portable @@ -23,7 +24,7 @@ 4 false - + pdbonly true @@ -33,7 +34,7 @@ 4 false - + PreserveNewest @@ -56,4 +57,4 @@ - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.Url/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Url/Languages/ru.xaml index 418731021..5110f65ef 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.Url/Languages/ru.xaml @@ -12,6 +12,6 @@ Open the typed URL from Flow Launcher Please set your browser path: - Choose + Выберите Application(*.exe)|*.exe|All files|*.* diff --git a/Plugins/Flow.Launcher.Plugin.Url/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Url/Languages/uk-UA.xaml index 418731021..45f29423a 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.Url/Languages/uk-UA.xaml @@ -1,17 +1,17 @@  - Open search in: - New Window - New Tab + Запустити пошук: + Нове вікно + Нова вкладка - Open url:{0} - Can't open url:{0} + Відкрити URL:{0} + Не вдається відкрити URL:{0} URL - Open the typed URL from Flow Launcher + Відкрити набрану URL-адресу з Flow Launcher - Please set your browser path: - Choose - Application(*.exe)|*.exe|All files|*.* + Будь ласка, встановіть шлях до вашого браузера: + Обрати + Application(*.exe)|*.exe|Усі файли|*.* diff --git a/Plugins/Flow.Launcher.Plugin.Url/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Url/Languages/vi.xaml new file mode 100644 index 000000000..41f87fe19 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Url/Languages/vi.xaml @@ -0,0 +1,17 @@ + + + + Mở tìm kiếm + Cửa sổ mới + Tab mới + + Mở url:{0} + Không thể mở url:{0} + + Địa chỉ URL + Mở URL đã nhập từ Flow Launcher + + Vui lòng đặt đường dẫn trình duyệt của bạn: + Chọn + Ứng dụng(*.exe)|*.exe|Tất cả các tệp|*.* + diff --git a/Plugins/Flow.Launcher.Plugin.Url/plugin.json b/Plugins/Flow.Launcher.Plugin.Url/plugin.json index da1a0bfd5..79a96171c 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Url/plugin.json @@ -4,7 +4,7 @@ "Name": "URL", "Description": "Open the typed URL from Flow Launcher", "Author": "qianlifeng", - "Version": "3.0.3", + "Version": "3.0.4", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Url.dll", diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/es.xaml index fefbfaaee..86e62f0e5 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/es.xaml +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/es.xaml @@ -20,7 +20,7 @@ Usar autocompletado en consultas de búsqueda: Autocompletar datos desde: Por favor, seleccione una búsqueda web - ¿Está seguro que desea eliminar {0}? + ¿Está seguro de que desea eliminar {0}? Si desea añadir una búsqueda de un sitio web concreto a Flow, introduzca primero una cadena de texto ficticia en la barra de búsqueda de ese sitio web, y ejecute la búsqueda. Ahora copie el contenido de la barra de direcciones del navegador y péguelo en el campo de la URL abajo indicado. Sustituya su cadena de prueba por {q}. Por ejemplo, si busca Casino en Netflix, su barra de direcciones debe decir https://www.netflix.com/search?q=Casino diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/it.xaml index 6be89607d..dc9bd7a2e 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/it.xaml @@ -1,7 +1,7 @@  - Search Source Setting + Impostazioni Sorgenti di Ricerca Apri ricerca in: Nuova Finestra Nuova Scheda @@ -12,40 +12,40 @@ Aggiungi Abilitato Abilitato - Disabled - Confirm - Action Keyword + Disabilitato + Conferma + Parola Chiave URL - Search - Use Search Query Autocomplete: - Autocomplete Data from: - Please select a web search + Cerca + Usa Autocompletamento Ricerca: + Autocompleta i dati da: + Seleziona una ricerca web Sei sicuro di voler eliminare {0}? - If you want to add a search for a particular website to Flow, first enter a dummy text string in the search bar of that website, and launch the search. Now copy the contents of the browser's address bar, and paste it in the URL field below. Replace your test string with {q}. For example, if you search for casino on Netflix, its address bar reads + Se vuoi aggiungere una ricerca di un particolare sito web a Flow, prima immetti una stringa di testo fittizia nella barra di ricerca di quel sito web, e avvia la ricerca. Ora copia il contenuto della barra degli indirizzi del browser e incollalo nel campo URL sotto. Sostituisci la stringa di prova con {q}. Per esempio, se cerchi 'casino' su Netflix, sulla barra degli indirizzi ci sarà https://www.netflix.com/search?q=Casino - Now copy this entire string and paste it in the URL field below. - Then replace casino with {q}. - Thus, the generic formula for a search on Netflix is https://www.netflix.com/search?q={q} + Ora copia l'intera stringa e incollala nel campo URL sotto. + Sostituisci 'casino' con {q}. + Così la formula generica per una ricerca su Netflix è https://www.netflix.com/search?q={q} - Title - Status - Select Icon - Icon + Titolo + Stato + Seleziona Icona + Icona Annulla - Invalid web search - Please enter a title - Please enter an action keyword - Please enter a URL - Action keyword already exists, please enter a different one + Ricerca web non valida + Inserisci un titolo + Inserisci una parola chiave + Inserisci un URL + La parola chiave esiste già, inserirne una diversa Successo - Hint: You do not need to place custom images in this directory, if Flow's version is updated they will be lost. Flow will automatically copy any images outside of this directory across to WebSearch's custom image location. + Suggerimento: Non è necessario inserire immagini personalizzate in questa cartella, se Flow viene aggiornato queste verranno perse. Flow copierà automaticamente tutte le immagini al di fuori di questa cartella nella posizione delle immagini personalizzate di WebSearch. - Web Searches - Allows to perform web searches + Ricerche Web + Consente di eseguire ricerche web diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ru.xaml index fab878992..a2ec9405a 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ru.xaml @@ -5,8 +5,8 @@ Open search in: New Window New Tab - Set browser from path: - Choose + Установить браузер по пути: + Выберите Удалить Редактировать Добавить diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/uk-UA.xaml index 091102a0a..87419023b 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/uk-UA.xaml @@ -1,51 +1,51 @@  - Search Source Setting - Open search in: - New Window - New Tab - Set browser from path: - Choose + Налаштування джерела пошуку + Запустити пошук: + Нове вікно + Нова вкладка + Встановити браузер за шляхом: + Обрати Видалити Редагувати Додати - Enabled - Enabled - Disabled - Confirm - Action Keyword + Увімкнено + Увімкнено + Вимкнено + Підтвердити + Ключове слово дії URL - Search - Use Search Query Autocomplete: - Autocomplete Data from: - Please select a web search - Are you sure you want to delete {0}? - If you want to add a search for a particular website to Flow, first enter a dummy text string in the search bar of that website, and launch the search. Now copy the contents of the browser's address bar, and paste it in the URL field below. Replace your test string with {q}. For example, if you search for casino on Netflix, its address bar reads + Пошук + Використовувати автозаповнення пошукового запиту: + Автозаповнення даних з: + Будь ласка, виберіть пошуковий запит в Інтернеті + Ви впевнені, що хочете видалити {0}? + Якщо ви хочете додати до Flow пошук певного веб-сайту, спочатку введіть фіктивний текстовий рядок у пошуковий рядок цього веб-сайту і розпочніть пошук. Тепер скопіюйте вміст адресного рядка браузера і вставте його в поле URL нижче. Замініть тестовий рядок на {q}. Наприклад, якщо ви шукаєте казино на сайті Netflix, його адресний рядок матиме такий вигляд https://www.netflix.com/search?q=Casino - Now copy this entire string and paste it in the URL field below. - Then replace casino with {q}. - Thus, the generic formula for a search on Netflix is https://www.netflix.com/search?q={q} + Тепер скопіюйте весь цей рядок і вставте його в поле URL нижче. + Потім замініть casino на {q}. + Таким чином, загальна формула для пошуку на Netflix має вигляд https://www.netflix.com/search?q={q} - Title - Status - Select Icon - Icon + Назва + Статус + Обрати значок + Іконка Скасувати - Invalid web search - Please enter a title - Please enter an action keyword - Please enter a URL - Action keyword already exists, please enter a different one + Неправильний веб-пошук + Будь ласка, введіть назву + Будь ласка, введіть ключове слово дії + Будь ласка, введіть URL-адресу + Ключове слово дії вже існує, будь ласка, введіть інше Успішно - Hint: You do not need to place custom images in this directory, if Flow's version is updated they will be lost. Flow will automatically copy any images outside of this directory across to WebSearch's custom image location. + Підказка: Вам не потрібно розміщувати власні зображення в цьому каталозі, якщо версія Flow оновиться, вони будуть втрачені. Flow автоматично копіює всі зображення з цього каталогу до спеціального каталогу WebSearch. - Web Searches - Allows to perform web searches + Веб-пошук + Дозволяє здійснювати веб-пошук diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/vi.xaml new file mode 100644 index 000000000..a6de788b5 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/vi.xaml @@ -0,0 +1,51 @@ + + + + Cài đặt nguồn tìm kiếm + Mở tìm kiếm + Cửa sổ mới + Tab mới + Đặt trình duyệt từ đường dẫn: + Chọn + Xóa + Sửa + Thêm + Đã bật + Đã bật + Vô hiệu hóa + Xác nhận + Từ khóa hành động + Địa chỉ URL + Tìm kiếm + Sử dụng Tự động hoàn thành truy vấn tìm kiếm: + Tự động hoàn thành dữ liệu từ: + Vui lòng chọn tìm kiếm trên web + Bạn có chắc chắn muốn xóa {0} không? + If you want to add a search for a particular website to Flow, first enter a dummy text string in the search bar of that website, and launch the search. Now copy the contents of the browser's address bar, and paste it in the URL field below. Replace your test string with {q}. For example, if you search for casino on Netflix, its address bar reads + https://www.netflix.com/search?q=Casino + + Now copy this entire string and paste it in the URL field below. + Then replace casino with {q}. + Thus, the generic formula for a search on Netflix is https://www.netflix.com/search?q={q} + + + + + + Tiêu đề + Trạng thái + Chọn Biểu tượng + Biểu tượng + Hủy + Tìm kiếm trên web không hợp lệ + Vui lòng nhập tiêu đề + Vui lòng nhập từ khóa hành động + Hãy nhập URL + Từ khóa hành động đã tồn tại, vui lòng nhập một từ khóa khác + Thành công + Gợi ý: Bạn không cần đặt hình ảnh tùy chỉnh trong thư mục này, nếu phiên bản của Flow được cập nhật, chúng sẽ bị mất. Flow sẽ tự động sao chép mọi hình ảnh bên ngoài thư mục này sang vị trí hình ảnh tùy chỉnh của WebSearch. + + Tìm kiếm Web + Cho phép thực hiện tìm kiếm trên web + + diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs index 6cf1446af..f0e5ac327 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs @@ -197,7 +197,8 @@ namespace Flow.Launcher.Plugin.WebSearch public SuggestionSource[] Suggestions { get; set; } = { new Google(), new Baidu(), - new Bing() + new Bing(), + new DuckDuckGo() }; [JsonIgnore] diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs new file mode 100644 index 000000000..8fafb44cc --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Flow.Launcher.Infrastructure.Http; +using Flow.Launcher.Infrastructure.Logger; +using System.Net.Http; +using System.Threading; +using System.Text.Json; + +namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources +{ + public class DuckDuckGo : SuggestionSource + { + public override async Task> SuggestionsAsync(string query, CancellationToken token) + { + // When the search query is empty, DuckDuckGo returns `[]`. When it's not empty, it returns data + // in the following format: `["query", ["suggestion1", "suggestion2", ...]]`. + if (string.IsNullOrEmpty(query)) + { + return new List(); + } + + try + { + const string api = "https://duckduckgo.com/ac/?type=list&q="; + + await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query), token: token).ConfigureAwait(false); + + using var json = await JsonDocument.ParseAsync(resultStream, cancellationToken: token); + + var results = json.RootElement.EnumerateArray().ElementAt(1); + + return results.EnumerateArray().Select(o => o.GetString()).ToList(); + + } + catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException}) + { + Log.Exception("|DuckDuckGo.Suggestions|Can't get suggestion from DuckDuckGo", e); + return null; + } + catch (JsonException e) + { + Log.Exception("|DuckDuckGo.Suggestions|can't parse suggestions", e); + return new List(); + } + } + + public override string ToString() + { + return "DuckDuckGo"; + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json index 1d24556da..12e0566f9 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json @@ -26,7 +26,7 @@ "Name": "Web Searches", "Description": "Provide the web search ability", "Author": "qianlifeng", - "Version": "3.0.5", + "Version": "3.0.7", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll", diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj index 47ab3b2ba..73fcd9f83 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj @@ -9,6 +9,7 @@ prompt en-US enable + en @@ -68,4 +69,4 @@ - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.fr-FR.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.fr-FR.resx index 89cb40ace..9864cdfc1 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.fr-FR.resx +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.fr-FR.resx @@ -1737,136 +1737,136 @@ Mean zooming of things via a magnifier - Change device installation settings + Modifier les paramètres d'installation de l'appareil - Turn off background images + Désactiver les images d'arrière-plan - Navigation properties + Propriétés de navigation - Media streaming options + Options de diffusion des médias - Make a file type always open in a specific program + Faire en sorte qu'un type de fichier s'ouvre toujours dans un programme spécifique - Change the Narrator’s voice + Changer la voix du Narrateur - Find and fix keyboard problems + Trouver et résoudre les problèmes liés au clavier - Use screen reader + Utiliser le lecteur d'écran - Show which workgroup this computer is on + Afficher le groupe de travail sur lequel cet ordinateur est allumé - Change mouse wheel settings + Modifier les paramètres de la molette de la souris - Manage computer certificates + Gérer les certificats de l'ordinateur - Find and fix problems + Trouver et résoudre les problèmes - Change settings for content received using Tap and send + Modifier les paramètres pour le contenu reçu en utilisant Tap and send - Change default settings for media or devices + Modifier les paramètres par défaut pour les médias ou appareils - Print the speech reference card + Imprimer la carte de référence vocale - Calibrate display colour + Calibrer la couleur de l'affichage - Manage file encryption certificates + Gérer les certificats de chiffrement de fichiers - View recent messages about your computer + Voir les messages récents à propos de votre ordinateur - Give other users access to this computer + Donner accès à cet ordinateur aux autres utilisateurs - Show hidden files and folders + Afficher les fichiers et dossiers cachés - Change Windows To Go start-up options + Modifier les options de démarrage de Windows To Go - See which processes start up automatically when you start Windows + Voir quels processus démarrent automatiquement lorsque vous démarrez Windows - Tell if an RSS feed is available on a website + Indiquer si un flux RSS est disponible sur un site web - Add clocks for different time zones + Ajouter des horloges pour différents fuseaux horaires - Add a Bluetooth device + Ajouter un appareil Bluetooth - Customise the mouse buttons + Personnaliser les boutons de la souris - Set tablet buttons to perform certain tasks + Définir les boutons de tablette pour effectuer certaines tâches - View installed fonts + Afficher les polices installées - Change the way currency is displayed + Changer la façon dont la devise est affichée - Edit group policy + Modifier la politique de groupe - Manage browser add-ons + Gérer les modules complémentaires du navigateur - Check processor speed + Vérifier la vitesse du processeur - Check firewall status + Vérifier le statut du pare-feu - Send or receive a file + Envoyer ou recevoir un fichier - Add or remove user accounts + Ajouter ou supprimer des comptes utilisateur - Edit the system environment variables + Modifier les variables d'environnement système - Manage BitLocker + Gérer BitLocker - Auto-hide the taskbar + Masquer automatiquement la barre des tâches - Change sound card settings + Modifier les paramètres de la carte son - Make changes to accounts + Apporter des modifications aux comptes - Edit local users and groups + Modifier les utilisateurs et les groupes locaux - View network computers and devices + Afficher les ordinateurs et périphériques réseau - Install a program from the network + Installer un programme depuis le réseau - View scanners and cameras + Voir les scanners et caméras Microsoft IME Register Word (Japanese) @@ -1890,7 +1890,7 @@ Microsoft New Phonetic Settings - Generate a system health report + Générer un rapport de santé du système Fix problems with your computer @@ -1899,10 +1899,10 @@ Back up and Restore (Windows 7) - Preview, delete, show or hide fonts + Aperçu, supprimer, afficher ou masquer les polices - Microsoft Quick Settings + Paramètres rapides Microsoft View reliability history @@ -1929,16 +1929,16 @@ Change what closing the lid does - Turn off unnecessary animations + Désactiver les animations non nécessaires - Create a restore point + Créer un point de restauration - Turn off automatic window arrangement + Désactiver l'agencement automatique des fenêtres - Troubleshooting History + Historique de dépannage Diagnose your computer's memory problems @@ -1950,16 +1950,16 @@ Change cursor blink rate - Add or remove programs + Ajouter ou supprimer des programmes - Create a password reset disk + Créer un disque de réinitialisation de mot de passe Configure advanced user profile properties - Start or stop using AutoPlay for all media and devices + Démarrer ou arrêter l'utilisation de la fonction AutoPlay pour tous les médias et appareils Change Automatic Maintenance settings @@ -1971,25 +1971,25 @@ Select users who can use remote desktop - Show which programs are installed on your computer + Afficher les programmes installés sur votre ordinateur Allow remote access to your computer - View advanced system settings + Afficher les paramètres avancés du système - How to install a program + Comment installer un programme - Change how your keyboard works + Modifier le fonctionnement de votre clavier - Automatically adjust for daylight saving time + Ajustement automatique à l'heure d'été - Change the order of Windows SideShow gadgets + Modifier l'ordre des gadgets Windows SideShow Check keyboard status @@ -2013,7 +2013,7 @@ Change your homepage - Group similar windows on the taskbar + Grouper les fenêtres similaires sur la barre des tâches Change Windows SideShow settings @@ -2031,7 +2031,7 @@ Change when the computer sleeps - Set up a virtual private network (VPN) connection + Configurer une connexion à un réseau privé virtuel (VPN) Accommodate learning abilities @@ -2040,10 +2040,10 @@ Set up a dial-up connection - Set up a connection or network + Configurer une connexion ou un réseau - How to change your Windows password + Comment modifier votre mot de passe Windows Make it easier to see the mouse pointer @@ -2097,13 +2097,13 @@ Show how much RAM is on this computer - Edit power plan + Modifier le mode de gestion d'alimentation - Adjust system volume + Ajuster le volume système - Defragment and optimise your drives + Défragmenter et optimiser les lecteurs Set up ODBC data sources (32-bit) @@ -2115,7 +2115,7 @@ Magnify portions of the screen using Magnifier - Change the file type associated with a file extension + Modifier le type de fichier associé à une extension de fichier View event logs @@ -2140,10 +2140,10 @@ - Turn Windows features on or off + Activer ou désactiver des fonctionnalités Windows - Show which operating system your computer is running + Afficher le système d'exploitation utilisé par l'ordinateur View local services @@ -2161,10 +2161,10 @@ Advanced printer setup - Change default printer + Changer l'imprimante par défaut - Edit environment variables for your account + Modifier les variables d'environnement pour votre compte Optimise visual display @@ -2182,7 +2182,7 @@ Clear disk space by deleting unnecessary files - View devices and printers + Afficher les appareils et les imprimantes Private Character Editor @@ -2206,16 +2206,16 @@ Choose the order of how your screen rotates - Change how Windows searches + Modifier le mode de recherche de Windows Set flicks to perform certain tasks - Change account type + Changer le type de compte - Change screen saver + Modifier l'économiseur d'écran Change User Account Control settings @@ -2224,10 +2224,10 @@ Turn on easy access keys - Identify and repair network problems + Identifier et réparer les problèmes de réseau - Find and fix networking and connection problems + Rechercher et résoudre les problèmes de réseau et de connexion Play CDs or other media automatically @@ -2236,34 +2236,34 @@ View basic information about your computer - Choose how you open links + Choisissez comment ouvrir les liens Allow Remote Assistance invitations to be sent from this computer - Task Manager + Gestionnaire des tâches Turn flicks on or off - Add a language + Ajouter une langue - View network status and tasks + Afficher l'état du réseau et les tâches - Turn Magnifier on or off + Activer ou désactiver la loupe - See the name of this computer + Afficher le nom de cet ordinateur - View network connections + Afficher les connexions réseau - Perform recommended maintenance tasks automatically + Effectuer automatiquement les tâches de maintenance recommandées Manage disk space used by your offline files @@ -2281,7 +2281,7 @@ Change the way dates and lists are displayed - Manage audio devices + Gérer les appareils audio Change security settings @@ -2290,7 +2290,7 @@ Check security status - Delete cookies or temporary files + Supprimer les cookies ou les fichiers temporaires Specify which hand you write with @@ -2323,7 +2323,7 @@ Enable or disable session cookies - Give administrative rights to a domain user + Donner des droits d'administration à un utilisateur du domaine Choose when to turn off display @@ -2332,7 +2332,7 @@ Move the pointer with the keypad using MouseKeys - Change Windows SideShow-compatible device settings + Modifier les paramètres des appareils compatibles avec Windows SideShow Adjust commonly used mobility settings @@ -2341,7 +2341,7 @@ Change text-to-speech settings - Set the time and date + Définir l'heure et la date Change location settings @@ -2350,16 +2350,16 @@ Change mouse settings - Manage Storage Spaces + Gérer les espaces de stockage Show or hide file extensions - Allow an app through Windows Firewall + Autoriser une application via le pare-feu Windows - Change system sounds + Modifier les sons du système Adjust ClearType text @@ -2368,25 +2368,25 @@ Turn screen saver on or off - Find and fix windows update problems + Rechercher et résoudre les problèmes de mise à jour de Windows Change Bluetooth settings - Connect to a network + Se connecter à un réseau - Change the search provider in Internet Explorer + Changer le moteur de recherche dans Internet Explorer Join a domain - Add a device + Ajouter un appareil - Find and fix problems with Windows Search + Rechercher et résoudre les problèmes avec Windows Search Choose a power plan @@ -2395,7 +2395,7 @@ Change how the mouse pointer looks when it’s moving - Uninstall a program + Désinstaller un programme Create and format hard disk partitions @@ -2407,7 +2407,7 @@ Change PC wake-up settings - Manage network passwords + Gérer les mots de passe réseau Change input methods @@ -2419,10 +2419,10 @@ Change battery settings - Rename this computer + Renommer cet ordinateur - Lock or unlock the taskbar + Verrouiller ou déverrouiller la barre des tâches Manage Web Credentials @@ -2431,16 +2431,16 @@ Change the time zone - Start speech recognition + Démarrer la reconnaissance vocale - View installed updates + Afficher les mises à jour installées - What's happened to the Quick Launch toolbar? + Qu'est-il arrivé à la barre d'outils de lancement rapide ? - Change search options for files and folders + Modifier les options de recherche pour les fichiers et les dossiers Adjust settings before giving a presentation @@ -2464,13 +2464,13 @@ Set up a broadband connection - Calibrate the screen for pen or touch input + Calibrer l'écran pour le stylet ou la touche d'entrée - Manage user certificates + Gérer les certificats utilisateur - Schedule tasks + Planifier des tâches Ignore repeated keystrokes using FilterKeys @@ -2485,7 +2485,7 @@ Delete browsing history - Change what the power buttons do + Changer ce que font les boutons d'alimentation Create standard user account @@ -2494,13 +2494,13 @@ Take speech tutorials - View system resource usage in Task Manager + Afficher l'utilisation des ressources du système dans le gestionnaire des tâches - Create an account + Créer un compte - Get more features with a new edition of Windows + Obtenez plus de fonctionnalités avec une nouvelle édition de Windows Panneau de configuration @@ -2509,6 +2509,6 @@ TaskLink - Unknown + Inconnu \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.it-IT.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.it-IT.resx index c0319e5fd..bcd3617ba 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.it-IT.resx +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.it-IT.resx @@ -837,7 +837,7 @@ Modalità chiara - Location + Posizione Area Privacy diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.nl-NL.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.nl-NL.resx index e5ff84248..c28489cc8 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.nl-NL.resx +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.nl-NL.resx @@ -456,7 +456,7 @@ Area Personalization - Command + Opdracht The command to direct start a setting diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.pt-PT.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.pt-PT.resx index b243dafd8..45dc2c8cc 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.pt-PT.resx +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.pt-PT.resx @@ -2443,7 +2443,7 @@ Change search options for files and folders - Adjust settings before giving a presentation + Ajustar definições antes de uma apresentação Digitalizar um documento ou imagem @@ -2467,10 +2467,10 @@ Calibrate the screen for pen or touch input - Manage user certificates + Gerir certificados do utilizador - Schedule tasks + Agendar tarefas Ignore repeated keystrokes using FilterKeys diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.uk-UA.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.uk-UA.resx index 3b598e40c..9d801e0d4 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.uk-UA.resx +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.uk-UA.resx @@ -126,66 +126,66 @@ File name, Should not translated - Accessibility Options + Параметри доступності Area Control Panel (legacy settings) - Accessory apps + Додаткові програми Area Privacy - Access work or school + Доступ до роботи або навчання Area UserAccounts - Account info + Інформація про обліковий запис Area Privacy - Accounts + Облікові записи Area SurfaceHub - Action Center + Центр дій Area Control Panel (legacy settings) - Activation + Активація Area UpdateAndSecurity - Activity history + Історія активності Area Privacy - Add Hardware + Додати обладнання Area Control Panel (legacy settings) - Add/Remove Programs + Додавання/видалення програм Area Control Panel (legacy settings) - Add your phone + Додайте свій телефон Area Phone - Administrative Tools + Адміністративні інструменти Area System - Advanced display settings + Розширені налаштування дисплея Area System, only available on devices that support advanced display options - Advanced graphics + Розширена графіка - Advertising ID + Рекламний ідентифікатор Area Privacy, Deprecated in Windows 10, version 1809 and later - Airplane mode + Режим польоту Area NetworkAndInternet @@ -193,40 +193,40 @@ Means the key combination "Tabulator+Alt" on the keyboard - Alternative names + Альтернативні назви - Animations + Анімації - App color + Колір програми - App diagnostics + Діагностика програми Area Privacy - App features + Можливості програми Area Apps - App + Застосунок Short/modern name for application - Apps and Features + Додатки та функції Area Apps - System settings + Налаштування системи Type of the setting is a "Modern Windows settings". We use the same term as used in start menu search at the moment. - Apps for websites + Додатки для веб-сайтів Area Apps - App volume and device preferences + Гучність програми та налаштування пристрою Area System, Added in Windows 10, version 1903 @@ -234,159 +234,159 @@ File name, Should not translated - Area + Область Mean the settings area or settings category - Accounts + Облікові записи - Administrative Tools + Адміністративні інструменти Area Control Panel (legacy settings) - Appearance and Personalization + Зовнішній вигляд та персоналізація - Apps + Додатки - Clock and Region + Час і регіон - Control Panel + Панель управління - Cortana + Кортана - Devices + Пристрої - Ease of access + Легкість доступу - Extras + Доповнення - Gaming + Ігри - Hardware and Sound + Апаратне забезпечення та звук - Home page + Головна сторінка - Mixed reality + Змішана реальність - Network and Internet + Мережа та Інтернет - Personalization + Персоналізація - Phone + Телефон - Privacy + Конфіденційність - Programs + Програми SurfaceHub - System + Система - System and Security + Система та безпека - Time and language + Час і мова - Update and security + Оновлення та безпека - User accounts + Облікові записи користувачів - Assigned access + Призначений доступ - Audio + Аудіо Area EaseOfAccess - Audio alerts + Звукові сповіщення - Audio and speech + Аудіо та мовлення Area MixedReality, only available if the Mixed Reality Portal app is installed. - Automatic file downloads + Автоматичне завантаження файлів Area Privacy - AutoPlay + Автовідтворення Area Device - Background + Тло Area Personalization - Background Apps + Фонові програми Area Privacy - Backup + Резервне копіювання Area UpdateAndSecurity - Backup and Restore + Резервне копіювання та відновлення Area Control Panel (legacy settings) - Battery Saver + Економія заряду батареї Area System, only available on devices that have a battery, such as a tablet - Battery Saver settings + Налаштування енергозбереження Area System, only available on devices that have a battery, such as a tablet - Battery saver usage details + Детальна інформація про використання економії заряду акумулятора - Battery use + Використання батареї Area System, only available on devices that have a battery, such as a tablet - Biometric Devices + Біометричні пристрої Area Control Panel (legacy settings) - BitLocker Drive Encryption + Шифрування диска BitLocker Area Control Panel (legacy settings) - Blue light + Синє світло Bluetooth Area Device - Bluetooth devices + Пристрої Bluetooth Area Control Panel (legacy settings) - Blue-yellow + Синьо-жовтий Bopomofo IME @@ -397,22 +397,22 @@ Should not translated - Broadcasting + Трансляція Area Gaming - Calendar + Календар Area Privacy - Call history + Історія дзвінків Area Privacy - calling + виклик - Camera + Камера Area Privacy @@ -424,118 +424,118 @@ Mean the "Caps Lock" key - Cellular and SIM + Стільниковий зв'язок та SIM-карта Area NetworkAndInternet - Choose which folders appear on Start + Виберіть, які папки відображатимуться на панелі Пуск Area Personalization - Client service for NetWare + Обслуговування клієнтів для NetWare Area Control Panel (legacy settings) - Clipboard + Буфер обміну Area System - Closed captions + Субтитри Area EaseOfAccess - Color filters + Кольорові фільтри Area EaseOfAccess - Color management + Керування кольором Area Control Panel (legacy settings) - Colors + Кольори Area Personalization - Command + Команда The command to direct start a setting - Connected Devices + Підключені пристрої Area Device - Contacts + Контакти Area Privacy - Control Panel + Панель управління Type of the setting is a "(legacy) Control Panel setting" - Copy command + Скопіювати команду - Core Isolation + Ізоляція ядра Means the protection of the system core - Cortana + Кортана Area Cortana - Cortana across my devices + Кортана на всіх моїх пристроях Area Cortana - Cortana - Language + Кортана - Мова Area Cortana - Credential manager + Менеджер облікових даних Area Control Panel (legacy settings) - Crossdevice + Кроспристрійний - Custom devices + Спеціальні пристрої - Dark color + Темний колір - Dark mode + Темний режим - Data usage + Використання даних Area NetworkAndInternet - Date and time + Дата і час Area TimeAndLanguage - Default apps + Програми за замовчуванням Area Apps - Default camera + Камера за замовчуванням Area Device - Default location + Розташування за замовчуванням Area Control Panel (legacy settings) - Default programs + Програми за замовчуванням Area Control Panel (legacy settings) - Default Save Locations + Місця збереження за замовчуванням Area System - Delivery Optimization + Оптимізація доставки Area UpdateAndSecurity @@ -543,19 +543,19 @@ File name, Should not translated - Desktop themes + Теми для робочого столу Area Control Panel (legacy settings) - deuteranopia + дейтеранопія Medical: Mean you don't can see red colors - Device manager + Диспетчер пристроїв Area Control Panel (legacy settings) - Devices and printers + Пристрої та принтери Area Control Panel (legacy settings) @@ -563,63 +563,63 @@ Should not translated - Dial-up + Комутований зв'язок Area NetworkAndInternet - Direct access + Прямий доступ Area NetworkAndInternet, only available if DirectAccess is enabled - Direct open your phone + Пряме відкриття телефону Area EaseOfAccess - Display + Відображення Area EaseOfAccess - Display properties + Властивості відображення Area Control Panel (legacy settings) - DNS + ДНС Should not translated - Documents + Документи Area Privacy - Duplicating my display + Дублювання мого дисплею Area System - During these hours + Протягом цих годин Area System - Ease of access center + Центр легкого доступу Area Control Panel (legacy settings) - Edition + Видання Means the "Windows Edition" - Email + Електронна пошта Area Privacy - Email and app accounts + Облікові записи електронної пошти та додатків Area UserAccounts - Encryption + Шифрування Area System - Environment + Середовище Area MixedReality, only available if the Mixed Reality Portal app is installed. @@ -627,30 +627,30 @@ Area NetworkAndInternet - Exploit Protection + Захист від експлойтів - Extras + Доповнення Area Extra, , only used for setting of 3rd-Party tools - Eye control + Зоровий контроль Area EaseOfAccess - Eye tracker + Відстежувач очей Area Privacy, requires eyetracker hardware - Family and other people + Сім'я та інші люди Area UserAccounts - Feedback and diagnostics + Зворотній зв'язок та діагностика Area Privacy - File system + Файлова система Area Privacy @@ -662,42 +662,42 @@ File name, Should not translated - Find My Device + Знайти мій пристрій Area UpdateAndSecurity - Firewall + Брандмауер - Focus assist - Quiet hours + Допомога з фокусуванням - Тихі години Area System - Focus assist - Quiet moments + Допомога з фокусуванням - Тихі моменти Area System - Folder options + Параметри папки Area Control Panel (legacy settings) - Fonts + Шрифти Area EaseOfAccess - For developers + Для розробників Area UpdateAndSecurity - Game bar + Ігрова панель Area Gaming - Game controllers + Ігрові контролери Area Control Panel (legacy settings) - Game DVR + Ігровий DVR Area Gaming @@ -705,7 +705,7 @@ Area Gaming - Gateway + Шлюз Should not translated @@ -713,61 +713,61 @@ Area Privacy - Get programs + Отримати програми Area Control Panel (legacy settings) - Getting started + Початок роботи Area Control Panel (legacy settings) - Glance + Погляд Area Personalization, Deprecated in Windows 10, version 1809 and later - Graphics settings + Графічні налаштування Area System - Grayscale + Відтінки сірого - Green week + Зелений тиждень Mean you don't can see green colors - Headset display + Відображення гарнітури Area MixedReality, only available if the Mixed Reality Portal app is installed. - High contrast + Високий контраст Area EaseOfAccess - Holographic audio + Голографічний звук - Holographic Environment + Голографічне середовище - Holographic Headset + Голографічна гарнітура - Holographic Management + Голографічне управління - Home group + Домашня група Area Control Panel (legacy settings) - ID + Ідентифікатор MEans The "Windows Identifier" - Image + Зображення - Indexing options + Параметри індексації Area Control Panel (legacy settings) @@ -775,15 +775,15 @@ File name, Should not translated - Infrared + Інфрачервоне випромінювання Area Control Panel (legacy settings) - Inking and typing + Малювання та друк Area Privacy - Internet options + Параметри Інтернету Area Control Panel (legacy settings) @@ -791,17 +791,17 @@ File name, Should not translated - Inverted colors + Інвертовані кольори - IP + IP-адреса Should not translated - Isolated Browsing + Ізольований перегляд - Japan IME settings + Налаштування IME в Японії Area TimeAndLanguage, available if the Microsoft Japan input method editor is installed @@ -809,7 +809,7 @@ File name, Should not translated - Joystick properties + Властивості джойстика Area Control Panel (legacy settings) @@ -817,39 +817,39 @@ Should not translated - Keyboard + Клавіатура Area EaseOfAccess - Keypad + Клавіатура - Keys + Клавіші Мова Area TimeAndLanguage - Light color + Світлий колір - Light mode + Легкий режим - Location + Місцезнаходження Area Privacy - Lock screen + Екран блокування Area Personalization - Magnifier + Лупа Area EaseOfAccess - Mail - Microsoft Exchange or Windows Messaging + Пошта - Microsoft Exchange або Windows Messaging Area Control Panel (legacy settings) @@ -857,26 +857,26 @@ File name, Should not translated - Manage known networks + Керування відомими мережами Area NetworkAndInternet - Manage optional features + Керування додатковими функціями Area Apps - Messaging + Обмін повідомленнями Area Privacy - Metered connection + Дозоване підключення - Microphone + Мікрофон Area Privacy - Microsoft Mail Post Office + Поштова служба Microsoft Mail Area Control Panel (legacy settings) @@ -888,10 +888,10 @@ File name, Should not translated - Mobile devices + Мобільні пристрої - Mobile hotspot + Мобільна точка доступу Area NetworkAndInternet @@ -899,46 +899,46 @@ File name, Should not translated - Mono + Моно - More details + Детальніше Area Cortana - Motion + Рух Area Privacy - Mouse + Миша Area EaseOfAccess - Mouse and touchpad + Миша та тачпад Area Device - Mouse, Fonts, Keyboard, and Printers properties + Властивості миші, шрифтів, клавіатури та принтерів Area Control Panel (legacy settings) - Mouse pointer + Вказівник миші Area EaseOfAccess - Multimedia properties + Властивості мультимедіа Area Control Panel (legacy settings) - Multitasking + Багатозадачність Area System - Narrator + Диктор Area EaseOfAccess - Navigation bar + Панель навігації Area Personalization @@ -950,27 +950,27 @@ File name, Should not translated - Network + Мережа Area NetworkAndInternet - Network and sharing center + Мережа та центр обміну даними Area Control Panel (legacy settings) - Network connection + Підключення до мережі Area Control Panel (legacy settings) - Network properties + Властивості мережі Area Control Panel (legacy settings) - Network Setup Wizard + Майстер налаштування мережі Area Control Panel (legacy settings) - Network status + Стан мережі Area NetworkAndInternet @@ -978,88 +978,88 @@ Area NetworkAndInternet - NFC Transactions + NFC-транзакції "NFC should not translated" - Night light + Нічник - Night light settings + Налаштування нічника Area System - Note + Примітка - Only available when you have connected a mobile device to your device. + Доступно лише після підключення мобільного пристрою до вашого пристрою. - Only available on devices that support advanced graphics options. + Доступно лише на пристроях, що підтримують розширені графічні опції. - Only available on devices that have a battery, such as a tablet. + Доступно лише на пристроях з акумулятором, таких як планшет. - Deprecated in Windows 10, version 1809 (build 17763) and later. + Застаріла у Windows 10, версії 1809 (збірка 17763) та пізніших версіях. - Only available if Dial is paired. + Доступно, якщо циферблат під'єднано. - Only available if DirectAccess is enabled. + Доступно, якщо увімкнено DirectAccess. - Only available on devices that support advanced display options. + Доступно лише на пристроях, які підтримують розширені параметри відображення. - Only present if user is enrolled in WIP. + Присутній лише якщо користувач зареєстрований у WIP. - Requires eyetracker hardware. + Вимагає обладнання для відстеження очей. - Available if the Microsoft Japan input method editor is installed. + Доступно, якщо встановлено редактор методів введення Microsoft Japan. - Available if the Microsoft Pinyin input method editor is installed. + Доступно, якщо встановлено редактор способів введення Microsoft Pinyin. - Available if the Microsoft Wubi input method editor is installed. + Доступно, якщо встановлено редактор методів введення Microsoft Wubi. - Only available if the Mixed Reality Portal app is installed. + Доступно лише якщо встановлено додаток Портал змішаної реальності. - Only available on mobile and if the enterprise has deployed a provisioning package. + Доступно лише на мобільних пристроях або якщо на підприємстві розгорнуто пакет забезпечення. - Added in Windows 10, version 1903 (build 18362). + Додано у Windows 10, версія 1903 (збірка 18362). - Added in Windows 10, version 2004 (build 19041). + Додано у Windows 10, версія 2004 (збірка 19041). - Only available if "settings apps" are installed, for example, by a 3rd party. + Доступно, якщо "програми налаштувань" встановлені, наприклад, третьою стороною. - Only available if touchpad hardware is present. + Доступно лише за наявності апаратного забезпечення тачпада. - Only available if the device has a Wi-Fi adapter. + Доступно лише за наявності на пристрої адаптера Wi-Fi. - Device must be Windows Anywhere-capable. + Пристрій повинен підтримувати Windows Anywhere. - Only available if enterprise has deployed a provisioning package. + Доступно, якщо на підприємстві розгорнуто пакет забезпечення. - Notifications + Сповіщення Area Privacy - Notifications and actions + Сповіщення та дії Area System @@ -1075,45 +1075,45 @@ File name, Should not translated - ODBC Data Source Administrator (32-bit) + Адміністратор джерел даних ODBC (32-розрядний) Area Control Panel (legacy settings) - ODBC Data Source Administrator (64-bit) + Адміністратор джерел даних ODBC (64-розрядний) Area Control Panel (legacy settings) - Offline files + Офлайн файли Area Control Panel (legacy settings) - Offline Maps + Офлайн карти Area Apps - Offline Maps - Download maps + Офлайн карти - Завантажити карти Area Apps - On-Screen + Екранний - OS + ОС Means the "Operating System" - Other devices + Інші пристрої Area Privacy - Other options + Інші параметри Area EaseOfAccess - Other users + Інші користувачі - Parental controls + Батьківський контроль Area Control Panel (legacy settings) @@ -1124,92 +1124,92 @@ File name, Should not translated - Password properties + Параметри паролів Area Control Panel (legacy settings) - Pen and input devices + Ручка та пристрої введення Area Control Panel (legacy settings) - Pen and touch + Ручка та сенсорний дотик Area Control Panel (legacy settings) - Pen and Windows Ink + Ручка та чорнило для Windows Area Device - People Near Me + Люди поруч зі мною Area Control Panel (legacy settings) - Performance information and tools + Інформація про ефективність та інструменти Area Control Panel (legacy settings) - Permissions and history + Дозволи та історія Area Cortana - Personalization (category) + Персоналізація (категорія) Area Personalization - Phone + Телефон Area Phone - Phone and modem + Телефон і модем Area Control Panel (legacy settings) - Phone and modem - Options + Телефон і модем - Опції Area Control Panel (legacy settings) - Phone calls + Телефонні дзвінки Area Privacy - Phone - Default apps + Телефон - Програми за замовчуванням Area System - Picture + Зображення - Pictures + Картинки Area Privacy - Pinyin IME settings + Налаштування піньїнь IME Area TimeAndLanguage, available if the Microsoft Pinyin input method editor is installed - Pinyin IME settings - domain lexicon + Налаштування IME піньїнь - лексика домену Area TimeAndLanguage - Pinyin IME settings - Key configuration + Налаштування піньїнь IME - Конфігурація ключів Area TimeAndLanguage - Pinyin IME settings - UDP + Налаштування піньїнь IME - UDP Area TimeAndLanguage - Playing a game full screen + Відтворення гри на весь екран Area Gaming - Plugin to search for Windows settings + Плагін для пошуку налаштувань Windows - Windows Settings + Налаштування Windows - Power and sleep + Живлення та сон Area System @@ -1217,118 +1217,118 @@ File name, Should not translated - Power options + Параметри живлення Area Control Panel (legacy settings) - Presentation + Подання - Printers + Принтери Area Control Panel (legacy settings) - Printers and scanners + Принтери та сканери Area Device - Print screen + Print Screen Mean the "Print screen" key - Problem reports and solutions + Звіти про проблеми та їх вирішення Area Control Panel (legacy settings) - Processor + Процесор - Programs and features + Програми та функції Area Control Panel (legacy settings) - Projecting to this PC + Проектування на цей ПК Area System - protanopia + протанопія Medical: Mean you don't can see green colors - Provisioning + Забезпечення Area UserAccounts, only available if enterprise has deployed a provisioning package - Proximity + Близькість Area NetworkAndInternet - Proxy + Проксі Area NetworkAndInternet - Quickime + Швидкий час Area TimeAndLanguage - Quiet moments game + Тихі моменти - Radios + Радіо Area Privacy - RAM + ОЗП Means the Read-Access-Memory (typical the used to inform about the size) - Recognition + Розпізнавання - Recovery + Відновлення Area UpdateAndSecurity - Red eye + Червоне око Mean red eye effect by over-the-night flights - Red-green + Червоно-зелений Mean the weakness you can't differ between red and green colors - Red week + Червоний тиждень Mean you don't can see red colors - Region + Регіон Area TimeAndLanguage - Regional language + Регіональна мова Area TimeAndLanguage - Regional settings properties + Властивості регіональних налаштувань Area Control Panel (legacy settings) - Region and language + Регіон та мова Area Control Panel (legacy settings) - Region formatting + Форматування регіону - RemoteApp and desktop connections + Підключення RemoteApp і робочого столу Area Control Panel (legacy settings) - Remote Desktop + Віддалений робочий стіл Area System - Scanners and cameras + Сканери та камери Area Control Panel (legacy settings) @@ -1336,18 +1336,18 @@ File name, Should not translated - Scheduled + Заплановано - Scheduled tasks + Заплановані завдання Area Control Panel (legacy settings) - Screen rotation + Обертання екрану Area System - Scroll bars + Смуги прокрутки Scroll Lock @@ -1358,7 +1358,7 @@ Should not translated - Searching Windows + Пошук у Windows Area Cortana @@ -1366,71 +1366,71 @@ Should not translated - Security Center + Центр безпеки Area Control Panel (legacy settings) - Security Processor + Процесор безпеки - Session cleanup + Очищення сесії Area SurfaceHub - Settings home page + Головна сторінка налаштувань Area Home, Overview-page for all areas of settings - Set up a kiosk + Встановіть кіоск Area UserAccounts - Shared experiences + Спільний досвід Area System - Shortcuts + Скорочення wifi dont translate this, is a short term to find entries - Sign-in options + Параметри входу Area UserAccounts - Sign-in options - Dynamic lock + Параметри входу - Динамічне блокування Area UserAccounts - Size + Розмір Size for text and symbols - Sound + Звук Area System - Speech + Мовлення Area EaseOfAccess - Speech recognition + Розпізнавання мови Area Control Panel (legacy settings) - Speech typing + Набір тексту з голосу - Start + Пуск Area Personalization - Start places + Місця старту - Startup apps + Початкові програми Area Apps @@ -1438,11 +1438,11 @@ File name, Should not translated - Storage + Сховище Area System - Storage policies + Політики зберігання Area System @@ -1450,15 +1450,15 @@ Area System - in + в Example: Area "System" in System settings - Sync center + Центр синхронізації Area Control Panel (legacy settings) - Sync your settings + Синхронізація налаштувань Area UserAccounts @@ -1466,57 +1466,57 @@ File name, Should not translated - System + Система Area Control Panel (legacy settings) - System properties and Add New Hardware wizard + Властивості системи та майстер додавання нового обладнання Area Control Panel (legacy settings) - Tab + Вкладка Means the key "Tabulator" on the keyboard - Tablet mode + Режим планшета Area System - Tablet PC settings + Налаштування планшетного ПК Area Control Panel (legacy settings) - Talk + Розмова - Talk to Cortana + Розмовляйте з Кортаною Area Cortana - Taskbar + Панель завдань Area Personalization - Taskbar color + Колір панелі завдань - Tasks + Задачі Area Privacy - Team Conferencing + Командні конференції Area SurfaceHub - Team device management + Керування командними пристроями Area SurfaceHub - Text to speech + Перетворення тексту у мовлення Area Control Panel (legacy settings) - Themes + Теми Area Personalization @@ -1528,27 +1528,27 @@ File name, Should not translated - Timeline + Хронологія - Touch + Дотик - Touch feedback + Зворотний зв'язок на дотик - Touchpad + Тачпад Area Device - Transparency + Прозорість - tritanopia + тританопія Medical: Mean you don't can see yellow and blue colors - Troubleshoot + Усунення несправностей Area UpdateAndSecurity @@ -1556,11 +1556,11 @@ Area Gaming - Typing + Набір тексту Area Device - Uninstall + Видалити Area MixedReality, only available if the Mixed Reality Portal app is installed. @@ -1568,7 +1568,7 @@ Area Device - User accounts + Облікові записи користувачів Area Control Panel (legacy settings) @@ -1576,43 +1576,43 @@ Means The "Windows Version" - Video playback + Відтворення відео Area Apps - Videos + Відео Area Privacy - Virtual Desktops + Віртуальні робочі столи - Virus + Вірус Means the virus in computers and software - Voice activation + Голосова активація Area Privacy - Volume + Гучність VPN Area NetworkAndInternet - Wallpaper + Шпалери - Warmer color + Тепліший колір - Welcome center + Інформаційний центр Area Control Panel (legacy settings) - Welcome screen + Екран привітання Area SurfaceHub @@ -1620,7 +1620,7 @@ File name, Should not translated - Wheel + Колесо Area Device @@ -1628,18 +1628,18 @@ Area NetworkAndInternet, only available if Wi-Fi calling is enabled - Wi-Fi Calling + Дзвінки через Wi-Fi Area NetworkAndInternet, only available if Wi-Fi calling is enabled - Wi-Fi settings + Налаштування Wi-Fi "Wi-Fi" should not translated - Window border + Рамка вікна - Windows Anytime Upgrade + Оновлення Windows у будь-який час Area Control Panel (legacy settings) @@ -1651,864 +1651,864 @@ Area Control Panel (legacy settings) - Windows Defender + Захисник Windows Area Control Panel (legacy settings) - Windows Firewall + Брандмауер Windows Area Control Panel (legacy settings) - Windows Hello setup - Face + Налаштування Windows Hello - Обличчя Area UserAccounts - Windows Hello setup - Fingerprint + Налаштування Windows Hello - Відбиток пальця Area UserAccounts - Windows Insider Program + Програма для інсайдерів Windows Area UpdateAndSecurity - Windows Mobility Center + Центр мобільності Windows Area Control Panel (legacy settings) - Windows search + Пошук у Windows Area Cortana - Windows Security + Безпека Windows Area UpdateAndSecurity - Windows Update + Оновлення Windows Area UpdateAndSecurity - Windows Update - Advanced options + Оновлення Windows - Додаткові параметри Area UpdateAndSecurity - Windows Update - Check for updates + Оновлення Windows - перевірка наявності оновлень Area UpdateAndSecurity - Windows Update - Restart options + Оновлення Windows - Параметри перезапуску Area UpdateAndSecurity - Windows Update - View optional updates + Оновлення Windows - Перегляд додаткових оновлень Area UpdateAndSecurity - Windows Update - View update history + Оновлення Windows - Перегляд журналу оновлень Area UpdateAndSecurity - Wireless + Бездротовий - Workplace + Робоче місце - Workplace provisioning + Забезпечення робочого місця Area UserAccounts - Wubi IME settings + Налаштування Wubi IME Area TimeAndLanguage, available if the Microsoft Wubi input method editor is installed - Wubi IME settings - UDP + Налаштування Wubi IME - UDP Area TimeAndLanguage - Xbox Networking + Мережа Xbox Networking Area Gaming - Your info + Ваша інформація Area UserAccounts - Zoom + Збільшити Mean zooming of things via a magnifier - Change device installation settings + Зміна налаштувань встановлення пристрою - Turn off background images + Вимкнути фонові зображення - Navigation properties + Властивості навігації - Media streaming options + Параметри потокової передачі медіа - Make a file type always open in a specific program + Завжди відкривати файл певного типу у визначеній програмі - Change the Narrator’s voice + Змінити голос диктора - Find and fix keyboard problems + Пошук та усунення проблем з клавіатурою - Use screen reader + Використовуйте програму для читання з екрана - Show which workgroup this computer is on + Показати, в якій робочій групі знаходиться цей комп'ютер - Change mouse wheel settings + Зміна налаштувань коліщатка миші - Manage computer certificates + Керування комп'ютерними сертифікатами - Find and fix problems + Пошук та усунення проблем - Change settings for content received using Tap and send + Зміна налаштувань для вмісту, отриманого за допомогою функції "Доторкнутись і відправити" - Change default settings for media or devices + Зміна налаштувань за замовчуванням для носіїв або пристроїв - Print the speech reference card + Роздрукувати мовленнєву довідкову картку - Calibrate display colour + Відкалібрувати колір дисплея - Manage file encryption certificates + Керування сертифікатами шифрування файлів - View recent messages about your computer + Перегляд останніх повідомлень про комп'ютер - Give other users access to this computer + Надати іншим користувачам доступ до цього комп'ютера - Show hidden files and folders + Показувати приховані файли та папки - Change Windows To Go start-up options + Зміна параметрів запуску Windows To Go - See which processes start up automatically when you start Windows + Переглянути, які процеси запускаються автоматично під час запуску Windows - Tell if an RSS feed is available on a website + Визначити, чи є на веб-сайті RSS-канал - Add clocks for different time zones + Додати годинник для різних часових поясів - Add a Bluetooth device + Додати пристрій Bluetooth - Customise the mouse buttons + Налаштування кнопок миші - Set tablet buttons to perform certain tasks + Налаштування кнопок планшета на виконання певних завдань - View installed fonts + Переглянути встановлені шрифти - Change the way currency is displayed + Змінити спосіб відображення валюти - Edit group policy + Редагування групової політики - Manage browser add-ons + Керування доповненнями браузера - Check processor speed + Перевірити швидкість процесора - Check firewall status + Перевірити стан брандмауера - Send or receive a file + Надіслати або отримати файл - Add or remove user accounts + Додавання або видалення облікових записів користувачів - Edit the system environment variables + Редагування змінних системного середовища - Manage BitLocker + Керування BitLocker - Auto-hide the taskbar + Автоматичне приховування панелі завдань - Change sound card settings + Зміна налаштувань звукової карти - Make changes to accounts + Внесення змін до облікових записів - Edit local users and groups + Редагування локальних користувачів і груп - View network computers and devices + Перегляд мережевих комп'ютерів і пристроїв - Install a program from the network + Встановлення програми з мережі - View scanners and cameras + Перегляд сканерів та камер - Microsoft IME Register Word (Japanese) + Microsoft IME Register Word (японська) - Restore your files with File History + Відновлення файлів за допомогою Історії файлів - Turn On-Screen keyboard on or off + Увімкнення або вимкнення екранної клавіатури - Block or allow third-party cookies + Блокувати або дозволяти сторонні файли cookie - Find and fix audio recording problems + Пошук та усунення проблем із записом звуку - Create a recovery drive + Створення диска відновлення - Microsoft New Phonetic Settings + Нові фонетичні параметри Microsoft - Generate a system health report + Створення звіту про стан системи - Fix problems with your computer + Вирішення проблем з комп'ютером - Back up and Restore (Windows 7) + Резервне копіювання та відновлення (Windows 7) - Preview, delete, show or hide fonts + Попередній перегляд, видалення, показ або приховування шрифтів - Microsoft Quick Settings + Швидкі налаштування Microsoft - View reliability history + Переглянути історію надійності - Access RemoteApp and desktops + Доступ до RemoteApp і робочих столів - Set up ODBC data sources + Налаштування джерел даних ODBC - Reset Security Policies + Скидання політик безпеки - Block or allow pop-ups + Блокувати або дозволяти спливаючі вікна - Turn autocomplete in Internet Explorer on or off + Увімкнення або вимкнення автозаповнення в Internet Explorer - Microsoft Pinyin SimpleFast Options + Параметри Microsoft Pinyin SimpleFast - Change what closing the lid does + Зміна ефекту від закриття корпусу - Turn off unnecessary animations + Вимкнути непотрібні анімації - Create a restore point + Створення точки відновлення - Turn off automatic window arrangement + Вимкнення автоматичного розташування вікон - Troubleshooting History + Історія усунення несправностей - Diagnose your computer's memory problems + Діагностика проблем з пам'яттю комп'ютера - View recommended actions to keep Windows running smoothly + Переглянути рекомендовані дії для забезпечення безперебійної роботи Windows - Change cursor blink rate + Змінити частоту блимання курсору - Add or remove programs + Додавання або видалення програм - Create a password reset disk + Створення диска для скидання пароля - Configure advanced user profile properties + Налаштування розширених властивостей профілю користувача - Start or stop using AutoPlay for all media and devices + Увімкнути або вимкнути автоматичне відтворення для всіх медіа та пристроїв - Change Automatic Maintenance settings + Змінити налаштування автоматичного обслуговування - Specify single- or double-click to open + Вкажіть одинарний або подвійний клік для відкриття - Select users who can use remote desktop + Вибір користувачів, які можуть використовувати віддалений робочий стіл - Show which programs are installed on your computer + Показати, які програми встановлені на вашому комп'ютері - Allow remote access to your computer + Дозволити віддалений доступ до комп'ютера - View advanced system settings + Перегляд розширених налаштувань системи - How to install a program + Як встановити програму - Change how your keyboard works + Змінити роботу клавіатури - Automatically adjust for daylight saving time + Автоматичне переведення на літній час - Change the order of Windows SideShow gadgets + Зміна порядку розташування гаджетів Windows SideShow - Check keyboard status + Перевірити стан клавіатури - Control the computer without the mouse or keyboard + Керування комп'ютером без миші або клавіатури - Change or remove a program + Змінити або видалити програму - Change multi-touch gesture settings + Зміна налаштувань жестів мультидотику - Set up ODBC data sources (64-bit) + Налаштування джерел даних ODBC (64-біт) - Configure proxy server + Налаштування проксі-сервера - Change your homepage + Змінити домашню сторінку - Group similar windows on the taskbar + Групувати схожі вікна на панелі завдань - Change Windows SideShow settings + Зміна налаштувань Windows SideShow - Use audio description for video + Використання аудіодискрипції для відео - Change workgroup name + Змінити назву робочої групи - Find and fix printing problems + Пошук і усунення проблем із друком - Change when the computer sleeps + Змінити коли комп'ютер перебуває в режимі сну - Set up a virtual private network (VPN) connection + Налаштування підключення до віртуальної приватної мережі (VPN) - Accommodate learning abilities + Враховувати здібності до навчання - Set up a dial-up connection + Налаштування комутованого з'єднання - Set up a connection or network + Налаштування з'єднання або мережі - How to change your Windows password + Як змінити пароль до Windows - Make it easier to see the mouse pointer + Полегшити видимість вказівника миші - Set up iSCSI initiator + Налаштування ініціатора iSCSI - Accommodate low vision + Пристосовано для людей зі слабким зором - Manage offline files + Керування офлайн-файлами - Review your computer's status and resolve issues + Перегляд стану комп'ютера та вирішення проблем - Microsoft ChangJie Settings + Налаштування Microsoft ChangJie - Replace sounds with visual cues + Замінити звуки візуальними підказками - Change temporary Internet file settings + Зміна налаштувань тимчасового інтернет-файлу - Connect to the Internet + Підключення до Інтернету - Find and fix audio playback problems + Пошук та усунення проблем із відтворенням аудіо - Change the mouse pointer display or speed + Змінити відображення або швидкість вказівника миші - Back up your recovery key + Створення резервної копії ключа відновлення - Save backup copies of your files with File History + Збереження резервних копій файлів за допомогою Історії файлів - View current accessibility settings + Перегляд поточних налаштувань доступності - Change tablet pen settings + Зміна налаштувань пера планшета - Change how your mouse works + Змінити роботу миші - Show how much RAM is on this computer + Показати обсяг оперативної пам'яті на цьому комп'ютері - Edit power plan + Редагування плану енергоспоживання - Adjust system volume + Регулювання гучності системи - Defragment and optimise your drives + Дефрагментація та оптимізація дисків - Set up ODBC data sources (32-bit) + Налаштування джерел даних ODBC (32-біт) - Change Font Settings + Зміна параметрів шрифту - Magnify portions of the screen using Magnifier + Збільшити частину екрана за допомогою лупи - Change the file type associated with a file extension + Зміна типу файлу, пов'язаного з розширенням файлу - View event logs + Перегляд журналів подій - Manage Windows Credentials + Керування обліковими даними Windows - Set up a microphone + Налаштування мікрофона - Change how the mouse pointer looks + Змінити вигляд вказівника миші - Change power-saving settings + Зміна налаштувань енергозбереження - Optimise for blindness + Оптимізація для незрячих - Turn Windows features on or off + Увімкнення чи вимкнення функцій Windows - Show which operating system your computer is running + Показати, яка операційна система працює на вашому комп'ютері - View local services + Перегляд локальних послуг - Manage Work Folders + Керування робочими папками - Encrypt your offline files + Шифрування офлайн-файлів - Train the computer to recognise your voice + Навчити комп'ютер розпізнавати ваш голос - Advanced printer setup + Розширене налаштування принтера - Change default printer + Змінити принтер за замовчуванням - Edit environment variables for your account + Редагування змінних середовища для вашого облікового запису - Optimise visual display + Оптимізація візуального відображення - Change mouse click settings + Змінити налаштування клацання мишею - Change advanced colour management settings for displays, scanners and printers + Зміна розширених параметрів керування кольором для дисплеїв, сканерів і принтерів - Let Windows suggest Ease of Access settings + Дозволити Windows пропонувати налаштування Ease of Access - Clear disk space by deleting unnecessary files + Звільнити місце на диску, видаливши непотрібні файли - View devices and printers + Перегляд пристроїв і принтерів - Private Character Editor + Приватний редактор символів - Record steps to reproduce a problem + Записати кроки для відтворення проблеми - Adjust the appearance and performance of Windows + Налаштування зовнішнього вигляду та продуктивності Windows - Settings for Microsoft IME (Japanese) + Налаштування для Microsoft IME (японська) - Invite someone to connect to your PC and help you, or offer to help someone else + Запросіть когось підключитися до вашого комп'ютера і допомогти вам, або запропонуйте допомогти комусь іншому - Run programs made for previous versions of Windows + Запуск програм, створених для попередніх версій Windows - Choose the order of how your screen rotates + Виберіть порядок обертання екрана - Change how Windows searches + Зміна способу пошуку в Windows - Set flicks to perform certain tasks + Налаштувати кліки на виконання певних завдань - Change account type + Змінити тип облікового запису - Change screen saver + Змінити режим заощадження екрана - Change User Account Control settings + Зміна налаштувань контролю облікових записів користувачів - Turn on easy access keys + Увімкнути клавіші швидкого доступу - Identify and repair network problems + Виявлення та усунення мережевих проблем - Find and fix networking and connection problems + Пошук та усунення проблем з мережею та з'єднанням - Play CDs or other media automatically + Автоматичне відтворення компакт-дисків та інших носіїв - View basic information about your computer + Перегляд основних відомостей про ваш комп'ютер - Choose how you open links + Виберіть спосіб відкриття посилань - Allow Remote Assistance invitations to be sent from this computer + Дозволити надсилання запрошень до віддаленої допомоги з цього комп'ютера - Task Manager + Диспетчер завдань - Turn flicks on or off + Увімкнення або вимкнення фліків - Add a language + Додати мову - View network status and tasks + Перегляд стану мережі та завдань - Turn Magnifier on or off + Увімкнути або вимкнути лупу - See the name of this computer + Подивитися ім'я цього комп'ютера - View network connections + Перегляд мережевих підключень - Perform recommended maintenance tasks automatically + Автоматичне виконання рекомендованих завдань з технічного обслуговування - Manage disk space used by your offline files + Керування дисковим простором, що використовується офлайн-файлами - Turn High Contrast on or off + Увімкнути або вимкнути високу контрастність - Change the way time is displayed + Змінити спосіб відображення часу - Change how web pages are displayed in tabs + Зміна способу відображення веб-сторінок у вкладках - Change the way dates and lists are displayed + Зміна способу відображення дат і списків - Manage audio devices + Керування аудіопристроями - Change security settings + Зміна налаштувань безпеки - Check security status + Перевірити стан безпеки - Delete cookies or temporary files + Видалення файлів cookie або тимчасових файлів - Specify which hand you write with + Вкажіть, якою рукою ви пишете - Change touch input settings + Зміна налаштувань сенсорного введення - How to change the size of virtual memory + Як змінити розмір віртуальної пам'яті - Hear text read aloud with Narrator + Послухайте текст, прочитаний вголос диктором - Set up USB game controllers + Налаштування ігрових контролерів USB - Show which domain your computer is on + Показати, в якому домені знаходиться ваш комп'ютер - View all problem reports + Переглянути всі звіти про проблеми - 16-Bit Application Support + Підтримка 16-бітних додатків - Set up dialling rules + Налаштуйте правила набору - Enable or disable session cookies + Увімкнути або вимкнути сесійні файли cookie - Give administrative rights to a domain user + Надання адміністративних прав користувачеві домену - Choose when to turn off display + Виберіть, коли вимкнути дисплей - Move the pointer with the keypad using MouseKeys + Переміщення вказівника за допомогою клавіатури через MouseKeys - Change Windows SideShow-compatible device settings + Зміна налаштувань пристрою, сумісного з Windows SideShow - Adjust commonly used mobility settings + Налаштування часто використовуваних параметрів мобільності - Change text-to-speech settings + Зміна налаштувань перетворення тексту в мовлення - Set the time and date + Встановлення часу та дати - Change location settings + Змінити налаштування місцезнаходження - Change mouse settings + Зміна налаштувань миші - Manage Storage Spaces + Керування простором для зберігання - Show or hide file extensions + Показати або приховати розширення файлів - Allow an app through Windows Firewall + Дозволити програму через брандмауер Windows - Change system sounds + Змінити системні звуки - Adjust ClearType text + Налаштування тексту ClearType - Turn screen saver on or off + Увімкнення або вимкнення режиму енергозбереження - Find and fix windows update problems + Знайти та виправити проблеми з оновленням Windows - Change Bluetooth settings + Змінити налаштування Bluetooth - Connect to a network + Підключення до мережі - Change the search provider in Internet Explorer + Зміна пошукового провайдера в Internet Explorer - Join a domain + Приєднатися до домену - Add a device + Додати пристрій - Find and fix problems with Windows Search + Виявлення та усунення проблем із пошуком Windows - Choose a power plan + Оберіть план живлення - Change how the mouse pointer looks when it’s moving + Змінити вигляд вказівника миші під час руху - Uninstall a program + Видалення програми - Create and format hard disk partitions + Створення та форматування розділів жорсткого диска - Change date, time or number formats + Зміна форматів дати, часу або чисел - Change PC wake-up settings + Зміна налаштувань пробудження комп'ютера - Manage network passwords + Керування мережевими паролями - Change input methods + Змінити методи введення - Manage advanced sharing settings + Керування розширеними налаштуваннями спільного доступу - Change battery settings + Зміна налаштувань акумулятора - Rename this computer + Перейменувати цей комп'ютер - Lock or unlock the taskbar + Блокування або розблокування панелі завдань - Manage Web Credentials + Керування веб-обліковими даними - Change the time zone + Зміна часового поясу - Start speech recognition + Запустити розпізнавання мови - View installed updates + Перегляд встановлених оновлень - What's happened to the Quick Launch toolbar? + Що сталося з панеллю швидкого запуску? - Change search options for files and folders + Зміна параметрів пошуку файлів і папок - Adjust settings before giving a presentation + Налаштування параметрів перед проведенням презентації - Scan a document or picture + Сканувати документ або зображення - Change the way measurements are displayed + Змінити спосіб відображення вимірювань - Press key combinations one at a time + Натискайте комбінації клавіш по черзі - Restore data, files or computer from backup (Windows 7) + Відновлення даних, файлів або комп'ютера з резервної копії (Windows 7) - Set your default programs + Налаштування програм за замовчуванням - Set up a broadband connection + Налаштування широкосмугового з'єднання - Calibrate the screen for pen or touch input + Калібрування екрана для введення пером або сенсорного керування - Manage user certificates + Керування сертифікатами користувачів - Schedule tasks + Планування завдань - Ignore repeated keystrokes using FilterKeys + Ігнорувати повторні натискання клавіш за допомогою FilterKeys - Find and fix bluescreen problems + Пошук та усунення проблем із синім екраном - Hear a tone when keys are pressed + Чути тон під час натискання клавіш - Delete browsing history + Видалити історію переглядів - Change what the power buttons do + Змінити призначення кнопок увімкнення - Create standard user account + Створення стандартного облікового запису користувача - Take speech tutorials + Візьміть уроки мовлення - View system resource usage in Task Manager + Перегляд використання системних ресурсів у диспетчері завдань - Create an account + Створити обліковий запис - Get more features with a new edition of Windows + Отримайте більше можливостей у новій версії Windows - Control Panel + Панель керування TaskLink - Unknown + Невідомо \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.vi-VN.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.vi-VN.resx new file mode 100644 index 000000000..851470744 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.vi-VN.resx @@ -0,0 +1,2515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Giới thiệu + Area System + + + truy cập.cpl + File name, Should not translated + + + Tùy chọn khả năng tiếp cận + Area Control Panel (legacy settings) + + + Ứng dụng phụ kiện + Area Privacy + + + Truy cập cơ quan hoặc trường học + Area UserAccounts + + + Thông tin tài khoản + Area Privacy + + + Tài khoản + Area SurfaceHub + + + Trung tâm thông báo + Area Control Panel (legacy settings) + + + Kích hoạt + Area UpdateAndSecurity + + + Lịch sử hoạt động + Area Privacy + + + Thêm phần cứng + Area Control Panel (legacy settings) + + + Thêm chương trình xóa + Area Control Panel (legacy settings) + + + Thêm điện thoại của bạn + Area Phone + + + Các công cụ quản trị + Area System + + + Cài đặt hiển thị nâng cao + Area System, only available on devices that support advanced display options + + + Đồ họa nâng cao + + + ID quảng cáo + Area Privacy, Deprecated in Windows 10, version 1809 and later + + + Chế độ trên máy bay + Area NetworkAndInternet + + + Alt-Tab thay thế cho phép tìm kiếm thông qua các cửa sổ của bạn. + Means the key combination "Tabulator+Alt" on the keyboard + + + Tên khác + + + Hình ảnh động + + + Màu ứng dụng + + + Chẩn đoán DM + Area Privacy + + + Tính năng ứng dụng + Area Apps + + + Ứng dụng + Short/modern name for application + + + Ứng dụng và tính năng + Area Apps + + + Thiết lập hệ thống + Type of the setting is a "Modern Windows settings". We use the same term as used in start menu search at the moment. + + + Ứng dụng cho trang web + Area Apps + + + Âm lượng của từng ứng dụng và tùy chọn thiết bị + Area System, Added in Windows 10, version 1903 + + + appwiz.cpl + File name, Should not translated + + + Khu vực + Mean the settings area or settings category + + + Tài khoản + + + Các công cụ quản trị + Area Control Panel (legacy settings) + + + Ngoại hình và cá nhân hóa + + + Ứng dụng + + + Đồng hồ và khu vực + + + Bảng điều khiển + + + Cortana + + + Thiết bị + + + Truy cập nhanh + + + Phụ trợ + + + Trò chơi + + + Phần cứng và âm thanh + + + Trang chủ + + + Thực tế hỗn hợp + + + Mạng và Internet + + + Cá nhân hóa + + + Điện thoại + + + Quyền riêng tư + + + Chương trình + + + SurfaceHub + + + Hệ thống + + + Hệ thống và bảo mật + + + Thời gian và ngôn ngữ + + + Cập nhật và bảo mật + + + Tài khoản người sử dụng + + + Quyền truy cập được chỉ định + + + Âm thanh + Area EaseOfAccess + + + Cảnh báo âm thanh + + + Âm thanh và lời nói + Area MixedReality, only available if the Mixed Reality Portal app is installed. + + + Tải tập tin tự động + Area Privacy + + + Tự động phát + Area Device + + + Nền + Area Personalization + + + Ứng dụng nền + Area Privacy + + + Sao lưu + Area UpdateAndSecurity + + + Sao lưu và Khôi phục + Area Control Panel (legacy settings) + + + Trình tiết kiệm pin + Area System, only available on devices that have a battery, such as a tablet + + + Cài đặt tiết kiệm pin + Area System, only available on devices that have a battery, such as a tablet + + + Chi tiết sử dụng trình tiết kiệm pin + + + Sử dụng Pin + Area System, only available on devices that have a battery, such as a tablet + + + Thiết bị sinh trắc học + Area Control Panel (legacy settings) + + + Mã hóa ổ đĩa BitLocker + Area Control Panel (legacy settings) + + + Xanh dương (sáng) + + + Bluetooth + Area Device + + + Thiết bị Bluetooth + Area Control Panel (legacy settings) + + + Xanh lam – vàng + + + Bopomofo IME + Area TimeAndLanguage + + + bpmf + Should not translated + + + Đang phát sóng + Area Gaming + + + Lịch + Area Privacy + + + Lịch sử cuộc gọi + Area Privacy + + + đang gọi + + + Máy ảnh + Area Privacy + + + Cangjie IME + Area TimeAndLanguage + + + Caps Lock + Mean the "Caps Lock" key + + + Di động và SIM + Area NetworkAndInternet + + + Chọn thư mục nào xuất hiện trên Bắt đầu + Area Personalization + + + Dịch vụ khách hàng cho NetWare + Area Control Panel (legacy settings) + + + Nhận văn bản từ bảng nhớ tạm. + Area System + + + Phụ đề chi tiết + Area EaseOfAccess + + + Bộ lọc màu + Area EaseOfAccess + + + Quản lý màu sắc + Area Control Panel (legacy settings) + + + Màu + Area Personalization + + + Lệnh + The command to direct start a setting + + + Các thiết bị đã kết nối + Area Device + + + Danh bạ + Area Privacy + + + Bảng điều khiển + Type of the setting is a "(legacy) Control Panel setting" + + + Sao chép lệnh + + + Cách ly lõi + Means the protection of the system core + + + Cortana + Area Cortana + + + Cortana trên các thiết bị của tôi + Area Cortana + + + Cortana - Ngôn ngữ + Area Cortana + + + Người quản lý thông tin xác thực + Area Control Panel (legacy settings) + + + Thiết bị chéo + + + Thiết bị tùy chỉnh + + + Màu tối + + + Chế độ tối + + + Sử dụng dữ liệu + Area NetworkAndInternet + + + Ngày và giờ + Area TimeAndLanguage + + + Ứng dụng mặc định + Area Apps + + + Camera mặc định + Area Device + + + Vị trí mặc định + Area Control Panel (legacy settings) + + + Chương trình mặc định + Area Control Panel (legacy settings) + + + Vị trí mặc định + Area System + + + Delivery Optimization + Area UpdateAndSecurity + + + desk.cpl + File name, Should not translated + + + Desktop themes + Area Control Panel (legacy settings) + + + Mù màu Deuteranopia + Medical: Mean you don't can see red colors + + + Quản lý thiết bị + Area Control Panel (legacy settings) + + + Devices and printers + Area Control Panel (legacy settings) + + + DHCP + Should not translated + + + Dial-up + Area NetworkAndInternet + + + Truy cập thẳng + Area NetworkAndInternet, only available if DirectAccess is enabled + + + Direct open your phone + Area EaseOfAccess + + + Hiển thị + Area EaseOfAccess + + + Hiển thị thuộc tính + Area Control Panel (legacy settings) + + + DNS + Should not translated + + + Tài liệu + Area Privacy + + + Duplicating my display + Area System + + + During these hours + Area System + + + Ease of access center + Area Control Panel (legacy settings) + + + Bản in + Means the "Windows Edition" + + + Email + Area Privacy + + + Email and app accounts + Area UserAccounts + + + Mã hóa + Area System + + + Môi trường + Area MixedReality, only available if the Mixed Reality Portal app is installed. + + + Ethernet + Area NetworkAndInternet + + + Exploit Protection + + + Phụ trợ + Area Extra, , only used for setting of 3rd-Party tools + + + Eye control + Area EaseOfAccess + + + Eye tracker + Area Privacy, requires eyetracker hardware + + + Family and other people + Area UserAccounts + + + Feedback and diagnostics + Area Privacy + + + Tệp tin hệ thống + Area Privacy + + + FindFast + Area Control Panel (legacy settings) + + + findfast.cpl + File name, Should not translated + + + Tìm thiết bị + Area UpdateAndSecurity + + + Tường lửa + + + Focus assist - Quiet hours + Area System + + + Focus assist - Quiet moments + Area System + + + Tùy chọn thư mục + Area Control Panel (legacy settings) + + + Phông chữ + Area EaseOfAccess + + + Dành cho nhà phát triển + Area UpdateAndSecurity + + + Game bar + Area Gaming + + + Trình điều khiển trò chơi + Area Control Panel (legacy settings) + + + Game DVR + Area Gaming + + + Chế độ trò chơi + Area Gaming + + + Cổng + Should not translated + + + Tổng quan + Area Privacy + + + Get programs + Area Control Panel (legacy settings) + + + Bắt đầu + Area Control Panel (legacy settings) + + + Ánh Nhìn + Area Personalization, Deprecated in Windows 10, version 1809 and later + + + Cài đặt đồ họa + Area System + + + Thang độ xám + + + Green week + Mean you don't can see green colors + + + Headset display + Area MixedReality, only available if the Mixed Reality Portal app is installed. + + + Độ tương phản cao + Area EaseOfAccess + + + Holographic audio + + + Holographic Environment + + + Holographic Headset + + + Holographic Management + + + Home group + Area Control Panel (legacy settings) + + + Mã ID + MEans The "Windows Identifier" + + + Hình ảnh + + + Tùy chọn lập chỉ mục + Area Control Panel (legacy settings) + + + inetcpl.cpl + File name, Should not translated + + + Hồng ngoại + Area Control Panel (legacy settings) + + + Inking and typing + Area Privacy + + + Tùy chọn Internet + Area Control Panel (legacy settings) + + + intl.cpl + File name, Should not translated + + + Đảo ngược màu + + + IP + Should not translated + + + Isolated Browsing + + + Japan IME settings + Area TimeAndLanguage, available if the Microsoft Japan input method editor is installed + + + joy.cpl + File name, Should not translated + + + Joystick properties + Area Control Panel (legacy settings) + + + jpnime + Should not translated + + + Bàn phím + Area EaseOfAccess + + + Bàn phím số + + + Keys + + + Ngôn ngữ + Area TimeAndLanguage + + + Màu sắc đèn + + + Chế độ sáng + + + Vị trí + Area Privacy + + + Khoá màn hình + Area Personalization + + + Phóng + Area EaseOfAccess + + + Mail - Microsoft Exchange or Windows Messaging + Area Control Panel (legacy settings) + + + main.cpl + File name, Should not translated + + + Manage known networks + Area NetworkAndInternet + + + Manage optional features + Area Apps + + + Tin nhắn + Area Privacy + + + Metered connection + + + Micro điện thoại + Area Privacy + + + Microsoft Mail Post Office + Area Control Panel (legacy settings) + + + mlcfg32.cpl + File name, Should not translated + + + mmsys.cpl + File name, Should not translated + + + Thiết bị di động + + + Điểm phát sóng di động + Area NetworkAndInternet + + + modem.cpl + File name, Should not translated + + + Đơn âm + + + Xem chi tiết + Area Cortana + + + Chuyển động + Area Privacy + + + Chuột + Area EaseOfAccess + + + Mouse and touchpad + Area Device + + + Mouse, Fonts, Keyboard, and Printers properties + Area Control Panel (legacy settings) + + + Con trỏ chuột + Area EaseOfAccess + + + Multimedia properties + Area Control Panel (legacy settings) + + + Đa nhiệm + Area System + + + Người dẫn chuyện + Area EaseOfAccess + + + Thanh điều hướng + Area Personalization + + + netcpl.cpl + File name, Should not translated + + + netsetup.cpl + File name, Should not translated + + + Mạng + Area NetworkAndInternet + + + Network and sharing center + Area Control Panel (legacy settings) + + + Kết nối mạng + Area Control Panel (legacy settings) + + + Network properties + Area Control Panel (legacy settings) + + + Network Setup Wizard + Area Control Panel (legacy settings) + + + Trạng thái mạng + Area NetworkAndInternet + + + NFC + Area NetworkAndInternet + + + NFC Transactions + "NFC should not translated" + + + Chế độ đêm + + + Night light settings + Area System + + + Ghi chú + + + Chỉ khả dụng khi bạn đã kết nối thiết bị di động với thiết bị của mình. + + + Chỉ khả dụng trên các thiết bị hỗ trợ tùy chọn đồ họa nâng cao. + + + Chỉ khả dụng trên các thiết bị có pin, chẳng hạn như máy tính bảng. + + + Không được dùng nữa trong Windows 10, phiên bản 1809 (bản dựng 17763) trở lên. + + + Only available if Dial is paired. + + + Only available if DirectAccess is enabled. + + + Chỉ khả dụng trên các thiết bị hỗ trợ tùy chọn đồ họa nâng cao. + + + Only present if user is enrolled in WIP. + + + Requires eyetracker hardware. + + + Available if the Microsoft Japan input method editor is installed. + + + Available if the Microsoft Pinyin input method editor is installed. + + + Available if the Microsoft Wubi input method editor is installed. + + + Only available if the Mixed Reality Portal app is installed. + + + Only available on mobile and if the enterprise has deployed a provisioning package. + + + Added in Windows 10, version 1903 (build 18362). + + + Added in Windows 10, version 2004 (build 19041). + + + Only available if "settings apps" are installed, for example, by a 3rd party. + + + Only available if touchpad hardware is present. + + + Only available if the device has a Wi-Fi adapter. + + + Device must be Windows Anywhere-capable. + + + Only available if enterprise has deployed a provisioning package. + + + Thông báo + Area Privacy + + + Hành động thông báo + Area System + + + Num Lock + Mean the "Num Lock" key + + + nwc.cpl + File name, Should not translated + + + odbccp32.cpl + File name, Should not translated + + + ODBC Data Source Administrator (32-bit) + Area Control Panel (legacy settings) + + + ODBC Data Source Administrator (64-bit) + Area Control Panel (legacy settings) + + + Offline files + Area Control Panel (legacy settings) + + + Bàn đồ ngoại tuyến + Area Apps + + + Offline Maps - Download maps + Area Apps + + + On-Screen + + + Hệ điều hành + Means the "Operating System" + + + thiết bị khác + Area Privacy + + + Tùy chọn khác + Area EaseOfAccess + + + Người dùng khác + + + Kiểm soát phụ huynh + Area Control Panel (legacy settings) + + + Mật khẩu + + + password.cpl + File name, Should not translated + + + Password properties + Area Control Panel (legacy settings) + + + Pen and input devices + Area Control Panel (legacy settings) + + + Pen and touch + Area Control Panel (legacy settings) + + + Pen and Windows Ink + Area Device + + + Ghép đôi người xung quanh + Area Control Panel (legacy settings) + + + Performance information and tools + Area Control Panel (legacy settings) + + + Permissions and history + Area Cortana + + + Personalization (category) + Area Personalization + + + Điện thoại + Area Phone + + + Phone and modem + Area Control Panel (legacy settings) + + + Phone and modem - Options + Area Control Panel (legacy settings) + + + Phone calls + Area Privacy + + + Phone - Default apps + Area System + + + Picture + + + Hình ảnh + Area Privacy + + + Pinyin IME settings + Area TimeAndLanguage, available if the Microsoft Pinyin input method editor is installed + + + Pinyin IME settings - domain lexicon + Area TimeAndLanguage + + + Pinyin IME settings - Key configuration + Area TimeAndLanguage + + + Pinyin IME settings - UDP + Area TimeAndLanguage + + + Playing a game full screen + Area Gaming + + + Plugin to search for Windows settings + + + Windows Settings + + + Power and sleep + Area System + + + powercfg.cpl + File name, Should not translated + + + Tùy chọn chế độ Nguồn + Area Control Panel (legacy settings) + + + Trình bày + + + Máy in + Area Control Panel (legacy settings) + + + Printers and scanners + Area Device + + + chụp màn hình + Mean the "Print screen" key + + + Problem reports and solutions + Area Control Panel (legacy settings) + + + Vi xử lý + + + Programs and features + Area Control Panel (legacy settings) + + + Projecting to this PC + Area System + + + Mù màu Protanopia + Medical: Mean you don't can see green colors + + + Đang cấp phép + Area UserAccounts, only available if enterprise has deployed a provisioning package + + + Cảm ứng tiệm cận + Area NetworkAndInternet + + + Proxy + Area NetworkAndInternet + + + Quickime + Area TimeAndLanguage + + + Quiet moments game + + + Radio + Area Privacy + + + RAM + Means the Read-Access-Memory (typical the used to inform about the size) + + + Sự công nhận + + + Phục Hồi + Area UpdateAndSecurity + + + Red eye + Mean red eye effect by over-the-night flights + + + Đỏ – xanh lục + Mean the weakness you can't differ between red and green colors + + + Red week + Mean you don't can see red colors + + + Khu vực + Area TimeAndLanguage + + + Khu vực, ngôn ngữ + Area TimeAndLanguage + + + Regional settings properties + Area Control Panel (legacy settings) + + + Khu vực, ngôn ngữ + Area Control Panel (legacy settings) + + + Region formatting + + + RemoteApp and desktop connections + Area Control Panel (legacy settings) + + + VNC + Area System + + + Scanners and cameras + Area Control Panel (legacy settings) + + + schedtasks + File name, Should not translated + + + Lịch trình + + + Hoạt động theo lịch trình + Area Control Panel (legacy settings) + + + Xoay màn hình + Area System + + + Thanh cuộn + + + Khóa cuộn + Mean the "Scroll Lock" key + + + SDNS + Should not translated + + + Searching Windows + Area Cortana + + + SecureDNS + Should not translated + + + Security Center + Area Control Panel (legacy settings) + + + Security Processor + + + Session cleanup + Area SurfaceHub + + + Settings home page + Area Home, Overview-page for all areas of settings + + + Set up a kiosk + Area UserAccounts + + + Shared experiences + Area System + + + Shortcuts + + + wifi + dont translate this, is a short term to find entries + + + Sign-in options + Area UserAccounts + + + Sign-in options - Dynamic lock + Area UserAccounts + + + Kích thước + Size for text and symbols + + + Âm thanh + Area System + + + Nói + + Area EaseOfAccess + + + Nhận dạng tiếng nói + Area Control Panel (legacy settings) + + + Speech typing + + + Start + Area Personalization + + + Start places + + + Startup apps + Area Apps + + + sticpl.cpl + File name, Should not translated + + + Storage + Area System + + + Storage policies + Area System + + + Storage Sense + Area System + + + in + Example: Area "System" in System settings + + + Sync center + Area Control Panel (legacy settings) + + + Sync your settings + Area UserAccounts + + + sysdm.cpl + File name, Should not translated + + + Hệ thống + Area Control Panel (legacy settings) + + + System properties and Add New Hardware wizard + Area Control Panel (legacy settings) + + + Tab + Means the key "Tabulator" on the keyboard + + + Tablet mode + Area System + + + Tablet PC settings + Area Control Panel (legacy settings) + + + Talk + + + Talk to Cortana + Area Cortana + + + Thanh tác vụ + Area Personalization + + + Taskbar color + + + Công việc + Area Privacy + + + Web Conferencing + Area SurfaceHub + + + Team device management + Area SurfaceHub + + + Văn bản thành giọng nói + Area Control Panel (legacy settings) + + + Giao diện + Area Personalization + + + themes.cpl + File name, Should not translated + + + timedate.cpl + File name, Should not translated + + + Dòng thời gian + + + Chạm + + + Phản hồi khi chạm + + + Touchpad + Area Device + + + Minh bạch + + + Mù màu lam + Medical: Mean you don't can see yellow and blue colors + + + Khắc phục sự cố + Area UpdateAndSecurity + + + TruePlay + Area Gaming + + + Đang nhập + Area Device + + + Gỡ cài đặt + Area MixedReality, only available if the Mixed Reality Portal app is installed. + + + USB + Area Device + + + Tài khoản người sử dụng + Area Control Panel (legacy settings) + + + Phiên bản + Means The "Windows Version" + + + Phát lại video + Area Apps + + + Video + Area Privacy + + + Máy ảo + + + Virus + Means the virus in computers and software + + + giọng nói "gây nghiện" + Area Privacy + + + Âm lượng + + + VPN + Area NetworkAndInternet + + + Hình nền + + + Warmer color + + + Welcome center + Area Control Panel (legacy settings) + + + Màn hình chào mừng + Area SurfaceHub + + + wgpocpl.cpl + File name, Should not translated + + + Bánh xe + Area Device + + + Wifi + Area NetworkAndInternet, only available if Wi-Fi calling is enabled + + + Gọi qua Wi-Fi + Area NetworkAndInternet, only available if Wi-Fi calling is enabled + + + Cài đặt WiFi + "Wi-Fi" should not translated + + + chế độ cửa sổ + + + Windows Anytime Upgrade + Area Control Panel (legacy settings) + + + Windows Anywhere + Area UserAccounts, device must be Windows Anywhere-capable + + + Windows CardSpace + Area Control Panel (legacy settings) + + + Windows Defender + Area Control Panel (legacy settings) + + + Windows Firewall + Area Control Panel (legacy settings) + + + Windows Hello setup - Face + Area UserAccounts + + + Windows Hello setup - Fingerprint + Area UserAccounts + + + Windows Insider Program + Area UpdateAndSecurity + + + Windows Mobility Center + Area Control Panel (legacy settings) + + + Windows search + Area Cortana + + + Windows Security + Area UpdateAndSecurity + + + Windows Update + Area UpdateAndSecurity + + + Windows Update - Advanced options + Area UpdateAndSecurity + + + Windows Update - Check for updates + Area UpdateAndSecurity + + + Windows Update - Restart options + Area UpdateAndSecurity + + + Windows Update - View optional updates + Area UpdateAndSecurity + + + Windows Update - View update history + Area UpdateAndSecurity + + + Wireless + + + Workplace + + + Workplace provisioning + Area UserAccounts + + + Wubi IME settings + Area TimeAndLanguage, available if the Microsoft Wubi input method editor is installed + + + Wubi IME settings - UDP + Area TimeAndLanguage + + + Xbox Networking + Area Gaming + + + Your info + Area UserAccounts + + + Zoom + Mean zooming of things via a magnifier + + + Change device installation settings + + + Turn off background images + + + Navigation properties + + + Media streaming options + + + Make a file type always open in a specific program + + + Change the Narrator’s voice + + + Find and fix keyboard problems + + + Use screen reader + + + Show which workgroup this computer is on + + + Change mouse wheel settings + + + Manage computer certificates + + + Find and fix problems + + + Change settings for content received using Tap and send + + + Change default settings for media or devices + + + Print the speech reference card + + + Calibrate display colour + + + Manage file encryption certificates + + + View recent messages about your computer + + + Give other users access to this computer + + + Show hidden files and folders + + + Change Windows To Go start-up options + + + See which processes start up automatically when you start Windows + + + Tell if an RSS feed is available on a website + + + Add clocks for different time zones + + + Add a Bluetooth device + + + Customise the mouse buttons + + + Set tablet buttons to perform certain tasks + + + View installed fonts + + + Change the way currency is displayed + + + Edit group policy + + + Manage browser add-ons + + + Check processor speed + + + Check firewall status + + + Send or receive a file + + + Add or remove user accounts + + + Edit the system environment variables + + + Manage BitLocker + + + Auto-hide the taskbar + + + Change sound card settings + + + Make changes to accounts + + + Edit local users and groups + + + View network computers and devices + + + Install a program from the network + + + View scanners and cameras + + + Microsoft IME Register Word (Japanese) + + + Restore your files with File History + + + Turn On-Screen keyboard on or off + + + Block or allow third-party cookies + + + Find and fix audio recording problems + + + Create a recovery drive + + + Microsoft New Phonetic Settings + + + Generate a system health report + + + Fix problems with your computer + + + Back up and Restore (Windows 7) + + + Preview, delete, show or hide fonts + + + Microsoft Quick Settings + + + View reliability history + + + Access RemoteApp and desktops + + + Set up ODBC data sources + + + Reset Security Policies + + + Block or allow pop-ups + + + Turn autocomplete in Internet Explorer on or off + + + Microsoft Pinyin SimpleFast Options + + + Change what closing the lid does + + + Turn off unnecessary animations + + + Create a restore point + + + Turn off automatic window arrangement + + + Troubleshooting History + + + Diagnose your computer's memory problems + + + View recommended actions to keep Windows running smoothly + + + Change cursor blink rate + + + Add or remove programs + + + Create a password reset disk + + + Configure advanced user profile properties + + + Start or stop using AutoPlay for all media and devices + + + Change Automatic Maintenance settings + + + Specify single- or double-click to open + + + Select users who can use remote desktop + + + Show which programs are installed on your computer + + + Allow remote access to your computer + + + View advanced system settings + + + How to install a program + + + Change how your keyboard works + + + Automatically adjust for daylight saving time + + + Change the order of Windows SideShow gadgets + + + Check keyboard status + + + Control the computer without the mouse or keyboard + + + Change or remove a program + + + Change multi-touch gesture settings + + + Set up ODBC data sources (64-bit) + + + Configure proxy server + + + Change your homepage + + + Group similar windows on the taskbar + + + Change Windows SideShow settings + + + Use audio description for video + + + Change workgroup name + + + Find and fix printing problems + + + Change when the computer sleeps + + + Set up a virtual private network (VPN) connection + + + Accommodate learning abilities + + + Set up a dial-up connection + + + Set up a connection or network + + + How to change your Windows password + + + Make it easier to see the mouse pointer + + + Set up iSCSI initiator + + + Accommodate low vision + + + Manage offline files + + + Review your computer's status and resolve issues + + + Microsoft ChangJie Settings + + + Replace sounds with visual cues + + + Change temporary Internet file settings + + + Connect to the Internet + + + Find and fix audio playback problems + + + Change the mouse pointer display or speed + + + Back up your recovery key + + + Save backup copies of your files with File History + + + View current accessibility settings + + + Change tablet pen settings + + + Change how your mouse works + + + Show how much RAM is on this computer + + + Edit power plan + + + Adjust system volume + + + Defragment and optimise your drives + + + Set up ODBC data sources (32-bit) + + + Change Font Settings + + + Magnify portions of the screen using Magnifier + + + Change the file type associated with a file extension + + + View event logs + + + Manage Windows Credentials + + + Set up a microphone + + + Change how the mouse pointer looks + + + Change power-saving settings + + + Optimise for blindness + + + + + + + Turn Windows features on or off + + + Show which operating system your computer is running + + + View local services + + + Manage Work Folders + + + Encrypt your offline files + + + Train the computer to recognise your voice + + + Advanced printer setup + + + Change default printer + + + Edit environment variables for your account + + + Optimise visual display + + + Change mouse click settings + + + Change advanced colour management settings for displays, scanners and printers + + + Let Windows suggest Ease of Access settings + + + Clear disk space by deleting unnecessary files + + + View devices and printers + + + Private Character Editor + + + Record steps to reproduce a problem + + + Adjust the appearance and performance of Windows + + + Settings for Microsoft IME (Japanese) + + + Invite someone to connect to your PC and help you, or offer to help someone else + + + Run programs made for previous versions of Windows + + + Choose the order of how your screen rotates + + + Change how Windows searches + + + Set flicks to perform certain tasks + + + Change account type + + + Change screen saver + + + Change User Account Control settings + + + Turn on easy access keys + + + Identify and repair network problems + + + Find and fix networking and connection problems + + + Play CDs or other media automatically + + + View basic information about your computer + + + Choose how you open links + + + Allow Remote Assistance invitations to be sent from this computer + + + Task Manager + + + Turn flicks on or off + + + Add a language + + + View network status and tasks + + + Turn Magnifier on or off + + + See the name of this computer + + + View network connections + + + Perform recommended maintenance tasks automatically + + + Manage disk space used by your offline files + + + Turn High Contrast on or off + + + Change the way time is displayed + + + Change how web pages are displayed in tabs + + + Change the way dates and lists are displayed + + + Manage audio devices + + + Change security settings + + + Check security status + + + Delete cookies or temporary files + + + Specify which hand you write with + + + Change touch input settings + + + How to change the size of virtual memory + + + Hear text read aloud with Narrator + + + Set up USB game controllers + + + Show which domain your computer is on + + + View all problem reports + + + 16-Bit Application Support + + + Set up dialling rules + + + Enable or disable session cookies + + + Give administrative rights to a domain user + + + Choose when to turn off display + + + Move the pointer with the keypad using MouseKeys + + + Change Windows SideShow-compatible device settings + + + Adjust commonly used mobility settings + + + Change text-to-speech settings + + + Set the time and date + + + Change location settings + + + Change mouse settings + + + Manage Storage Spaces + + + Show or hide file extensions + + + Allow an app through Windows Firewall + + + Change system sounds + + + Adjust ClearType text + + + Turn screen saver on or off + + + Find and fix windows update problems + + + Change Bluetooth settings + + + Connect to a network + + + Change the search provider in Internet Explorer + + + Join a domain + + + Add a device + + + Find and fix problems with Windows Search + + + Choose a power plan + + + Change how the mouse pointer looks when it’s moving + + + Uninstall a program + + + Create and format hard disk partitions + + + Change date, time or number formats + + + Change PC wake-up settings + + + Manage network passwords + + + Change input methods + + + Manage advanced sharing settings + + + Change battery settings + + + Rename this computer + + + Lock or unlock the taskbar + + + Manage Web Credentials + + + Change the time zone + + + Start speech recognition + + + View installed updates + + + What's happened to the Quick Launch toolbar? + + + Change search options for files and folders + + + Adjust settings before giving a presentation + + + Scan a document or picture + + + Change the way measurements are displayed + + + Press key combinations one at a time + + + Khôi phục dữ liệu, tập tin hoặc máy tính từ bản sao lưu (Windows 7) + + + Set your default programs + + + Set up a broadband connection + + + Calibrate the screen for pen or touch input + + + Manage user certificates + + + Schedule tasks + + + Ignore repeated keystrokes using FilterKeys + + + Find and fix bluescreen problems + + + Hear a tone when keys are pressed + + + Delete browsing history + + + Change what the power buttons do + + + Create standard user account + + + Take speech tutorials + + + View system resource usage in Task Manager + + + Create an account + + + Get more features with a new edition of Windows + + + Bảng điều khiển + + + TaskLink + + + Unknown + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.zh-cn.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.zh-cn.resx index 16cd7a3f0..2b39d48e4 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.zh-cn.resx +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.zh-cn.resx @@ -1926,13 +1926,13 @@ 微软拼音 SimpleFast 选项 - Change what closing the lid does + 更改盖上盖子时的操作 - Turn off unnecessary animations + 关闭不必要的动画效果 - Create a restore point + 创建还原点 Turn off automatic window arrangement @@ -2007,7 +2007,7 @@ Set up ODBC data sources (64-bit) - Configure proxy server + 设置代理服务器 Change your homepage @@ -2242,7 +2242,7 @@ Allow Remote Assistance invitations to be sent from this computer - Task Manager + 任务管理器 Turn flicks on or off @@ -2356,7 +2356,7 @@ 是否显示文件扩展名 - Allow an app through Windows Firewall + 允许应用程序通过 Windows 防火墙 更改系统声音 diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json b/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json index 630116ae0..dc8bfa22c 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json @@ -4,7 +4,7 @@ "Description": "Search settings inside Control Panel and Settings App", "Name": "Windows Settings", "Author": "TobiasSekan", - "Version": "4.0.5", + "Version": "4.0.7", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.WindowsSettings.dll", diff --git a/README.md b/README.md index f121f2b75..858d39d1c 100644 --- a/README.md +++ b/README.md @@ -16,70 +16,15 @@

-Dedicated to making your workflow flow more seamless. Search everything from applications, files, bookmarks, YouTube, Twitter and more. Flow will continue to evolve, designed to be open and built with the community at heart. +A quick file search and app launcher for Windows with community-made plugins.

+ +

+Dedicated to making your work flow more seamless. Search everything from applications, files, bookmarks, YouTube, Twitter and more. Flow will continue to evolve, designed to be open and built with the community at heart.

Remember to star it, flow will love you more :)

- -## 🎅 New Features🤶 -### Preview Panel - - -- Use the F1 key to open/hide the preview panel. -- Media files will be displayed as large images, otherwise a large icon and entire path will be displayed. -- Turn on preview permanently via Settings (Always Preview). -- Use hotkeys (Ctrl+Plus,Minus / Ctrl+],[) to adjust flow's search window width and height quickly if the preview area is too narrow. -- This feature is currently in its early stages. - -### Everything Plugin Merged Into Explorer - - -- Switch easily between Everything and Windows Search to take advantage of both search engines (remember to remove existing Everything plugin). -- Use features available to both Everything and Explorer plugins - -### Date & Time Display In Search Window - - - -- Display the date and time when the search window is triggered. - -### Drag & Drop - - -- Drag an item to Discord or computer location. -- The target program determines whether the drop is to copy or move the item (can change via CTRL or Alt), and the operation is displayed on the mouse cursor. - -### Custom Shortcut - - - - -- New shortcut functionality to set additional action keywords or search terms. - -### Improved Program Plugin -- PATH is now indexed -- Support for .url files, flow can now search installed steam/epic games. -- Improved UWP indexing. - -### Improved Memory Usage -- Fixed a memory leak and reduced overall memory usage. - -### Improved Plugin / Plugin Store -- Search plugins in the Plugin Store and existing plugin tab. -- Categorised sections in Plugin Store to easily see new and updated plugins. - -### Improved Non-C# Plugin's Panel Design - - -- The design has been adjusted to align to the overall look and feel of flow. -- Simplified the information displayed on buttons - -🚂Full Changelogs - - -

Getting StartedFeatures • @@ -97,15 +42,29 @@ Dedicated to making your workflow flow more seamless. Search everything from app ### Installation -| [Windows 7+ installer](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest/download/Flow-Launcher-Setup.exe) | [Portable](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest/download/Flow-Launcher-Portable.zip) | -| :----------------------------------------------------------: | :----------------------------------------------------------: | +[Windows 7+ Installer](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest/download/Flow-Launcher-Setup.exe) or [Portable Version](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest/download/Flow-Launcher-Portable.zip) -| `winget install "Flow Launcher"` | `scoop install Flow-Launcher` | `choco install Flow-Launcher` | -| :------------------------------: | :------------------------------: | :------------------------------: | +#### Winget + +``` +winget install "Flow Launcher" +``` + +#### Scoop + +``` +scoop install Flow-Launcher +``` + +#### Chocolatey + +``` +choco install Flow-Launcher +``` > When installing for the first time Windows may raise an issue about security due to code not being signed, if you downloaded from this repo then you are good to continue the set up. -And you can download early access version. +Or download the [early access version](https://github.com/Flow-Launcher/Prereleases/releases). @@ -115,12 +74,11 @@ And you can download - - Search for apps, files or file contents. +- Supports Everything and Windows Index. - - Support search using environment variable paths. ### Web Searches & URLs @@ -129,8 +87,6 @@ And you can download - - ### Browser Bookmarks @@ -140,7 +96,7 @@ And you can download - Provides system related commands. shutdown, lock, settings, etc. -- System command list +- [System command list](#system-command-list) ### Calculator @@ -152,9 +108,8 @@ And you can download - - Run batch and PowerShell commands as Administrator or a different user. -- Ctrl+Enter to Run as Administrator. +- Ctrl+Shift+Enter to Run as Administrator. ### Explorer @@ -162,19 +117,35 @@ And you can download + +- Drag a file/folder to File Exlporer, or even Discord. +- Copy/move behavior can be change via Ctrl or Shift, and the operation is displayed on the mouse cursor. + ### Windows & Control Panel Settings - Search for Windows & Control Panel settings. - ### Priority - Prioritise the order of each plugin's results. +### Preview Panel + + + +- Use F1 to toggle the preview panel. +- Media files will be displayed as large images, otherwise a large icon and full path will be displayed. +- Turn on preview permanently via Settings (Always Preview). +- Use Ctrl++/- and Ctrl+[/] to adjust search window width and height quickly if the preview area is too narrow. + + ### Customizations @@ -186,12 +157,48 @@ And you can download + +- Display date and time in search window. + +### 💬 Languages - Supports languages from Chinese to Italian and more. -- Supports Pinyin search. +- Supports Pinyin (拼音) search. - [Crowdin](https://crowdin.com/project/flow-launcher) support for language translations. +
+Supported languages +
    +
  • English
  • +
  • 中文
  • +
  • 中文(繁体)
  • +
  • Українська
  • +
  • Русский
  • +
  • Français
  • +
  • 日本語
  • +
  • Dutch
  • +
  • Polski
  • +
  • Dansk
  • +
  • de, Deutsch
  • +
  • ko, 한국어
  • +
  • Srpski
  • +
  • Português
  • +
  • Português (Brasil)
  • +
  • Spanish
  • +
  • es-419, Spanish (Latin America)
  • +
  • Italiano
  • +
  • Norsk Bokmål
  • +
  • Slovenčina
  • +
  • Türkçe
  • +
  • čeština
  • +
  • اللغة العربية
  • +
  • Tiếng Việt
  • +
+
+ ### Portable - Fully portable. @@ -205,7 +212,8 @@ And you can download
- Pause hotkey activation when you are playing games. -- When in search window use Ctrl+F12 to toggle on/off. +- When in search window use Ctrl+F12 to toggle on/off. +- Type `Toggle Game Mode` @@ -222,92 +230,98 @@ And you can download

-### SpotifyPremium +### [SpotifyPremium](https://github.com/fow5040/Flow.Launcher.Plugin.SpotifyPremium) + +### [Steam Search](https://github.com/Garulf/Steam-Search) -### Steam Search +### [Clipboard History](https://github.com/liberize/Flow.Launcher.Plugin.ClipboardHistory) -### Clipboard History -### Home Assistant Commander +### [Home Assistant Commander](https://github.com/Garulf/HA-Commander) + -### Colors +### [Colors](https://github.com/Flow-Launcher/Flow.Launcher.Plugin.Color) -### GitHub +### [GitHub](https://github.com/JohnTheGr8/Flow.Plugin.Github) + -### Window Walker +### [Window Walker](https://github.com/taooceros/Flow.Plugin.WindowWalker) + -......and more! +......and [more!](https://flowlauncher.com/docs/#/plugins) ### 🛒 Plugin Store - + - You can view the full plugin list or quickly install a plugin via the Plugin Store menu inside Settings - or type `pm` `install`/`uninstall`/`update` + the plugin name in the search window, - ## ⌨️ Hotkeys -| Hotkey | Description | -| ------------------------------------------------------------ | -------------------------------------------- | -| Alt+ Space | Open search window (default and configurable)| -| Enter | Execute | -| Ctrl+Shift+Enter | Run as admin | -| | Scroll up & down | -| | Back to result / Open Context Menu | -| Ctrl +O , Shift +Enter | Open Context Menu | -| Tab | Autocomplete | -| F1 | Toggle Preview Panel (default and configurable)| -| Esc | Back to results / hide search window | -| Ctrl +C | Copy the actual folder / file | -| Ctrl +I | Open flow's settings | -| Ctrl +R | Run the current query again (refresh results)| -| F5 | Reload all plugin data | -| Ctrl + F12 | Toggle Game Mode when in search window | -| Ctrl + +,- | Quickly change maximum results shown | -| Ctrl + [,] | Quickly change search window width | -| Ctrl + H | Open search history | -| Ctrl + Backspace | Back to previous directory | - +| Hotkey | Description | +| ------------------------------------------------------------------------- | ----------------------------------------------- | +| Alt+Space | Open search window (default and configurable) | +| Enter | Execute | +| Ctrl+Enter | Open containing folder | +| Ctrl+Shift+Enter | Run as admin | +| /, Shift+Tab/Tab | Previous / Next result | +| / | Back to result / Open Context Menu | +| Ctrl+O , Shift+Enter | Open Context Menu | +| Ctrl+Tab | Autocomplete | +| F1 | Toggle Preview Panel (default and configurable) | +| Esc | Back to results / hide search window | +| Ctrl+C | Copy folder / file | +| Ctrl+Shift+C | Copy folder / file path | +| Ctrl+I | Open Flow's settings | +| Ctrl+R | Run the current query again (refresh results) | +| F5 | Reload all plugin data | +| Ctrl+F12 | Toggle Game Mode when in search window | +| Ctrl++,- | Adjust maximum results shown | +| Ctrl+[,] | Adjust search window width | +| Ctrl+H | Open search history | +| Ctrl+Backspace | Back to previous directory | +| PageUp/PageDown | Previous / Next Page | ## System Command List -| Command | Description | -| ---------------------- | ------------------------------------------------------------ | -| Shutdown | Shutdown computer | -| Restart | Restart computer | -| Restart with advance | Restart the computer with Advanced Boot option for safe and debugging modes | -| Log off | Log off | -| Lock | Lock computer | -| Sleep | Put computer to sleep | -| Hibernate | Hibernate computer | -| Empty Recycle Bin | Empty recycle bin | -| Open Recycle Bin | Open recycle bin | -| Exit | Close Flow Launcher | -| Save Settings | Save all Flow Launcher settings | -| Restart Flow Launcher | Restart Flow Launcher | -| Settings | Tweak this app | -| Reload Plugin Data | Refreshes plugin data with new content | -| Check For Update | Check for new Flow Launcher update | -| Open Log Location | Open Flow Launcher's log location | -| Index Option | Open Windows Search Index window | -| Flow Launcher Tip | Visit Flow Launcher's documentation for more help and how to use tips | -| Flow Launcher UserData | Open the location where Flow Launcher's settings are stored | +| Command | Description | +| ---------------------------------- | --------------------------------------------------------------------------- | +| Shutdown | Shutdown computer | +| Restart | Restart computer | +| Restart With Advanced Boot Options | Restart the computer with Advanced Boot option for safe and debugging modes | +| Log Off/Sign Out | Log off | +| Lock | Lock computer | +| Sleep | Put computer to sleep | +| Hibernate | Hibernate computer | +| Empty Recycle Bin | Empty recycle bin | +| Open Recycle Bin | Open recycle bin | +| Exit | Close Flow Launcher | +| Save Settings | Save all Flow Launcher settings | +| Restart Flow Launcher | Restart Flow Launcher | +| Settings | Tweak this app | +| Reload Plugin Data | Refreshes plugin data with new content | +| Check For Update | Check for new Flow Launcher update | +| Open Log Location | Open Flow Launcher's log location | +| Index Option | Open Windows Search Index window | +| Flow Launcher Tips | Visit Flow Launcher's documentation for more help and how to use tips | +| Flow Launcher UserData Folder | Open the location where Flow Launcher's settings are stored | +| Toggle Game Mode | Toggle Game Mode | ### 💁‍♂️ Tips @@ -319,7 +333,7 @@ And you can download - +
@@ -335,16 +349,16 @@ And you can download -       -       +

### Mentions -- Why I Chose to Support Flow-Launcher - Appwrite -- Softpedia Editor's Pick + +- [Why I Chose to Support Flow-Launcher](https://dev.to/appwrite/appwrite-loves-open-source-why-i-chose-to-support-flow-launcher-54pj) - Appwrite +- [Softpedia Editor's Pick](https://www.softpedia.com/get/System/Launchers-Shutdown-Tools/Flow-Launcher.shtml) @@ -376,7 +390,7 @@ Get in touch if you like to join the Flow-Launcher Team and help build this grea - Install Visual Studio 2022 -- Install .Net 7 SDK +- Install .Net 7 SDK - via Visual Studio installer - via winget `winget install Microsoft.DotNet.SDK.7` - Manually from [here](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) diff --git a/Settings.XamlStyler b/Settings.XamlStyler new file mode 100644 index 000000000..7ea0744f1 --- /dev/null +++ b/Settings.XamlStyler @@ -0,0 +1,42 @@ +{ + "AttributesTolerance": 2, + "KeepFirstAttributeOnSameLine": false, + "MaxAttributeCharactersPerLine": 0, + "MaxAttributesPerLine": 1, + "NewlineExemptionElements": "RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter", + "SeparateByGroups": false, + "AttributeIndentation": 0, + "AttributeIndentationStyle": 1, + "RemoveDesignTimeReferences": false, + "IgnoreDesignTimeReferencePrefix": false, + "EnableAttributeReordering": true, + "AttributeOrderingRuleGroups": [ + "x:Class", + "xmlns, xmlns:x", + "xmlns:*", + "x:Key, Key, x:Name, Name, x:Uid, Uid, Title", + "Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom", + "Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight", + "Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex", + "*:*, *", + "PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint", + "mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText", + "Storyboard.*, From, To, Duration" + ], + "FirstLineAttributes": "", + "OrderAttributesByName": true, + "PutEndingBracketOnNewLine": false, + "RemoveEndingTagOfEmptyElement": true, + "SpaceBeforeClosingSlash": true, + "RootElementLineBreakRule": 0, + "ReorderVSM": 2, + "ReorderGridChildren": false, + "ReorderCanvasChildren": false, + "ReorderSetters": 0, + "FormatMarkupExtension": true, + "NoNewLineMarkupExtensions": "x:Bind, Binding", + "ThicknessSeparator": 1, + "ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin, CornerRadius", + "FormatOnSave": true, + "CommentPadding": 2, +} diff --git a/appveyor.yml b/appveyor.yml index d8c5d7964..e0f3fc104 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '1.16.2.{build}' +version: '1.18.0.{build}' init: - ps: |