diff --git a/.cm/gitstream.cm b/.cm/gitstream.cm
index fe7e777c8..767982e3b 100644
--- a/.cm/gitstream.cm
+++ b/.cm/gitstream.cm
@@ -10,7 +10,7 @@ triggers:
branch:
- l10n_dev
- dev
- - r/(?i)(Dependabot|Renovate)/
+ - r/([Dd]ependabot|[Rr]enovate)/
automations:
diff --git a/.github/workflows/pr_assignee.yml b/.github/workflows/pr_assignee.yml
index af6daff02..5be603df6 100644
--- a/.github/workflows/pr_assignee.yml
+++ b/.github/workflows/pr_assignee.yml
@@ -1,19 +1,17 @@
name: Assign PR to creator
-# Due to GitHub token limitation, only able to assign org members not authors from forks.
-# https://github.com/thomaseizinger/assign-pr-creator-action/issues/3
-
on:
- pull_request:
+ pull_request_target:
types: [opened]
branches-ignore:
- l10n_dev
+permissions:
+ pull-requests: write
+
jobs:
automation:
runs-on: ubuntu-latest
steps:
- name: Assign PR to creator
- uses: thomaseizinger/assign-pr-creator-action@v1.0.0
- with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
+ uses: toshimaru/auto-author-assign@v2.1.1
diff --git a/.github/workflows/pr_milestone.yml b/.github/workflows/pr_milestone.yml
index b343a39cc..e2365f554 100644
--- a/.github/workflows/pr_milestone.yml
+++ b/.github/workflows/pr_milestone.yml
@@ -3,9 +3,12 @@ name: Set Milestone
# Assigns the earliest created milestone that matches the below glob pattern.
on:
- pull_request:
+ pull_request_target:
types: [opened]
+permissions:
+ pull-requests: write
+
jobs:
automation:
runs-on: ubuntu-latest
diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs
index b58154dcb..d7c73fb46 100644
--- a/Flow.Launcher.Core/Configuration/Portable.cs
+++ b/Flow.Launcher.Core/Configuration/Portable.cs
@@ -40,7 +40,7 @@ namespace Flow.Launcher.Core.Configuration
#endif
IndicateDeletion(DataLocation.PortableDataPath);
- MessageBox.Show("Flow Launcher needs to restart to finish disabling portable mode, " +
+ MessageBoxEx.Show("Flow Launcher needs to restart to finish disabling portable mode, " +
"after the restart your portable data profile will be deleted and roaming data profile kept");
UpdateManager.RestartApp(Constant.ApplicationFileName);
@@ -64,7 +64,7 @@ namespace Flow.Launcher.Core.Configuration
#endif
IndicateDeletion(DataLocation.RoamingDataPath);
- MessageBox.Show("Flow Launcher needs to restart to finish enabling portable mode, " +
+ MessageBoxEx.Show("Flow Launcher needs to restart to finish enabling portable mode, " +
"after the restart your roaming data profile will be deleted and portable data profile kept");
UpdateManager.RestartApp(Constant.ApplicationFileName);
@@ -95,13 +95,13 @@ namespace Flow.Launcher.Core.Configuration
public void MoveUserDataFolder(string fromLocation, string toLocation)
{
- FilesFolders.CopyAll(fromLocation, toLocation);
+ FilesFolders.CopyAll(fromLocation, toLocation, MessageBoxEx.Show);
VerifyUserDataAfterMove(fromLocation, toLocation);
}
public void VerifyUserDataAfterMove(string fromLocation, string toLocation)
{
- FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation);
+ FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, MessageBoxEx.Show);
}
public void CreateShortcuts()
@@ -157,13 +157,13 @@ namespace Flow.Launcher.Core.Configuration
// delete it and prompt the user to pick the portable data location
if (File.Exists(roamingDataDeleteFilePath))
{
- FilesFolders.RemoveFolderIfExists(roamingDataDir);
+ FilesFolders.RemoveFolderIfExists(roamingDataDir, MessageBoxEx.Show);
- if (MessageBox.Show("Flow Launcher has detected you enabled portable mode, " +
+ if (MessageBoxEx.Show("Flow Launcher has detected you enabled portable mode, " +
"would you like to move it to a different location?", string.Empty,
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
- FilesFolders.OpenPath(Constant.RootDirectory);
+ FilesFolders.OpenPath(Constant.RootDirectory, MessageBoxEx.Show);
Environment.Exit(0);
}
@@ -172,9 +172,9 @@ namespace Flow.Launcher.Core.Configuration
// delete it and notify the user about it.
else if (File.Exists(portableDataDeleteFilePath))
{
- FilesFolders.RemoveFolderIfExists(portableDataDir);
+ FilesFolders.RemoveFolderIfExists(portableDataDir, MessageBoxEx.Show);
- MessageBox.Show("Flow Launcher has detected you disabled portable mode, " +
+ MessageBoxEx.Show("Flow Launcher has detected you disabled portable mode, " +
"the relevant shortcuts and uninstaller entry have been created");
}
}
@@ -186,7 +186,7 @@ namespace Flow.Launcher.Core.Configuration
if (roamingLocationExists && portableLocationExists)
{
- MessageBox.Show(string.Format("Flow Launcher detected your user data exists both in {0} and " +
+ MessageBoxEx.Show(string.Format("Flow Launcher detected your user data exists both in {0} and " +
"{1}. {2}{2}Please delete {1} in order to proceed. No changes have occurred.",
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
index 30e812c6f..6d41e2383 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
@@ -4,8 +4,8 @@ using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
+using System.Windows;
using System.Windows.Forms;
using Flow.Launcher.Core.Resource;
@@ -57,7 +57,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
EnvName,
Environment.NewLine
);
- if (MessageBox.Show(noRuntimeMessage, string.Empty, MessageBoxButtons.YesNo) == DialogResult.No)
+ if (MessageBoxEx.Show(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
{
var msg = string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
string selectedFile;
@@ -82,7 +82,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
}
else
{
- MessageBox.Show(string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
+ MessageBoxEx.Show(string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
Log.Error("PluginsLoader",
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
$"{Language}Environment");
@@ -98,7 +98,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
if (expectedPath == currentPath)
return;
- FilesFolders.RemoveFolderIfExists(installedDirPath);
+ FilesFolders.RemoveFolderIfExists(installedDirPath, MessageBoxEx.Show);
InstallEnvironment();
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs
index 5676e12f5..96c29646e 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs
@@ -28,7 +28,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override void InstallEnvironment()
{
- FilesFolders.RemoveFolderIfExists(InstallPath);
+ FilesFolders.RemoveFolderIfExists(InstallPath, MessageBoxEx.Show);
// 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
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs
index 70341f711..0d6f109e0 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs
@@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override void InstallEnvironment()
{
- FilesFolders.RemoveFolderIfExists(InstallPath);
+ FilesFolders.RemoveFolderIfExists(InstallPath, MessageBoxEx.Show);
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs
index 11ed94d3f..582a4407c 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs
@@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override void InstallEnvironment()
{
- FilesFolders.RemoveFolderIfExists(InstallPath);
+ FilesFolders.RemoveFolderIfExists(InstallPath, MessageBoxEx.Show);
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
index 082d7da67..df2f4d2cb 100644
--- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj
+++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj
@@ -54,11 +54,11 @@
-
+
-
+
diff --git a/Flow.Launcher/MessageBoxEx.xaml b/Flow.Launcher.Core/MessageBoxEx.xaml
similarity index 81%
rename from Flow.Launcher/MessageBoxEx.xaml
rename to Flow.Launcher.Core/MessageBoxEx.xaml
index f466a640e..fff107a68 100644
--- a/Flow.Launcher/MessageBoxEx.xaml
+++ b/Flow.Launcher.Core/MessageBoxEx.xaml
@@ -1,9 +1,9 @@
-
+
@@ -33,14 +33,11 @@
-
-
-
-
-
+
+
+
+
+
+
@@ -74,7 +75,7 @@
Grid.Column="0"
Width="18"
Height="18"
- Margin="0 0 0 0"
+ Margin="0 0 10 0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
RenderOptions.BitmapScalingMode="Fant"
@@ -84,7 +85,7 @@
x:Name="TitleTextBlock"
Grid.Column="1"
MaxWidth="400"
- Margin="10 0 26 0"
+ Margin="0 0 26 0"
VerticalAlignment="Center"
FontFamily="Segoe UI"
FontSize="20"
@@ -94,14 +95,26 @@
-
+
+
-
-
+
+
diff --git a/Flow.Launcher.Core/MessageBoxEx.xaml.cs b/Flow.Launcher.Core/MessageBoxEx.xaml.cs
new file mode 100644
index 000000000..a01b5f68d
--- /dev/null
+++ b/Flow.Launcher.Core/MessageBoxEx.xaml.cs
@@ -0,0 +1,203 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Input;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Infrastructure.Image;
+using Flow.Launcher.Infrastructure.Logger;
+
+namespace Flow.Launcher.Core
+{
+ public partial class MessageBoxEx : Window
+ {
+ private static MessageBoxEx msgBox;
+ private static MessageBoxResult _result = MessageBoxResult.None;
+
+ private readonly MessageBoxButton _button;
+
+ private MessageBoxEx(MessageBoxButton button)
+ {
+ _button = button;
+ InitializeComponent();
+ }
+
+ public static MessageBoxResult Show(string messageBoxText)
+ => Show(messageBoxText, string.Empty, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.OK);
+
+ public static MessageBoxResult Show(
+ string messageBoxText,
+ string caption = "",
+ MessageBoxButton button = MessageBoxButton.OK,
+ MessageBoxImage icon = MessageBoxImage.None,
+ MessageBoxResult defaultResult = MessageBoxResult.OK)
+ {
+ if (!Application.Current.Dispatcher.CheckAccess())
+ {
+ return Application.Current.Dispatcher.Invoke(() => Show(messageBoxText, caption, button, icon, defaultResult));
+ }
+
+ try
+ {
+ msgBox = new MessageBoxEx(button);
+ if (caption == string.Empty && button == MessageBoxButton.OK && icon == MessageBoxImage.None)
+ {
+ msgBox.Title = messageBoxText;
+ msgBox.DescOnlyTextBlock.Visibility = Visibility.Visible;
+ msgBox.DescOnlyTextBlock.Text = messageBoxText;
+ }
+ else
+ {
+ msgBox.Title = caption;
+ msgBox.TitleTextBlock.Text = caption;
+ msgBox.DescTextBlock.Text = messageBoxText;
+ _ = SetImageOfMessageBoxAsync(icon);
+ }
+ SetButtonVisibilityFocusAndResult(button, defaultResult);
+ msgBox.ShowDialog();
+ return _result;
+ }
+ catch (Exception e)
+ {
+ Log.Error($"|MessageBoxEx.Show|An error occurred: {e.Message}");
+ msgBox = null;
+ return MessageBoxResult.None;
+ }
+ }
+
+ private static void SetButtonVisibilityFocusAndResult(MessageBoxButton button, MessageBoxResult defaultResult)
+ {
+ switch (button)
+ {
+ case MessageBoxButton.OK:
+ msgBox.btnCancel.Visibility = Visibility.Collapsed;
+ msgBox.btnNo.Visibility = Visibility.Collapsed;
+ msgBox.btnYes.Visibility = Visibility.Collapsed;
+ msgBox.btnOk.Focus();
+ _result = MessageBoxResult.OK;
+ break;
+ case MessageBoxButton.OKCancel:
+ msgBox.btnNo.Visibility = Visibility.Collapsed;
+ msgBox.btnYes.Visibility = Visibility.Collapsed;
+ if (defaultResult == MessageBoxResult.Cancel)
+ {
+ msgBox.btnCancel.Focus();
+ _result = MessageBoxResult.Cancel;
+ }
+ else
+ {
+ msgBox.btnOk.Focus();
+ _result = MessageBoxResult.OK;
+ }
+ break;
+ case MessageBoxButton.YesNo:
+ msgBox.btnOk.Visibility = Visibility.Collapsed;
+ msgBox.btnCancel.Visibility = Visibility.Collapsed;
+ if (defaultResult == MessageBoxResult.No)
+ {
+ msgBox.btnNo.Focus();
+ _result = MessageBoxResult.No;
+ }
+ else
+ {
+ msgBox.btnYes.Focus();
+ _result = MessageBoxResult.Yes;
+ }
+ break;
+ case MessageBoxButton.YesNoCancel:
+ msgBox.btnOk.Visibility = Visibility.Collapsed;
+ if (defaultResult == MessageBoxResult.No)
+ {
+ msgBox.btnNo.Focus();
+ _result = MessageBoxResult.No;
+ }
+ else if (defaultResult == MessageBoxResult.Cancel)
+ {
+ msgBox.btnCancel.Focus();
+ _result = MessageBoxResult.Cancel;
+ }
+ else
+ {
+ msgBox.btnYes.Focus();
+ _result = MessageBoxResult.Yes;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private static async Task SetImageOfMessageBoxAsync(MessageBoxImage icon)
+ {
+ switch (icon)
+ {
+ case MessageBoxImage.Exclamation:
+ await msgBox.SetImageAsync("Exclamation.png");
+ msgBox.Img.Visibility = Visibility.Visible;
+ break;
+ case MessageBoxImage.Question:
+ await msgBox.SetImageAsync("Question.png");
+ msgBox.Img.Visibility = Visibility.Visible;
+ break;
+ case MessageBoxImage.Information:
+ await msgBox.SetImageAsync("Information.png");
+ msgBox.Img.Visibility = Visibility.Visible;
+ break;
+ case MessageBoxImage.Error:
+ await msgBox.SetImageAsync("Error.png");
+ msgBox.Img.Visibility = Visibility.Visible;
+ break;
+ default:
+ msgBox.Img.Visibility = Visibility.Collapsed;
+ break;
+ }
+ }
+
+ private async Task SetImageAsync(string imageName)
+ {
+ var imagePath = Path.Combine(Constant.ProgramDirectory, "Images", imageName);
+ var imageSource = await ImageLoader.LoadAsync(imagePath);
+ Img.Source = imageSource;
+ }
+
+ private void KeyEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
+ {
+ if (_button == MessageBoxButton.YesNo)
+ return;
+ else if (_button == MessageBoxButton.OK)
+ _result = MessageBoxResult.OK;
+ else
+ _result = MessageBoxResult.Cancel;
+ DialogResult = false;
+ Close();
+ }
+
+ private void Button_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender == btnOk)
+ _result = MessageBoxResult.OK;
+ else if (sender == btnYes)
+ _result = MessageBoxResult.Yes;
+ else if (sender == btnNo)
+ _result = MessageBoxResult.No;
+ else if (sender == btnCancel)
+ _result = MessageBoxResult.Cancel;
+ else
+ _result = MessageBoxResult.None;
+ msgBox.Close();
+ msgBox = null;
+ }
+
+ private void Button_Cancel(object sender, RoutedEventArgs e)
+ {
+ if (_button == MessageBoxButton.YesNo)
+ return;
+ else if (_button == MessageBoxButton.OK)
+ _result = MessageBoxResult.OK;
+ else
+ _result = MessageBoxResult.Cancel;
+ msgBox.Close();
+ msgBox = null;
+ }
+ }
+}
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
index 5a6633525..305b28150 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
@@ -26,54 +26,33 @@ namespace Flow.Launcher.Core.Plugin
protected override async Task ExecuteResultAsync(JsonRPCResult result)
{
- try
- {
- var res = await RPC.InvokeAsync(result.JsonRPCAction.Method,
- argument: result.JsonRPCAction.Parameters);
+ var res = await RPC.InvokeAsync(result.JsonRPCAction.Method,
+ argument: result.JsonRPCAction.Parameters);
- return res.Hide;
- }
- catch
- {
- return false;
- }
+ return res.Hide;
}
private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
public override List LoadContextMenus(Result selectedResult)
{
- try
- {
- var res = JTF.Run(() => RPC.InvokeWithCancellationAsync("context_menu",
- new object[] { selectedResult.ContextData }));
+ var res = JTF.Run(() => RPC.InvokeWithCancellationAsync("context_menu",
+ new object[] { selectedResult.ContextData }));
- var results = ParseResults(res);
+ var results = ParseResults(res);
- return results;
- }
- catch
- {
- return new List();
- }
+ return results;
}
public override async Task> QueryAsync(Query query, CancellationToken token)
{
- try
- {
- var res = await RPC.InvokeWithCancellationAsync("query",
- new object[] { query, Settings.Inner },
- token);
+ var res = await RPC.InvokeWithCancellationAsync("query",
+ new object[] { query, Settings.Inner },
+ token);
- var results = ParseResults(res);
+ var results = ParseResults(res);
- return results;
- }
- catch
- {
- return new List();
- }
+ return results;
}
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 91cb36a0e..5c4eaa1da 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -519,7 +519,7 @@ namespace Flow.Launcher.Core.Plugin
var newPluginPath = Path.Combine(installDirectory, folderName);
- FilesFolders.CopyAll(pluginFolderPath, newPluginPath);
+ FilesFolders.CopyAll(pluginFolderPath, newPluginPath, MessageBoxEx.Show);
Directory.Delete(tempFolderPluginPath, true);
diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
index 0f2e4f996..7973c66ba 100644
--- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs
+++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
-using System.Windows.Forms;
+using System.Windows;
using Flow.Launcher.Core.ExternalPlugins.Environments;
#pragma warning disable IDE0005
using Flow.Launcher.Infrastructure.Logger;
@@ -119,10 +119,10 @@ namespace Flow.Launcher.Core.Plugin
_ = Task.Run(() =>
{
- MessageBox.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
+ MessageBoxEx.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
$"Please refer to the logs for more information", "",
- MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ MessageBoxButton.OK, MessageBoxImage.Warning);
});
}
diff --git a/Flow.Launcher.Core/Resource/AvailableLanguages.cs b/Flow.Launcher.Core/Resource/AvailableLanguages.cs
index 46c1ecb54..ecaecf646 100644
--- a/Flow.Launcher.Core/Resource/AvailableLanguages.cs
+++ b/Flow.Launcher.Core/Resource/AvailableLanguages.cs
@@ -28,7 +28,7 @@ namespace Flow.Launcher.Core.Resource
public static Language Czech = new Language("cs", "čeština");
public static Language Arabic = new Language("ar", "اللغة العربية");
public static Language Vietnamese = new Language("vi-vn", "Tiếng Việt");
-
+ public static Language Hebrew = new Language("he", "עברית");
public static List GetAvailableLanguages()
{
@@ -57,9 +57,43 @@ namespace Flow.Launcher.Core.Resource
Turkish,
Czech,
Arabic,
- Vietnamese
+ Vietnamese,
+ Hebrew
};
return languages;
}
+
+ public static string GetSystemTranslation(string languageCode)
+ {
+ return languageCode switch
+ {
+ "en" => "System",
+ "zh-cn" => "系统",
+ "zh-tw" => "系統",
+ "uk-UA" => "Система",
+ "ru" => "Система",
+ "fr" => "Système",
+ "ja" => "システム",
+ "nl" => "Systeem",
+ "pl" => "System",
+ "da" => "System",
+ "de" => "System",
+ "ko" => "시스템",
+ "sr" => "Систем",
+ "pt-pt" => "Sistema",
+ "pt-br" => "Sistema",
+ "es" => "Sistema",
+ "es-419" => "Sistema",
+ "it" => "Sistema",
+ "nb-NO" => "System",
+ "sk" => "Systém",
+ "tr" => "Sistem",
+ "cs" => "Systém",
+ "ar" => "النظام",
+ "vi-vn" => "Hệ thống",
+ "he" => "מערכת",
+ _ => "System",
+ };
+ }
}
}
diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs
index f6f35589d..ef38e8be0 100644
--- a/Flow.Launcher.Core/Resource/Internationalization.cs
+++ b/Flow.Launcher.Core/Resource/Internationalization.cs
@@ -18,23 +18,52 @@ namespace Flow.Launcher.Core.Resource
{
public Settings Settings { get; set; }
private const string Folder = "Languages";
+ private const string DefaultLanguageCode = "en";
private const string DefaultFile = "en.xaml";
private const string Extension = ".xaml";
private readonly List _languageDirectories = new List();
private readonly List _oldResources = new List();
+ private readonly string SystemLanguageCode;
public Internationalization()
{
AddFlowLauncherLanguageDirectory();
+ SystemLanguageCode = GetSystemLanguageCodeAtStartup();
}
-
private void AddFlowLauncherLanguageDirectory()
{
var directory = Path.Combine(Constant.ProgramDirectory, Folder);
_languageDirectories.Add(directory);
}
+ private static string GetSystemLanguageCodeAtStartup()
+ {
+ var availableLanguages = AvailableLanguages.GetAvailableLanguages();
+
+ // Retrieve the language identifiers for the current culture.
+ // ChangeLanguage method overrides the CultureInfo.CurrentCulture, so this needs to
+ // be called at startup in order to get the correct lang code of system.
+ var currentCulture = CultureInfo.CurrentCulture;
+ var twoLetterCode = currentCulture.TwoLetterISOLanguageName;
+ var threeLetterCode = currentCulture.ThreeLetterISOLanguageName;
+ var fullName = currentCulture.Name;
+
+ // Try to find a match in the available languages list
+ foreach (var language in availableLanguages)
+ {
+ var languageCode = language.LanguageCode;
+
+ if (string.Equals(languageCode, twoLetterCode, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(languageCode, threeLetterCode, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(languageCode, fullName, StringComparison.OrdinalIgnoreCase))
+ {
+ return languageCode;
+ }
+ }
+
+ return DefaultLanguageCode;
+ }
internal void AddPluginLanguageDirectories(IEnumerable plugins)
{
@@ -68,8 +97,18 @@ namespace Flow.Launcher.Core.Resource
public void ChangeLanguage(string languageCode)
{
languageCode = languageCode.NonNull();
- Language language = GetLanguageByLanguageCode(languageCode);
- ChangeLanguage(language);
+
+ // Get actual language if language code is system
+ var isSystem = false;
+ if (languageCode == Constant.SystemLanguageCode)
+ {
+ languageCode = SystemLanguageCode;
+ isSystem = true;
+ }
+
+ // Get language by language code and change language
+ var language = GetLanguageByLanguageCode(languageCode);
+ ChangeLanguage(language, isSystem);
}
private Language GetLanguageByLanguageCode(string languageCode)
@@ -87,11 +126,10 @@ namespace Flow.Launcher.Core.Resource
}
}
- public void ChangeLanguage(Language language)
+ private void ChangeLanguage(Language language, bool isSystem)
{
language = language.NonNull();
-
RemoveOldLanguageFiles();
if (language != AvailableLanguages.English)
{
@@ -103,7 +141,7 @@ namespace Flow.Launcher.Core.Resource
CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture;
// Raise event after culture is set
- Settings.Language = language.LanguageCode;
+ Settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode;
_ = Task.Run(() =>
{
UpdatePluginMetadataTranslations();
@@ -124,7 +162,7 @@ namespace Flow.Launcher.Core.Resource
// "Do you want to search with pinyin?"
string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?" ;
- if (MessageBox.Show(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
+ if (MessageBoxEx.Show(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
return false;
return true;
@@ -167,7 +205,9 @@ namespace Flow.Launcher.Core.Resource
public List LoadAvailableLanguages()
{
- return AvailableLanguages.GetAvailableLanguages();
+ var list = AvailableLanguages.GetAvailableLanguages();
+ list.Insert(0, new Language(Constant.SystemLanguageCode, AvailableLanguages.GetSystemTranslation(SystemLanguageCode)));
+ return list;
}
public string GetTranslation(string key)
diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs
index c3a3e9891..1d8409306 100644
--- a/Flow.Launcher.Core/Resource/Theme.cs
+++ b/Flow.Launcher.Core/Resource/Theme.cs
@@ -3,10 +3,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
-using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
-using System.Windows.Interop;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Effects;
@@ -98,19 +96,19 @@ namespace Flow.Launcher.Core.Resource
_oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath);
}
- BlurEnabled = IsBlurTheme();
+ BlurEnabled = Win32Helper.IsBlurTheme();
if (Settings.UseDropShadowEffect && !BlurEnabled)
AddDropShadowEffectToCurrentTheme();
- SetBlurForWindow();
+ Win32Helper.SetBlurForWindow(Application.Current.MainWindow, BlurEnabled);
}
catch (DirectoryNotFoundException)
{
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found");
if (theme != defaultTheme)
{
- MessageBox.Show(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_path_not_exists"), theme));
+ MessageBoxEx.Show(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_path_not_exists"), theme));
ChangeTheme(defaultTheme);
}
return false;
@@ -120,7 +118,7 @@ namespace Flow.Launcher.Core.Resource
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse");
if (theme != defaultTheme)
{
- MessageBox.Show(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_parse_error"), theme));
+ MessageBoxEx.Show(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_parse_error"), theme));
ChangeTheme(defaultTheme);
}
return false;
@@ -357,98 +355,6 @@ namespace Flow.Launcher.Core.Resource
UpdateResourceDictionary(dict);
}
- #region Blur Handling
- /*
- Found on https://github.com/riverar/sample-win10-aeroglass
- */
- private enum AccentState
- {
- ACCENT_DISABLED = 0,
- ACCENT_ENABLE_GRADIENT = 1,
- ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
- ACCENT_ENABLE_BLURBEHIND = 3,
- ACCENT_INVALID_STATE = 4
- }
-
- [StructLayout(LayoutKind.Sequential)]
- private struct AccentPolicy
- {
- public AccentState AccentState;
- public int AccentFlags;
- public int GradientColor;
- public int AnimationId;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- private struct WindowCompositionAttributeData
- {
- public WindowCompositionAttribute Attribute;
- public IntPtr Data;
- public int SizeOfData;
- }
-
- private enum WindowCompositionAttribute
- {
- WCA_ACCENT_POLICY = 19
- }
- [DllImport("user32.dll")]
- private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
-
- ///
- /// Sets the blur for a window via SetWindowCompositionAttribute
- ///
- public void SetBlurForWindow()
- {
- if (BlurEnabled)
- {
- SetWindowAccent(Application.Current.MainWindow, AccentState.ACCENT_ENABLE_BLURBEHIND);
- }
- else
- {
- SetWindowAccent(Application.Current.MainWindow, AccentState.ACCENT_DISABLED);
- }
- }
-
- private bool IsBlurTheme()
- {
- if (Environment.OSVersion.Version >= new Version(6, 2))
- {
- var resource = Application.Current.TryFindResource("ThemeBlurEnabled");
-
- if (resource is bool)
- return (bool)resource;
-
- return false;
- }
-
- return false;
- }
-
- private void SetWindowAccent(Window w, AccentState state)
- {
- var windowHelper = new WindowInteropHelper(w);
-
- windowHelper.EnsureHandle();
-
- var accent = new AccentPolicy { AccentState = state };
- var accentStructSize = Marshal.SizeOf(accent);
-
- var accentPtr = Marshal.AllocHGlobal(accentStructSize);
- Marshal.StructureToPtr(accent, accentPtr, false);
-
- var data = new WindowCompositionAttributeData
- {
- Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY,
- SizeOfData = accentStructSize,
- Data = accentPtr
- };
-
- SetWindowCompositionAttribute(windowHelper.Handle, ref data);
-
- Marshal.FreeHGlobal(accentPtr);
- }
- #endregion
-
public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null);
}
}
diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs
index 3f64b273e..b92d86568 100644
--- a/Flow.Launcher.Core/Updater.cs
+++ b/Flow.Launcher.Core/Updater.cs
@@ -53,7 +53,7 @@ namespace Flow.Launcher.Core
if (newReleaseVersion <= currentVersion)
{
if (!silentUpdate)
- MessageBox.Show(api.GetTranslation("update_flowlauncher_already_on_latest"));
+ MessageBoxEx.Show(api.GetTranslation("update_flowlauncher_already_on_latest"));
return;
}
@@ -68,9 +68,9 @@ namespace Flow.Launcher.Core
if (DataLocation.PortableDataLocationInUse())
{
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
- FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination);
- if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination))
- MessageBox.Show(string.Format(api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
+ FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, MessageBoxEx.Show);
+ if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, MessageBoxEx.Show))
+ MessageBoxEx.Show(string.Format(api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
DataLocation.PortableDataPath,
targetDestination));
}
@@ -83,7 +83,7 @@ namespace Flow.Launcher.Core
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
- if (MessageBox.Show(newVersionTips, api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ if (MessageBoxEx.Show(newVersionTips, api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs
index 8a95ee79f..c86ed4324 100644
--- a/Flow.Launcher.Infrastructure/Constant.cs
+++ b/Flow.Launcher.Infrastructure/Constant.cs
@@ -31,6 +31,7 @@ namespace Flow.Launcher.Infrastructure
public static readonly string ErrorIcon = Path.Combine(ImagesDirectory, "app_error.png");
public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png");
public static readonly string LoadingImgIcon = Path.Combine(ImagesDirectory, "loading.png");
+ public static readonly string ImageIcon = Path.Combine(ImagesDirectory, "image.png");
public static string PythonPath;
public static string NodePath;
@@ -51,5 +52,7 @@ namespace Flow.Launcher.Infrastructure
public const string SponsorPage = "https://github.com/sponsors/Flow-Launcher";
public const string GitHub = "https://github.com/Flow-Launcher/Flow.Launcher";
public const string Docs = "https://flowlauncher.com/docs";
+
+ public const string SystemLanguageCode = "system";
}
}
diff --git a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs
index 76695a4e3..d908b0fde 100644
--- a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs
+++ b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices;
+using Windows.Win32;
namespace Flow.Launcher.Infrastructure
{
@@ -54,10 +54,6 @@ namespace Flow.Launcher.Infrastructure
return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First;
}
- [DllImport("user32.dll")]
- [return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
-
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
///
@@ -70,9 +66,9 @@ namespace Flow.Launcher.Infrastructure
var index = 0;
var numRemaining = hWnds.Count;
- EnumWindows((wnd, _) =>
+ PInvoke.EnumWindows((wnd, _) =>
{
- var searchIndex = hWnds.FindIndex(x => x.HWND == wnd.ToInt32());
+ var searchIndex = hWnds.FindIndex(x => new IntPtr(x.HWND) == wnd);
if (searchIndex != -1)
{
z[searchIndex] = index;
diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
index d9cb5893a..1d6ee5c86 100644
--- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
+++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
@@ -35,6 +35,10 @@
false
+
+
+
+
@@ -49,13 +53,17 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs
index f847ab189..b2a140755 100644
--- a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs
+++ b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs
@@ -1,6 +1,11 @@
-using System;
+using System;
+using System.Diagnostics;
using System.Runtime.InteropServices;
using Flow.Launcher.Plugin;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.Input.KeyboardAndMouse;
+using Windows.Win32.UI.WindowsAndMessaging;
namespace Flow.Launcher.Infrastructure.Hotkey
{
@@ -10,44 +15,45 @@ namespace Flow.Launcher.Infrastructure.Hotkey
///
public unsafe class GlobalHotkey : IDisposable
{
- private static readonly IntPtr hookId;
-
-
-
+ private static readonly HOOKPROC _procKeyboard = HookKeyboardCallback;
+ private static readonly UnhookWindowsHookExSafeHandle hookId;
+
public delegate bool KeyboardCallback(KeyEvent keyEvent, int vkCode, SpecialKeyState state);
internal static Func hookedKeyboardCallback;
- //Modifier key constants
- private const int VK_SHIFT = 0x10;
- private const int VK_CONTROL = 0x11;
- private const int VK_ALT = 0x12;
- private const int VK_WIN = 91;
-
static GlobalHotkey()
{
// Set the hook
- hookId = InterceptKeys.SetHook(& LowLevelKeyboardProc);
+ hookId = SetHook(_procKeyboard, WINDOWS_HOOK_ID.WH_KEYBOARD_LL);
+ }
+
+ private static UnhookWindowsHookExSafeHandle SetHook(HOOKPROC proc, WINDOWS_HOOK_ID hookId)
+ {
+ using var curProcess = Process.GetCurrentProcess();
+ using var curModule = curProcess.MainModule;
+ return PInvoke.SetWindowsHookEx(hookId, proc, PInvoke.GetModuleHandle(curModule.ModuleName), 0);
}
public static SpecialKeyState CheckModifiers()
{
SpecialKeyState state = new SpecialKeyState();
- if ((InterceptKeys.GetKeyState(VK_SHIFT) & 0x8000) != 0)
+ if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_SHIFT) & 0x8000) != 0)
{
//SHIFT is pressed
state.ShiftPressed = true;
}
- if ((InterceptKeys.GetKeyState(VK_CONTROL) & 0x8000) != 0)
+ if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_CONTROL) & 0x8000) != 0)
{
//CONTROL is pressed
state.CtrlPressed = true;
}
- if ((InterceptKeys.GetKeyState(VK_ALT) & 0x8000) != 0)
+ if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_MENU) & 0x8000) != 0)
{
//ALT is pressed
state.AltPressed = true;
}
- if ((InterceptKeys.GetKeyState(VK_WIN) & 0x8000) != 0)
+ if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_LWIN) & 0x8000) != 0 ||
+ (PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_RWIN) & 0x8000) != 0)
{
//WIN is pressed
state.WinPressed = true;
@@ -56,33 +62,33 @@ namespace Flow.Launcher.Infrastructure.Hotkey
return state;
}
- [UnmanagedCallersOnly]
- private static IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
+ private static LRESULT HookKeyboardCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
bool continues = true;
if (nCode >= 0)
{
- if (wParam.ToUInt32() == (int)KeyEvent.WM_KEYDOWN ||
- wParam.ToUInt32() == (int)KeyEvent.WM_KEYUP ||
- wParam.ToUInt32() == (int)KeyEvent.WM_SYSKEYDOWN ||
- wParam.ToUInt32() == (int)KeyEvent.WM_SYSKEYUP)
+ if (wParam.Value == (int)KeyEvent.WM_KEYDOWN ||
+ wParam.Value == (int)KeyEvent.WM_KEYUP ||
+ wParam.Value == (int)KeyEvent.WM_SYSKEYDOWN ||
+ wParam.Value == (int)KeyEvent.WM_SYSKEYUP)
{
if (hookedKeyboardCallback != null)
- continues = hookedKeyboardCallback((KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), CheckModifiers());
+ continues = hookedKeyboardCallback((KeyEvent)wParam.Value, Marshal.ReadInt32(lParam), CheckModifiers());
}
}
if (continues)
{
- return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
+ return PInvoke.CallNextHookEx(hookId, nCode, wParam, lParam);
}
- return (IntPtr)(-1);
+
+ return new LRESULT(1);
}
public void Dispose()
{
- InterceptKeys.UnhookWindowsHookEx(hookId);
+ hookId.Dispose();
}
~GlobalHotkey()
@@ -90,4 +96,4 @@ namespace Flow.Launcher.Infrastructure.Hotkey
Dispose();
}
}
-}
\ No newline at end of file
+}
diff --git a/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs b/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs
deleted file mode 100644
index d33bac34c..000000000
--- a/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace Flow.Launcher.Infrastructure.Hotkey
-{
- internal static unsafe class InterceptKeys
- {
- public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);
-
- private const int WH_KEYBOARD_LL = 13;
-
- public static IntPtr SetHook(delegate* unmanaged proc)
- {
- using (Process curProcess = Process.GetCurrentProcess())
- using (ProcessModule curModule = curProcess.MainModule)
- {
- return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
- }
- }
-
- [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- public static extern IntPtr SetWindowsHookEx(int idHook, delegate* unmanaged lpfn, IntPtr hMod, uint dwThreadId);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool UnhookWindowsHookEx(IntPtr hhk);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);
-
- [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- public static extern IntPtr GetModuleHandle(string lpModuleName);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
- public static extern short GetKeyState(int keyCode);
- }
-}
\ No newline at end of file
diff --git a/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs b/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs
index 15e306883..95bb25837 100644
--- a/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs
+++ b/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs
@@ -1,3 +1,5 @@
+using Windows.Win32;
+
namespace Flow.Launcher.Infrastructure.Hotkey
{
public enum KeyEvent
@@ -5,21 +7,21 @@ namespace Flow.Launcher.Infrastructure.Hotkey
///
/// Key down
///
- WM_KEYDOWN = 256,
+ WM_KEYDOWN = (int)PInvoke.WM_KEYDOWN,
///
/// Key up
///
- WM_KEYUP = 257,
+ WM_KEYUP = (int)PInvoke.WM_KEYUP,
///
/// System key up
///
- WM_SYSKEYUP = 261,
+ WM_SYSKEYUP = (int)PInvoke.WM_SYSKEYUP,
///
/// System key down
///
- WM_SYSKEYDOWN = 260
+ WM_SYSKEYDOWN = (int)PInvoke.WM_SYSKEYDOWN
}
-}
\ No newline at end of file
+}
diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
index 612f495be..6f7b1cd90 100644
--- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
@@ -22,6 +22,7 @@ namespace Flow.Launcher.Infrastructure.Image
private static readonly ConcurrentDictionary GuidToKey = new();
private static IImageHashGenerator _hashGenerator;
private static readonly bool EnableImageHash = true;
+ public static ImageSource Image { get; } = new BitmapImage(new Uri(Constant.ImageIcon));
public static ImageSource MissingImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon));
public static ImageSource LoadingImage { get; } = new BitmapImage(new Uri(Constant.LoadingImgIcon));
public const int SmallIconSize = 64;
@@ -139,7 +140,7 @@ namespace Flow.Launcher.Infrastructure.Image
return new ImageResult(image, ImageType.ImageFile);
}
- if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
+ if (path.StartsWith("data:image", StringComparison.OrdinalIgnoreCase))
{
var imageSource = new BitmapImage(new Uri(path));
imageSource.Freeze();
@@ -215,8 +216,16 @@ namespace Flow.Launcher.Infrastructure.Image
type = ImageType.ImageFile;
if (loadFullImage)
{
- image = LoadFullImage(path);
- type = ImageType.FullImageFile;
+ try
+ {
+ image = LoadFullImage(path);
+ type = ImageType.FullImageFile;
+ }
+ catch (NotSupportedException)
+ {
+ image = Image;
+ type = ImageType.Error;
+ }
}
else
{
diff --git a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
index 247238bb6..2fb8cf363 100644
--- a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
@@ -1,12 +1,19 @@
using System;
using System.Runtime.InteropServices;
using System.IO;
+using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
-using System.Windows;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.Shell;
+using Windows.Win32.Graphics.Gdi;
namespace Flow.Launcher.Infrastructure.Image
{
+ ///
+ /// Subclass of
+ ///
[Flags]
public enum ThumbnailOptions
{
@@ -22,91 +29,13 @@ namespace Flow.Launcher.Infrastructure.Image
{
// Based on https://stackoverflow.com/questions/21751747/extract-thumbnail-for-any-file-in-windows
- private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
-
- [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- internal static extern int SHCreateItemFromParsingName(
- [MarshalAs(UnmanagedType.LPWStr)] string path,
- IntPtr pbc,
- ref Guid riid,
- [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
-
- [DllImport("gdi32.dll")]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool DeleteObject(IntPtr hObject);
-
- [ComImport]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
- internal interface IShellItem
- {
- void BindToHandler(IntPtr pbc,
- [MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
- [MarshalAs(UnmanagedType.LPStruct)]Guid riid,
- out IntPtr ppv);
-
- void GetParent(out IShellItem ppsi);
- void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName);
- void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
- void Compare(IShellItem psi, uint hint, out int piOrder);
- };
-
- internal enum SIGDN : uint
- {
- NORMALDISPLAY = 0,
- PARENTRELATIVEPARSING = 0x80018001,
- PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
- DESKTOPABSOLUTEPARSING = 0x80028000,
- PARENTRELATIVEEDITING = 0x80031001,
- DESKTOPABSOLUTEEDITING = 0x8004c000,
- FILESYSPATH = 0x80058000,
- URL = 0x80068000
- }
-
- internal enum HResult
- {
- Ok = 0x0000,
- False = 0x0001,
- InvalidArguments = unchecked((int)0x80070057),
- OutOfMemory = unchecked((int)0x8007000E),
- NoInterface = unchecked((int)0x80004002),
- Fail = unchecked((int)0x80004005),
- ExtractionFailed = unchecked((int)0x8004B200),
- ElementNotFound = unchecked((int)0x80070490),
- TypeElementNotFound = unchecked((int)0x8002802B),
- NoObject = unchecked((int)0x800401E5),
- Win32ErrorCanceled = 1223,
- Canceled = unchecked((int)0x800704C7),
- ResourceInUse = unchecked((int)0x800700AA),
- AccessDenied = unchecked((int)0x80030005)
- }
-
- [ComImportAttribute()]
- [GuidAttribute("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
- [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
- internal interface IShellItemImageFactory
- {
- [PreserveSig]
- HResult GetImage(
- [In, MarshalAs(UnmanagedType.Struct)] NativeSize size,
- [In] ThumbnailOptions flags,
- [Out] out IntPtr phbm);
- }
-
- [StructLayout(LayoutKind.Sequential)]
- internal struct NativeSize
- {
- private int width;
- private int height;
-
- public int Width { set { width = value; } }
- public int Height { set { height = value; } }
- };
+ private static readonly Guid GUID_IShellItem = typeof(IShellItem).GUID;
+ private static readonly HRESULT S_ExtractionFailed = (HRESULT)0x8004B200;
public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
{
- IntPtr hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
+ HBITMAP hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
try
{
@@ -115,39 +44,56 @@ namespace Flow.Launcher.Infrastructure.Image
finally
{
// delete HBitmap to avoid memory leaks
- DeleteObject(hBitmap);
+ PInvoke.DeleteObject(hBitmap);
}
}
-
- private static IntPtr GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
- {
- IShellItem nativeShellItem;
- Guid shellItem2Guid = new Guid(IShellItem2Guid);
- int retCode = SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out nativeShellItem);
- if (retCode != 0)
+ private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
+ {
+ var retCode = PInvoke.SHCreateItemFromParsingName(
+ fileName,
+ null,
+ GUID_IShellItem,
+ out var nativeShellItem);
+
+ if (retCode != HRESULT.S_OK)
throw Marshal.GetExceptionForHR(retCode);
- NativeSize nativeSize = new NativeSize
+ if (nativeShellItem is not IShellItemImageFactory imageFactory)
{
- Width = width,
- Height = height
- };
-
- IntPtr hBitmap;
- HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, options, out hBitmap);
-
- // if extracting image thumbnail and failed, extract shell icon
- if (options == ThumbnailOptions.ThumbnailOnly && hr == HResult.ExtractionFailed)
- {
- hr = ((IShellItemImageFactory) nativeShellItem).GetImage(nativeSize, ThumbnailOptions.IconOnly, out hBitmap);
+ Marshal.ReleaseComObject(nativeShellItem);
+ nativeShellItem = null;
+ throw new InvalidOperationException("Failed to get IShellItemImageFactory");
}
- Marshal.ReleaseComObject(nativeShellItem);
+ SIZE size = new SIZE
+ {
+ cx = width,
+ cy = height
+ };
- if (hr == HResult.Ok) return hBitmap;
+ HBITMAP hBitmap = default;
+ try
+ {
+ try
+ {
+ imageFactory.GetImage(size, (SIIGBF)options, &hBitmap);
+ }
+ catch (COMException ex) when (ex.HResult == S_ExtractionFailed && options == ThumbnailOptions.ThumbnailOnly)
+ {
+ // Fallback to IconOnly if ThumbnailOnly fails
+ imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
+ }
+ }
+ finally
+ {
+ if (nativeShellItem != null)
+ {
+ Marshal.ReleaseComObject(nativeShellItem);
+ }
+ }
- throw new COMException($"Error while extracting thumbnail for {fileName}", Marshal.GetExceptionForHR((int)hr));
+ return hBitmap;
}
}
-}
\ No newline at end of file
+}
diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt
new file mode 100644
index 000000000..f117534a1
--- /dev/null
+++ b/Flow.Launcher.Infrastructure/NativeMethods.txt
@@ -0,0 +1,19 @@
+SHCreateItemFromParsingName
+DeleteObject
+IShellItem
+IShellItemImageFactory
+S_OK
+
+SetWindowsHookEx
+UnhookWindowsHookEx
+CallNextHookEx
+GetModuleHandle
+GetKeyState
+VIRTUAL_KEY
+
+WM_KEYDOWN
+WM_KEYUP
+WM_SYSKEYDOWN
+WM_SYSKEYUP
+
+EnumWindows
\ No newline at end of file
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index 0c7de10fd..c412fb32f 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -13,7 +13,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
public class Settings : BaseModel, IHotkeySettings
{
- private string language = "en";
+ private string language = Constant.SystemLanguageCode;
private string _theme = Constant.DefaultTheme;
public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}";
public string OpenResultModifiers { get; set; } = KeyConstant.Alt;
@@ -62,7 +62,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public double ItemHeightSize { get; set; } = 58;
public double QueryBoxFontSize { get; set; } = 20;
public double ResultItemFontSize { get; set; } = 16;
- public double ResultSubItemFontSize { get; set; } = 13;
+ public double ResultSubItemFontSize { get; set; } = 13;
public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name;
public string QueryBoxFontStyle { get; set; }
public string QueryBoxFontWeight { get; set; }
@@ -187,7 +187,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool ShouldUsePinyin { get; set; } = false;
public bool AlwaysPreview { get; set; } = false;
-
+
public bool AlwaysStartEn { get; set; } = false;
private SearchPrecisionScore _querySearchPrecision = SearchPrecisionScore.Regular;
@@ -370,7 +370,9 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
Selected,
Empty,
- Preserved
+ Preserved,
+ ActionKeywordPreserved,
+ ActionKeywordSelected
}
public enum ColorSchemes
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
new file mode 100644
index 000000000..867fef4f5
--- /dev/null
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Windows.Interop;
+using System.Windows;
+
+namespace Flow.Launcher.Infrastructure
+{
+ public static class Win32Helper
+ {
+ #region Blur Handling
+
+ /*
+ Found on https://github.com/riverar/sample-win10-aeroglass
+ */
+
+ private enum AccentState
+ {
+ ACCENT_DISABLED = 0,
+ ACCENT_ENABLE_GRADIENT = 1,
+ ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
+ ACCENT_ENABLE_BLURBEHIND = 3,
+ ACCENT_INVALID_STATE = 4
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct AccentPolicy
+ {
+ public AccentState AccentState;
+ public int AccentFlags;
+ public int GradientColor;
+ public int AnimationId;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct WindowCompositionAttributeData
+ {
+ public WindowCompositionAttribute Attribute;
+ public IntPtr Data;
+ public int SizeOfData;
+ }
+
+ private enum WindowCompositionAttribute
+ {
+ WCA_ACCENT_POLICY = 19
+ }
+
+ [DllImport("user32.dll")]
+ private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
+
+ ///
+ /// Checks if the blur theme is enabled
+ ///
+ public static bool IsBlurTheme()
+ {
+ if (Environment.OSVersion.Version >= new Version(6, 2))
+ {
+ var resource = Application.Current.TryFindResource("ThemeBlurEnabled");
+
+ if (resource is bool b)
+ return b;
+
+ return false;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Sets the blur for a window via SetWindowCompositionAttribute
+ ///
+ public static void SetBlurForWindow(Window w, bool blur)
+ {
+ SetWindowAccent(w, blur ? AccentState.ACCENT_ENABLE_BLURBEHIND : AccentState.ACCENT_DISABLED);
+ }
+
+ private static void SetWindowAccent(Window w, AccentState state)
+ {
+ var windowHelper = new WindowInteropHelper(w);
+
+ windowHelper.EnsureHandle();
+
+ var accent = new AccentPolicy { AccentState = state };
+ var accentStructSize = Marshal.SizeOf(accent);
+
+ var accentPtr = Marshal.AllocHGlobal(accentStructSize);
+ Marshal.StructureToPtr(accent, accentPtr, false);
+
+ var data = new WindowCompositionAttributeData
+ {
+ Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY,
+ SizeOfData = accentStructSize,
+ Data = accentPtr
+ };
+
+ SetWindowCompositionAttribute(windowHelper.Handle, ref data);
+
+ Marshal.FreeHGlobal(accentPtr);
+ }
+ #endregion
+ }
+}
diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
index 35b9af1c9..2feb21b12 100644
--- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
+++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
@@ -57,7 +57,11 @@
-
+
+
+
+
+
@@ -68,6 +72,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index c95a8ce7b..a0186b7a2 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -1,4 +1,4 @@
-using Flow.Launcher.Plugin.SharedModels;
+using Flow.Launcher.Plugin.SharedModels;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
@@ -7,6 +7,7 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
+using System.Windows;
namespace Flow.Launcher.Plugin
{
@@ -294,9 +295,26 @@ namespace Flow.Launcher.Plugin
///
/// Reloads the query.
- /// This method should run
+ /// This method should run when selected item is from query results.
///
/// Choose the first result after reload if true; keep the last selected result if false. Default is true.
public void ReQuery(bool reselect = true);
+
+ ///
+ /// Back to the query results.
+ /// This method should run when selected item is from context menu or history.
+ ///
+ public void BackToQueryResults();
+
+ ///
+ /// Displays a standardised Flow message box.
+ ///
+ /// The message of the message box.
+ /// The caption of the message box.
+ /// Specifies which button or buttons to display.
+ /// Specifies the icon to display.
+ /// Specifies the default result of the message box.
+ /// Specifies which message box button is clicked by the user.
+ public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK);
}
}
diff --git a/Flow.Launcher.Plugin/NativeMethods.txt b/Flow.Launcher.Plugin/NativeMethods.txt
new file mode 100644
index 000000000..e3e2b705e
--- /dev/null
+++ b/Flow.Launcher.Plugin/NativeMethods.txt
@@ -0,0 +1,3 @@
+EnumThreadWindows
+GetWindowText
+GetWindowTextLength
\ No newline at end of file
diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs
index 9b42b1021..c6ca81cf3 100644
--- a/Flow.Launcher.Plugin/Result.cs
+++ b/Flow.Launcher.Plugin/Result.cs
@@ -70,7 +70,8 @@ namespace Flow.Launcher.Plugin
&& !string.IsNullOrEmpty(PluginDirectory)
&& !Path.IsPathRooted(value)
&& !value.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
- && !value.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
+ && !value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)
+ && !value.StartsWith("data:image", StringComparison.OrdinalIgnoreCase))
{
_icoPath = Path.Combine(PluginDirectory, value);
}
@@ -156,27 +157,6 @@ namespace Flow.Launcher.Plugin
}
}
- ///
- public override bool Equals(object obj)
- {
- var r = obj as Result;
-
- var equality = string.Equals(r?.Title, Title) &&
- string.Equals(r?.SubTitle, SubTitle) &&
- string.Equals(r?.AutoCompleteText, AutoCompleteText) &&
- string.Equals(r?.CopyText, CopyText) &&
- string.Equals(r?.IcoPath, IcoPath) &&
- TitleHighlightData == r.TitleHighlightData;
-
- return equality;
- }
-
- ///
- public override int GetHashCode()
- {
- return HashCode.Combine(Title, SubTitle, AutoCompleteText, CopyText, IcoPath);
- }
-
///
public override string ToString()
{
@@ -262,6 +242,16 @@ namespace Flow.Launcher.Plugin
///
public PreviewInfo Preview { get; set; } = PreviewInfo.Default;
+ ///
+ /// Determines if the user selection count should be added to the score. This can be useful when set to false to allow the result sequence order to be the same everytime instead of changing based on selection.
+ ///
+ public bool AddSelectedCount { get; set; } = true;
+
+ ///
+ /// Maximum score. This can be useful when set one result to the top by default. This is the score for the results set to the topmost by users.
+ ///
+ public const int MaxScore = int.MaxValue;
+
///
/// Info of the preview section of a
///
diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
index dd8c4b112..5f003e351 100644
--- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
+++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
@@ -21,7 +21,8 @@ namespace Flow.Launcher.Plugin.SharedCommands
///
///
///
- public static void CopyAll(this string sourcePath, string targetPath)
+ ///
+ public static void CopyAll(this string sourcePath, string targetPath, Func messageBoxExShow = null)
{
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourcePath);
@@ -54,7 +55,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(targetPath, subdir.Name);
- CopyAll(subdir.FullName, temppath);
+ CopyAll(subdir.FullName, temppath, messageBoxExShow);
}
}
catch (Exception)
@@ -62,8 +63,9 @@ namespace Flow.Launcher.Plugin.SharedCommands
#if DEBUG
throw;
#else
- MessageBox.Show(string.Format("Copying path {0} has failed, it will now be deleted for consistency", targetPath));
- RemoveFolderIfExists(targetPath);
+ messageBoxExShow ??= MessageBox.Show;
+ messageBoxExShow(string.Format("Copying path {0} has failed, it will now be deleted for consistency", targetPath));
+ RemoveFolderIfExists(targetPath, messageBoxExShow);
#endif
}
@@ -75,8 +77,9 @@ namespace Flow.Launcher.Plugin.SharedCommands
///
///
///
+ ///
///
- public static bool VerifyBothFolderFilesEqual(this string fromPath, string toPath)
+ public static bool VerifyBothFolderFilesEqual(this string fromPath, string toPath, Func messageBoxExShow = null)
{
try
{
@@ -96,7 +99,8 @@ namespace Flow.Launcher.Plugin.SharedCommands
#if DEBUG
throw;
#else
- MessageBox.Show(string.Format("Unable to verify folders and files between {0} and {1}", fromPath, toPath));
+ messageBoxExShow ??= MessageBox.Show;
+ messageBoxExShow(string.Format("Unable to verify folders and files between {0} and {1}", fromPath, toPath));
return false;
#endif
}
@@ -107,7 +111,8 @@ namespace Flow.Launcher.Plugin.SharedCommands
/// Deletes a folder if it exists
///
///
- public static void RemoveFolderIfExists(this string path)
+ ///
+ public static void RemoveFolderIfExists(this string path, Func messageBoxExShow = null)
{
try
{
@@ -119,7 +124,8 @@ namespace Flow.Launcher.Plugin.SharedCommands
#if DEBUG
throw;
#else
- MessageBox.Show(string.Format("Not able to delete folder {0}, please go to the location and manually delete it", path));
+ messageBoxExShow ??= MessageBox.Show;
+ messageBoxExShow(string.Format("Not able to delete folder {0}, please go to the location and manually delete it", path));
#endif
}
}
@@ -148,7 +154,8 @@ namespace Flow.Launcher.Plugin.SharedCommands
/// Open a directory window (using the OS's default handler, usually explorer)
///
///
- public static void OpenPath(string fileOrFolderPath)
+ ///
+ public static void OpenPath(string fileOrFolderPath, Func messageBoxExShow = null)
{
var psi = new ProcessStartInfo
{
@@ -166,7 +173,8 @@ namespace Flow.Launcher.Plugin.SharedCommands
#if DEBUG
throw;
#else
- MessageBox.Show(string.Format("Unable to open the path {0}, please check if it exists", fileOrFolderPath));
+ messageBoxExShow ??= MessageBox.Show;
+ messageBoxExShow(string.Format("Unable to open the path {0}, please check if it exists", fileOrFolderPath));
#endif
}
}
@@ -177,7 +185,8 @@ namespace Flow.Launcher.Plugin.SharedCommands
/// File path
/// Working directory
/// Open as Administrator
- public static void OpenFile(string filePath, string workingDir = "", bool asAdmin = false)
+ ///
+ public static void OpenFile(string filePath, string workingDir = "", bool asAdmin = false, Func messageBoxExShow = null)
{
var psi = new ProcessStartInfo
{
@@ -196,7 +205,8 @@ namespace Flow.Launcher.Plugin.SharedCommands
#if DEBUG
throw;
#else
- MessageBox.Show(string.Format("Unable to open the path {0}, please check if it exists", filePath));
+ messageBoxExShow ??= MessageBox.Show;
+ messageBoxExShow(string.Format("Unable to open the path {0}, please check if it exists", filePath));
#endif
}
}
diff --git a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs
index 49f78b458..a0440e30d 100644
--- a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs
+++ b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs
@@ -2,18 +2,15 @@
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
-using System.Runtime.InteropServices;
-using System.Text;
using System.Threading;
+using Windows.Win32;
+using Windows.Win32.Foundation;
namespace Flow.Launcher.Plugin.SharedCommands
{
public static class ShellCommand
{
public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam);
- [DllImport("user32.dll")] static extern bool EnumThreadWindows(uint threadId, EnumThreadDelegate lpfn, IntPtr lParam);
- [DllImport("user32.dll")] static extern int GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount);
- [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr hwnd);
private static bool containsSecurityWindow;
@@ -28,6 +25,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
CheckSecurityWindow();
Thread.Sleep(25);
}
+
while (containsSecurityWindow) // while this process contains a "Windows Security" dialog, stay open
{
containsSecurityWindow = false;
@@ -42,24 +40,33 @@ namespace Flow.Launcher.Plugin.SharedCommands
{
ProcessThreadCollection ptc = Process.GetCurrentProcess().Threads;
for (int i = 0; i < ptc.Count; i++)
- EnumThreadWindows((uint)ptc[i].Id, CheckSecurityThread, IntPtr.Zero);
+ PInvoke.EnumThreadWindows((uint)ptc[i].Id, CheckSecurityThread, IntPtr.Zero);
}
- private static bool CheckSecurityThread(IntPtr hwnd, IntPtr lParam)
+ private static BOOL CheckSecurityThread(HWND hwnd, LPARAM lParam)
{
if (GetWindowTitle(hwnd) == "Windows Security")
containsSecurityWindow = true;
return true;
}
- private static string GetWindowTitle(IntPtr hwnd)
+ private static unsafe string GetWindowTitle(HWND hwnd)
{
- StringBuilder sb = new StringBuilder(GetWindowTextLength(hwnd) + 1);
- GetWindowText(hwnd, sb, sb.Capacity);
- return sb.ToString();
+ var capacity = PInvoke.GetWindowTextLength(hwnd) + 1;
+ int length;
+ Span buffer = capacity < 1024 ? stackalloc char[capacity] : new char[capacity];
+ fixed (char* pBuffer = buffer)
+ {
+ // If the window has no title bar or text, if the title bar is empty,
+ // or if the window or control handle is invalid, the return value is zero.
+ length = PInvoke.GetWindowText(hwnd, pBuffer, capacity);
+ }
+
+ return buffer[..length].ToString();
}
- public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "", bool createNoWindow = false)
+ public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "",
+ string arguments = "", string verb = "", bool createNoWindow = false)
{
var info = new ProcessStartInfo
{
diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
index a4bc4ab19..8286e142e 100644
--- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj
+++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
@@ -54,7 +54,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
\ No newline at end of file
diff --git a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
index 3d05e5679..42a4630fe 100644
--- a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
+++ b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs
@@ -63,28 +63,5 @@ namespace Flow.Launcher.Test.Plugins
})
};
- [TestCaseSource(typeof(JsonRPCPluginTest), nameof(ResponseModelsSource))]
- public async Task GivenModel_WhenSerializeWithDifferentNamingPolicy_ThenExpectSameResult_Async(JsonRPCQueryResponseModel reference)
- {
- var camelText = JsonSerializer.Serialize(reference, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
-
- var pascalText = JsonSerializer.Serialize(reference);
-
- var results1 = await QueryAsync(new Query { Search = camelText }, default);
- var results2 = await QueryAsync(new Query { Search = pascalText }, default);
-
- Assert.IsNotNull(results1);
- Assert.IsNotNull(results2);
-
- foreach (var ((result1, result2), referenceResult) in results1.Zip(results2).Zip(reference.Result))
- {
- Assert.AreEqual(result1, result2);
- Assert.AreEqual(result1, referenceResult);
-
- Assert.IsNotNull(result1);
- Assert.IsNotNull(result1.AsyncAction);
- }
- }
-
}
}
diff --git a/Flow.Launcher/ActionKeywords.xaml.cs b/Flow.Launcher/ActionKeywords.xaml.cs
index 371b2dd07..ba47a4ded 100644
--- a/Flow.Launcher/ActionKeywords.xaml.cs
+++ b/Flow.Launcher/ActionKeywords.xaml.cs
@@ -2,6 +2,7 @@
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
+using Flow.Launcher.Core;
namespace Flow.Launcher
{
@@ -43,7 +44,7 @@ namespace Flow.Launcher
else
{
string msg = translater.GetTranslation("newActionKeywordsHasBeenAssigned");
- MessageBox.Show(msg);
+ MessageBoxEx.Show(msg);
}
}
}
diff --git a/Flow.Launcher/App.xaml b/Flow.Launcher/App.xaml
index 13e943c95..17c0ae0d5 100644
--- a/Flow.Launcher/App.xaml
+++ b/Flow.Launcher/App.xaml
@@ -20,6 +20,11 @@
+
+
+
+
+
diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs
index 5db6115ad..81e7600b8 100644
--- a/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs
+++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml.cs
@@ -6,7 +6,7 @@ using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
-using Flow.Launcher.ViewModel;
+using Flow.Launcher.Core;
namespace Flow.Launcher
{
@@ -63,7 +63,7 @@ namespace Flow.Launcher
o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey);
if (updateCustomHotkey == null)
{
- MessageBox.Show(InternationalizationManager.Instance.GetTranslation("invalidPluginHotkey"));
+ MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("invalidPluginHotkey"));
Close();
return;
}
diff --git a/Flow.Launcher/CustomShortcutSetting.xaml.cs b/Flow.Launcher/CustomShortcutSetting.xaml.cs
index 531b29d50..dec3506eb 100644
--- a/Flow.Launcher/CustomShortcutSetting.xaml.cs
+++ b/Flow.Launcher/CustomShortcutSetting.xaml.cs
@@ -3,6 +3,7 @@ using System;
using System.Windows;
using System.Windows.Input;
using Flow.Launcher.SettingPages.ViewModels;
+using Flow.Launcher.Core;
namespace Flow.Launcher
{
@@ -42,13 +43,13 @@ namespace Flow.Launcher
{
if (String.IsNullOrEmpty(Key) || String.IsNullOrEmpty(Value))
{
- MessageBox.Show(InternationalizationManager.Instance.GetTranslation("emptyShortcut"));
+ MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("emptyShortcut"));
return;
}
// Check if key is modified or adding a new one
if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key))
{
- MessageBox.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut"));
+ MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut"));
return;
}
DialogResult = !update || originalKey != Key || originalValue != Value;
diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj
index afc3fbbaa..788beddfb 100644
--- a/Flow.Launcher/Flow.Launcher.csproj
+++ b/Flow.Launcher/Flow.Launcher.csproj
@@ -83,19 +83,23 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
-
+
diff --git a/Flow.Launcher/Helper/DWMDropShadow.cs b/Flow.Launcher/Helper/DWMDropShadow.cs
index e448acd4c..58817d70e 100644
--- a/Flow.Launcher/Helper/DWMDropShadow.cs
+++ b/Flow.Launcher/Helper/DWMDropShadow.cs
@@ -1,20 +1,16 @@
using System;
-using System.Drawing.Printing;
-using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Graphics.Dwm;
+using Windows.Win32.UI.Controls;
namespace Flow.Launcher.Helper;
public class DwmDropShadow
{
- [DllImport("dwmapi.dll", PreserveSig = true)]
- private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
-
- [DllImport("dwmapi.dll")]
- private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref Margins pMarInset);
-
///
/// Drops a standard shadow to a WPF Window, even if the window isborderless. Only works with DWM (Vista and Seven).
/// This method is much more efficient than setting AllowsTransparency to true and using the DropShadow effect,
@@ -43,24 +39,22 @@ public class DwmDropShadow
///
/// Window to which the shadow will be applied
/// True if the method succeeded, false if not
- private static bool DropShadow(Window window)
+ private static unsafe bool DropShadow(Window window)
{
try
{
WindowInteropHelper helper = new WindowInteropHelper(window);
int val = 2;
- int ret1 = DwmSetWindowAttribute(helper.Handle, 2, ref val, 4);
+ var ret1 = PInvoke.DwmSetWindowAttribute(new (helper.Handle), DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY, &val, 4);
- if (ret1 == 0)
+ if (ret1 == HRESULT.S_OK)
{
- Margins m = new Margins { Bottom = 0, Left = 0, Right = 0, Top = 0 };
- int ret2 = DwmExtendFrameIntoClientArea(helper.Handle, ref m);
- return ret2 == 0;
- }
- else
- {
- return false;
+ var m = new MARGINS { cyBottomHeight = 0, cxLeftWidth = 0, cxRightWidth = 0, cyTopHeight = 0 };
+ var ret2 = PInvoke.DwmExtendFrameIntoClientArea(new(helper.Handle), &m);
+ return ret2 == HRESULT.S_OK;
}
+
+ return false;
}
catch (Exception)
{
diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs
index 21ddf276a..8b30b8be1 100644
--- a/Flow.Launcher/Helper/HotKeyMapper.cs
+++ b/Flow.Launcher/Helper/HotKeyMapper.cs
@@ -4,8 +4,8 @@ using System;
using NHotkey;
using NHotkey.Wpf;
using Flow.Launcher.Core.Resource;
-using System.Windows;
using Flow.Launcher.ViewModel;
+using Flow.Launcher.Core;
namespace Flow.Launcher.Helper;
@@ -46,7 +46,7 @@ internal static class HotKeyMapper
{
string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle");
- MessageBox.Show(errorMsg,errorMsgTitle);
+ MessageBoxEx.Show(errorMsg, errorMsgTitle);
}
}
diff --git a/Flow.Launcher/Helper/SingleInstance.cs b/Flow.Launcher/Helper/SingleInstance.cs
index 739fed378..e0e3075f6 100644
--- a/Flow.Launcher/Helper/SingleInstance.cs
+++ b/Flow.Launcher/Helper/SingleInstance.cs
@@ -1,11 +1,5 @@
using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.IO;
-using System.Runtime.InteropServices;
using System.IO.Pipes;
-using System.Security;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -14,172 +8,6 @@ using System.Windows;
// modified to allow single instace restart
namespace Flow.Launcher.Helper
{
- internal enum WM
- {
- NULL = 0x0000,
- CREATE = 0x0001,
- DESTROY = 0x0002,
- MOVE = 0x0003,
- SIZE = 0x0005,
- ACTIVATE = 0x0006,
- SETFOCUS = 0x0007,
- KILLFOCUS = 0x0008,
- ENABLE = 0x000A,
- SETREDRAW = 0x000B,
- SETTEXT = 0x000C,
- GETTEXT = 0x000D,
- GETTEXTLENGTH = 0x000E,
- PAINT = 0x000F,
- CLOSE = 0x0010,
- QUERYENDSESSION = 0x0011,
- QUIT = 0x0012,
- QUERYOPEN = 0x0013,
- ERASEBKGND = 0x0014,
- SYSCOLORCHANGE = 0x0015,
- SHOWWINDOW = 0x0018,
- ACTIVATEAPP = 0x001C,
- SETCURSOR = 0x0020,
- MOUSEACTIVATE = 0x0021,
- CHILDACTIVATE = 0x0022,
- QUEUESYNC = 0x0023,
- GETMINMAXINFO = 0x0024,
-
- WINDOWPOSCHANGING = 0x0046,
- WINDOWPOSCHANGED = 0x0047,
-
- CONTEXTMENU = 0x007B,
- STYLECHANGING = 0x007C,
- STYLECHANGED = 0x007D,
- DISPLAYCHANGE = 0x007E,
- GETICON = 0x007F,
- SETICON = 0x0080,
- NCCREATE = 0x0081,
- NCDESTROY = 0x0082,
- NCCALCSIZE = 0x0083,
- NCHITTEST = 0x0084,
- NCPAINT = 0x0085,
- NCACTIVATE = 0x0086,
- GETDLGCODE = 0x0087,
- SYNCPAINT = 0x0088,
- NCMOUSEMOVE = 0x00A0,
- NCLBUTTONDOWN = 0x00A1,
- NCLBUTTONUP = 0x00A2,
- NCLBUTTONDBLCLK = 0x00A3,
- NCRBUTTONDOWN = 0x00A4,
- NCRBUTTONUP = 0x00A5,
- NCRBUTTONDBLCLK = 0x00A6,
- NCMBUTTONDOWN = 0x00A7,
- NCMBUTTONUP = 0x00A8,
- NCMBUTTONDBLCLK = 0x00A9,
-
- SYSKEYDOWN = 0x0104,
- SYSKEYUP = 0x0105,
- SYSCHAR = 0x0106,
- SYSDEADCHAR = 0x0107,
- COMMAND = 0x0111,
- SYSCOMMAND = 0x0112,
-
- MOUSEMOVE = 0x0200,
- LBUTTONDOWN = 0x0201,
- LBUTTONUP = 0x0202,
- LBUTTONDBLCLK = 0x0203,
- RBUTTONDOWN = 0x0204,
- RBUTTONUP = 0x0205,
- RBUTTONDBLCLK = 0x0206,
- MBUTTONDOWN = 0x0207,
- MBUTTONUP = 0x0208,
- MBUTTONDBLCLK = 0x0209,
- MOUSEWHEEL = 0x020A,
- XBUTTONDOWN = 0x020B,
- XBUTTONUP = 0x020C,
- XBUTTONDBLCLK = 0x020D,
- MOUSEHWHEEL = 0x020E,
-
-
- CAPTURECHANGED = 0x0215,
-
- ENTERSIZEMOVE = 0x0231,
- EXITSIZEMOVE = 0x0232,
-
- IME_SETCONTEXT = 0x0281,
- IME_NOTIFY = 0x0282,
- IME_CONTROL = 0x0283,
- IME_COMPOSITIONFULL = 0x0284,
- IME_SELECT = 0x0285,
- IME_CHAR = 0x0286,
- IME_REQUEST = 0x0288,
- IME_KEYDOWN = 0x0290,
- IME_KEYUP = 0x0291,
-
- NCMOUSELEAVE = 0x02A2,
-
- DWMCOMPOSITIONCHANGED = 0x031E,
- DWMNCRENDERINGCHANGED = 0x031F,
- DWMCOLORIZATIONCOLORCHANGED = 0x0320,
- DWMWINDOWMAXIMIZEDCHANGE = 0x0321,
-
- #region Windows 7
- DWMSENDICONICTHUMBNAIL = 0x0323,
- DWMSENDICONICLIVEPREVIEWBITMAP = 0x0326,
- #endregion
-
- USER = 0x0400,
-
- // This is the hard-coded message value used by WinForms for Shell_NotifyIcon.
- // It's relatively safe to reuse.
- TRAYMOUSEMESSAGE = 0x800, //WM_USER + 1024
- APP = 0x8000
- }
-
- [SuppressUnmanagedCodeSecurity]
- internal static class NativeMethods
- {
- ///
- /// Delegate declaration that matches WndProc signatures.
- ///
- public delegate IntPtr MessageHandler(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled);
-
- [DllImport("shell32.dll", EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
- private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
-
-
- [DllImport("kernel32.dll", EntryPoint = "LocalFree", SetLastError = true)]
- private static extern IntPtr _LocalFree(IntPtr hMem);
-
-
- public static string[] CommandLineToArgvW(string cmdLine)
- {
- IntPtr argv = IntPtr.Zero;
- try
- {
- int numArgs = 0;
-
- argv = _CommandLineToArgvW(cmdLine, out numArgs);
- if (argv == IntPtr.Zero)
- {
- throw new Win32Exception();
- }
- var result = new string[numArgs];
-
- for (int i = 0; i < numArgs; i++)
- {
- IntPtr currArg = Marshal.ReadIntPtr(argv, i * Marshal.SizeOf(typeof(IntPtr)));
- result[i] = Marshal.PtrToStringUni(currArg);
- }
-
- return result;
- }
- finally
- {
-
- IntPtr p = _LocalFree(argv);
- // Otherwise LocalFree failed.
- // Assert.AreEqual(IntPtr.Zero, p);
- }
- }
-
- }
-
public interface ISingleInstanceApp
{
void OnSecondAppStarted();
@@ -219,10 +47,6 @@ namespace Flow.Launcher.Helper
#endregion
- #region Public Properties
-
- #endregion
-
#region Public Methods
///
@@ -264,56 +88,6 @@ namespace Flow.Launcher.Helper
#region Private Methods
- ///
- /// Gets command line args - for ClickOnce deployed applications, command line args may not be passed directly, they have to be retrieved.
- ///
- /// List of command line arg strings.
- private static IList GetCommandLineArgs( string uniqueApplicationName )
- {
- string[] args = null;
-
- try
- {
- // The application was not clickonce deployed, get args from standard API's
- args = Environment.GetCommandLineArgs();
- }
- catch (NotSupportedException)
- {
-
- // The application was clickonce deployed
- // Clickonce deployed apps cannot recieve traditional commandline arguments
- // As a workaround commandline arguments can be written to a shared location before
- // the app is launched and the app can obtain its commandline arguments from the
- // shared location
- string appFolderPath = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), uniqueApplicationName);
-
- string cmdLinePath = Path.Combine(appFolderPath, "cmdline.txt");
- if (File.Exists(cmdLinePath))
- {
- try
- {
- using (TextReader reader = new StreamReader(cmdLinePath, Encoding.Unicode))
- {
- args = NativeMethods.CommandLineToArgvW(reader.ReadToEnd());
- }
-
- File.Delete(cmdLinePath);
- }
- catch (IOException)
- {
- }
- }
- }
-
- if (args == null)
- {
- args = new string[] { };
- }
-
- return new List(args);
- }
-
///
/// Creates a remote server pipe for communication.
/// Once receives signal from client, will activate first instance.
diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
index e08e227cc..8a42d480f 100644
--- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
+++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
@@ -2,29 +2,27 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
+using System.Windows.Documents;
using System.Windows.Media;
using Microsoft.Win32;
+using Windows.Win32;
+using Windows.Win32.UI.WindowsAndMessaging;
namespace Flow.Launcher.Helper;
public static class WallpaperPathRetrieval
{
- [DllImport("user32.dll", CharSet = CharSet.Unicode)]
- private static extern Int32 SystemParametersInfo(UInt32 action,
- Int32 uParam, StringBuilder vParam, UInt32 winIni);
- private static readonly UInt32 SPI_GETDESKWALLPAPER = 0x73;
- private static int MAX_PATH = 260;
+ private static readonly int MAX_PATH = 260;
- public static string GetWallpaperPath()
+ public static unsafe string GetWallpaperPath()
{
- var wallpaper = new StringBuilder(MAX_PATH);
- SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, wallpaper, 0);
-
- var str = wallpaper.ToString();
- if (string.IsNullOrEmpty(str))
- return null;
-
- return str;
+ var wallpaperPtr = stackalloc char[MAX_PATH];
+ PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, (uint)MAX_PATH,
+ wallpaperPtr,
+ 0);
+ var wallpaper = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(wallpaperPtr);
+
+ return wallpaper.ToString();
}
public static Color GetWallpaperColor()
@@ -35,13 +33,14 @@ public static class WallpaperPathRetrieval
{
try
{
- var parts = strResult.Trim().Split(new[] {' '}, 3).Select(byte.Parse).ToList();
+ var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList();
return Color.FromRgb(parts[0], parts[1], parts[2]);
}
catch
{
}
}
+
return Colors.Transparent;
}
}
diff --git a/Flow.Launcher/Helper/WindowsInteropHelper.cs b/Flow.Launcher/Helper/WindowsInteropHelper.cs
index 89fbec967..caf3f0a7f 100644
--- a/Flow.Launcher/Helper/WindowsInteropHelper.cs
+++ b/Flow.Launcher/Helper/WindowsInteropHelper.cs
@@ -1,75 +1,52 @@
using System;
+using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
-using System.Text;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using System.Windows.Media;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.WindowsAndMessaging;
using Point = System.Windows.Point;
namespace Flow.Launcher.Helper;
public class WindowsInteropHelper
{
- private const int GWL_STYLE = -16; //WPF's Message code for Title Bar's Style
- private const int WS_SYSMENU = 0x80000; //WPF's Message code for System Menu
- private static IntPtr _hwnd_shell;
- private static IntPtr _hwnd_desktop;
+ private static HWND _hwnd_shell;
+ private static HWND _hwnd_desktop;
//Accessors for shell and desktop handlers
//Will set the variables once and then will return them
- private static IntPtr HWND_SHELL
+ private static HWND HWND_SHELL
{
get
{
- return _hwnd_shell != IntPtr.Zero ? _hwnd_shell : _hwnd_shell = GetShellWindow();
+ return _hwnd_shell != HWND.Null ? _hwnd_shell : _hwnd_shell = PInvoke.GetShellWindow();
}
}
- private static IntPtr HWND_DESKTOP
+
+ private static HWND HWND_DESKTOP
{
get
{
- return _hwnd_desktop != IntPtr.Zero ? _hwnd_desktop : _hwnd_desktop = GetDesktopWindow();
+ return _hwnd_desktop != HWND.Null ? _hwnd_desktop : _hwnd_desktop = PInvoke.GetDesktopWindow();
}
}
- [DllImport("user32.dll", SetLastError = true)]
- internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);
-
- [DllImport("user32.dll")]
- internal static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
-
- [DllImport("user32.dll")]
- internal static extern IntPtr GetForegroundWindow();
-
- [DllImport("user32.dll")]
- internal static extern IntPtr GetDesktopWindow();
-
- [DllImport("user32.dll")]
- internal static extern IntPtr GetShellWindow();
-
- [DllImport("user32.dll", SetLastError = true)]
- internal static extern int GetWindowRect(IntPtr hwnd, out RECT rc);
-
- [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
- internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
-
- [DllImport("user32.DLL")]
- public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
-
-
const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass";
const string WINDOW_CLASS_WINTAB = "Flip3D";
const string WINDOW_CLASS_PROGMAN = "Progman";
const string WINDOW_CLASS_WORKERW = "WorkerW";
- public static bool IsWindowFullscreen()
+ public unsafe static bool IsWindowFullscreen()
{
//get current active window
- IntPtr hWnd = GetForegroundWindow();
+ var hWnd = PInvoke.GetForegroundWindow();
- if (hWnd.Equals(IntPtr.Zero))
+ if (hWnd.Equals(HWND.Null))
{
return false;
}
@@ -80,9 +57,17 @@ public class WindowsInteropHelper
return false;
}
- StringBuilder sb = new StringBuilder(256);
- GetClassName(hWnd, sb, sb.Capacity);
- string windowClass = sb.ToString();
+ string windowClass;
+ const int capacity = 256;
+ Span buffer = stackalloc char[capacity];
+ int validLength;
+ fixed (char* pBuffer = buffer)
+ {
+ validLength = PInvoke.GetClassName(hWnd, pBuffer, capacity);
+ }
+
+ windowClass = buffer[..validLength].ToString();
+
//for Win+Tab (Flip3D)
if (windowClass == WINDOW_CLASS_WINTAB)
@@ -90,28 +75,28 @@ public class WindowsInteropHelper
return false;
}
- RECT appBounds;
- GetWindowRect(hWnd, out appBounds);
+ PInvoke.GetWindowRect(hWnd, out var appBounds);
//for console (ConsoleWindowClass), we have to check for negative dimensions
if (windowClass == WINDOW_CLASS_CONSOLE)
{
- return appBounds.Top < 0 && appBounds.Bottom < 0;
+ return appBounds.top < 0 && appBounds.bottom < 0;
}
//for desktop (Progman or WorkerW, depends on the system), we have to check
if (windowClass is WINDOW_CLASS_PROGMAN or WINDOW_CLASS_WORKERW)
{
- IntPtr hWndDesktop = FindWindowEx(hWnd, IntPtr.Zero, "SHELLDLL_DefView", null);
- hWndDesktop = FindWindowEx(hWndDesktop, IntPtr.Zero, "SysListView32", "FolderView");
- if (!hWndDesktop.Equals(IntPtr.Zero))
+ var hWndDesktop = PInvoke.FindWindowEx(hWnd, HWND.Null, "SHELLDLL_DefView", null);
+ hWndDesktop = PInvoke.FindWindowEx(hWndDesktop, HWND.Null, "SysListView32", "FolderView");
+ if (hWndDesktop.Value != (IntPtr.Zero))
{
return false;
}
}
Rectangle screenBounds = Screen.FromHandle(hWnd).Bounds;
- return (appBounds.Bottom - appBounds.Top) == screenBounds.Height && (appBounds.Right - appBounds.Left) == screenBounds.Width;
+ return (appBounds.bottom - appBounds.top) == screenBounds.Height &&
+ (appBounds.right - appBounds.left) == screenBounds.Width;
}
///
@@ -120,8 +105,24 @@ public class WindowsInteropHelper
///
public static void DisableControlBox(Window win)
{
- var hwnd = new WindowInteropHelper(win).Handle;
- SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
+ var hwnd = new HWND(new WindowInteropHelper(win).Handle);
+
+ var style = PInvoke.GetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
+
+ if (style == 0)
+ {
+ throw new Win32Exception(Marshal.GetLastPInvokeError());
+ }
+
+ style &= ~(int)WINDOW_STYLE.WS_SYSMENU;
+
+ var previousStyle = PInvoke.SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE,
+ style);
+
+ if (previousStyle == 0)
+ {
+ throw new Win32Exception(Marshal.GetLastPInvokeError());
+ }
}
///
@@ -144,16 +145,7 @@ public class WindowsInteropHelper
using var src = new HwndSource(new HwndSourceParameters());
matrix = src.CompositionTarget.TransformFromDevice;
}
+
return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY));
}
-
-
- [StructLayout(LayoutKind.Sequential)]
- public struct RECT
- {
- public int Left;
- public int Top;
- public int Right;
- public int Bottom;
- }
}
diff --git a/Flow.Launcher/Images/image.png b/Flow.Launcher/Images/image.png
index 9f26517e8..ea610046b 100644
Binary files a/Flow.Launcher/Images/image.png and b/Flow.Launcher/Images/image.png differ
diff --git a/Flow.Launcher/Images/warning.png b/Flow.Launcher/Images/warning.png
deleted file mode 100644
index 8d29625ee..000000000
Binary files a/Flow.Launcher/Images/warning.png and /dev/null differ
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index ffb03b635..4c465d61f 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -67,6 +67,8 @@
Preserve Last Query
Select last Query
Empty last Query
+ Preserve Last Action Keyword
+ Select Last Action Keyword
Fixed Window Height
The window height is not adjustable by dragging.
Maximum results shown
diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml
index b9ad97534..f5fd729d4 100644
--- a/Flow.Launcher/MainWindow.xaml
+++ b/Flow.Launcher/MainWindow.xaml
@@ -308,7 +308,7 @@
VerticalAlignment="Center"
Panel.ZIndex="2"
RenderOptions.BitmapScalingMode="HighQuality"
- Source="{Binding PluginIconPath}"
+ Source="{Binding PluginIconSource}"
Stretch="Uniform"
Style="{DynamicResource PluginActivationIcon}" />