diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs index 455ee096d..89286dfb0 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs @@ -11,6 +11,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { internal class PythonEnvironment : AbstractPluginEnvironment { + private static readonly string ClassName = nameof(PythonEnvironment); + internal override string Language => AllowedLanguage.Python; internal override string EnvName => DataLocation.PythonEnvironmentName; @@ -39,9 +41,20 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments // Python 3.11.4 is no longer Windows 7 compatible. If user is on Win 7 and // uses Python plugin they need to custom install and use v3.8.9 - JTF.Run(() => DroplexPackage.Drop(App.python_3_11_4_embeddable, InstallPath)); + JTF.Run(async () => + { + try + { + await DroplexPackage.Drop(App.python_3_11_4_embeddable, InstallPath); - PluginsSettingsFilePath = ExecutablePath; + PluginsSettingsFilePath = ExecutablePath; + } + catch (System.Exception e) + { + API.ShowMsgError(API.GetTranslation("failToInstallPythonEnv")); + API.LogException(ClassName, "Failed to install Python environment", e); + } + }); } internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata) diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs index 12965286f..724ae20f4 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs @@ -11,6 +11,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { internal class TypeScriptEnvironment : AbstractPluginEnvironment { + private static readonly string ClassName = nameof(TypeScriptEnvironment); + internal override string Language => AllowedLanguage.TypeScript; internal override string EnvName => DataLocation.NodeEnvironmentName; @@ -34,9 +36,20 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s)); - JTF.Run(() => DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath)); + JTF.Run(async () => + { + try + { + await DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath); - PluginsSettingsFilePath = ExecutablePath; + PluginsSettingsFilePath = ExecutablePath; + } + catch (System.Exception e) + { + API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv")); + API.LogException(ClassName, "Failed to install TypeScript environment", e); + } + }); } internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata) diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs index 6960b79c9..6a32664a1 100644 --- a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs @@ -11,6 +11,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { internal class TypeScriptV2Environment : AbstractPluginEnvironment { + private static readonly string ClassName = nameof(TypeScriptV2Environment); + internal override string Language => AllowedLanguage.TypeScriptV2; internal override string EnvName => DataLocation.NodeEnvironmentName; @@ -34,9 +36,20 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments { FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s)); - JTF.Run(() => DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath)); + JTF.Run(async () => + { + try + { + await DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath); - PluginsSettingsFilePath = ExecutablePath; + PluginsSettingsFilePath = ExecutablePath; + } + catch (System.Exception e) + { + API.ShowMsgError(API.GetTranslation("failToInstallTypeScriptEnv")); + API.LogException(ClassName, "Failed to install TypeScript environment", e); + } + }); } internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata) diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 9d511297e..e9e5ee367 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -124,13 +124,9 @@ namespace Flow.Launcher.Core.Plugin API.GetTranslation("pluginsHaveErrored") : API.GetTranslation("pluginHasErrored"); - _ = Task.Run(() => - { - API.ShowMsgBox($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + - $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + - API.GetTranslation("referToLogs"), string.Empty, - MessageBoxButton.OK, MessageBoxImage.Warning); - }); + API.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + + $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + + API.GetTranslation("referToLogs")); } return plugins; diff --git a/Flow.Launcher.Core/Resource/AvailableLanguages.cs b/Flow.Launcher.Core/Resource/AvailableLanguages.cs index ecaecf646..5534ea172 100644 --- a/Flow.Launcher.Core/Resource/AvailableLanguages.cs +++ b/Flow.Launcher.Core/Resource/AvailableLanguages.cs @@ -17,6 +17,7 @@ namespace Flow.Launcher.Core.Resource public static Language German = new Language("de", "Deutsch"); public static Language Korean = new Language("ko", "한국어"); public static Language Serbian = new Language("sr", "Srpski"); + public static Language Serbian_Cyrillic = new Language("sr-Cyrl-RS", "Српски"); public static Language Portuguese_Portugal = new Language("pt-pt", "Português"); public static Language Portuguese_Brazil = new Language("pt-br", "Português (Brasil)"); public static Language Spanish = new Language("es", "Spanish"); @@ -47,6 +48,7 @@ namespace Flow.Launcher.Core.Resource German, Korean, Serbian, + Serbian_Cyrillic, Portuguese_Portugal, Portuguese_Brazil, Spanish, @@ -79,7 +81,8 @@ namespace Flow.Launcher.Core.Resource "da" => "System", "de" => "System", "ko" => "시스템", - "sr" => "Систем", + "sr" => "Sistem", + "sr-Cyrl-RS" => "Систем", "pt-pt" => "Sistema", "pt-br" => "Sistema", "es" => "Sistema", diff --git a/Flow.Launcher.Core/packages.lock.json b/Flow.Launcher.Core/packages.lock.json index 598662936..f5f81e874 100644 --- a/Flow.Launcher.Core/packages.lock.json +++ b/Flow.Launcher.Core/packages.lock.json @@ -161,8 +161,8 @@ }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "9.0.8", - "contentHash": "64oQBN8I8EOcyHGNt6+rlcqm4tlcKfqOpswtBCJZnpzBQ15L6IFKqjnbjbOWBpG9wKq9A/aC2LFSmSqplbwTYA==" + "resolved": "7.0.0", + "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ==" }, "Mono.Cecil": { "type": "Transitive", @@ -222,10 +222,10 @@ }, "System.Drawing.Common": { "type": "Transitive", - "resolved": "9.0.8", - "contentHash": "ineIJmF+yuBEHGft9R153J9kmBFaeEfFL5Put5nzENneRcGGPUb3MvM8zrZ5pERQ0jPPSE3Wqw/clXvOq2F/Kw==", + "resolved": "7.0.0", + "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==", "dependencies": { - "Microsoft.Win32.SystemEvents": "9.0.8" + "Microsoft.Win32.SystemEvents": "7.0.0" } }, "System.IO.Pipelines": { diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index 9f01d5d0f..9aa8f9577 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -74,7 +74,9 @@ all - + + + diff --git a/Flow.Launcher.Infrastructure/packages.lock.json b/Flow.Launcher.Infrastructure/packages.lock.json index 6786b0f49..ac00d0202 100644 --- a/Flow.Launcher.Infrastructure/packages.lock.json +++ b/Flow.Launcher.Infrastructure/packages.lock.json @@ -108,11 +108,11 @@ }, "System.Drawing.Common": { "type": "Direct", - "requested": "[9.0.8, )", - "resolved": "9.0.8", - "contentHash": "ineIJmF+yuBEHGft9R153J9kmBFaeEfFL5Put5nzENneRcGGPUb3MvM8zrZ5pERQ0jPPSE3Wqw/clXvOq2F/Kw==", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==", "dependencies": { - "Microsoft.Win32.SystemEvents": "9.0.8" + "Microsoft.Win32.SystemEvents": "7.0.0" } }, "ToolGood.Words.Pinyin": { @@ -156,8 +156,8 @@ }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "9.0.8", - "contentHash": "64oQBN8I8EOcyHGNt6+rlcqm4tlcKfqOpswtBCJZnpzBQ15L6IFKqjnbjbOWBpG9wKq9A/aC2LFSmSqplbwTYA==" + "resolved": "7.0.0", + "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ==" }, "Microsoft.Windows.SDK.Win32Docs": { "type": "Transitive", diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index a459e9ee6..2c9b8d4fd 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -57,7 +57,10 @@ namespace Flow.Launcher.Plugin /// for user on the plugin result. If autocomplete action for example is tab, pressing tab will have /// the default constructed autocomplete text (result's Title), or the text provided here if not empty. /// - /// When a value is not set, the will be used. + /// + /// When a value is not set, the will be used. + /// Please include the action keyword prefix when necessary because Flow does not prepend it automatically. + /// public string AutoCompleteText { get; set; } /// @@ -257,6 +260,17 @@ namespace Flow.Launcher.Plugin /// public bool ShowBadge { get; set; } = false; + /// + /// This holds the text which can be shown as a query suggestion. + /// + /// + /// When a value is not set, the will be used. + /// Do not include the action keyword prefix because Flow prepends it automatically. + /// If the it does not start with the query text, it will not be shown as a suggestion. + /// So make sure to set this value to start with the query text. + /// + public string QuerySuggestionText { get; set; } + /// /// Run this result, asynchronously /// @@ -307,7 +321,8 @@ namespace Flow.Launcher.Plugin Preview = Preview, AddSelectedCount = AddSelectedCount, RecordKey = RecordKey, - ShowBadge = ShowBadge + ShowBadge = ShowBadge, + QuerySuggestionText = QuerySuggestionText }; } diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 6e053db29..0360c761e 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -297,6 +297,7 @@ namespace Flow.Launcher }); } + [Conditional("RELEASE")] private static void AutoPluginUpdates() { _ = Task.Run(async () => diff --git a/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs b/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs index eb492e334..c2d7d016c 100644 --- a/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs +++ b/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs @@ -33,15 +33,39 @@ public class QuerySuggestionBoxConverter : IMultiValueConverter { var selectedResult = selectedItem.Result; var selectedResultActionKeyword = string.IsNullOrEmpty(selectedResult.ActionKeywordAssigned) ? "" : selectedResult.ActionKeywordAssigned + " "; - var selectedResultPossibleSuggestion = selectedResultActionKeyword + selectedResult.Title; - if (!selectedResultPossibleSuggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)) + string selectedResultPossibleSuggestion = null; + + // Firstly check if the result has QuerySuggestionText + if (!string.IsNullOrEmpty(selectedResult.QuerySuggestionText)) + { + selectedResultPossibleSuggestion = selectedResultActionKeyword + selectedResult.QuerySuggestionText; + + // If this QuerySuggestionText does not start with the queryText, set it to null + if (!selectedResultPossibleSuggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)) + { + selectedResultPossibleSuggestion = null; + } + } + + // Then check Title as suggestion + if (string.IsNullOrEmpty(selectedResultPossibleSuggestion)) + { + selectedResultPossibleSuggestion = selectedResultActionKeyword + selectedResult.Title; + + // If this QuerySuggestionText does not start with the queryText, set it to null + if (!selectedResultPossibleSuggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)) + { + selectedResultPossibleSuggestion = null; + } + } + + if (string.IsNullOrEmpty(selectedResultPossibleSuggestion)) return string.Empty; - // For AutocompleteQueryCommand. // When user typed lower case and result title is uppercase, we still want to display suggestion - selectedItem.QuerySuggestionText = queryText + selectedResultPossibleSuggestion.Substring(queryText.Length); + selectedItem.QuerySuggestionText = string.Concat(queryText, selectedResultPossibleSuggestion.AsSpan(queryText.Length)); // Check if Text will be larger than our QueryTextBox Typeface typeface = new Typeface(queryTextBox.FontFamily, queryTextBox.FontStyle, queryTextBox.FontWeight, queryTextBox.FontStretch); diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index bf6cb674e..a9694eba8 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -34,6 +34,10 @@ Please try again Unable to parse Http Proxy + + Failed to install TypeScript environment. Please try again later + Failed to install Python environment. Please try again later. + Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. Failed to unregister hotkey "{0}". Please try again or see log for details diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index e0ed105cf..38ca18147 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Net; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -379,24 +380,35 @@ namespace Flow.Launcher explorer.Start(); } } + catch (COMException ex) when (ex.ErrorCode == unchecked((int)0x80004004)) + { + /* + * The COMException with HResult 0x80004004 is E_ABORT (operation aborted). + * Shell APIs often return this when the operation is canceled or the shell cannot complete it cleanly. + * It most likely comes from Win32Helper.OpenFolderAndSelectFile(targetPath). + * Typical triggers: + * The target file/folder was deleted/moved between computing targetPath and the shell call. + * The folder is on an offline network/removable drive. + * Explorer is restarting/busy and aborts the request. + * A selection request to a new/closing Explorer window is canceled. + * Because it is commonly user- or environment-driven and not actionable, + * we should treat it as expected noise and ignore it to avoid bothering users. + */ + } catch (Win32Exception ex) when (ex.NativeErrorCode == 2) { LogError(ClassName, "File Manager not found"); - ShowMsgBox( - string.Format(GetTranslation("fileManagerNotFound"), ex.Message), + ShowMsgError( GetTranslation("fileManagerNotFoundTitle"), - MessageBoxButton.OK, - MessageBoxImage.Error + string.Format(GetTranslation("fileManagerNotFound"), ex.Message) ); } catch (Exception ex) { LogException(ClassName, "Failed to open folder", ex); - ShowMsgBox( - string.Format(GetTranslation("folderOpenError"), ex.Message), + ShowMsgError( GetTranslation("errorTitle"), - MessageBoxButton.OK, - MessageBoxImage.Error + string.Format(GetTranslation("folderOpenError"), ex.Message) ); } } @@ -424,11 +436,9 @@ namespace Flow.Launcher { var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window"; LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e); - ShowMsgBox( - GetTranslation("browserOpenError"), + ShowMsgError( GetTranslation("errorTitle"), - MessageBoxButton.OK, - MessageBoxImage.Error + GetTranslation("browserOpenError") ); } } diff --git a/Flow.Launcher/packages.lock.json b/Flow.Launcher/packages.lock.json index 9b1bc94a7..a4a3bcd6c 100644 --- a/Flow.Launcher/packages.lock.json +++ b/Flow.Launcher/packages.lock.json @@ -552,8 +552,8 @@ }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "9.0.8", - "contentHash": "64oQBN8I8EOcyHGNt6+rlcqm4tlcKfqOpswtBCJZnpzBQ15L6IFKqjnbjbOWBpG9wKq9A/aC2LFSmSqplbwTYA==" + "resolved": "7.0.0", + "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ==" }, "Mono.Cecil": { "type": "Transitive", @@ -677,10 +677,10 @@ }, "System.Drawing.Common": { "type": "Transitive", - "resolved": "9.0.8", - "contentHash": "ineIJmF+yuBEHGft9R153J9kmBFaeEfFL5Put5nzENneRcGGPUb3MvM8zrZ5pERQ0jPPSE3Wqw/clXvOq2F/Kw==", + "resolved": "7.0.0", + "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==", "dependencies": { - "Microsoft.Win32.SystemEvents": "9.0.8" + "Microsoft.Win32.SystemEvents": "7.0.0" } }, "System.Globalization": {