diff --git a/.github/workflows/default_plugins.yml b/.github/workflows/default_plugins.yml
index 59cedc1e8..d0ba62c17 100644
--- a/.github/workflows/default_plugins.yml
+++ b/.github/workflows/default_plugins.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 988548bee..9a09198fe 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -20,7 +20,7 @@ jobs:
NUGET_CERT_REVOCATION_MODE: offline
BUILD_NUMBER: ${{ github.run_number }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set Flow.Launcher.csproj version
id: update
uses: vers-one/dotnet-project-version-updater@v1.7
diff --git a/.github/workflows/release_pr.yml b/.github/workflows/release_pr.yml
index 451bf386c..65946755b 100644
--- a/.github/workflows/release_pr.yml
+++ b/.github/workflows/release_pr.yml
@@ -11,7 +11,7 @@ jobs:
update-pr:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
diff --git a/Directory.Build.props b/Directory.Build.props
index fa499273c..a5545af12 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,5 +1,7 @@
true
+
+ false
\ No newline at end of file
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 caec08ebf..5e9abc24c 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.7",
- "contentHash": "lFGY2aGgmMREPJEfOmZcA6v0CLjWVpcfNHRgqYMoSQhy80+GxhYqdW5xe+DCLrVqE1M7/0RpOkIo49/KH/cd/A=="
+ "resolved": "7.0.0",
+ "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ=="
},
"Mono.Cecil": {
"type": "Transitive",
@@ -222,10 +222,10 @@
},
"System.Drawing.Common": {
"type": "Transitive",
- "resolved": "9.0.7",
- "contentHash": "1k/Pk7hcM3vP2tfIRRS2ECCCN7ya+hvocsM1JMc4ZDCU6qw7yOuUmqmCDfgXZ4Q4FS6jass2EAai5ByKodDi0g==",
+ "resolved": "7.0.0",
+ "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==",
"dependencies": {
- "Microsoft.Win32.SystemEvents": "9.0.7"
+ "Microsoft.Win32.SystemEvents": "7.0.0"
}
},
"System.IO.Pipelines": {
@@ -262,7 +262,7 @@
"NLog": "[6.0.1, )",
"NLog.OutputDebugString": "[6.0.1, )",
"SharpVectors.Wpf": "[1.8.4.2, )",
- "System.Drawing.Common": "[9.0.7, )",
+ "System.Drawing.Common": "[7.0.0, )",
"ToolGood.Words.Pinyin": "[3.1.0.3, )"
}
},
diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
index 51b1d5175..c32c36248 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 87b4bb6da..abd250f7c 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.7, )",
- "resolved": "9.0.7",
- "contentHash": "1k/Pk7hcM3vP2tfIRRS2ECCCN7ya+hvocsM1JMc4ZDCU6qw7yOuUmqmCDfgXZ4Q4FS6jass2EAai5ByKodDi0g==",
+ "requested": "[7.0.0, )",
+ "resolved": "7.0.0",
+ "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==",
"dependencies": {
- "Microsoft.Win32.SystemEvents": "9.0.7"
+ "Microsoft.Win32.SystemEvents": "7.0.0"
}
},
"ToolGood.Words.Pinyin": {
@@ -156,8 +156,8 @@
},
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
- "resolved": "9.0.7",
- "contentHash": "lFGY2aGgmMREPJEfOmZcA6v0CLjWVpcfNHRgqYMoSQhy80+GxhYqdW5xe+DCLrVqE1M7/0RpOkIo49/KH/cd/A=="
+ "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 07cff6fd0..56b344789 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; }
+
///
/// List of hotkey IDs that are supported for this result.
/// Those hotkeys should be registered by IPluginHotkey interface.
@@ -314,6 +328,7 @@ namespace Flow.Launcher.Plugin
AddSelectedCount = AddSelectedCount,
RecordKey = RecordKey,
ShowBadge = ShowBadge,
+ QuerySuggestionText = QuerySuggestionText,
HotkeyIds = HotkeyIds,
};
}
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/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj
index 7db769c70..fe84b5e1e 100644
--- a/Flow.Launcher/Flow.Launcher.csproj
+++ b/Flow.Launcher/Flow.Launcher.csproj
@@ -39,6 +39,48 @@
false
+
+
+
+
+
+
+
+
@@ -136,7 +178,6 @@
-
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 69548c328..fc63ca80a 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/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml
index 70b6c1bf4..21d3ae9b9 100644
--- a/Flow.Launcher/MainWindow.xaml
+++ b/Flow.Launcher/MainWindow.xaml
@@ -371,10 +371,8 @@
MinHeight="380"
MaxHeight="{Binding ElementName=ResultListBox, Path=ActualHeight}"
Padding="0 0 10 10"
- d:DataContext="{d:DesignInstance vm:ResultViewModel}"
- DataContext="{Binding PreviewSelectedItem, Mode=OneWay}"
Visibility="{Binding ShowCustomizedPreview}">
-
+
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/SettingPages/Views/SettingsPanePlugins.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml
index 52d77f914..aa7c219e5 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml
@@ -122,6 +122,7 @@
FontSize="14"
ItemContainerStyle="{StaticResource PluginList}"
ItemsSource="{Binding Source={StaticResource PluginCollectionView}}"
+ Loaded="PluginListBox_Loaded"
ScrollViewer.CanContentScroll="False"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SnapsToDevicePixels="True"
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs
index f486a3443..e77a30843 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs
+++ b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs
@@ -1,4 +1,6 @@
using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Navigation;
@@ -33,6 +35,14 @@ public partial class SettingsPanePlugins
base.OnNavigatedTo(e);
}
+ private void PluginListBox_Loaded(object sender, RoutedEventArgs e)
+ {
+ // After list is loaded, we need to clear selection to make sure all items can be mouse hovered
+ // because the selected item cannot be hovered.
+ if (sender is not ListBox listBox) return;
+ listBox.SelectedIndex = -1;
+ }
+
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SettingsPanePluginsViewModel.FilterText))
diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs
index dba0561b2..56c1e112f 100644
--- a/Flow.Launcher/ViewModel/MainViewModel.cs
+++ b/Flow.Launcher/ViewModel/MainViewModel.cs
@@ -9,6 +9,7 @@ using System.Threading.Channels;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
+using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.DependencyInjection;
@@ -890,6 +891,12 @@ namespace Flow.Launcher.ViewModel
}
}
+ public Visibility ShowCustomizedPreview
+ => InternalPreviewVisible && PreviewSelectedItem?.Result.PreviewPanel != null ? Visibility.Visible : Visibility.Collapsed;
+
+ public UserControl CustomizedPreviewControl
+ => ShowCustomizedPreview == Visibility.Visible ? PreviewSelectedItem?.Result.PreviewPanel.Value : null;
+
public Visibility ProgressBarVisibility { get; set; }
public Visibility MainWindowVisibility { get; set; }
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index ea222d023..d889bdd52 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -37,12 +37,17 @@ namespace Flow.Launcher.ViewModel
OnPropertyChanged(nameof(Image));
}
+ private bool _imageLoaded = false;
+
public ImageSource Image
{
get
{
- if (_image == ImageLoader.MissingImage)
+ if (!_imageLoaded)
+ {
+ _imageLoaded = true;
_ = LoadIconAsync();
+ }
return _image;
}
diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs
index c58abae28..d4382fb7f 100644
--- a/Flow.Launcher/ViewModel/ResultViewModel.cs
+++ b/Flow.Launcher/ViewModel/ResultViewModel.cs
@@ -66,8 +66,6 @@ namespace Flow.Launcher.ViewModel
public Visibility ShowDefaultPreview => Result.PreviewPanel == null ? Visibility.Visible : Visibility.Collapsed;
- public Visibility ShowCustomizedPreview => Result.PreviewPanel == null ? Visibility.Collapsed : Visibility.Visible;
-
public Visibility ShowIcon
{
get
diff --git a/Flow.Launcher/packages.lock.json b/Flow.Launcher/packages.lock.json
index 1047b1f3f..32b78c334 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.7",
- "contentHash": "lFGY2aGgmMREPJEfOmZcA6v0CLjWVpcfNHRgqYMoSQhy80+GxhYqdW5xe+DCLrVqE1M7/0RpOkIo49/KH/cd/A=="
+ "resolved": "7.0.0",
+ "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ=="
},
"Mono.Cecil": {
"type": "Transitive",
@@ -677,10 +677,10 @@
},
"System.Drawing.Common": {
"type": "Transitive",
- "resolved": "9.0.7",
- "contentHash": "1k/Pk7hcM3vP2tfIRRS2ECCCN7ya+hvocsM1JMc4ZDCU6qw7yOuUmqmCDfgXZ4Q4FS6jass2EAai5ByKodDi0g==",
+ "resolved": "7.0.0",
+ "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==",
"dependencies": {
- "Microsoft.Win32.SystemEvents": "9.0.7"
+ "Microsoft.Win32.SystemEvents": "7.0.0"
}
},
"System.Globalization": {
@@ -862,7 +862,7 @@
"NLog": "[6.0.1, )",
"NLog.OutputDebugString": "[6.0.1, )",
"SharpVectors.Wpf": "[1.8.4.2, )",
- "System.Drawing.Common": "[9.0.7, )",
+ "System.Drawing.Common": "[7.0.0, )",
"ToolGood.Words.Pinyin": "[3.1.0.3, )"
}
},
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs
index 68e5d5caa..6a099cb07 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using Flow.Launcher.Plugin.BrowserBookmark.Helper;
@@ -134,10 +135,6 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
try
{
- if (string.IsNullOrEmpty(bookmark.Url))
- return;
-
- // Extract domain from URL
if (!Uri.TryCreate(bookmark.Url, UriKind.Absolute, out Uri uri))
return;
@@ -146,43 +143,49 @@ public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader
// Query for latest Firefox version favicon structure
using var cmd = connection.CreateCommand();
cmd.CommandText = @"
- SELECT i.data
+ SELECT i.id, i.data
FROM moz_icons i
JOIN moz_icons_to_pages ip ON i.id = ip.icon_id
JOIN moz_pages_w_icons p ON ip.page_id = p.id
- WHERE p.page_url LIKE @url
- AND i.data IS NOT NULL
- ORDER BY i.width DESC -- Select largest icon available
+ WHERE p.page_url LIKE @domain
+ ORDER BY i.width DESC
LIMIT 1";
- cmd.Parameters.AddWithValue("@url", $"%{domain}%");
+ cmd.Parameters.AddWithValue("@domain", $"%{domain}%");
using var reader = cmd.ExecuteReader();
- if (!reader.Read() || reader.IsDBNull(0))
+ if (!reader.Read() || reader.IsDBNull(1))
return;
+ var iconId = reader.GetInt64(0).ToString();
var imageData = (byte[])reader["data"];
if (imageData is not { Length: > 0 })
return;
- string faviconPath;
- if (FaviconHelper.IsSvgData(imageData))
+ // Check if the image data is compressed (GZip)
+ if (imageData.Length > 2 && imageData[0] == 0x1f && imageData[1] == 0x8b)
{
- faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.svg");
- }
- else
- {
- faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}.png");
+ using var inputStream = new MemoryStream(imageData);
+ using var gZipStream = new GZipStream(inputStream, CompressionMode.Decompress);
+ using var outputStream = new MemoryStream();
+ gZipStream.CopyTo(outputStream);
+ imageData = outputStream.ToArray();
}
- // Filter out duplicate favicons
- if (savedPaths.TryAdd(faviconPath, true))
+ // Convert the image data to WebP format
+ var webpData = FaviconHelper.TryConvertToWebp(imageData);
+ if (webpData != null)
{
- FaviconHelper.SaveBitmapData(imageData, faviconPath);
- }
+ var faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}_{iconId}.webp");
- bookmark.FaviconPath = faviconPath;
+ if (savedPaths.TryAdd(faviconPath, true))
+ {
+ FaviconHelper.SaveBitmapData(webpData, faviconPath);
+ }
+
+ bookmark.FaviconPath = faviconPath;
+ }
}
catch (Exception ex)
{
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index 078476242..7d3cf164c 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -45,12 +45,14 @@
$(OutputPath)runtimes\linux-musl-arm;
$(OutputPath)runtimes\linux-musl-arm64;
$(OutputPath)runtimes\linux-musl-x64;
+ $(OutputPath)runtimes\linux-musl-s390x;
$(OutputPath)runtimes\linux-ppc64le;
$(OutputPath)runtimes\linux-s390x;
$(OutputPath)runtimes\linux-x64;
$(OutputPath)runtimes\linux-x86;
$(OutputPath)runtimes\maccatalyst-arm64;
$(OutputPath)runtimes\maccatalyst-x64;
+ $(OutputPath)runtimes\osx;
$(OutputPath)runtimes\osx-arm64;
$(OutputPath)runtimes\osx-x64"/>
@@ -64,12 +66,14 @@
$(PublishDir)runtimes\linux-musl-arm;
$(PublishDir)runtimes\linux-musl-arm64;
$(PublishDir)runtimes\linux-musl-x64;
+ $(PublishDir)runtimes\linux-musl-s390x;
$(PublishDir)runtimes\linux-ppc64le;
$(PublishDir)runtimes\linux-s390x;
$(PublishDir)runtimes\linux-x64;
$(PublishDir)runtimes\linux-x86;
$(PublishDir)runtimes\maccatalyst-arm64;
$(PublishDir)runtimes\maccatalyst-x64;
+ $(PublishDir)runtimes\osx;
$(PublishDir)runtimes\osx-arm64;
$(PublishDir)runtimes\osx-x64"/>
@@ -96,7 +100,9 @@
-
+
+
+
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs
index b88bd7640..7f17dc6bf 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs
@@ -1,5 +1,7 @@
using System;
using System.IO;
+using SkiaSharp;
+using Svg.Skia;
namespace Flow.Launcher.Plugin.BrowserBookmark.Helper;
@@ -65,12 +67,58 @@ public static class FaviconHelper
}
}
- public static bool IsSvgData(byte[] data)
+ public static byte[] TryConvertToWebp(byte[] data)
{
- if (data.Length < 5)
- return false;
- string start = System.Text.Encoding.ASCII.GetString(data, 0, Math.Min(100, data.Length));
- return start.Contains("
[JsonIgnore]
- public string IconPath
- {
- get
- {
- if (CustomIcon)
- return Path.Combine(Main.CustomImagesDirectory, Icon);
-
- return Path.Combine(Main.DefaultImagesDirectory, Icon);
- }
- }
+ public string IconPath => CustomIcon
+ ? Path.Combine(Main.CustomImagesDirectory, Icon)
+ : Path.Combine(Main.DefaultImagesDirectory, Icon);
public string Url { get; set; }
[JsonIgnore]
public bool Status => Enabled;
+
+ public bool IsPrivateMode { get; set; }
+
public bool Enabled { get; set; }
public SearchSource DeepCopy()
@@ -45,8 +42,10 @@ namespace Flow.Launcher.Plugin.WebSearch
Url = Url,
Icon = Icon,
CustomIcon = CustomIcon,
+ IsPrivateMode = IsPrivateMode,
Enabled = Enabled
};
+
return webSearch;
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml
index 746c9cf84..c6f9b27f3 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml
@@ -101,6 +101,7 @@
+
+ Text="{DynamicResource flowlauncher_plugin_websearch_private_mode_label}" />
+
+
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs
index f0e5ac327..0c0ac4b84 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Settings.cs
@@ -191,7 +191,19 @@ namespace Flow.Launcher.Plugin.WebSearch
[JsonIgnore]
public SearchSource SelectedSearchSource { get; set; }
- public bool EnableSuggestion { get; set; }
+ private bool enableSuggestion;
+ public bool EnableSuggestion
+ {
+ get => enableSuggestion;
+ set
+ {
+ if (enableSuggestion != value)
+ {
+ enableSuggestion = value;
+ OnPropertyChanged(nameof(EnableSuggestion));
+ }
+ }
+ }
[JsonIgnore]
public SuggestionSource[] Suggestions { get; set; } = {
@@ -221,9 +233,5 @@ namespace Flow.Launcher.Plugin.WebSearch
}
}
}
-
- public string BrowserPath { get; set; }
-
- public bool OpenInNewBrowser { get; set; } = true;
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SettingsControl.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/SettingsControl.xaml
index 1ce9b70b4..33461e9e7 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/SettingsControl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SettingsControl.xaml
@@ -11,10 +11,6 @@
mc:Ignorable="d">
-
@@ -96,6 +92,20 @@
+
+
+
+
+
+
+
@@ -138,7 +148,7 @@
Margin="{StaticResource SettingPanelItemLeftMargin}"
VerticalAlignment="Center"
FontSize="11"
- IsEnabled="{Binding ElementName=EnableSuggestion, Path=IsChecked}"
+ IsEnabled="{Binding Settings.EnableSuggestion}"
ItemsSource="{Binding Settings.Suggestions}"
SelectedItem="{Binding Settings.SelectedSuggestion}" />
-
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SettingsControl.xaml.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SettingsControl.xaml.cs
index e53f4ec75..71dc6ece7 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/SettingsControl.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SettingsControl.xaml.cs
@@ -94,7 +94,8 @@ namespace Flow.Launcher.Plugin.WebSearch
var columnBinding = headerClicked.Column.DisplayMemberBinding as Binding;
var sortBy = columnBinding?.Path.Path ?? headerClicked.Column.Header as string;
- if(sortBy != null) {
+ if (sortBy != null)
+ {
Sort(sortBy, direction);
if (direction == ListSortDirection.Ascending)
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/setting.json b/Plugins/Flow.Launcher.Plugin.WebSearch/setting.json
index 533b894b8..a10ea6f33 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/setting.json
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/setting.json
@@ -141,6 +141,6 @@
"Enabled": true
}
],
- "EnableWebSearchSuggestion": false,
- "WebSearchSuggestionSource": "Google"
+ "EnableSuggestion": false,
+ "Suggestion": "Google"
}
\ No newline at end of file