diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..11a0bcdf6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,146 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs +############################### +# Core EditorConfig Options # +############################### +# All files +[*] +indent_style = space + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +############################### +# Naming Conventions # +############################### +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# 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 +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 2 +end_of_line = crlf +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_diagnostic.CA1416.severity = silent +############################### +# C# Coding Conventions # +############################### +[*.cs] +dotnet_diagnostics.VSTHRD200.severity = none # VSTHRD200: Use "Async" suffix for async methods +dotnet_analyzer_diagnostic.VSTHRD200.severity = none # VSTHRD200: Use "Async" suffix for async methods +# var preferences +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +############################### +# C# Formatting Rules # +############################### +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +# Wrapping preferences +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_prefer_method_group_conversion = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +############################### +# VB Coding Conventions # +############################### +[*.vb] +# Modifier preferences +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index 23ced593e..000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: "\U0001F41E Bug report" -about: Create a bug report to help us improve Flow Launcher -title: "[Describe Your Bug]" -labels: 'bug' -assignees: '' - ---- - -**Describe the bug/issue** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. ... -2. ... -3. ... - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Your System** -``` -Windows build number: (run "ver" at a command prompt) -Flow Launcher version: (Settings => About) -``` -**Flow Launcher Error Log** - diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml new file mode 100644 index 000000000..87bb5045a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -0,0 +1,78 @@ +name: "\U0001F41E Bug Report" +description: Create a bug report to help us improve Flow Launcher +title: "BUG: " +labels: ["bug"] + +body: + - type: markdown + attributes: + value: Thanks for taking the time to fill out this bug report! + + - type: checkboxes + attributes: + label: Checks + options: + - label: > + I have checked that this issue has not already been reported. + + - type: textarea + attributes: + label: Problem Description + description: A clear and concise description of what the problem is. + validations: + required: true + + - type: textarea + attributes: + label: To Reproduce + description: Steps to reproduce the behavior. + value: > + 1. ... + + 2. ... + + 3. ... + + - type: textarea + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain your problem. + + - type: input + attributes: + label: Flow Launcher Version + description: Go to "Settings" => "About". + value: v1.8.3 + + - type: input + attributes: + label: Windows Build Number + description: Run "ver" at CMD (command prompt). + value: 10.0.19043.1288 + + - type: textarea + id: logs + attributes: + label: Error Log + description: > + Log file place: + + - The latest version place: `%AppData%\FlowLauncher\Logs\\.txt` + + - For portable mode: `%LocalAppData%\FlowLauncher\\UserData\Logs\\.txt` + value: > +
+ + + ```shell + + + Replace this line with the important log contents. + + + ``` + +
+ + + diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 6abd850ed..052e1d225 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,9 +15,12 @@ jobs: steps: - uses: actions/stale@v4 with: - stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' - days-before-stale: 30 - days-before-close: 5 + 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 days-before-pr-close: -1 exempt-all-milestones: true - close-issue-message: 'This issue was closed because it has been stale for 5 days with no activity. If you feel this issue still needs attention please feel free to reopen.' \ No newline at end of file + 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-pr-labels: 'keep-fresh,awaiting-approval,work-in-progress' diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs index 5bca087b8..bd77ea7cf 100644 --- a/Flow.Launcher.Core/Configuration/Portable.cs +++ b/Flow.Launcher.Core/Configuration/Portable.cs @@ -1,4 +1,4 @@ -using Microsoft.Win32; +using Microsoft.Win32; using Squirrel; using System; using System.IO; @@ -127,7 +127,7 @@ namespace Flow.Launcher.Core.Configuration using (var portabilityUpdater = NewUpdateManager()) { - portabilityUpdater.CreateUninstallerRegistryEntry(); + _ = portabilityUpdater.CreateUninstallerRegistryEntry(); } } diff --git a/Flow.Launcher.Core/ExternalPlugins/FlowPluginException.cs b/Flow.Launcher.Core/ExternalPlugins/FlowPluginException.cs new file mode 100644 index 000000000..47bc285c9 --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/FlowPluginException.cs @@ -0,0 +1,24 @@ +using Flow.Launcher.Plugin; +using System; + +namespace Flow.Launcher.Core.ExternalPlugins +{ + public class FlowPluginException : Exception + { + public PluginMetadata Metadata { get; set; } + + public FlowPluginException(PluginMetadata metadata, Exception e) : base(e.Message, e) + { + Metadata = metadata; + } + + public override string ToString() + { + return $@"{Metadata.Name} Exception: +Websites: {Metadata.Website} +Author: {Metadata.Author} +Version: {Metadata.Version} +{base.ToString()}"; + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index fdd23a0d2..9f9fa8ff5 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -56,7 +56,7 @@ - + diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs index cf75e4aa3..e937779a1 100644 --- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs +++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs @@ -93,4 +93,4 @@ namespace Flow.Launcher.Core.Plugin public Dictionary SettingsChange { get; set; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 4cfa83382..e3efcd296 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -1,33 +1,27 @@ -using Accessibility; -using Flow.Launcher.Core.Resource; +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.Reflection; +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 ICSharpCode.SharpZipLib.Zip; -using JetBrains.Annotations; using Microsoft.IO; -using System.Text.Json.Serialization; 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 Label = System.Windows.Controls.Label; using Orientation = System.Windows.Controls.Orientation; using TextBox = System.Windows.Controls.TextBox; using UserControl = System.Windows.Controls.UserControl; -using System.Windows.Data; namespace Flow.Launcher.Core.Plugin { @@ -69,7 +63,13 @@ namespace Flow.Launcher.Core.Plugin private static readonly JsonSerializerOptions options = 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() @@ -82,16 +82,19 @@ namespace Flow.Launcher.Core.Plugin }; private Dictionary Settings { get; set; } - private Dictionary _settingControls = new(); + private readonly Dictionary _settingControls = new(); private async Task> DeserializedResultAsync(Stream output) { - if (output == Stream.Null) return null; + await using (output) + { + if (output == Stream.Null) return null; - var queryResponseModel = - await JsonSerializer.DeserializeAsync(output, options); + var queryResponseModel = + await JsonSerializer.DeserializeAsync(output, options); - return ParseResults(queryResponseModel); + return ParseResults(queryResponseModel); + } } private List DeserializedResult(string output) @@ -115,7 +118,7 @@ namespace Flow.Launcher.Core.Plugin foreach (var result in queryResponseModel.Result) { - result.Action = c => + result.AsyncAction = async c => { UpdateSettings(result.SettingsChange); @@ -133,15 +136,15 @@ namespace Flow.Launcher.Core.Plugin } else { - var actionResponse = Request(result.JsonRPCAction); + await using var actionResponse = await RequestAsync(result.JsonRPCAction); - if (string.IsNullOrEmpty(actionResponse)) + if (actionResponse.Length == 0) { return !result.JsonRPCAction.DontHideAfterAction; } - var jsonRpcRequestModel = - JsonSerializer.Deserialize(actionResponse, options); + var jsonRpcRequestModel = await + JsonSerializer.DeserializeAsync(actionResponse, options); if (jsonRpcRequestModel?.Method?.StartsWith("Flow.Launcher.") ?? false) { @@ -166,19 +169,20 @@ namespace Flow.Launcher.Core.Plugin private void ExecuteFlowLauncherAPI(string method, object[] parameters) { var parametersTypeArray = parameters.Select(param => param.GetType()).ToArray(); - MethodInfo methodInfo = PluginManager.API.GetType().GetMethod(method, parametersTypeArray); - if (methodInfo != null) + var methodInfo = typeof(IPublicAPI).GetMethod(method, parametersTypeArray); + if (methodInfo == null) + { + return; + } + try + { + methodInfo.Invoke(PluginManager.API, parameters); + } + catch (Exception) { - try - { - methodInfo.Invoke(PluginManager.API, parameters); - } - catch (Exception) - { #if (DEBUG) - throw; + throw; #endif - } } } @@ -240,73 +244,55 @@ namespace Flow.Launcher.Core.Plugin protected async Task ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default) { - Process process = null; - bool disposed = false; + using var process = Process.Start(startInfo); + if (process == null) + { + Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process"); + return Stream.Null; + } + + var sourceBuffer = BufferManager.GetStream(); + using var errorBuffer = BufferManager.GetStream(); + + var sourceCopyTask = process.StandardOutput.BaseStream.CopyToAsync(sourceBuffer, token); + var errorCopyTask = process.StandardError.BaseStream.CopyToAsync(errorBuffer, token); + + await using var registeredEvent = token.Register(() => + { + if (!process.HasExited) + process.Kill(); + sourceBuffer.Dispose(); + }); + try { - process = Process.Start(startInfo); - if (process == null) - { - Log.Error("|JsonRPCPlugin.ExecuteAsync|Can't start new process"); - return Stream.Null; - } - - await using var source = process.StandardOutput.BaseStream; - - var buffer = BufferManager.GetStream(); - - token.Register(() => - { - // ReSharper disable once AccessToModifiedClosure - // Manually Check whether disposed - if (!disposed && !process.HasExited) - process.Kill(); - }); - - try - { - // token expire won't instantly trigger the exception, - // manually kill process at before - await source.CopyToAsync(buffer, token); - } - catch (OperationCanceledException) - { - await buffer.DisposeAsync(); - return Stream.Null; - } - - buffer.Seek(0, SeekOrigin.Begin); - - token.ThrowIfCancellationRequested(); - - if (buffer.Length == 0) - { - var errorMessage = process.StandardError.EndOfStream ? - "Empty JSONRPC Response" : - await process.StandardError.ReadToEndAsync(); - throw new InvalidDataException($"{context.CurrentPluginMetadata.Name}|{errorMessage}"); - } - - if (!process.StandardError.EndOfStream) - { - using var standardError = process.StandardError; - var error = await standardError.ReadToEndAsync(); - - if (!string.IsNullOrEmpty(error)) - { - Log.Error($"|{context.CurrentPluginMetadata.Name}.{nameof(ExecuteAsync)}|{error}"); - } - } - - return buffer; + // token expire won't instantly trigger the exception, + // manually kill process at before + await process.WaitForExitAsync(token); + await Task.WhenAll(sourceCopyTask, errorCopyTask); } - finally + catch (OperationCanceledException) { - process?.Dispose(); - disposed = true; + await sourceBuffer.DisposeAsync(); + return Stream.Null; } + + switch (sourceBuffer.Length, errorBuffer.Length) + { + case (0, 0): + const string errorMessage = "Empty JSON-RPC Response."; + Log.Warn($"|{nameof(JsonRPCPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}"); + break; + case (_, not 0): + throw new InvalidDataException(Encoding.UTF8.GetString(errorBuffer.ToArray())); // The process has exited with an error message + } + + sourceBuffer.Seek(0, SeekOrigin.Begin); + + return sourceBuffer; } + public async Task> QueryAsync(Query query, CancellationToken token) { var request = new JsonRPCRequestModel @@ -358,6 +344,7 @@ namespace Flow.Launcher.Core.Plugin private static readonly Thickness settingPanelMargin = new(15, 20, 15, 20); private static readonly Thickness settingTextBlockMargin = new(10, 4, 10, 4); private JsonRpcConfigurationModel _settingsTemplate; + public Control CreateSettingPanel() { if (Settings == null) @@ -365,8 +352,7 @@ namespace Flow.Launcher.Core.Plugin var settingWindow = new UserControl(); var mainPanel = new StackPanel { - Margin = settingPanelMargin, - Orientation = Orientation.Vertical + Margin = settingPanelMargin, Orientation = Orientation.Vertical }; settingWindow.Content = mainPanel; @@ -374,8 +360,7 @@ namespace Flow.Launcher.Core.Plugin { var panel = new StackPanel { - Orientation = Orientation.Horizontal, - Margin = settingControlMargin + Orientation = Orientation.Horizontal, Margin = settingControlMargin }; var name = new TextBlock() { @@ -391,84 +376,84 @@ namespace Flow.Launcher.Core.Plugin switch (type) { case "textBlock": + { + contentControl = new TextBlock { - contentControl = new TextBlock - { - Text = attribute.Description.Replace("\\r\\n", "\r\n"), - Margin = settingTextBlockMargin, - MaxWidth = 500, - TextWrapping = TextWrapping.WrapWithOverflow - }; - break; - } + Text = attribute.Description.Replace("\\r\\n", "\r\n"), + Margin = settingTextBlockMargin, + MaxWidth = 500, + TextWrapping = TextWrapping.WrapWithOverflow + }; + break; + } case "input": + { + var textBox = new TextBox() { - var textBox = new TextBox() - { - Width = 300, - Text = Settings[attribute.Name] as string ?? string.Empty, - Margin = settingControlMargin, - ToolTip = attribute.Description - }; - textBox.TextChanged += (_, _) => - { - Settings[attribute.Name] = textBox.Text; - }; - contentControl = textBox; - break; - } + Width = 300, + Text = Settings[attribute.Name] as string ?? string.Empty, + Margin = settingControlMargin, + ToolTip = attribute.Description + }; + textBox.TextChanged += (_, _) => + { + Settings[attribute.Name] = textBox.Text; + }; + contentControl = textBox; + break; + } case "textarea": + { + var textBox = new TextBox() { - var textBox = new TextBox() - { - Width = 300, - Height = 120, - Margin = settingControlMargin, - TextWrapping = TextWrapping.WrapWithOverflow, - AcceptsReturn = true, - Text = Settings[attribute.Name] as string ?? string.Empty, - ToolTip = attribute.Description - }; - textBox.TextChanged += (sender, _) => - { - Settings[attribute.Name] = ((TextBox)sender).Text; - }; - contentControl = textBox; - break; - } + Width = 300, + Height = 120, + Margin = settingControlMargin, + TextWrapping = TextWrapping.WrapWithOverflow, + AcceptsReturn = true, + Text = Settings[attribute.Name] as string ?? string.Empty, + ToolTip = attribute.Description + }; + textBox.TextChanged += (sender, _) => + { + Settings[attribute.Name] = ((TextBox)sender).Text; + }; + contentControl = textBox; + break; + } case "passwordBox": + { + var passwordBox = new PasswordBox() { - var passwordBox = new PasswordBox() - { - Width = 300, - Margin = settingControlMargin, - Password = Settings[attribute.Name] as string ?? string.Empty, - PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar, - ToolTip = attribute.Description - }; - passwordBox.PasswordChanged += (sender, _) => - { - Settings[attribute.Name] = ((PasswordBox)sender).Password; - }; - contentControl = passwordBox; - break; - } + Width = 300, + Margin = settingControlMargin, + Password = Settings[attribute.Name] as string ?? string.Empty, + PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar, + ToolTip = attribute.Description + }; + passwordBox.PasswordChanged += (sender, _) => + { + Settings[attribute.Name] = ((PasswordBox)sender).Password; + }; + contentControl = passwordBox; + break; + } case "dropdown": + { + var comboBox = new ComboBox() { - var comboBox = new ComboBox() - { - ItemsSource = attribute.Options, - SelectedItem = Settings[attribute.Name], - Margin = settingControlMargin, - ToolTip = attribute.Description - }; - comboBox.SelectionChanged += (sender, _) => - { - Settings[attribute.Name] = (string)((ComboBox)sender).SelectedItem; - }; - contentControl = comboBox; - break; - } + ItemsSource = attribute.Options, + SelectedItem = Settings[attribute.Name], + Margin = settingControlMargin, + ToolTip = attribute.Description + }; + comboBox.SelectionChanged += (sender, _) => + { + Settings[attribute.Name] = (string)((ComboBox)sender).SelectedItem; + }; + contentControl = comboBox; + break; + } case "checkbox": var checkBox = new CheckBox { @@ -493,6 +478,7 @@ namespace Flow.Launcher.Core.Plugin } return settingWindow; } + public void Save() { if (Settings != null) @@ -535,4 +521,5 @@ namespace Flow.Launcher.Core.Plugin } } } -} \ No newline at end of file + +} diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 134c3c002..383689c83 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -1,4 +1,5 @@ -using System; +using Flow.Launcher.Core.ExternalPlugins; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -74,7 +75,7 @@ namespace Flow.Launcher.Core.Plugin } } - public static async Task ReloadData() + public static async Task ReloadDataAsync() { await Task.WhenAll(AllPlugins.Select(plugin => plugin.Plugin switch { @@ -109,7 +110,7 @@ namespace Flow.Launcher.Core.Plugin /// Call initialize for all plugins /// /// return the list of failed to init plugins or null for none - public static async Task InitializePlugins(IPublicAPI api) + public static async Task InitializePluginsAsync(IPublicAPI api) { API = api; var failedPlugins = new ConcurrentQueue(); @@ -182,13 +183,11 @@ namespace Flow.Launcher.Core.Plugin public static async Task> QueryForPluginAsync(PluginPair pair, Query query, CancellationToken token) { var results = new List(); + var metadata = pair.Metadata; + try { - var metadata = pair.Metadata; - - long milliseconds = -1L; - - milliseconds = await Stopwatch.DebugAsync($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", + var milliseconds = await Stopwatch.DebugAsync($"|PluginManager.QueryForPlugin|Cost for {metadata.Name}", async () => results = await pair.Plugin.QueryAsync(query, token).ConfigureAwait(false)); token.ThrowIfCancellationRequested(); @@ -206,7 +205,10 @@ namespace Flow.Launcher.Core.Plugin // null will be fine since the results will only be added into queue if the token hasn't been cancelled return null; } - + catch (Exception e) + { + throw new FlowPluginException(metadata, e); + } return results; } @@ -327,4 +329,4 @@ namespace Flow.Launcher.Core.Plugin } } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index b3d56221a..752174263 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -11,7 +11,6 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; -using System.Diagnostics; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Core.Plugin @@ -41,14 +40,6 @@ namespace Flow.Launcher.Core.Plugin var milliseconds = Stopwatch.Debug( $"|PluginsLoader.DotNetPlugins|Constructor init cost for {metadata.Name}", () => { -#if DEBUG - var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath); - var assembly = assemblyLoader.LoadAssemblyAndDependencies(); - var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, - typeof(IAsyncPlugin)); - - var plugin = Activator.CreateInstance(type) as IAsyncPlugin; -#else Assembly assembly = null; IAsyncPlugin plugin = null; @@ -62,6 +53,12 @@ namespace Flow.Launcher.Core.Plugin plugin = Activator.CreateInstance(type) as IAsyncPlugin; } +#if DEBUG + catch (Exception e) + { + throw; + } +#else catch (Exception e) when (assembly == null) { Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e); @@ -79,6 +76,7 @@ namespace Flow.Launcher.Core.Plugin Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e); } #endif + if (plugin == null) { erroredPlugins.Add(metadata.Name); @@ -98,7 +96,7 @@ namespace Flow.Launcher.Core.Plugin + (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ") + "errored and cannot be loaded:"; - Task.Run(() => + _ = Task.Run(() => { MessageBox.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs index ef387b693..a819f94b7 100644 --- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs +++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs @@ -16,7 +16,7 @@ namespace Flow.Launcher.Core.Plugin return null; } - var rawQuery = string.Join(Query.TermSeparator, terms); + var rawQuery = text; string actionKeyword, search; string possibleActionKeyword = terms[0]; string[] searchTerms; @@ -24,13 +24,13 @@ namespace Flow.Launcher.Core.Plugin if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled) { // use non global plugin for query actionKeyword = possibleActionKeyword; - search = terms.Length > 1 ? rawQuery[(actionKeyword.Length + 1)..] : string.Empty; + search = terms.Length > 1 ? rawQuery[(actionKeyword.Length + 1)..].TrimStart() : string.Empty; searchTerms = terms[1..]; } else { // non action keyword actionKeyword = string.Empty; - search = rawQuery; + search = rawQuery.TrimStart(); searchTerms = terms; } diff --git a/Flow.Launcher.Core/Properties/AssemblyInfo.cs b/Flow.Launcher.Core/Properties/AssemblyInfo.cs index 40017c46c..ad60e2c9f 100644 --- a/Flow.Launcher.Core/Properties/AssemblyInfo.cs +++ b/Flow.Launcher.Core/Properties/AssemblyInfo.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Flow.Launcher.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("Flow.Launcher.Test")] diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 374f7c71f..5c99bc239 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -99,7 +99,7 @@ namespace Flow.Launcher.Core.Resource Settings.Language = language.LanguageCode; CultureInfo.CurrentCulture = new CultureInfo(language.LanguageCode); CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; - Task.Run(() => + _ = Task.Run(() => { UpdatePluginMetadataTranslations(); }); @@ -182,6 +182,7 @@ namespace Flow.Launcher.Core.Resource { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); + pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e) { diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 6561419a1..205abcd34 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -85,10 +85,13 @@ namespace Flow.Launcher.Core.Resource Settings.Theme = theme; + // reload all resources even if the theme itself hasn't changed in order to pickup changes + // to things like fonts + UpdateResourceDictionary(GetResourceDictionary()); + //always allow re-loading default theme, in case of failure of switching to a new theme from default theme if (_oldTheme != theme || theme == defaultTheme) { - UpdateResourceDictionary(GetResourceDictionary()); _oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath); } @@ -99,7 +102,7 @@ namespace Flow.Launcher.Core.Resource SetBlurForWindow(); } - catch (DirectoryNotFoundException e) + catch (DirectoryNotFoundException) { Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found"); if (theme != defaultTheme) @@ -109,7 +112,7 @@ namespace Flow.Launcher.Core.Resource } return false; } - catch (XamlParseException e) + catch (XamlParseException) { Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse"); if (theme != defaultTheme) diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 69b537b39..976c4eec1 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Net.Http; @@ -40,7 +40,7 @@ namespace Flow.Launcher.Core api.ShowMsg(api.GetTranslation("pleaseWait"), api.GetTranslation("update_flowlauncher_update_check")); - using var updateManager = await GitHubUpdateManager(GitHubRepository).ConfigureAwait(false); + using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false); // UpdateApp CheckForUpdate will return value only if the app is squirrel installed var newUpdateInfo = await updateManager.CheckForUpdate().NonNull().ConfigureAwait(false); @@ -115,7 +115,7 @@ namespace Flow.Launcher.Core } /// https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs - private async Task GitHubUpdateManager(string repository) + private async Task GitHubUpdateManagerAsync(string repository) { var uri = new Uri(repository); var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases"; @@ -145,4 +145,4 @@ namespace Flow.Launcher.Core } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Infrastructure/Exception/ExceptionFormatter.cs b/Flow.Launcher.Infrastructure/Exception/ExceptionFormatter.cs index 3849f6e30..40ac6b121 100644 --- a/Flow.Launcher.Infrastructure/Exception/ExceptionFormatter.cs +++ b/Flow.Launcher.Infrastructure/Exception/ExceptionFormatter.cs @@ -78,7 +78,7 @@ namespace Flow.Launcher.Infrastructure.Exception sb.AppendLine(); sb.AppendLine("## Assemblies - " + AppDomain.CurrentDomain.FriendlyName); sb.AppendLine(); - foreach (var ass in AppDomain.CurrentDomain.GetAssemblies().OrderBy(o => o.GlobalAssemblyCache ? 50 : 0)) + foreach (var ass in AppDomain.CurrentDomain.GetAssemblies()) { sb.Append("* "); sb.Append(ass.FullName); @@ -166,7 +166,7 @@ namespace Flow.Launcher.Infrastructure.Exception } return result; } - catch (System.Exception e) + catch { return new List(); } diff --git a/Flow.Launcher.Infrastructure/Image/ImageHashGenerator.cs b/Flow.Launcher.Infrastructure/Image/ImageHashGenerator.cs index 736133052..2611e99e8 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageHashGenerator.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageHashGenerator.cs @@ -14,30 +14,23 @@ namespace Flow.Launcher.Infrastructure.Image { public string GetHashFromImage(ImageSource imageSource) { - if (!(imageSource is BitmapSource image)) + if (imageSource is not BitmapSource image) { return null; } try { - using (var outStream = new MemoryStream()) - { - // PngBitmapEncoder enc2 = new PngBitmapEncoder(); - // enc2.Frames.Add(BitmapFrame.Create(tt)); - - var enc = new JpegBitmapEncoder(); - var bitmapFrame = BitmapFrame.Create(image); - bitmapFrame.Freeze(); - enc.Frames.Add(bitmapFrame); - enc.Save(outStream); - var byteArray = outStream.GetBuffer(); - using (var sha1 = new SHA1CryptoServiceProvider()) - { - var hash = Convert.ToBase64String(sha1.ComputeHash(byteArray)); - return hash; - } - } + using var outStream = new MemoryStream(); + var enc = new JpegBitmapEncoder(); + var bitmapFrame = BitmapFrame.Create(image); + bitmapFrame.Freeze(); + enc.Frames.Add(bitmapFrame); + enc.Save(outStream); + var byteArray = outStream.GetBuffer(); + using var sha1 = SHA1.Create(); + var hash = Convert.ToBase64String(sha1.ComputeHash(byteArray)); + return hash; } catch { @@ -46,4 +39,4 @@ namespace Flow.Launcher.Infrastructure.Image } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index ac333d567..8c1d7d74f 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -13,11 +13,11 @@ namespace Flow.Launcher.Infrastructure.Image { public static class ImageLoader { - private static readonly ImageCache ImageCache = new ImageCache(); + private static readonly ImageCache ImageCache = new(); private static BinaryStorage> _storage; - private static readonly ConcurrentDictionary GuidToKey = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary GuidToKey = new(); private static IImageHashGenerator _hashGenerator; - private static bool EnableImageHash = true; + private static readonly bool EnableImageHash = true; public static ImageSource DefaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); @@ -46,7 +46,7 @@ namespace Flow.Launcher.Infrastructure.Image ImageCache[icon] = img; } - Task.Run(() => + _ = Task.Run(() => { Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => { @@ -243,7 +243,6 @@ namespace Flow.Launcher.Infrastructure.Image ImageCache[path] = img; } - return img; } diff --git a/Flow.Launcher.Infrastructure/Properties/AssemblyInfo.cs b/Flow.Launcher.Infrastructure/Properties/AssemblyInfo.cs index 4cdadffc9..3c29d0dcb 100644 --- a/Flow.Launcher.Infrastructure/Properties/AssemblyInfo.cs +++ b/Flow.Launcher.Infrastructure/Properties/AssemblyInfo.cs @@ -2,4 +2,5 @@ [assembly: InternalsVisibleTo("Flow.Launcher")] [assembly: InternalsVisibleTo("Flow.Launcher.Core")] -[assembly: InternalsVisibleTo("Flow.Launcher.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("Flow.Launcher.Test")] +[assembly: System.Runtime.Versioning.SupportedOSPlatform("Windows10.0.19041.0")] diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index 5205543b1..ea2d42773 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -9,6 +9,7 @@ using Flow.Launcher.Infrastructure.UserSettings; 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 @@ -113,4 +114,5 @@ namespace Flow.Launcher.Infrastructure.Storage } } } +#pragma warning restore SYSLIB0011 } diff --git a/Flow.Launcher.Plugin/AllowedLanguage.cs b/Flow.Launcher.Plugin/AllowedLanguage.cs index 827958a7b..94c645d27 100644 --- a/Flow.Launcher.Plugin/AllowedLanguage.cs +++ b/Flow.Launcher.Plugin/AllowedLanguage.cs @@ -1,33 +1,58 @@ namespace Flow.Launcher.Plugin { + /// + /// Allowed plugin languages + /// public static class AllowedLanguage { + /// + /// Python + /// public static string Python { get { return "PYTHON"; } } + /// + /// C# + /// public static string CSharp { get { return "CSHARP"; } } + /// + /// F# + /// public static string FSharp { get { return "FSHARP"; } } + /// + /// Standard .exe + /// public static string Executable { get { return "EXECUTABLE"; } } + /// + /// Determines if this language is a .NET language + /// + /// + /// public static bool IsDotNet(string language) { return language.ToUpper() == CSharp || language.ToUpper() == FSharp; } + /// + /// Determines if this language is supported + /// + /// + /// public static bool IsAllowed(string language) { return IsDotNet(language) @@ -35,4 +60,4 @@ || language.ToUpper() == Executable.ToUpper(); } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/BaseModel.cs b/Flow.Launcher.Plugin/BaseModel.cs index 5bb558702..a4d666ced 100644 --- a/Flow.Launcher.Plugin/BaseModel.cs +++ b/Flow.Launcher.Plugin/BaseModel.cs @@ -4,14 +4,24 @@ using JetBrains.Annotations; namespace Flow.Launcher.Plugin { + /// + /// Base model for plugin classes + /// public class BaseModel : INotifyPropertyChanged { + /// + /// Property changed event handler + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// Invoked when a property changes + /// + /// [NotifyPropertyChangedInvocator] protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/EventHandler.cs b/Flow.Launcher.Plugin/EventHandler.cs index 476668584..009e1721c 100644 --- a/Flow.Launcher.Plugin/EventHandler.cs +++ b/Flow.Launcher.Plugin/EventHandler.cs @@ -3,9 +3,24 @@ using System.Windows.Input; namespace Flow.Launcher.Plugin { + /// + /// Delegate for key down event + /// + /// public delegate void FlowLauncherKeyDownEventHandler(FlowLauncherKeyDownEventArgs e); + + /// + /// Delegate for query event + /// + /// public delegate void AfterFlowLauncherQueryEventHandler(FlowLauncherQueryEventArgs e); + /// + /// Delegate for drop events [unused?] + /// + /// + /// + /// public delegate void ResultItemDropEventHandler(Result result, IDataObject dropObject, DragEventArgs e); /// @@ -17,14 +32,30 @@ namespace Flow.Launcher.Plugin /// return true to continue handling, return false to intercept system handling public delegate bool FlowLauncherGlobalKeyboardEventHandler(int keyevent, int vkcode, SpecialKeyState state); + /// + /// Arguments container for the Key Down event + /// public class FlowLauncherKeyDownEventArgs { + /// + /// The actual query + /// public string Query { get; set; } + + /// + /// Relevant key events for this event + /// public KeyEventArgs keyEventArgs { get; set; } } + /// + /// Arguments container for the Query event + /// public class FlowLauncherQueryEventArgs { + /// + /// The actual query + /// public Query Query { get; set; } } } diff --git a/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs b/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs index bd4500a7e..3d5f44a0d 100644 --- a/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs +++ b/Flow.Launcher.Plugin/Interfaces/IAsyncReloadable.cs @@ -15,6 +15,10 @@ namespace Flow.Launcher.Plugin /// public interface IAsyncReloadable : IFeatures { + /// + /// Reload plugin data + /// + /// Task ReloadDataAsync(); } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 69057820e..41d062570 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -1,7 +1,8 @@ -using Flow.Launcher.Plugin.SharedModels; +using Flow.Launcher.Plugin.SharedModels; using JetBrains.Annotations; using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Runtime.CompilerServices; using System.Threading; @@ -41,7 +42,7 @@ namespace Flow.Launcher.Plugin /// /// Copy Text to clipboard /// - /// Text to save on clipboard + /// Text to save on clipboard public void CopyToClipboard(string text); /// @@ -163,6 +164,7 @@ namespace Flow.Launcher.Plugin /// Download the specific url to a cretain file path /// /// URL to download file + /// path to save downloaded file /// place to store file /// Task showing the progress Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, CancellationToken token = default); @@ -178,7 +180,7 @@ namespace Flow.Launcher.Plugin /// Remove ActionKeyword for specific plugin /// /// ID for plugin that needs to remove action keyword - /// The actionkeyword that is supposed to be removed + /// The actionkeyword that is supposed to be removed void RemoveActionKeyword(string pluginId, string oldActionKeyword); /// diff --git a/Flow.Launcher.Plugin/Interfaces/IReloadable.cs b/Flow.Launcher.Plugin/Interfaces/IReloadable.cs index bd1ad406e..707f8d92c 100644 --- a/Flow.Launcher.Plugin/Interfaces/IReloadable.cs +++ b/Flow.Launcher.Plugin/Interfaces/IReloadable.cs @@ -17,6 +17,9 @@ /// public interface IReloadable : IFeatures { + /// + /// Synchronously reload plugin data + /// void ReloadData(); } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/Properties/AssemblyInfo.cs b/Flow.Launcher.Plugin/Properties/AssemblyInfo.cs index 4cdadffc9..0a602a472 100644 --- a/Flow.Launcher.Plugin/Properties/AssemblyInfo.cs +++ b/Flow.Launcher.Plugin/Properties/AssemblyInfo.cs @@ -2,4 +2,4 @@ [assembly: InternalsVisibleTo("Flow.Launcher")] [assembly: InternalsVisibleTo("Flow.Launcher.Core")] -[assembly: InternalsVisibleTo("Flow.Launcher.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("Flow.Launcher.Test")] diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs index 3fcf3c1d7..95547d273 100644 --- a/Flow.Launcher.Plugin/Query.cs +++ b/Flow.Launcher.Plugin/Query.cs @@ -16,7 +16,9 @@ namespace Flow.Launcher.Plugin { Search = search; RawQuery = rawQuery; +#pragma warning disable CS0618 Terms = terms; +#pragma warning restore CS0618 SearchTerms = searchTerms; ActionKeyword = actionKeyword; } @@ -98,4 +100,4 @@ namespace Flow.Launcher.Plugin public override string ToString() => RawQuery; } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index 4a5eb39af..136395467 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -1,11 +1,14 @@ -using System; +using System; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using System.Windows.Media; namespace Flow.Launcher.Plugin { - + /// + /// Describes the result of a plugin + /// public class Result { @@ -64,6 +67,10 @@ namespace Flow.Launcher.Plugin } } + /// + /// Delegate function, see + /// + /// public delegate ImageSource IconDelegate(); /// @@ -85,6 +92,14 @@ namespace Flow.Launcher.Plugin /// public Func Action { get; set; } + /// + /// Delegate. An Async action to take in the form of a function call when the result has been selected + /// + /// true to hide flowlauncher after select result + /// + /// + public Func> AsyncAction { get; set; } + /// /// Priority of the current result /// default: 0 @@ -96,6 +111,9 @@ namespace Flow.Launcher.Plugin /// public IList TitleHighlightData { get; set; } + /// + /// Deprecated as of Flow Launcher v1.9.1. Subtitle highlighting is no longer offered + /// [Obsolete("Deprecated as of Flow Launcher v1.9.1. Subtitle highlighting is no longer offered")] public IList SubTitleHighlightData { get; set; } @@ -169,5 +187,26 @@ namespace Flow.Launcher.Plugin /// Show message as ToolTip on result SubTitle hover over /// public string SubTitleToolTip { get; set; } + + /// + /// Run this result, asynchronously + /// + /// + /// + public ValueTask ExecuteAsync(ActionContext context) + { + return AsyncAction?.Invoke(context) ?? ValueTask.FromResult(Action?.Invoke(context) ?? false); + } + + /// + /// Progress bar display. Providing an int value between 0-100 will trigger the progress bar to be displayed on the result + /// + public int? ProgressBar { get; set; } + + /// + /// Optionally set the color of the progress bar + /// + /// #26a0da (blue) + public string ProgressBarColor { get; set; } = "#26a0da"; } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index be33bd86c..5cb3a171a 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -5,6 +5,9 @@ using System.Windows; namespace Flow.Launcher.Plugin.SharedCommands { + /// + /// Commands that are useful to run on files... and folders! + /// public static class FilesFolders { private const string FileExplorerProgramName = "explorer"; @@ -53,10 +56,10 @@ namespace Flow.Launcher.Plugin.SharedCommands CopyAll(subdir.FullName, temppath); } } - catch (Exception e) + catch (Exception) { #if DEBUG - throw e; + throw; #else MessageBox.Show(string.Format("Copying path {0} has failed, it will now be deleted for consistency", targetPath)); RemoveFolderIfExists(targetPath); @@ -65,6 +68,13 @@ namespace Flow.Launcher.Plugin.SharedCommands } + /// + /// Check if the files and directories are identical between + /// and + /// + /// + /// + /// public static bool VerifyBothFolderFilesEqual(this string fromPath, string toPath) { try @@ -80,10 +90,10 @@ namespace Flow.Launcher.Plugin.SharedCommands return true; } - catch (Exception e) + catch (Exception) { #if DEBUG - throw e; + throw; #else MessageBox.Show(string.Format("Unable to verify folders and files between {0} and {1}", fromPath, toPath)); return false; @@ -92,6 +102,10 @@ namespace Flow.Launcher.Plugin.SharedCommands } + /// + /// Deletes a folder if it exists + /// + /// public static void RemoveFolderIfExists(this string path) { try @@ -99,26 +113,40 @@ namespace Flow.Launcher.Plugin.SharedCommands if (Directory.Exists(path)) Directory.Delete(path, true); } - catch (Exception e) + catch (Exception) { #if DEBUG - throw e; + throw; #else MessageBox.Show(string.Format("Not able to delete folder {0}, please go to the location and manually delete it", path)); #endif } } + /// + /// Checks if a directory exists + /// + /// + /// public static bool LocationExists(this string path) { return Directory.Exists(path); } + /// + /// Checks if a file exists + /// + /// + /// public static bool FileExists(this string filePath) { return File.Exists(filePath); } + /// + /// Open a directory window (using the OS's default handler, usually explorer) + /// + /// public static void OpenPath(string fileOrFolderPath) { var psi = new ProcessStartInfo { FileName = FileExplorerProgramName, UseShellExecute = true, Arguments = '"' + fileOrFolderPath + '"' }; @@ -127,16 +155,20 @@ namespace Flow.Launcher.Plugin.SharedCommands if (LocationExists(fileOrFolderPath) || FileExists(fileOrFolderPath)) Process.Start(psi); } - catch (Exception e) + catch (Exception) { #if DEBUG - throw e; + throw; #else MessageBox.Show(string.Format("Unable to open the path {0}, please check if it exists", fileOrFolderPath)); #endif } } + /// + /// Open the folder that contains + /// + /// public static void OpenContainingFolder(string path) { Process.Start(FileExplorerProgramEXE, $" /select,\"{path}\""); diff --git a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs index a2eea19a7..c18f8b90c 100644 --- a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs +++ b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs @@ -1,6 +1,8 @@ -using System; +using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -87,7 +89,8 @@ namespace Flow.Launcher.Plugin.SharedCommands /// /// Runs a windows command using the provided ProcessStartInfo using a custom execute command function /// - /// allows you to pass in a custom command execution function + /// allows you to pass in a custom command execution function + /// allows you to pass in the info that will be passed to startProcess /// Thrown when unable to find the file specified in the command /// Thrown when error occurs during the execution of the command public static void Execute(Func startProcess, ProcessStartInfo info) diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 9d7fccad9..78be463e4 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.Explorer; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo; @@ -19,10 +19,12 @@ namespace Flow.Launcher.Test.Plugins [TestFixture] public class ExplorerTest { +#pragma warning disable CS1998 // async method with no await (more readable to leave it async to match the tested signature) private async Task> MethodWindowsIndexSearchReturnsZeroResultsAsync(Query dummyQuery, string dummyString, CancellationToken dummyToken) { return new List(); } +#pragma warning restore CS1998 private List MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString, CancellationToken token) { @@ -151,7 +153,7 @@ namespace Flow.Launcher.Test.Plugins } [TestCase] - public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearch() + public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldContinueDirectoryInfoClassSearchAsync() { // Given var searchManager = new SearchManager(new Settings(), new PluginInitContext()); @@ -172,7 +174,7 @@ namespace Flow.Launcher.Test.Plugins } [TestCase] - public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearch() + public async Task GivenTopLevelDirectorySearch_WhenIndexSearchNotRequired_ThenSearchMethodShouldNotContinueDirectoryInfoClassSearchAsync() { // Given var searchManager = new SearchManager(new Settings(), new PluginInitContext()); diff --git a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs index 7231dfbe0..fb91c6388 100644 --- a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs +++ b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs @@ -48,7 +48,7 @@ namespace Flow.Launcher.Test.Plugins foreach (var result in results) { Assert.IsNotNull(result); - Assert.IsNotNull(result.Action); + Assert.IsNotNull(result.AsyncAction); Assert.IsNotNull(result.Title); } @@ -92,7 +92,7 @@ namespace Flow.Launcher.Test.Plugins Assert.AreEqual(result1, referenceResult); Assert.IsNotNull(result1); - Assert.IsNotNull(result1.Action); + Assert.IsNotNull(result1.AsyncAction); } } diff --git a/Flow.Launcher.Test/QueryBuilderTest.cs b/Flow.Launcher.Test/QueryBuilderTest.cs index 6090ecc65..45ff8fc9e 100644 --- a/Flow.Launcher.Test/QueryBuilderTest.cs +++ b/Flow.Launcher.Test/QueryBuilderTest.cs @@ -17,7 +17,7 @@ namespace Flow.Launcher.Test Query q = QueryBuilder.Build("> file.txt file2 file3", nonGlobalPlugins); - Assert.AreEqual("file.txt file2 file3", q.Search); + Assert.AreEqual("file.txt file2 file3", q.Search); Assert.AreEqual(">", q.ActionKeyword); } @@ -31,7 +31,7 @@ namespace Flow.Launcher.Test Query q = QueryBuilder.Build("> file.txt file2 file3", nonGlobalPlugins); - Assert.AreEqual("> file.txt file2 file3", q.Search); + Assert.AreEqual("> file.txt file2 file3", q.Search); } [Test] diff --git a/Flow.Launcher.sln b/Flow.Launcher.sln index b8deae553..0d9eea06c 100644 --- a/Flow.Launcher.sln +++ b/Flow.Launcher.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29806.167 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32901.215 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Test", "Flow.Launcher.Test\Flow.Launcher.Test.csproj", "{FF742965-9A80-41A5-B042-D6C7D3A21708}" ProjectSection(ProjectDependencies) = postProject @@ -43,6 +43,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Url", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FFD651C7-0546-441F-BC8C-D4EE8FD01EA7}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig .gitattributes = .gitattributes .gitignore = .gitignore appveyor.yml = appveyor.yml @@ -212,7 +213,6 @@ Global {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|x86.ActiveCfg = Release|Any CPU {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|x86.Build.0 = Release|Any CPU {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x64.ActiveCfg = Debug|Any CPU {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x64.Build.0 = Debug|Any CPU {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x86.ActiveCfg = Debug|Any CPU diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 4ebff16a9..cb8e79d63 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Text; using System.Threading.Tasks; @@ -74,7 +74,7 @@ namespace Flow.Launcher Http.API = API; Http.Proxy = _settings.Proxy; - await PluginManager.InitializePlugins(API); + await PluginManager.InitializePluginsAsync(API); var window = new MainWindow(_settings, _mainVM); Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}"); @@ -104,14 +104,22 @@ namespace Flow.Launcher }); } - private void AutoStartup() { - if (_settings.StartFlowLauncherOnSystemStartup) + // we try to enable auto-startup on first launch, or reenable if it was removed + // but the user still has the setting set + if (_settings.StartFlowLauncherOnSystemStartup && !Helper.AutoStartup.IsEnabled) { - if (!SettingWindow.StartupSet()) + try { - SettingWindow.SetStartup(); + Helper.AutoStartup.Enable(); + } + catch (Exception e) + { + // 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); } } } diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs index ab4047cec..110beeded 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs @@ -81,7 +81,7 @@ namespace Flow.Launcher } tbAction.Text = updateCustomHotkey.ActionKeyword; - ctlHotkey.SetHotkey(updateCustomHotkey.Hotkey, false); + _ = ctlHotkey.SetHotkeyAsync(updateCustomHotkey.Hotkey, false); update = true; lblAdd.Text = InternationalizationManager.Instance.GetTranslation("update"); } diff --git a/Flow.Launcher/Helper/AutoStartup.cs b/Flow.Launcher/Helper/AutoStartup.cs new file mode 100644 index 000000000..956324020 --- /dev/null +++ b/Flow.Launcher/Helper/AutoStartup.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Flow.Launcher.Infrastructure; +using Flow.Launcher.Infrastructure.Logger; +using Microsoft.Win32; + +namespace Flow.Launcher.Helper +{ + public class AutoStartup + { + private const string StartupPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"; + + public static bool IsEnabled + { + get + { + try + { + using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true); + var path = key?.GetValue(Constant.FlowLauncher) as string; + return path == Constant.ExecutablePath; + } + catch (Exception e) + { + Log.Error("AutoStartup", $"Ignoring non-critical registry error (querying if enabled): {e}"); + } + + return false; + } + } + + public static void Disable() + { + try + { + using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true); + key?.DeleteValue(Constant.FlowLauncher, false); + } + catch (Exception e) + { + Log.Error("AutoStartup", $"Failed to disable auto-startup: {e}"); + throw; + } + } + + internal static void Enable() + { + try + { + using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true); + key?.SetValue(Constant.FlowLauncher, Constant.ExecutablePath); + } + catch (Exception e) + { + Log.Error("AutoStartup", $"Failed to enable auto-startup: {e}"); + throw; + } + } + } +} diff --git a/Flow.Launcher/Helper/SingleInstance.cs b/Flow.Launcher/Helper/SingleInstance.cs index 1a1e6ec3c..d684596be 100644 --- a/Flow.Launcher/Helper/SingleInstance.cs +++ b/Flow.Launcher/Helper/SingleInstance.cs @@ -385,27 +385,5 @@ namespace Flow.Launcher.Helper } #endregion - - #region Private Classes - - /// - /// Remoting service class which is exposed by the server i.e the first instance and called by the second instance - /// to pass on the command line arguments to the first instance and cause it to activate itself. - /// - private class IPCRemoteService : MarshalByRefObject - { - - /// - /// Remoting Object's ease expires after every 5 minutes by default. We need to override the InitializeLifetimeService class - /// to ensure that lease never expires. - /// - /// Always null. - public override object InitializeLifetimeService() - { - return null; - } - } - - #endregion } } diff --git a/Flow.Launcher/Helper/WindowsInteropHelper.cs b/Flow.Launcher/Helper/WindowsInteropHelper.cs index f1e8b2099..4811eb224 100644 --- a/Flow.Launcher/Helper/WindowsInteropHelper.cs +++ b/Flow.Launcher/Helper/WindowsInteropHelper.cs @@ -69,7 +69,7 @@ namespace Flow.Launcher.Helper //get current active window IntPtr hWnd = GetForegroundWindow(); - if (hWnd != null && !hWnd.Equals(IntPtr.Zero)) + if (!hWnd.Equals(IntPtr.Zero)) { //if current active window is NOT desktop or shell if (!(hWnd.Equals(HWND_DESKTOP) || hWnd.Equals(HWND_SHELL))) @@ -98,7 +98,7 @@ namespace Flow.Launcher.Helper { IntPtr hWndDesktop = FindWindowEx(hWnd, IntPtr.Zero, "SHELLDLL_DefView", null); hWndDesktop = FindWindowEx(hWndDesktop, IntPtr.Zero, "SysListView32", "FolderView"); - if (hWndDesktop != null && !hWndDesktop.Equals(IntPtr.Zero)) + if (!hWndDesktop.Equals(IntPtr.Zero)) { return false; } @@ -160,4 +160,4 @@ namespace Flow.Launcher.Helper public int Bottom; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index bc437d862..7e07695d8 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -65,11 +65,11 @@ namespace Flow.Launcher { await Task.Delay(500, token); if (!token.IsCancellationRequested) - await SetHotkey(hotkeyModel); + await SetHotkeyAsync(hotkeyModel); }); } - public async Task SetHotkey(HotkeyModel keyModel, bool triggerValidate = true) + public async Task SetHotkeyAsync(HotkeyModel keyModel, bool triggerValidate = true) { CurrentHotkey = keyModel; @@ -101,9 +101,9 @@ namespace Flow.Launcher } } - public void SetHotkey(string keyStr, bool triggerValidate = true) + public Task SetHotkeyAsync(string keyStr, bool triggerValidate = true) { - SetHotkey(new HotkeyModel(keyStr), triggerValidate); + return SetHotkeyAsync(new HotkeyModel(keyStr), triggerValidate); } private bool CheckHotkeyAvailability() => HotKeyMapper.CheckAvailability(CurrentHotkey); @@ -116,4 +116,4 @@ namespace Flow.Launcher tbMsg.Foreground = tbMsgForegroundColorOriginal; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher/Languages/da.xaml b/Flow.Launcher/Languages/da.xaml index a67894e97..6bcd3c0f7 100644 --- a/Flow.Launcher/Languages/da.xaml +++ b/Flow.Launcher/Languages/da.xaml @@ -28,6 +28,7 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). Start Flow Launcher ved system start + Error setting launch on startup Skjul Flow Launcher ved mistet fokus Vis ikke notifikationer om nye versioner Husk seneste position diff --git a/Flow.Launcher/Languages/de.xaml b/Flow.Launcher/Languages/de.xaml index e83c5bf6c..cf73baa22 100644 --- a/Flow.Launcher/Languages/de.xaml +++ b/Flow.Launcher/Languages/de.xaml @@ -28,6 +28,7 @@ Portabler Modus Speichern Sie alle Einstellungen und Benutzerdaten in einem Ordner (nützlich bei Verwendung mit Wechseldatenträgern oder Clouddiensten). Starte Flow Launcher bei Systemstart + Error setting launch on startup Verstecke Flow Launcher wenn der Fokus verloren geht Zeige keine Nachricht wenn eine neue Version vorhanden ist Merke letzte Ausführungsposition diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index a42f6058e..634f00bf1 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -30,6 +30,7 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). Start Flow Launcher on system startup + Error setting launch on startup Hide Flow Launcher when focus is lost Do not show new version notifications Remember last launch location diff --git a/Flow.Launcher/Languages/es-419.xaml b/Flow.Launcher/Languages/es-419.xaml index 302b932a0..8ce9f12cf 100644 --- a/Flow.Launcher/Languages/es-419.xaml +++ b/Flow.Launcher/Languages/es-419.xaml @@ -28,6 +28,7 @@ Modo portable Almacena todos los ajustes y datos de usuario en una sola carpeta (útil cuando se utiliza con unidades extraíbles o servicios en la nube). Iniciar Flow Launcher al arrancar el sistema + Error setting launch on startup Ocultar Flow Launcher cuando se pierde el enfoque No mostrar notificaciones de nuevas versiones Recordar última ubicación de inicio diff --git a/Flow.Launcher/Languages/es.xaml b/Flow.Launcher/Languages/es.xaml index f3d322206..d30cd16c9 100644 --- a/Flow.Launcher/Languages/es.xaml +++ b/Flow.Launcher/Languages/es.xaml @@ -1,13 +1,13 @@  - No se ha podido registrar la tecla de acceso directo: {0} + No se ha podido registrar el atajo de teclado: {0} No se ha podido iniciar {0} Formato de archivo del plugin Flow Launcher no válido Establecer como primer resultado en esta consulta Cancelar como primer resultado en esta consulta Ejecutar consulta: {0} - Última ejecución: {0} + Hora de la última ejecución: {0} Abrir Configuración Acerca de @@ -19,52 +19,53 @@ Archivo Carpeta Texto - Modo de juego - Suspender el uso de atajos de teclado. + Modo Juego + Suspende el uso de atajos de teclado. - Opciones de Flow Launcher + Configuración de Flow Launcher General - Modo portable - Guarda todos los ajustes y datos de usuario en una carpeta (útil cuando se utiliza con unidades extraíbles o servicios en la nube). + Modo Portable + Guarda toda la configuración y datos de usuario en una carpeta (Útil cuando se utiliza con unidades extraíbles o servicios en la nube). Cargar Flow Launcher al iniciar el sistema - Ocultar Flow Launcher cuando se pierda el foco - No mostrar notificaciones de versiones nuevas - Recordar última posición del Launcher + Error de configuración de arranque al iniciar + Ocultar Flow Launcher cuando se pierde el foco + No mostrar notificaciones de nuevas versiones + Recordar última posición de Flow Launcher Idioma - Comportamiento de la consulta previa - Mostrar/Ocultar resultados anteriores cuando Flow Launcher es reactivado. - Preservar última consulta - Seleccionar última consulta - Limpiar última consulta - Resultados máximos mostrados - Ignorar atajos de teclado en pantalla completa - Deshabilita la activación de Flow Launcher cuando una aplicación de pantalla completa está activa (Recomendado para juegos). - Gestor de archivos por defecto - Selecciona el gestor de archivos a utilizar al abrir carpetas. - Navegador web por defecto - Ajuste para Nueva Pestaña, Nueva Ventana y Modo privado. + Estilo de la última consulta + Muestra/Oculta resultados anteriores cuando Flow Launcher es reactivado. + Mantener la última consulta + Seleccionar la última consulta + Limpiar la última consulta + Número máximo de resultados mostrados + Ignorar atajos de teclado en modo pantalla completa + Desactiva Flow Launcher cuando una aplicación de pantalla completa está activa (Recomendado para juegos). + Administrador de archivos predeterminado + Selecciona el administrador de archivos que se desea utilizar para abrir la carpeta. + Navegador web predeterminado + Configuración para Nueva Pestaña, Nueva Ventana, Modo Privado. Carpeta de Python Actualización automática Seleccionar Ocultar Flow Launcher al inicio - Ocultar icono de la bandeja - Precisión de búsqueda - Establece la puntuación mínima requerida en la comparación con los resultados. + Ocultar icono de la bandeja del sistema + Precisión en la búsqueda de consultas + Cambia la puntuación mínima requerida para la coincidencia de los resultados. Utilizar Pinyin - Permite usar Pinyin para la búsqueda. Pinyin es el sistema estándar de ortografía romanizada para traducir chino - El efecto de sombra no se puede usar si el tema actual hace uso del efecto de desenfoque + Permite utilizar Pinyin para la búsqueda. Pinyin es el sistema estándar de ortografía romanizado para traducir chino + El efecto de sombra no está permitido mientras el tema actual tenga el efecto de desenfoque activado Complemento Buscar más complementos - On - Off - Ajuste de palabra clave + Activado + Desactivado + Configuración de la palabra clave de acción Palabra clave de acción Palabra clave de acción actual - Nueva palabra clave de la acción - Cambiar palabras clave de la acción + Nueva palabra clave de acción + Cambia las palabras clave de acción Prioridad actual Nueva prioridad Prioridad @@ -78,7 +79,7 @@ Tienda de complementos - Recargar + Refrescar Instalar @@ -86,12 +87,12 @@ Galería de temas Cómo crear un tema Hola - Fuente de la caja de búsqueda - Fuente de los resultados - Modo ventana + Fuente del texto del cuadro de consulta + Fuente del texto de los resultados + Modo Ventana Opacidad El tema {0} no existe, activando el tema por defecto - Fallo al cargar el tema {0}, activando el tema por defecto + Fallo al cargar el tema {0}, activando el tema predeterminado Carpeta de temas Abrir carpeta de temas Esquema de colores @@ -99,30 +100,30 @@ Claro Oscuro Efecto de sonido - Reproducir un pequeño sonido cuando se abre el cuadro de búsqueda + Reproduce un pequeño sonido cuando se abre el cuadro de búsqueda Animación - Usar animación en la IU + Usar animación en la Interfaz de Usuario Atajo de teclado Atajo de teclado de Flow Launcher Introduzca el atajo de teclado para mostrar/ocultar Flow Launcher. - Tecla modificadora de apertura de resultado + Tecla modificadora para abrir resultado Seleccione una tecla modificadora para abrir el resultado seleccionado con el teclado. Mostrar atajo de teclado - Mostrar atajo de apertura en los resultados. - Atajos de búsqueda personalizada + Muestra atajo de teclado de selección junto a los resultados. + Atajo de teclado de consulta personalizada Consulta - Borrar + Eliminar Editar Añadir Por favor, seleccione un elemento - ¿Estás seguro de que quieres eliminar el atajo de teclado {0}? - Efecto de sombra de ventana + ¿Está seguro que desea eliminar el atajo de teclado del complemento {0}? + Efecto de sombra de la ventana de consultas El efecto de sombra hace un uso sustancial de la GPU. No se recomienda para ordenadores de rendimiento limitado. - Tamaño de ancho de ventana + Tamaño del ancho de la ventana Usar iconos Segoe Fluent - Usar iconos Segoe Fluent para resultados de búsqueda donde estén soportados + Usa iconos Segoe Fluent para los resultados de la consulta cuando sean compatibles Proxy HTTP @@ -133,76 +134,76 @@ Contraseña Probar proxy Guardar - El campo servidor no puede estar vacío + El campo del servidor no puede estar vacío El campo puerto no puede estar vacío - Formato de puerto incorrecto + Formato de puerto no válido Configuración del proxy guardada correctamente Proxy configurado correctamente - Conexión con el proxy fallida + La conexión con el proxy ha fallado Acerca de Sitio web GitHub - Docs + Documentación Versión - Has activado Flow Launcher {0} veces + Ha activado Flow Launcher {0} veces Buscar actualizaciones La nueva versión {0} está disponible, ¿desea reiniciar Flow Launcher para actualizar? - Falló la comprobación de actualizaciones, compruebe su conexión y cambie la configuración de proxy a api.github.com. + La comprobación de las actualizaciones ha fallado, por favor, compruebe la configuración de su proxy y conexión a api.github.com. - Falló la descarga de actualizaciones, por favor compruebe su conexión y cambie el proxy a github-cloud.s3.amazonaws.com, - o vaya a https://github.com/Flow-Launcher/Flow.Launcher/releases para descargar actualizaciones manualmente. + La descarga de las actualizaciones ha fallado, por favor, compruebe la configuración de su proxy y conexión a github-cloud.s3.amazonaws.com, + o diríjase a https://github.com/Flow-Launcher/Flow.Launcher/releases para descargar actualizaciones manualmente. Notas de la versión Consejos de uso Herramientas de desarrolador Carpeta de configuración - Carpeta de logs + Carpeta de registros Asistente - Seleccionar gestor de archivos - Por favor, especifique la ubicación del gestor de archivos que utilice y añada argumentos si es necesario. El argumento por defecto es "%d", introduciendo una ruta en esa ubicación. Por ejemplo, si se requiere un comando como "totalcmd.exe /A c:\windows", el argumento es /A "%d". - "%f" es un argumento que representa la ruta del archivo. Se utiliza para especificar el nombre de archivo/carpeta al abrir una ubicación específica con gestores de archivos de terceros. Este argumento sólo está disponible en el elemento "Arg para archivo". Si el gestor de archivos no tiene esa función, puede utilizar "%d". - Gestor de archivos - Nombre de perfil - Ruta del gestor de archivos - Arg para la carpeta - Arg para archivo + Seleccionar administrador de archivos + Por favor, especifique la ubicación del administrador de archivos que desea utilizar y añada argumentos si es necesario. El argumento por defecto es "%d", introduciendo una ruta en esa ubicación. Por ejemplo, si se requiere un comando como "totalcmd.exe /A c:\windows", el argumento es /A "%d". + "%f" es un argumento que representa la ruta del archivo. Se utiliza para especificar el nombre de archivo/carpeta al abrir una ubicación específica con administradores de archivos de terceros. Este argumento sólo está disponible en el elemento "Argumentos del archivo". Si el administrador de archivos no tiene esa función, puede utilizar "%d". + Administrador de archivos + Nombre del perfil + Ruta del administrador de archivos + Argumentos de la carpeta + Argumentos del archivo - Navegador web por defecto - La configuración por defecto utiliza el navegador predeterminado del sistema operativo. Si se especifica por separado, Flow Launcher utiliza este otro navegador. + Navegador web predeterminado + La configuración por defecto utiliza el navegador predeterminado del sistema operativo. Si se indica uno específicamente, Flow Launcher utilizará este otro navegador. Navegador Nombre del navegador Ruta del navegador Nueva ventana - Nueva Pestaña + Nueva pestaña Modo privado - Cambiar prioridad - A mayor número, más alto aparecerá el resultado. Intentalo con 5. Si quieres que los resultados aparezcan más abajo que los de cualquier otro complemento, usa un número negativo - ¡Por favor, proporcione un entero válido para la prioridad! + Cambiar la prioridad + A mayor número, más alto aparecerá el resultado. Inténtelo con 5. Si quiere que los resultados aparezcan más abajo que los de cualquier otro complemento, utilice un número negativo + ¡Por favor, proporcione un número entero válido para la prioridad! - Palabra clave de acción antigua - Palabra clave de acción nueva + Antigua palabra clave de acción + Nueva palabra clave de acción Cancelar - Listo + Aceptar No se puede encontrar el complemento especificado La nueva palabra clave de acción no puede estar vacía Esta nueva palabra clave de acción ya está asignada a otro complemento, por favor elija una diferente - Éxito - Completado correctamente - Introduce la palabra clave que deseas utilizar para iniciar el complemento. Utiliza * si no deseas especificar ninguna, y el complemento se activará sin ninguna palabra clave. + Correcto + Finalizado correctamente + Introduzca la palabra clave que desea utilizar para iniciar el complemento. Utilice * si no desea especificar ninguna, y el complemento se activará sin ninguna palabra clave. - Atajos de búsqueda personalizada - Presione el atajo de teclado personalizado para iniciar automáticamente la búsqueda especificada. + Atajo de teclado de consulta personalizada + Pulse el atajo de teclado personalizado para insertar automáticamente la consulta especificada. Vista previa - El atajo de teclado no está disponible, por favor selecciona uno nuevo + El atajo de teclado no está disponible, por favor seleccione uno nuevo Atajo de teclado de complemento no válido Actualizar @@ -211,66 +212,66 @@ Versión - Fecha - Por favor, díganos cómo falló la aplicación para que podamos arreglarla + Hora + Por favor, informe del fallo de la aplicación para poder solucionarlo Enviar informe Cancelar General Excepciones Tipo de excepción Origen - Traza de Pila + Rastreo de Pila Enviando Informe enviado correctamente - No se pudo enviar el informe + No se ha podido enviar el informe Flow Launcher ha tenido un error - Por favor, espera... + Por favor espere... Comprobando actualizaciones - Ya tienes la versión más reciente de Flow Launcher + Ya tiene la última versión de Flow Launcher Actualización encontrada Actualizando... - Flow Launcher no pudo migrar los datos de su perfil de usuario de la antigua versión a la nueva. - Por favor, mueve manualmente la carpeta de datos de tu perfil de {0} a {1} + Flow Launcher no pudo mover sus datos de perfil de usuario a la nueva versión de actualización. + Por favor, mueva manualmente la carpeta de datos de su perfil de {0} a {1} Nueva actualización La nueva versión {0} de Flow Launcher está disponible - Ocurrió un error mientras se trataba de actualizar + Se ha producido un error al intentar instalar actualizaciones de software Actualizar Cancelar - Actualización fallida - Compruebe su conexión e intente cambiar la configuración del proxy a github-cloud.s3.amazonaws.com. + La actualización ha fallado + Compruebe su conexión e intente actualizar la configuración del proxy a github-cloud.s3.amazonaws.com. Esta actualización reiniciará Flow Launcher - Los siguientes archivos serán actualizados + Se actualizarán los siguientes archivos Actualizar archivos - Modificar descripción + Actualizar descripción Omitir Bienvenido a Flow Launcher - Hola, ¡Esta es la primera vez que ejecutas Flow Launcher! - Antes de comenzar, este asistente te ayudará a configurar Flow Launcher. Puedes saltártelo si quieres. Por favor, elige un idioma - Busca y ejecuta los archivos y aplicaciones en tu equipo - Busca cualquier cosa desde aplicaciones, archivos, marcadores, YouTube, Twitter y mucho más. Todo desde la comodidad de tu teclado sin tocar el ratón. - Flow Launcher se inicia con el atajo de teclado de abajo, el cual puedes probar ya mismo. Para cambiarlo, haz clic en la caja de texto y presiona la nueva combinación de teclas. - Atajo de teclado + Hola, ¡Esta es la primera vez que ejecuta Flow Launcher! + Antes de empezar, este asistente le ayudará a configurar Flow Launcher. Puede omitirlo si lo desea. Por favor, elija un idioma + Busque y ejecute todos los archivos y aplicaciones del equipo + Busque todo, desde aplicaciones, archivos, marcadores, YouTube, Twitter y más. Todo desde la comodidad del teclado y sin tener que tocar el ratón. + Flow Launcher se inicia con el atajo de teclado que se muestra a continuación, anímese y pruébelo ahora. Para cambiarlo, haga clic en la caja de texto y presione la nueva combinación de teclas. + Atajos de teclado Palabra clave de acción y comandos - Busca en la web, inicia aplicaciones o ejecuta varias funciones usando complementos de Flow Launcher. Algunas funciones comienzan con una palabra clave de acción, y si es necesario, pueden ser usadas sin ellas. Prueba las búsquedas de abajo en Flow Launcher. + Busque en la web, inicie aplicaciones o ejecute diversas funciones mediante los complementos de Flow Launcher. Algunas funciones comienzan con una palabra clave de acción y, si es necesario, pueden utilizarse sin ellas. Pruebe las siguientes consultas en Flow Launcher. Iniciemos Flow Launcher Terminado. Disfruta de Flow Launcher. No olvides el atajo de teclado para empezar :) Atrás / Menú contextual - Navegación de elementos + Navegación entre elementos Abrir menú contextual Abrir carpeta contenedora Ejecutar como administrador - Historial de búsquedas + Historial de consultas Volver al resultado en menú contextual Autocompletar Abrir / Ejecutar elemento seleccionado @@ -278,7 +279,7 @@ Recargar datos del complemento El tiempo - El tiempo en Google + El tiempo en los resultados de Google > ping 8.8.8.8 Comando de terminal Bluetooth diff --git a/Flow.Launcher/Languages/fr.xaml b/Flow.Launcher/Languages/fr.xaml index 1fc24ee11..cbcd84307 100644 --- a/Flow.Launcher/Languages/fr.xaml +++ b/Flow.Launcher/Languages/fr.xaml @@ -28,6 +28,7 @@ Mode Portable Stocker tous les paramètres et données utilisateur dans un seul dossier (Pratique en cas d'utilisation de disques amovibles ou de services cloud). Lancer Flow Launcher au démarrage du système + Error setting launch on startup Cacher Flow Launcher lors de la perte de focus Ne pas afficher les notifications lors d'une nouvelle version Se souvenir du dernier emplacement de la fenêtre @@ -42,16 +43,16 @@ Désactiver l'activation de Flow Launcher lorsqu'une application en plein écran est active (Recommandé pour les jeux). Gestionnaire de fichiers par défaut Select the file manager to use when opening the folder. - Default Web Browser + Navigateur web par défaut Setting for New Tab, New Window, Private Mode. Répertoire de Python Mettre à jour automatiquement Sélectionner Cacher Flow Launcher au démarrage - Hide tray icon + Masquer icône du plateau Query Search Precision Changes minimum match score required for results. - Should Use Pinyin + Devrait utiliser le pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese Shadow effect is not allowed while current theme has blur effect enabled @@ -173,7 +174,7 @@ Default Web Browser The default setting follows the OS default browser setting. If specified separately, flow uses that browser. - Browser + Navigateur Browser Name Browser Path New Window @@ -279,9 +280,9 @@ Weather Weather in Google Result > ping 8.8.8.8 - Shell Command + Commande Shell Bluetooth - Bluetooth in Windows Settings + Bluetooth dans les Paramètres de Windows sn Sticky Notes diff --git a/Flow.Launcher/Languages/it.xaml b/Flow.Launcher/Languages/it.xaml index 42ad82b7d..04655c226 100644 --- a/Flow.Launcher/Languages/it.xaml +++ b/Flow.Launcher/Languages/it.xaml @@ -28,6 +28,7 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). Avvia Wow all'avvio di Windows + Error setting launch on startup Nascondi Flow Launcher quando perde il focus Non mostrare le notifiche per una nuova versione Ricorda l'ultima posizione di avvio del launcher diff --git a/Flow.Launcher/Languages/ja.xaml b/Flow.Launcher/Languages/ja.xaml index b4e687035..e34c615b7 100644 --- a/Flow.Launcher/Languages/ja.xaml +++ b/Flow.Launcher/Languages/ja.xaml @@ -28,6 +28,7 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). スタートアップ時にFlow Launcherを起動する + Error setting launch on startup フォーカスを失った時にFlow Launcherを隠す 最新版が入手可能であっても、アップグレードメッセージを表示しない 前回のランチャーの位置を記憶 diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml index 094985b3a..a5ad2647f 100644 --- a/Flow.Launcher/Languages/ko.xaml +++ b/Flow.Launcher/Languages/ko.xaml @@ -16,9 +16,9 @@ 복사하기 잘라내기 붙여넣기 - File - Folder - Text + 파일 + 폴더 + 텍스트 게임 모드 단축키 사용을 일시중단합니다. @@ -28,6 +28,7 @@ 포터블 모드 모든 설정이 폴더안에 들어갑니다. USB 드라이브나 클라우드로 사용 가능합니다. 시스템 시작 시 Flow Launcher 실행 + Error setting launch on startup 포커스 잃으면 Flow Launcher 숨김 새 버전 알림 끄기 마지막 실행 위치 기억 @@ -85,7 +86,7 @@ 테마 테마 갤러리 테마 제작 안내 - Hi There + 안녕하세요. 쿼리 상자 글꼴 결과 항목 글꼴 윈도우 모드 @@ -177,8 +178,8 @@ 브라우저 브라우저 이름 브라우저 경로 - New Window - New Tab + 새 창 + 새 탭 사생활 보호 모드 @@ -243,7 +244,7 @@ 업데이트 취소 업데이트 실패 - Check your connection and try updating proxy settings to github-cloud.s3.amazonaws.com. + 연결을 확인하고 프록시 설정을 github-cloud.s3.amazonaws.com으로 업데이트해 보십시오. 업데이트를 위해 Flow Launcher를 재시작합니다. 아래 파일들이 업데이트됩니다. 업데이트 파일 diff --git a/Flow.Launcher/Languages/nb.xaml b/Flow.Launcher/Languages/nb.xaml index d85ab98da..52885ea47 100644 --- a/Flow.Launcher/Languages/nb.xaml +++ b/Flow.Launcher/Languages/nb.xaml @@ -28,6 +28,7 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). Start Flow Launcher on system startup + Error setting launch on startup Hide Flow Launcher when focus is lost Do not show new version notifications Remember last launch location diff --git a/Flow.Launcher/Languages/nl.xaml b/Flow.Launcher/Languages/nl.xaml index 715e934d5..1223df76f 100644 --- a/Flow.Launcher/Languages/nl.xaml +++ b/Flow.Launcher/Languages/nl.xaml @@ -28,6 +28,7 @@ Draagbare Modus Alle instellingen en gebruikersgegevens opslaan in één map (Nuttig bij het gebruik van verwijderbare schijven of cloud services). Start Flow Launcher als systeem opstart + Error setting launch on startup Verberg Flow Launcher als focus verloren is Laat geen nieuwe versie notificaties zien Herinner laatste opstart locatie diff --git a/Flow.Launcher/Languages/pl.xaml b/Flow.Launcher/Languages/pl.xaml index 8868af59d..901083f07 100644 --- a/Flow.Launcher/Languages/pl.xaml +++ b/Flow.Launcher/Languages/pl.xaml @@ -12,22 +12,23 @@ Ustawienia O programie Wyjdź - Close + Zamknij Copy - Cut - Paste + Wytnij + Wklej File Folder Text - Game Mode - Suspend the use of Hotkeys. + Tryb grania + Wstrzymaj używanie skrótów. Ustawienia Flow Launcher Ogólne - Portable Mode - Store all settings and user data in one folder (Useful when used with removable drives or cloud services). + Tryb przenośny + Przechowuj wszystkie ustawienia i dane użytkownika w jednym folderze (Przydatne, gdy używane na dyskach wymiennych lub usługach chmurowych). Uruchamiaj Flow Launcher przy starcie systemu + Error setting launch on startup Ukryj okno Flow Launcher kiedy przestanie ono być aktywne Nie pokazuj powiadomienia o nowej wersji Zapamiętaj ostatnią pozycję okna @@ -39,16 +40,16 @@ Empty last Query Maksymalna liczba wyników Ignoruj skróty klawiszowe w trybie pełnego ekranu - Disable Flow Launcher activation when a full screen application is active (Recommended for games). - Default File Manager - Select the file manager to use when opening the folder. - Default Web Browser - Setting for New Tab, New Window, Private Mode. + Wyłącz aktywowanie Flow Launcher, gdy uruchomiona jest aplikacja pełnoekranowa (Zalecane dla gier). + Domyślny menedżer plików + Wybierz menedżer plików używany do otwierania folderów. + Domyślna przeglądarka + Ustawienie dla nowej karty, nowego okna i trybu prywatnego. Folder biblioteki Python Automatyczne aktualizacje Wybierz Uruchamiaj Flow Launcher zminimalizowany - Hide tray icon + Ukryj ikonę zasobnika Query Search Precision Changes minimum match score required for results. Should Use Pinyin @@ -172,7 +173,7 @@ Arg For File - Default Web Browser + Domyślna przeglądarka The default setting follows the OS default browser setting. If specified separately, flow uses that browser. Browser Browser Name diff --git a/Flow.Launcher/Languages/pt-br.xaml b/Flow.Launcher/Languages/pt-br.xaml index ff69d2965..d23c24e7f 100644 --- a/Flow.Launcher/Languages/pt-br.xaml +++ b/Flow.Launcher/Languages/pt-br.xaml @@ -28,6 +28,7 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). Iniciar Flow Launcher com inicialização do sistema + Error setting launch on startup Esconder Flow Launcher quando foco for perdido Não mostrar notificações de novas versões Lembrar última localização de lançamento diff --git a/Flow.Launcher/Languages/pt-pt.xaml b/Flow.Launcher/Languages/pt-pt.xaml index 1e2847a55..c78519966 100644 --- a/Flow.Launcher/Languages/pt-pt.xaml +++ b/Flow.Launcher/Languages/pt-pt.xaml @@ -28,6 +28,7 @@ Modo portátil Guardar todas as definições e dados do utilizador numa pasta (indicado se utilizar discos amovíveis ou serviços cloud) Iniciar Flow Launcher ao arrancar o sistema + Erro ao definir para iniciar ao arrancar Ocultar Flow Launcher ao perder o foco Não notificar acerca de novas versões Memorizar localização anterior diff --git a/Flow.Launcher/Languages/ru.xaml b/Flow.Launcher/Languages/ru.xaml index 55fa3b02d..691d37538 100644 --- a/Flow.Launcher/Languages/ru.xaml +++ b/Flow.Launcher/Languages/ru.xaml @@ -28,6 +28,7 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). Запускать Flow Launcher при запуске системы + Error setting launch on startup Скрывать Flow Launcher, если потерян фокуc Не отображать сообщение об обновлении, когда доступна новая версия Запомнить последнее место запуска diff --git a/Flow.Launcher/Languages/sk.xaml b/Flow.Launcher/Languages/sk.xaml index 1ce0f3f45..20b259f9f 100644 --- a/Flow.Launcher/Languages/sk.xaml +++ b/Flow.Launcher/Languages/sk.xaml @@ -28,6 +28,7 @@ Prenosný režim Uloží všetky nastavenia a používateľské údaje do jedného priečinka (Užitočné pri vymeniteľných diskoch a cloudových službách). Spustiť Flow Launcher pri spustení systému + Chybné nastavenie spustenia pri spustení Schovať Flow Launcher po strate fokusu Nezobrazovať upozornenia na novú verziu Zapamätať si posledné umiestnenie diff --git a/Flow.Launcher/Languages/sr.xaml b/Flow.Launcher/Languages/sr.xaml index 0515e3e67..b15bbc194 100644 --- a/Flow.Launcher/Languages/sr.xaml +++ b/Flow.Launcher/Languages/sr.xaml @@ -28,6 +28,7 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). Pokreni Flow Launcher pri podizanju sistema + Error setting launch on startup Sakri Flow Launcher kada se izgubi fokus Ne prikazuj obaveštenje o novoj verziji Zapamti lokaciju poslednjeg pokretanja diff --git a/Flow.Launcher/Languages/tr.xaml b/Flow.Launcher/Languages/tr.xaml index 3318cdc14..ec609de37 100644 --- a/Flow.Launcher/Languages/tr.xaml +++ b/Flow.Launcher/Languages/tr.xaml @@ -28,6 +28,7 @@ Taşınabilir Mod Tüm ayarları ve kullanıcı verilerini tek bir klasörde saklayın (Çıkarılabilir sürücüler veya bulut hizmetleri ile kullanıldığında kullanışlıdır). Flow Launcher'u başlangıçta başlat + Error setting launch on startup Odak pencereden ayrıldığında Flow Launcher'u gizle Güncelleme bildirimlerini gösterme Pencere konumunu hatırla diff --git a/Flow.Launcher/Languages/uk-UA.xaml b/Flow.Launcher/Languages/uk-UA.xaml index b7e9525ab..c2d5302a6 100644 --- a/Flow.Launcher/Languages/uk-UA.xaml +++ b/Flow.Launcher/Languages/uk-UA.xaml @@ -28,6 +28,7 @@ Портативний режим Зберігати всі налаштування і дані користувача в одній теці (буде корисно при видаленні дисків або хмарних сервісах). Запускати Flow Launcher при запуску системи + Error setting launch on startup Сховати Flow Launcher, якщо втрачено фокус Не повідомляти про доступні нові версії Запам'ятати останнє місце запуску diff --git a/Flow.Launcher/Languages/zh-cn.xaml b/Flow.Launcher/Languages/zh-cn.xaml index 5dee1cc75..b3bd5fd1f 100644 --- a/Flow.Launcher/Languages/zh-cn.xaml +++ b/Flow.Launcher/Languages/zh-cn.xaml @@ -28,6 +28,7 @@ 便携模式 将所有设置和用户数据存储在一个文件夹中 (可用于可移除驱动器或云服务)。 开机自启 + Error setting launch on startup 失去焦点时自动隐藏 Flow Launcher 不显示新版本提示 记住上次启动位置 @@ -39,7 +40,7 @@ 清空上次搜索关键字 最大结果显示个数 全屏模式下忽略热键 - 当全屏应用程序激活时禁用快捷键。 + 当全屏应用程序激活时禁用快捷键(建议游戏时打开)。 默认文件管理器 选择打开文件夹时要使用的文件管理器。 默认浏览器 @@ -148,7 +149,7 @@ 版本 你已经激活了 Flow Launcher {0} 次 检查更新 - 发现新版本 {0} , 请重启 Flow Launcher + 发现新版本 {0}, 请重启 Flow Launcher 下载更新失败,请检查您与 api.github.com 的连接状态或检查代理设置 下载更新失败,请检查您与 github-cloud.s3.amazonaws.com 的连接状态或检查代理设置, diff --git a/Flow.Launcher/Languages/zh-tw.xaml b/Flow.Launcher/Languages/zh-tw.xaml index 373ef2239..fea505526 100644 --- a/Flow.Launcher/Languages/zh-tw.xaml +++ b/Flow.Launcher/Languages/zh-tw.xaml @@ -28,6 +28,7 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). 開機時啟動 + Error setting launch on startup 失去焦點時自動隱藏 Flow Launcher 不顯示新版本提示 記住上次啟動位置 @@ -38,7 +39,7 @@ Select last Query Empty last Query 最大結果顯示個數 - 全螢幕模式下忽略熱鍵 + 全螢幕模式下忽略快捷鍵 全螢幕模式下停用快捷鍵(推薦用於遊戲時)。 預設檔案管理器 選擇打開資料夾時要使用的檔案管理器。 @@ -77,7 +78,7 @@ - 插件商店 + 外掛商店 重新整理 安裝 @@ -117,7 +118,7 @@ 編輯 新增 請選擇一項 - 確定要刪除外掛 {0} 的熱鍵嗎? + 確定要刪除外掛 {0} 的快捷鍵嗎? 查詢窗口陰影效果 陰影效果將佔用大量的 GPU 資源。如果你的電腦效能有限,不建議使用。 窗口寬度 @@ -168,8 +169,8 @@ 檔案管理器 檔案名稱 檔案管理器路徑 - Arg For Folder - Arg For File + 資料夾參數 + 檔案參數 預設瀏覽器 @@ -183,7 +184,7 @@ 更改優先度 - 數字越大,查詢結過會排在越前面。嘗試將其設定為 5。如果你希望查詢結果低於任何其他插件,請提供負數 + 數字越大,查詢結果會排在越前面。嘗試將其設定為 5。如果你希望查詢結果低於任何其他外掛,請提供負數 請為優先度提供一個有效的整數! @@ -199,15 +200,15 @@ 如果不想設定觸發關鍵字,可以使用*代替 - 自定義熱鍵查詢 + 自定義快捷鍵查詢 Press the custom hotkey to automatically insert the specified query. 預覽 - 熱鍵不存在,請設定一個新的熱鍵 + 快捷鍵不存在,請設定一個新的快捷鍵 外掛熱鍵無法使用 更新 - 熱鍵無法使用 + 快捷鍵無法使用 版本 @@ -254,28 +255,28 @@ 歡迎使用 Flow Launcher 你好,這是你第一次運行 Flow Launcher! 在開始之前,此嚮導將幫助設定 Flow Launcher。如果你想,你可以跳過這個。請選擇一種語言 - Search and run all files and applications on your PC - Search everything from applications, files, bookmarks, YouTube, Twitter and more. All from the comfort of your keyboard without ever touching the mouse. - Flow Launcher starts with the hotkey below, go ahead and try it out now. To change it, click on the input and press the desired hotkey on the keyboard. + 在PC上搜尋並執行所有文件和應用程式 + 只需使用鍵盤搜尋應用程式、文件、書籤、Youtube、Twitter等 + Flow Launcher 需要搭配快捷鍵使用,請馬上試試吧! 如果想更改它,請點擊"輸入"並輸入你想要的快捷鍵。 快捷鍵 - Action Keyword and Commands + 關鍵字與指令 Search the web, launch applications or run various functions through Flow Launcher plugins. Certain functions start with an action keyword, and if necessary, they can be used without action keywords. Try the queries below in Flow Launcher. - Let's Start Flow Launcher - Finished. Enjoy Flow Launcher. Don't forget the hotkey to start :) + 開始使用 Flow Launcher吧! + 大功告成! 別忘了使用快捷鍵以開始 :) - Back / Context Menu + 返回 / 快捷選單 Item Navigation - Open Context Menu - Open Contaning Folder + 打開選單 + 開啟檔案位置 以管理員身分執行 查詢歷史 Back to Result in Context Menu Autocomplete Open / Run Selected Item 開啟視窗設定 - 充新載入插件數據 + 重新載入外掛資料 天氣 Google 搜索的天氣結果 diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 0573a948b..2b7db38cf 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -517,6 +517,7 @@ namespace Flow.Launcher if (specialKeyState.CtrlPressed) { if (_viewModel.SelectedIsFromQueryResults() + && QueryTextBox.Text.Length > 0 && QueryTextBox.CaretIndex == QueryTextBox.Text.Length) { var queryWithoutActionKeyword = diff --git a/Flow.Launcher/Notification.cs b/Flow.Launcher/Notification.cs index 3f5565eeb..57c1e88f2 100644 --- a/Flow.Launcher/Notification.cs +++ b/Flow.Launcher/Notification.cs @@ -18,7 +18,7 @@ namespace Flow.Launcher } [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "")] - public static void Show(string title, string subTitle, string iconPath) + public static void Show(string title, string subTitle, string iconPath = null) { // Handle notification for win7/8/early win10 if (legacy) @@ -45,4 +45,4 @@ namespace Flow.Launcher msg.Show(title, subTitle, iconPath); } } -} \ No newline at end of file +} diff --git a/Flow.Launcher/Properties/AssemblyInfo.cs b/Flow.Launcher/Properties/AssemblyInfo.cs index 332bfac3f..b557656b1 100644 --- a/Flow.Launcher/Properties/AssemblyInfo.cs +++ b/Flow.Launcher/Properties/AssemblyInfo.cs @@ -4,4 +4,4 @@ using System.Windows; [assembly: ThemeInfo( ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly -)] \ No newline at end of file +)] diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 81f7a2389..5fef5499b 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -23,7 +23,6 @@ using System.Runtime.CompilerServices; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; using System.Collections.Concurrent; -using Flow.Launcher.Plugin.SharedCommands; using System.Diagnostics; namespace Flow.Launcher @@ -83,7 +82,7 @@ namespace Flow.Launcher ImageLoader.Save(); } - public Task ReloadAllPluginData() => PluginManager.ReloadData(); + public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync(); public void ShowMsgError(string title, string subTitle = "") => ShowMsg(title, subTitle, Constant.ErrorIcon, true); @@ -286,4 +285,4 @@ namespace Flow.Launcher #endregion } -} \ No newline at end of file +} diff --git a/Flow.Launcher/ReportWindow.xaml.cs b/Flow.Launcher/ReportWindow.xaml.cs index 6a9fd60e0..4899edc14 100644 --- a/Flow.Launcher/ReportWindow.xaml.cs +++ b/Flow.Launcher/ReportWindow.xaml.cs @@ -1,4 +1,5 @@ -using System; +using Flow.Launcher.Core.ExternalPlugins; +using System; using System.Diagnostics; using System.Globalization; using System.IO; @@ -22,13 +23,34 @@ namespace Flow.Launcher SetException(exception); } + private static string GetIssueUrl(string website) + { + if (!website.StartsWith("https://github.com")) + { + return website; + } + if(website.Contains("Flow-Launcher/Flow.Launcher")) + { + return Constant.Issue; + } + var treeIndex = website.IndexOf("tree", StringComparison.Ordinal); + return treeIndex == -1 ? $"{website}/issues/new" : $"{website[..treeIndex]}/issues/new"; + } + private void SetException(Exception exception) { string path = Log.CurrentLogDirectory; var directory = new DirectoryInfo(path); var log = directory.GetFiles().OrderByDescending(f => f.LastWriteTime).First(); - var paragraph = Hyperlink("Please open new issue in: ", Constant.Issue); + var websiteUrl = exception switch + { + FlowPluginException pluginException =>GetIssueUrl(pluginException.Metadata.Website), + _ => Constant.Issue + }; + + + var paragraph = Hyperlink("Please open new issue in: ", websiteUrl); paragraph.Inlines.Add($"1. upload log file: {log.FullName}\n"); paragraph.Inlines.Add($"2. copy below exception message"); ErrorTextbox.Document.Blocks.Add(paragraph); @@ -49,10 +71,12 @@ namespace Flow.Launcher var paragraph = new Paragraph(); paragraph.Margin = new Thickness(0); - var link = new Hyperlink { IsEnabled = true }; + var link = new Hyperlink + { + IsEnabled = true + }; link.Inlines.Add(url); link.NavigateUri = new Uri(url); - link.RequestNavigate += (s, e) => SearchWeb.OpenInBrowserTab(e.Uri.ToString()); link.Click += (s, e) => SearchWeb.OpenInBrowserTab(url); paragraph.Inlines.Add(textBeforeUrl); @@ -62,4 +86,4 @@ namespace Flow.Launcher return paragraph; } } -} +} \ No newline at end of file diff --git a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs index a433611f6..4f8d65378 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs +++ b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs @@ -27,7 +27,7 @@ namespace Flow.Launcher.Resources.Pages tbMsgTextOriginal = HotkeyControl.tbMsg.Text; tbMsgForegroundColorOriginal = HotkeyControl.tbMsg.Foreground; - HotkeyControl.SetHotkey(new Infrastructure.Hotkey.HotkeyModel(Settings.Hotkey), false); + HotkeyControl.SetHotkeyAsync(new Infrastructure.Hotkey.HotkeyModel(Settings.Hotkey), false); } private void HotkeyControl_OnGotFocus(object sender, RoutedEventArgs args) { diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index b96abca95..2c78f9bab 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -30,7 +30,7 @@ -