mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into merge_back_1.19.5
This commit is contained in:
commit
99323161b7
101 changed files with 1287 additions and 1335 deletions
|
|
@ -10,7 +10,7 @@ triggers:
|
|||
branch:
|
||||
- l10n_dev
|
||||
- dev
|
||||
- r/(?i)(Dependabot|Renovate)/
|
||||
- r/([Dd]ependabot|[Rr]enovate)/
|
||||
|
||||
|
||||
automations:
|
||||
|
|
|
|||
12
.github/workflows/pr_assignee.yml
vendored
12
.github/workflows/pr_assignee.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
5
.github/workflows/pr_milestone.yml
vendored
5
.github/workflows/pr_milestone.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -54,11 +54,11 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Droplex" Version="1.7.0" />
|
||||
<PackageReference Include="FSharp.Core" Version="8.0.401" />
|
||||
<PackageReference Include="FSharp.Core" Version="9.0.101" />
|
||||
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.0" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
|
||||
<PackageReference Include="StreamJsonRpc" Version="2.19.27" />
|
||||
<PackageReference Include="StreamJsonRpc" Version="2.20.20" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<Window
|
||||
x:Class="Flow.Launcher.MessageBoxEx"
|
||||
x:Class="Flow.Launcher.Core.MessageBoxEx"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Flow.Launcher"
|
||||
xmlns:local="clr-namespace:Flow.Launcher.Core"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
x:Name="MessageBoxWindow"
|
||||
Width="420"
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<KeyBinding Key="Escape" Command="Close" />
|
||||
</Window.InputBindings>
|
||||
<Window.CommandBindings>
|
||||
<CommandBinding Command="Close" Executed="cmdEsc_OnPress" />
|
||||
<CommandBinding Command="Close" Executed="KeyEsc_OnPress" />
|
||||
</Window.CommandBindings>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
|
|
@ -33,14 +33,11 @@
|
|||
<StackPanel>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Grid.Column="4"
|
||||
Grid.Column="1"
|
||||
Click="Button_Cancel"
|
||||
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||
<Path
|
||||
|
|
@ -63,8 +60,12 @@
|
|||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Margin="30 0 30 24">
|
||||
<Grid Grid.Column="0" Margin="0 0 0 12">
|
||||
<Grid Grid.Row="1" Margin="30 0 30 24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" Margin="0 0 0 12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
|
|
@ -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 @@
|
|||
</Grid>
|
||||
<TextBlock
|
||||
x:Name="DescTextBlock"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
MaxWidth="400"
|
||||
Margin="0 0 26 0"
|
||||
HorizontalAlignment="Stretch"
|
||||
FontSize="14"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
x:Name="DescOnlyTextBlock"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
MaxWidth="400"
|
||||
Margin="0 0 26 0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="Collapsed" />
|
||||
</Grid>
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Margin="0 0 0 0"
|
||||
|
|
@ -112,30 +125,30 @@
|
|||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
MinWidth="140"
|
||||
Margin="5 0 5 0"
|
||||
Click="Button_Click"
|
||||
Content="{DynamicResource commonCancel}" />
|
||||
<Button
|
||||
x:Name="btnNo"
|
||||
MinWidth="140"
|
||||
Margin="5 0 5 0"
|
||||
Click="Button_Click"
|
||||
Content="{DynamicResource commonNo}" />
|
||||
<Button
|
||||
x:Name="btnOk"
|
||||
MinWidth="140"
|
||||
MinWidth="120"
|
||||
Margin="5 0 5 0"
|
||||
Click="Button_Click"
|
||||
Content="{DynamicResource commonOK}" />
|
||||
<Button
|
||||
x:Name="btnYes"
|
||||
MinWidth="140"
|
||||
MinWidth="120"
|
||||
Margin="5 0 5 0"
|
||||
Click="Button_Click"
|
||||
Content="{DynamicResource commonYes}" />
|
||||
<Button
|
||||
x:Name="btnNo"
|
||||
MinWidth="120"
|
||||
Margin="5 0 5 0"
|
||||
Click="Button_Click"
|
||||
Content="{DynamicResource commonNo}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
MinWidth="120"
|
||||
Margin="5 0 5 0"
|
||||
Click="Button_Click"
|
||||
Content="{DynamicResource commonCancel}" />
|
||||
</WrapPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
203
Flow.Launcher.Core/MessageBoxEx.xaml.cs
Normal file
203
Flow.Launcher.Core/MessageBoxEx.xaml.cs
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,54 +26,33 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
protected override async Task<bool> ExecuteResultAsync(JsonRPCResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = await RPC.InvokeAsync<JsonRPCExecuteResponse>(result.JsonRPCAction.Method,
|
||||
argument: result.JsonRPCAction.Parameters);
|
||||
var res = await RPC.InvokeAsync<JsonRPCExecuteResponse>(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<Result> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = JTF.Run(() => RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("context_menu",
|
||||
new object[] { selectedResult.ContextData }));
|
||||
var res = JTF.Run(() => RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("context_menu",
|
||||
new object[] { selectedResult.ContextData }));
|
||||
|
||||
var results = ParseResults(res);
|
||||
var results = ParseResults(res);
|
||||
|
||||
return results;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public override async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = await RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("query",
|
||||
new object[] { query, Settings.Inner },
|
||||
token);
|
||||
var res = await RPC.InvokeWithCancellationAsync<JsonRPCQueryResponseModel>("query",
|
||||
new object[] { query, Settings.Inner },
|
||||
token);
|
||||
|
||||
var results = ParseResults(res);
|
||||
var results = ParseResults(res);
|
||||
|
||||
return results;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Language> 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",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string> _languageDirectories = new List<string>();
|
||||
private readonly List<ResourceDictionary> _oldResources = new List<ResourceDictionary>();
|
||||
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<PluginPair> 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<Language> 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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the blur for a window via SetWindowCompositionAttribute
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@
|
|||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="NativeMethods.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SolutionAssemblyInfo.cs" Link="Properties\SolutionAssemblyInfo.cs" />
|
||||
<None Include="FodyWeavers.xml" />
|
||||
|
|
@ -49,13 +53,17 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||
<PackageReference Include="BitFaster.Caching" Version="2.5.2" />
|
||||
<PackageReference Include="BitFaster.Caching" Version="2.5.3" />
|
||||
<PackageReference Include="Fody" Version="6.5.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MemoryPack" Version="1.21.3" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.10.48" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.12.19" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NLog" Version="4.7.10" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// </summary>
|
||||
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<KeyEvent, int, SpecialKeyState, bool> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<int, UIntPtr, IntPtr, IntPtr> 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<int, UIntPtr, IntPtr, IntPtr> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using Windows.Win32;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Hotkey
|
||||
{
|
||||
public enum KeyEvent
|
||||
|
|
@ -5,21 +7,21 @@ namespace Flow.Launcher.Infrastructure.Hotkey
|
|||
/// <summary>
|
||||
/// Key down
|
||||
/// </summary>
|
||||
WM_KEYDOWN = 256,
|
||||
WM_KEYDOWN = (int)PInvoke.WM_KEYDOWN,
|
||||
|
||||
/// <summary>
|
||||
/// Key up
|
||||
/// </summary>
|
||||
WM_KEYUP = 257,
|
||||
WM_KEYUP = (int)PInvoke.WM_KEYUP,
|
||||
|
||||
/// <summary>
|
||||
/// System key up
|
||||
/// </summary>
|
||||
WM_SYSKEYUP = 261,
|
||||
WM_SYSKEYUP = (int)PInvoke.WM_SYSKEYUP,
|
||||
|
||||
/// <summary>
|
||||
/// System key down
|
||||
/// </summary>
|
||||
WM_SYSKEYDOWN = 260
|
||||
WM_SYSKEYDOWN = (int)PInvoke.WM_SYSKEYDOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
private static readonly ConcurrentDictionary<string, string> 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Subclass of <see cref="Windows.Win32.UI.Shell.SIIGBF"/>
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
Flow.Launcher.Infrastructure/NativeMethods.txt
Normal file
19
Flow.Launcher.Infrastructure/NativeMethods.txt
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
101
Flow.Launcher.Infrastructure/Win32Helper.cs
Normal file
101
Flow.Launcher.Infrastructure/Win32Helper.cs
Normal file
|
|
@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the blur theme is enabled
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the blur for a window via SetWindowCompositionAttribute
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +57,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Readme.md" Pack="true" PackagePath="\"/>
|
||||
<AdditionalFiles Include="NativeMethods.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Readme.md" Pack="true" PackagePath="\" />
|
||||
<None Include="FodyWeavers.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
@ -68,6 +72,10 @@
|
|||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|||
|
||||
/// <summary>
|
||||
/// Reloads the query.
|
||||
/// This method should run
|
||||
/// This method should run when selected item is from query results.
|
||||
/// </summary>
|
||||
/// <param name="reselect">Choose the first result after reload if true; keep the last selected result if false. Default is true.</param>
|
||||
public void ReQuery(bool reselect = true);
|
||||
|
||||
/// <summary>
|
||||
/// Back to the query results.
|
||||
/// This method should run when selected item is from context menu or history.
|
||||
/// </summary>
|
||||
public void BackToQueryResults();
|
||||
|
||||
/// <summary>
|
||||
/// Displays a standardised Flow message box.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">The message of the message box.</param>
|
||||
/// <param name="caption">The caption of the message box.</param>
|
||||
/// <param name="button">Specifies which button or buttons to display.</param>
|
||||
/// <param name="icon">Specifies the icon to display.</param>
|
||||
/// <param name="defaultResult">Specifies the default result of the message box.</param>
|
||||
/// <returns>Specifies which message box button is clicked by the user.</returns>
|
||||
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
Flow.Launcher.Plugin/NativeMethods.txt
Normal file
3
Flow.Launcher.Plugin/NativeMethods.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
EnumThreadWindows
|
||||
GetWindowText
|
||||
GetWindowTextLength
|
||||
|
|
@ -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
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Title, SubTitle, AutoCompleteText, CopyText, IcoPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
|
|
@ -262,6 +242,16 @@ namespace Flow.Launcher.Plugin
|
|||
/// </summary>
|
||||
public PreviewInfo Preview { get; set; } = PreviewInfo.Default;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool AddSelectedCount { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public const int MaxScore = int.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Info of the preview section of a <see cref="Result"/>
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
/// </summary>
|
||||
/// <param name="sourcePath"></param>
|
||||
/// <param name="targetPath"></param>
|
||||
public static void CopyAll(this string sourcePath, string targetPath)
|
||||
/// <param name="messageBoxExShow"></param>
|
||||
public static void CopyAll(this string sourcePath, string targetPath, Func<string, MessageBoxResult> 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
|
|||
/// </summary>
|
||||
/// <param name="fromPath"></param>
|
||||
/// <param name="toPath"></param>
|
||||
/// <param name="messageBoxExShow"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyBothFolderFilesEqual(this string fromPath, string toPath)
|
||||
public static bool VerifyBothFolderFilesEqual(this string fromPath, string toPath, Func<string, MessageBoxResult> 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
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
public static void RemoveFolderIfExists(this string path)
|
||||
/// <param name="messageBoxExShow"></param>
|
||||
public static void RemoveFolderIfExists(this string path, Func<string, MessageBoxResult> 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)
|
||||
/// </summary>
|
||||
/// <param name="fileOrFolderPath"></param>
|
||||
public static void OpenPath(string fileOrFolderPath)
|
||||
/// <param name="messageBoxExShow"></param>
|
||||
public static void OpenPath(string fileOrFolderPath, Func<string, MessageBoxResult> 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
|
|||
/// <param name="filePath">File path</param>
|
||||
/// <param name="workingDir">Working directory</param>
|
||||
/// <param name="asAdmin">Open as Administrator</param>
|
||||
public static void OpenFile(string filePath, string workingDir = "", bool asAdmin = false)
|
||||
/// <param name="messageBoxExShow"></param>
|
||||
public static void OpenFile(string filePath, string workingDir = "", bool asAdmin = false, Func<string, MessageBoxResult> 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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<char> 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@
|
|||
<ResourceDictionary Source="pack://application:,,,/Resources/Dark.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Dark.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</ui:ThemeResources.ThemeDictionaries>
|
||||
</ui:ThemeResources>
|
||||
<ui:XamlControlsResources />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -83,19 +83,23 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="Fody" Version="6.5.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="InputSimulator" Version="1.0.4" />
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<!-- ModernWpfUI v0.9.5 introduced WinRT changes that causes Notification platform unavailable error on some machines -->
|
||||
<!-- https://github.com/Flow-Launcher/Flow.Launcher/issues/1772#issuecomment-1502440801 -->
|
||||
<PackageReference Include="ModernWpfUI" Version="0.9.4" />
|
||||
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
|
||||
<PackageReference Include="SemanticVersioning" Version="3.0.0-beta2" />
|
||||
<PackageReference Include="SemanticVersioning" Version="3.0.0" />
|
||||
<PackageReference Include="VirtualizingWrapPanel" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
|||
/// </summary>
|
||||
/// <param name="window">Window to which the shadow will be applied</param>
|
||||
/// <returns>True if the method succeeded, false if not</returns>
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate declaration that matches WndProc signatures.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -264,56 +88,6 @@ namespace Flow.Launcher.Helper
|
|||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets command line args - for ClickOnce deployed applications, command line args may not be passed directly, they have to be retrieved.
|
||||
/// </summary>
|
||||
/// <returns>List of command line arg strings.</returns>
|
||||
private static IList<string> 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<string>(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a remote server pipe for communication.
|
||||
/// Once receives signal from client, will activate first instance.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<char> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -120,8 +105,24 @@ public class WindowsInteropHelper
|
|||
/// </summary>
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 687 B After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 738 B |
|
|
@ -67,6 +67,8 @@
|
|||
<system:String x:Key="LastQueryPreserved">Preserve Last Query</system:String>
|
||||
<system:String x:Key="LastQuerySelected">Select last Query</system:String>
|
||||
<system:String x:Key="LastQueryEmpty">Empty last Query</system:String>
|
||||
<system:String x:Key="LastQueryActionKeywordPreserved">Preserve Last Action Keyword</system:String>
|
||||
<system:String x:Key="LastQueryActionKeywordSelected">Select Last Action Keyword</system:String>
|
||||
<system:String x:Key="KeepMaxResults">Fixed Window Height</system:String>
|
||||
<system:String x:Key="KeepMaxResultsToolTip">The window height is not adjustable by dragging.</system:String>
|
||||
<system:String x:Key="maxShowResults">Maximum results shown</system:String>
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@
|
|||
VerticalAlignment="Center"
|
||||
Panel.ZIndex="2"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Source="{Binding PluginIconPath}"
|
||||
Source="{Binding PluginIconSource}"
|
||||
Stretch="Uniform"
|
||||
Style="{DynamicResource PluginActivationIcon}" />
|
||||
<Canvas Style="{DynamicResource SearchIconPosition}">
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ using System.Media;
|
|||
using DataObject = System.Windows.DataObject;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Interop;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
|
|
@ -34,9 +34,6 @@ namespace Flow.Launcher
|
|||
{
|
||||
#region Private Fields
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr SetForegroundWindow(IntPtr hwnd);
|
||||
|
||||
private readonly Storyboard _progressBarStoryboard = new Storyboard();
|
||||
private bool isProgressBarStoryboardPaused;
|
||||
private Settings _settings;
|
||||
|
|
@ -81,21 +78,19 @@ namespace Flow.Launcher
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
private const int WM_ENTERSIZEMOVE = 0x0231;
|
||||
private const int WM_EXITSIZEMOVE = 0x0232;
|
||||
private int _initialWidth;
|
||||
private int _initialHeight;
|
||||
|
||||
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
if (msg == WM_ENTERSIZEMOVE)
|
||||
if (msg == PInvoke.WM_ENTERSIZEMOVE)
|
||||
{
|
||||
_initialWidth = (int)Width;
|
||||
_initialHeight = (int)Height;
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (msg == WM_EXITSIZEMOVE)
|
||||
if (msg == PInvoke.WM_EXITSIZEMOVE)
|
||||
{
|
||||
if (_initialHeight != (int)Height)
|
||||
{
|
||||
|
|
@ -424,7 +419,7 @@ namespace Flow.Launcher
|
|||
// Get context menu handle and bring it to the foreground
|
||||
if (PresentationSource.FromVisual(contextMenu) is HwndSource hwndSource)
|
||||
{
|
||||
_ = SetForegroundWindow(hwndSource.Handle);
|
||||
PInvoke.SetForegroundWindow(new(hwndSource.Handle));
|
||||
}
|
||||
|
||||
contextMenu.Focus();
|
||||
|
|
@ -692,7 +687,7 @@ namespace Flow.Launcher
|
|||
screen = Screen.PrimaryScreen;
|
||||
break;
|
||||
case SearchWindowScreens.Focus:
|
||||
IntPtr foregroundWindowHandle = WindowsInteropHelper.GetForegroundWindow();
|
||||
var foregroundWindowHandle = PInvoke.GetForegroundWindow().Value;
|
||||
screen = Screen.FromHandle(foregroundWindowHandle);
|
||||
break;
|
||||
case SearchWindowScreens.Custom:
|
||||
|
|
|
|||
|
|
@ -1,192 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using YamlDotNet.Core.Tokens;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class MessageBoxEx : Window
|
||||
{
|
||||
public MessageBoxEx()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public enum MessageBoxType
|
||||
{
|
||||
ConfirmationWithYesNo = 0,
|
||||
ConfirmationWithYesNoCancel,
|
||||
YesNo,
|
||||
Information,
|
||||
Error,
|
||||
Warning
|
||||
}
|
||||
|
||||
public enum MessageBoxImage
|
||||
{
|
||||
Warning = 0,
|
||||
Question,
|
||||
Information,
|
||||
Error,
|
||||
None
|
||||
}
|
||||
|
||||
static MessageBoxEx msgBox;
|
||||
static MessageBoxResult _result = MessageBoxResult.No;
|
||||
|
||||
|
||||
/// 1 parameter
|
||||
public static MessageBoxResult Show(string msg)
|
||||
{
|
||||
return Show(string.Empty, msg, MessageBoxButton.OK, MessageBoxImage.None);
|
||||
}
|
||||
|
||||
// 2 parameter
|
||||
public static MessageBoxResult Show(string caption, string text)
|
||||
{
|
||||
return Show(caption, text, MessageBoxButton.OK, MessageBoxImage.None);
|
||||
}
|
||||
|
||||
/// 3 parameter
|
||||
public static MessageBoxResult Show(string caption, string msg, MessageBoxType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MessageBoxType.ConfirmationWithYesNo:
|
||||
return Show(caption, msg, MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
case MessageBoxType.YesNo:
|
||||
return Show(caption, msg, MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
case MessageBoxType.ConfirmationWithYesNoCancel:
|
||||
return Show(caption, msg, MessageBoxButton.YesNoCancel,
|
||||
MessageBoxImage.Question);
|
||||
case MessageBoxType.Information:
|
||||
return Show(caption, msg, MessageBoxButton.OK,
|
||||
MessageBoxImage.Information);
|
||||
case MessageBoxType.Error:
|
||||
return Show(caption, msg, MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
case MessageBoxType.Warning:
|
||||
return Show(caption, msg, MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
default:
|
||||
return MessageBoxResult.No;
|
||||
}
|
||||
}
|
||||
|
||||
// 4 parameter, Final Display Message.
|
||||
public static MessageBoxResult Show(string caption, string text, MessageBoxButton button, MessageBoxImage image)
|
||||
{
|
||||
msgBox = new MessageBoxEx();
|
||||
msgBox.TitleTextBlock.Text = text;
|
||||
msgBox.DescTextBlock.Text = caption;
|
||||
msgBox.Title = text;
|
||||
SetVisibilityOfButtons(button);
|
||||
SetImageOfMessageBox(image);
|
||||
msgBox.ShowDialog();
|
||||
return _result;
|
||||
}
|
||||
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 static void SetVisibilityOfButtons(MessageBoxButton button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case MessageBoxButton.OK:
|
||||
msgBox.btnCancel.Visibility = Visibility.Collapsed;
|
||||
msgBox.btnNo.Visibility = Visibility.Collapsed;
|
||||
msgBox.btnYes.Visibility = Visibility.Collapsed;
|
||||
msgBox.btnOk.Focus();
|
||||
break;
|
||||
case MessageBoxButton.OKCancel:
|
||||
msgBox.btnNo.Visibility = Visibility.Collapsed;
|
||||
msgBox.btnYes.Visibility = Visibility.Collapsed;
|
||||
msgBox.btnCancel.Focus();
|
||||
break;
|
||||
case MessageBoxButton.YesNo:
|
||||
msgBox.btnOk.Visibility = Visibility.Collapsed;
|
||||
msgBox.btnCancel.Visibility = Visibility.Collapsed;
|
||||
msgBox.btnNo.Focus();
|
||||
break;
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
msgBox.btnOk.Visibility = Visibility.Collapsed;
|
||||
msgBox.btnCancel.Focus();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private static void SetImageOfMessageBox(MessageBoxImage image)
|
||||
{
|
||||
switch (image)
|
||||
{
|
||||
case MessageBoxImage.Warning:
|
||||
msgBox.SetImage("Warning.png");
|
||||
msgBox.Img.Visibility = Visibility.Visible;
|
||||
break;
|
||||
case MessageBoxImage.Question:
|
||||
msgBox.SetImage("Question.png");
|
||||
msgBox.Img.Visibility = Visibility.Visible;
|
||||
break;
|
||||
case MessageBoxImage.Information:
|
||||
msgBox.SetImage("Information.png");
|
||||
msgBox.Img.Visibility = Visibility.Visible;
|
||||
break;
|
||||
case MessageBoxImage.Error:
|
||||
msgBox.SetImage("Error.png");
|
||||
msgBox.Img.Visibility = Visibility.Visible;
|
||||
break;
|
||||
default:
|
||||
msgBox.Img.Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void SetImage(string imageName)
|
||||
{
|
||||
string uri = Constant.ProgramDirectory + "/Images/" + imageName;
|
||||
var uriSource = new Uri(uri, UriKind.RelativeOrAbsolute);
|
||||
Img.Source = new BitmapImage(uriSource);
|
||||
}
|
||||
private void cmdEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Button_Cancel(object sender, RoutedEventArgs e)
|
||||
{
|
||||
msgBox.Close();
|
||||
msgBox = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
17
Flow.Launcher/NativeMethods.txt
Normal file
17
Flow.Launcher/NativeMethods.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
DwmSetWindowAttribute
|
||||
DwmExtendFrameIntoClientArea
|
||||
SystemParametersInfo
|
||||
SetForegroundWindow
|
||||
|
||||
GetWindowLong
|
||||
SetWindowLong
|
||||
GetForegroundWindow
|
||||
GetDesktopWindow
|
||||
GetShellWindow
|
||||
GetWindowRect
|
||||
GetClassName
|
||||
FindWindowEx
|
||||
WINDOW_STYLE
|
||||
|
||||
WM_ENTERSIZEMOVE
|
||||
WM_EXITSIZEMOVE
|
||||
|
|
@ -5,6 +5,7 @@ using Flow.Launcher.ViewModel;
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using Flow.Launcher.Core;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
|
|
@ -23,7 +24,7 @@ namespace Flow.Launcher
|
|||
this.pluginViewModel = pluginViewModel;
|
||||
if (plugin == null)
|
||||
{
|
||||
MessageBox.Show(translater.GetTranslation("cannotFindSpecifiedPlugin"));
|
||||
MessageBoxEx.Show(translater.GetTranslation("cannotFindSpecifiedPlugin"));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +44,7 @@ namespace Flow.Launcher
|
|||
else
|
||||
{
|
||||
string msg = translater.GetTranslation("invalidPriority");
|
||||
MessageBox.Show(msg);
|
||||
MessageBoxEx.Show(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ using Flow.Launcher.Infrastructure.Storage;
|
|||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Specialized;
|
||||
using Flow.Launcher.Core;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
|
|
@ -125,9 +126,9 @@ namespace Flow.Launcher
|
|||
if (directCopy && (isFile || Directory.Exists(stringToCopy)))
|
||||
{
|
||||
var paths = new StringCollection
|
||||
{
|
||||
stringToCopy
|
||||
};
|
||||
{
|
||||
stringToCopy
|
||||
};
|
||||
|
||||
Clipboard.SetFileDropList(paths);
|
||||
|
||||
|
|
@ -318,6 +319,11 @@ namespace Flow.Launcher
|
|||
|
||||
public void ReQuery(bool reselect = true) => _mainVM.ReQuery(reselect);
|
||||
|
||||
public void BackToQueryResults() => _mainVM.BackToQueryResults();
|
||||
|
||||
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK) =>
|
||||
MessageBoxEx.Show(messageBoxText, caption, button, icon, defaultResult);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ using Flow.Launcher.Core.Resource;
|
|||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using static Flow.Launcher.MessageBoxEx;
|
||||
|
||||
namespace Flow.Launcher.SettingPages.ViewModels;
|
||||
|
||||
|
|
@ -66,7 +65,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
|||
var confirmResult = MessageBoxEx.Show(
|
||||
InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"),
|
||||
InternationalizationManager.Instance.GetTranslation("clearlogfolder"),
|
||||
MessageBoxType.YesNo
|
||||
MessageBoxButton.YesNo
|
||||
);
|
||||
|
||||
if (confirmResult == MessageBoxResult.Yes)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Flow.Launcher.Infrastructure;
|
|||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Core;
|
||||
|
||||
namespace Flow.Launcher.SettingPages.ViewModels;
|
||||
|
||||
|
|
@ -41,11 +42,11 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
|
|||
var item = SelectedCustomPluginHotkey;
|
||||
if (item is null)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||
return;
|
||||
}
|
||||
|
||||
var result = MessageBox.Show(
|
||||
var result = MessageBoxEx.Show(
|
||||
string.Format(
|
||||
InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey
|
||||
),
|
||||
|
|
@ -66,7 +67,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
|
|||
var item = SelectedCustomPluginHotkey;
|
||||
if (item is null)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -87,11 +88,11 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
|
|||
var item = SelectedCustomShortcut;
|
||||
if (item is null)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||
return;
|
||||
}
|
||||
|
||||
var result = MessageBox.Show(
|
||||
var result = MessageBoxEx.Show(
|
||||
string.Format(
|
||||
InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value
|
||||
),
|
||||
|
|
@ -111,7 +112,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
|
|||
var item = SelectedCustomShortcut;
|
||||
if (item is null)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Net;
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Core;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
|
|
@ -23,7 +22,7 @@ public partial class SettingsPaneProxyViewModel : BaseModel
|
|||
private void OnTestProxyClicked()
|
||||
{
|
||||
var message = TestProxy();
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation(message));
|
||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation(message));
|
||||
}
|
||||
|
||||
private string TestProxy()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -15,6 +14,7 @@ using Flow.Launcher.Infrastructure.UserSettings;
|
|||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using ModernWpf;
|
||||
using Flow.Launcher.Core;
|
||||
using ThemeManager = Flow.Launcher.Core.Resource.ThemeManager;
|
||||
using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager;
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
|
|||
{
|
||||
if (ThemeManager.Instance.BlurEnabled && value)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed"));
|
||||
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +132,9 @@ public partial class SettingsPaneThemeViewModel : BaseModel
|
|||
"ddd dd'/'MM",
|
||||
"dddd dd'/'MM",
|
||||
"dddd dd', 'MMMM",
|
||||
"dd', 'MMMM"
|
||||
"dd', 'MMMM",
|
||||
"dd.MM.yy",
|
||||
"dd.MM.yyyy"
|
||||
};
|
||||
|
||||
public string TimeFormat
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@
|
|||
IsPaneToggleButtonVisible="False"
|
||||
IsSettingsVisible="False"
|
||||
IsTabStop="False"
|
||||
Loaded="NavView_Loaded"
|
||||
OpenPaneLength="240"
|
||||
PaneDisplayMode="Left"
|
||||
SelectionChanged="NavigationView_SelectionChanged">
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ public partial class SettingWindow
|
|||
_api = api;
|
||||
InitializePosition();
|
||||
InitializeComponent();
|
||||
NavView.SelectedItem = NavView.MenuItems[0]; /* Set First Page */
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
|
|
@ -169,7 +168,11 @@ public partial class SettingWindow
|
|||
else
|
||||
{
|
||||
var selectedItem = (NavigationViewItem)args.SelectedItem;
|
||||
if (selectedItem == null) return;
|
||||
if (selectedItem == null)
|
||||
{
|
||||
NavView_Loaded(sender, null); /* Reset First Page */
|
||||
return;
|
||||
}
|
||||
|
||||
var pageType = selectedItem.Name switch
|
||||
{
|
||||
|
|
@ -186,5 +189,22 @@ public partial class SettingWindow
|
|||
}
|
||||
}
|
||||
|
||||
private void NavView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ContentFrame.IsLoaded)
|
||||
{
|
||||
ContentFrame_Loaded(sender, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentFrame.Loaded += ContentFrame_Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
private void ContentFrame_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
NavView.SelectedItem ??= NavView.MenuItems[0]; /* Set First Page */
|
||||
}
|
||||
|
||||
public record PaneData(Settings Settings, Updater Updater, IPortable Portable);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Storage
|
||||
{
|
||||
// todo this class is not thread safe.... but used from multiple threads.
|
||||
public class TopMostRecord
|
||||
{
|
||||
[JsonInclude]
|
||||
public Dictionary<string, Record> records { get; private set; } = new Dictionary<string, Record>();
|
||||
public ConcurrentDictionary<string, Record> records { get; private set; } = new ConcurrentDictionary<string, Record>();
|
||||
|
||||
internal bool IsTopMost(Result result)
|
||||
{
|
||||
if (records.Count == 0 || !records.ContainsKey(result.OriginQuery.RawQuery))
|
||||
if (records.IsEmpty || result.OriginQuery == null ||
|
||||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// since this dictionary should be very small (or empty) going over it should be pretty fast.
|
||||
return records[result.OriginQuery.RawQuery].Equals(result);
|
||||
return value.Equals(result);
|
||||
}
|
||||
|
||||
internal void Remove(Result result)
|
||||
{
|
||||
records.Remove(result.OriginQuery.RawQuery);
|
||||
records.Remove(result.OriginQuery.RawQuery, out _);
|
||||
}
|
||||
|
||||
internal void AddOrUpdate(Result result)
|
||||
|
|
@ -34,17 +35,15 @@ namespace Flow.Launcher.Storage
|
|||
Title = result.Title,
|
||||
SubTitle = result.SubTitle
|
||||
};
|
||||
records[result.OriginQuery.RawQuery] = record;
|
||||
|
||||
records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record);
|
||||
}
|
||||
|
||||
public void Load(Dictionary<string, Record> dictionary)
|
||||
{
|
||||
records = dictionary;
|
||||
records = new ConcurrentDictionary<string, Record>(dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Record
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ namespace Flow.Launcher.Storage
|
|||
|
||||
private static int GenerateQueryAndResultHashCode(Query query, Result result)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
return GenerateResultHashCode(result);
|
||||
}
|
||||
|
||||
int hashcode = GenerateStaticHashCode(query.ActionKeyword);
|
||||
hashcode = GenerateStaticHashCode(query.Search, hashcode);
|
||||
hashcode = GenerateStaticHashCode(result.Title, hashcode);
|
||||
|
|
@ -101,4 +106,4 @@ namespace Flow.Launcher.Storage
|
|||
return selectedCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
|
@ -23,6 +23,8 @@ using CommunityToolkit.Mvvm.Input;
|
|||
using System.Globalization;
|
||||
using System.Windows.Input;
|
||||
using System.ComponentModel;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
|
|
@ -231,8 +233,11 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
var token = e.Token == default ? _updateToken : e.Token;
|
||||
|
||||
PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query);
|
||||
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(e.Results, pair.Metadata, e.Query,
|
||||
// make a copy of results to avoid plugin change the result when updating view model
|
||||
var resultsCopy = e.Results.ToList();
|
||||
|
||||
PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query);
|
||||
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
|
||||
token)))
|
||||
{
|
||||
Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
|
||||
|
|
@ -484,6 +489,14 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public void BackToQueryResults()
|
||||
{
|
||||
if (!SelectedIsFromQueryResults())
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ToggleGameMode()
|
||||
{
|
||||
|
|
@ -722,6 +735,8 @@ namespace Flow.Launcher.ViewModel
|
|||
set => Settings.ResultSubItemFontSize = value;
|
||||
}
|
||||
|
||||
public ImageSource PluginIconSource { get; private set; } = null;
|
||||
|
||||
public string PluginIconPath { get; set; } = null;
|
||||
|
||||
public string OpenResultCommandModifiers => Settings.OpenResultModifiers;
|
||||
|
|
@ -759,7 +774,7 @@ namespace Flow.Launcher.ViewModel
|
|||
public string Image => Constant.QueryTextBoxIconImagePath;
|
||||
|
||||
public bool StartWithEnglishMode => Settings.AlwaysStartEn;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Preview
|
||||
|
|
@ -821,7 +836,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
|
||||
private void HidePreview()
|
||||
{
|
||||
{
|
||||
if (PluginManager.UseExternalPreview())
|
||||
CloseExternalPreview();
|
||||
|
||||
|
|
@ -900,7 +915,7 @@ namespace Flow.Launcher.ViewModel
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void UpdatePreview()
|
||||
{
|
||||
switch (PluginManager.UseExternalPreview())
|
||||
|
|
@ -1066,6 +1081,7 @@ namespace Flow.Launcher.ViewModel
|
|||
Results.Clear();
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
PluginIconPath = null;
|
||||
PluginIconSource = null;
|
||||
SearchIconVisibility = Visibility.Visible;
|
||||
return;
|
||||
}
|
||||
|
|
@ -1099,11 +1115,13 @@ namespace Flow.Launcher.ViewModel
|
|||
if (plugins.Count == 1)
|
||||
{
|
||||
PluginIconPath = plugins.Single().Metadata.IcoPath;
|
||||
PluginIconSource = await ImageLoader.LoadAsync(PluginIconPath);
|
||||
SearchIconVisibility = Visibility.Hidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginIconPath = null;
|
||||
PluginIconSource = null;
|
||||
SearchIconVisibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
|
|
@ -1386,6 +1404,16 @@ namespace Flow.Launcher.ViewModel
|
|||
await Task.Delay(100);
|
||||
LastQuerySelected = false;
|
||||
break;
|
||||
case LastQueryMode.ActionKeywordPreserved or LastQueryMode.ActionKeywordSelected:
|
||||
var newQuery = _lastQuery.ActionKeyword;
|
||||
if (!string.IsNullOrEmpty(newQuery))
|
||||
newQuery += " ";
|
||||
ChangeQueryText(newQuery);
|
||||
if (Settings.UseAnimation)
|
||||
await Task.Delay(100);
|
||||
if (Settings.LastQueryMode == LastQueryMode.ActionKeywordSelected)
|
||||
LastQuerySelected = false;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"wrong LastQueryMode: <{Settings.LastQueryMode}>");
|
||||
}
|
||||
|
|
@ -1446,12 +1474,14 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
if (_topMostRecord.IsTopMost(result))
|
||||
{
|
||||
result.Score = int.MaxValue;
|
||||
result.Score = Result.MaxScore;
|
||||
}
|
||||
else
|
||||
else if (result.Score != Result.MaxScore)
|
||||
{
|
||||
var priorityScore = metaResults.Metadata.Priority * 150;
|
||||
result.Score += _userSelectedRecord.GetSelectedCount(result) + priorityScore;
|
||||
result.Score += result.AddSelectedCount ?
|
||||
_userSelectedRecord.GetSelectedCount(result) + priorityScore :
|
||||
priorityScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ namespace Flow.Launcher.ViewModel
|
|||
/// <summary>
|
||||
/// To avoid deadlock, this method should not called from main thread
|
||||
/// </summary>
|
||||
public void AddResults(IEnumerable<ResultsForUpdate> resultsForUpdates, CancellationToken token, bool reselect = true)
|
||||
public void AddResults(ICollection<ResultsForUpdate> resultsForUpdates, CancellationToken token, bool reselect = true)
|
||||
{
|
||||
var newResults = NewResults(resultsForUpdates);
|
||||
|
||||
|
|
@ -228,12 +228,12 @@ namespace Flow.Launcher.ViewModel
|
|||
.ToList();
|
||||
}
|
||||
|
||||
private List<ResultViewModel> NewResults(IEnumerable<ResultsForUpdate> resultsForUpdates)
|
||||
private List<ResultViewModel> NewResults(ICollection<ResultsForUpdate> resultsForUpdates)
|
||||
{
|
||||
if (!resultsForUpdates.Any())
|
||||
return Results;
|
||||
|
||||
return Results.Where(r => r != null && !resultsForUpdates.Any(u => u.ID == r.Result.PluginID))
|
||||
return Results.Where(r => r?.Result != null && resultsForUpdates.All(u => u.ID != r.Result.PluginID))
|
||||
.Concat(resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)))
|
||||
.OrderByDescending(rv => rv.Result.Score)
|
||||
.ToList();
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
<ui:Frame
|
||||
x:Name="ContentFrame"
|
||||
HorizontalAlignment="Stretch"
|
||||
Loaded="ContentFrame_Loaded"
|
||||
ScrollViewer.CanContentScroll="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible">
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ namespace Flow.Launcher
|
|||
InitializeComponent();
|
||||
BackButton.IsEnabled = false;
|
||||
this.settings = settings;
|
||||
ContentFrame.Navigate(PageTypeSelector(1), settings);
|
||||
}
|
||||
|
||||
private NavigationTransitionInfo _transitionInfo = new SlideNavigationTransitionInfo()
|
||||
|
|
@ -102,9 +101,15 @@ namespace Flow.Launcher
|
|||
var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
|
||||
textBox.MoveFocus(tRequest);
|
||||
}
|
||||
|
||||
private void OnActivated(object sender, EventArgs e)
|
||||
{
|
||||
Keyboard.ClearFocus();
|
||||
}
|
||||
|
||||
private void ContentFrame_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ContentFrame.Navigate(PageTypeSelector(1), settings); /* Set First Page */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mages" Version="2.0.2" />
|
||||
<PackageReference Include="Mages" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Mages.Core;
|
||||
using Flow.Launcher.Plugin.Calculator.ViewModels;
|
||||
|
|
@ -101,7 +100,7 @@ namespace Flow.Launcher.Plugin.Calculator
|
|||
}
|
||||
catch (ExternalException)
|
||||
{
|
||||
MessageBox.Show("Copy failed, please try later");
|
||||
Context.API.ShowMsgBox("Copy failed, please try later");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,6 @@ using Flow.Launcher.Plugin.Explorer.Search;
|
|||
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
||||
using System.Linq;
|
||||
using Flow.Launcher.Plugin.Explorer.Helper;
|
||||
using MessageBox = System.Windows.Forms.MessageBox;
|
||||
using MessageBoxIcon = System.Windows.Forms.MessageBoxIcon;
|
||||
using MessageBoxButton = System.Windows.Forms.MessageBoxButtons;
|
||||
using DialogResult = System.Windows.Forms.DialogResult;
|
||||
using Flow.Launcher.Plugin.Explorer.ViewModels;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer
|
||||
|
|
@ -177,12 +173,12 @@ namespace Flow.Launcher.Plugin.Explorer
|
|||
{
|
||||
try
|
||||
{
|
||||
if (MessageBox.Show(
|
||||
if (Context.API.ShowMsgBox(
|
||||
string.Format(Context.API.GetTranslation("plugin_explorer_delete_folder_link"), record.FullPath),
|
||||
string.Empty,
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxIcon.Warning)
|
||||
== DialogResult.No)
|
||||
MessageBoxImage.Warning)
|
||||
== MessageBoxResult.No)
|
||||
return false;
|
||||
|
||||
if (isFile)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything;
|
||||
|
||||
|
|
@ -19,10 +20,10 @@ public static class EverythingDownloadHelper
|
|||
|
||||
if (string.IsNullOrEmpty(installedLocation))
|
||||
{
|
||||
if (System.Windows.Forms.MessageBox.Show(
|
||||
if (api.ShowMsgBox(
|
||||
string.Format(api.GetTranslation("flowlauncher_plugin_everything_installing_select"), Environment.NewLine),
|
||||
api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
|
||||
System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
|
||||
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
var dlg = new System.Windows.Forms.OpenFileDialog
|
||||
{
|
||||
|
|
@ -50,7 +51,7 @@ public static class EverythingDownloadHelper
|
|||
|
||||
installedLocation = "C:\\Program Files\\Everything\\Everything.exe";
|
||||
|
||||
FilesFolders.OpenPath(installedLocation);
|
||||
FilesFolders.OpenPath(installedLocation, (string str) => api.ShowMsgBox(str));
|
||||
|
||||
return installedLocation;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.Everything;
|
||||
using System.Windows.Input;
|
||||
using Path = System.IO.Path;
|
||||
|
|
@ -123,7 +122,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
|
||||
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -137,7 +136,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
|
||||
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +151,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
|
||||
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -315,7 +314,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, Context.API.GetTranslation("plugin_explorer_openfile_error"));
|
||||
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_openfile_error"));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -337,7 +336,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
private static void OpenFile(string filePath, string workingDir = "", bool asAdmin = false)
|
||||
{
|
||||
IncrementEverythingRunCounterIfNeeded(filePath);
|
||||
FilesFolders.OpenFile(filePath, workingDir, asAdmin);
|
||||
FilesFolders.OpenFile(filePath, workingDir, asAdmin, (string str) => Context.API.ShowMsgBox(str));
|
||||
}
|
||||
|
||||
private static void OpenFolder(string folderPath, string fileNameOrFilePath = null)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Search.Interop;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
||||
{
|
||||
public class QueryConstructor
|
||||
{
|
||||
private static Regex _specialCharacterMatcher = new(@"[\@\@\#\#\&\&*_;,\%\|\!\(\)\{\}\[\]\^\~\?\\""\/\:\=\-]+", RegexOptions.Compiled);
|
||||
private static Regex _multiWhiteSpacesMatcher = new(@"\s+", RegexOptions.Compiled);
|
||||
|
||||
private Settings settings { get; }
|
||||
|
||||
private const string SystemIndex = "SystemIndex";
|
||||
|
|
@ -76,8 +80,39 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
if (userSearchString.IsWhiteSpace())
|
||||
userSearchString = "*";
|
||||
|
||||
// Remove any special characters that might cause issues with the query
|
||||
var replacedSearchString = ReplaceSpecialCharacterWithTwoSideWhiteSpace(userSearchString);
|
||||
|
||||
// Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause
|
||||
return $"{CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString.ToString())} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
|
||||
return $"{CreateBaseQuery().GenerateSQLFromUserQuery(replacedSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If one special character have white space on one side, replace it with one white space.
|
||||
/// So command will not have "[special character]+*" which will cause OLEDB exception.
|
||||
/// </summary>
|
||||
private static string ReplaceSpecialCharacterWithTwoSideWhiteSpace(ReadOnlySpan<char> input)
|
||||
{
|
||||
const string whiteSpace = " ";
|
||||
|
||||
var inputString = input.ToString();
|
||||
|
||||
// Use regex to match special characters with whitespace on one side
|
||||
// and replace them with a single space
|
||||
var result = _specialCharacterMatcher.Replace(inputString, match =>
|
||||
{
|
||||
// Check if the match has whitespace on one side
|
||||
bool hasLeadingWhitespace = match.Index > 0 && char.IsWhiteSpace(inputString[match.Index - 1]);
|
||||
bool hasTrailingWhitespace = match.Index + match.Length < inputString.Length && char.IsWhiteSpace(inputString[match.Index + match.Length]);
|
||||
if (hasLeadingWhitespace || hasTrailingWhitespace)
|
||||
{
|
||||
return whiteSpace;
|
||||
}
|
||||
return match.Value;
|
||||
});
|
||||
|
||||
// Remove any extra spaces that might have been introduced
|
||||
return _multiWhiteSpacesMatcher.Replace(result, whiteSpace).Trim();
|
||||
}
|
||||
|
||||
///<summary>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ using System.Linq;
|
|||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using MessageBox = System.Windows.Forms.MessageBox;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||
{
|
||||
|
|
@ -358,7 +357,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
|||
private void ShowUnselectedMessage()
|
||||
{
|
||||
var warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning");
|
||||
MessageBox.Show(warning);
|
||||
Context.API.ShowMsgBox(warning);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
|
|
@ -62,10 +62,10 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled)
|
||||
{
|
||||
case (Settings.ActionKeyword.FileContentSearchActionKeyword, true):
|
||||
MessageBox.Show(api.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
|
||||
api.ShowMsgBox(api.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
|
||||
return;
|
||||
case (Settings.ActionKeyword.QuickAccessActionKeyword, true):
|
||||
MessageBox.Show(api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid"));
|
||||
api.ShowMsgBox(api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
|||
}
|
||||
|
||||
// The keyword is not valid, so show message
|
||||
MessageBox.Show(api.GetTranslation("newActionKeywordsHasBeenAssigned"));
|
||||
api.ShowMsgBox(api.GetTranslation("newActionKeywordsHasBeenAssigned"));
|
||||
}
|
||||
|
||||
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
Environment.NewLine);
|
||||
}
|
||||
|
||||
if (MessageBox.Show(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
|
||||
if (Context.API.ShowMsgBox(message, Context.API.GetTranslation("plugin_pluginsmanager_install_title"),
|
||||
MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
return;
|
||||
|
||||
|
|
@ -265,7 +265,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
Environment.NewLine);
|
||||
}
|
||||
|
||||
if (MessageBox.Show(message,
|
||||
if (Context.API.ShowMsgBox(message,
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
|
||||
MessageBoxButton.YesNo) != MessageBoxResult.Yes)
|
||||
{
|
||||
|
|
@ -360,7 +360,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
resultsForUpdate.Count());
|
||||
}
|
||||
|
||||
if (MessageBox.Show(message,
|
||||
if (Context.API.ShowMsgBox(message,
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
|
||||
MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
{
|
||||
|
|
@ -474,7 +474,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
if (Settings.WarnFromUnknownSource)
|
||||
{
|
||||
if (!InstallSourceKnown(plugin.UrlDownload)
|
||||
&& MessageBox.Show(string.Format(
|
||||
&& Context.API.ShowMsgBox(string.Format(
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"),
|
||||
Environment.NewLine),
|
||||
Context.API.GetTranslation(
|
||||
|
|
@ -511,7 +511,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
if (Settings.WarnFromUnknownSource)
|
||||
{
|
||||
if (!InstallSourceKnown(plugin.Website)
|
||||
&& MessageBox.Show(string.Format(
|
||||
&& Context.API.ShowMsgBox(string.Format(
|
||||
Context.API.GetTranslation(
|
||||
"plugin_pluginsmanager_install_unknown_source_warning"),
|
||||
Environment.NewLine),
|
||||
|
|
@ -648,7 +648,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
Environment.NewLine);
|
||||
}
|
||||
|
||||
if (MessageBox.Show(message,
|
||||
if (Context.API.ShowMsgBox(message,
|
||||
Context.API.GetTranslation("plugin_pluginsmanager_uninstall_title"),
|
||||
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
using Flow.Launcher.Core.ExternalPlugins;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Flow.Launcher.Plugin.PluginsManager
|
||||
{
|
||||
|
|
@ -72,12 +71,9 @@ namespace Flow.Launcher.Plugin.PluginsManager
|
|||
|
||||
if (pluginJsonEntry != null)
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(pluginJsonEntry.Open()))
|
||||
{
|
||||
string pluginJsonContent = reader.ReadToEnd();
|
||||
plugin = JsonConvert.DeserializeObject<UserPlugin>(pluginJsonContent);
|
||||
plugin.IcoPath = "Images\\zipfolder.png";
|
||||
}
|
||||
using Stream stream = pluginJsonEntry.Open();
|
||||
plugin = JsonSerializer.Deserialize<UserPlugin>(stream);
|
||||
plugin.IcoPath = "Images\\zipfolder.png";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,13 @@
|
|||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
QueryFullProcessImageName
|
||||
OpenProcess
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Threading;
|
||||
|
||||
namespace Flow.Launcher.Plugin.ProcessKiller
|
||||
{
|
||||
|
|
@ -84,43 +86,33 @@ namespace Flow.Launcher.Plugin.ProcessKiller
|
|||
}
|
||||
}
|
||||
|
||||
public string TryGetProcessFilename(Process p)
|
||||
public unsafe string TryGetProcessFilename(Process p)
|
||||
{
|
||||
try
|
||||
{
|
||||
int capacity = 2000;
|
||||
StringBuilder builder = new StringBuilder(capacity);
|
||||
IntPtr ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id);
|
||||
if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity))
|
||||
var handle = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, (uint)p.Id);
|
||||
if (handle.Value == IntPtr.Zero)
|
||||
{
|
||||
return String.Empty;
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
using var safeHandle = new SafeProcessHandle(handle.Value, true);
|
||||
uint capacity = 2000;
|
||||
Span<char> buffer = new char[capacity];
|
||||
fixed (char* pBuffer = buffer)
|
||||
{
|
||||
if (!PInvoke.QueryFullProcessImageName(safeHandle, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, (PWSTR)pBuffer, ref capacity))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return buffer[..(int)capacity].ToString();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum ProcessAccessFlags : uint
|
||||
{
|
||||
QueryLimitedInformation = 0x00001000
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool QueryFullProcessImageName(
|
||||
[In] IntPtr hProcess,
|
||||
[In] int dwFlags,
|
||||
[Out] StringBuilder lpExeName,
|
||||
ref int lpdwSize);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr OpenProcess(
|
||||
ProcessAccessFlags processAccess,
|
||||
bool bInheritHandle,
|
||||
int processId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace Flow.Launcher.Plugin.Program
|
|||
var (modified, msg) = ViewModel.AddOrUpdate();
|
||||
if (modified == false && msg != null)
|
||||
{
|
||||
MessageBox.Show(msg); // Invalid
|
||||
ViewModel.API.ShowMsgBox(msg); // Invalid
|
||||
return;
|
||||
}
|
||||
DialogResult = modified;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="NativeMethods.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
|
||||
|
|
@ -60,7 +64,11 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ini-parser" Version="2.5.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -41,9 +41,36 @@ namespace Flow.Launcher.Plugin.Program
|
|||
"uninst000.exe",
|
||||
"uninstall.exe"
|
||||
};
|
||||
// For cases when the uninstaller is named like "Uninstall Program Name.exe"
|
||||
private const string CommonUninstallerPrefix = "uninstall";
|
||||
private const string CommonUninstallerSuffix = ".exe";
|
||||
private static readonly string[] commonUninstallerPrefixs =
|
||||
{
|
||||
"uninstall",//en
|
||||
"卸载",//zh-cn
|
||||
"卸載",//zh-tw
|
||||
"видалити",//uk-UA
|
||||
"удалить",//ru
|
||||
"désinstaller",//fr
|
||||
"アンインストール",//ja
|
||||
"deïnstalleren",//nl
|
||||
"odinstaluj",//pl
|
||||
"afinstallere",//da
|
||||
"deinstallieren",//de
|
||||
"삭제",//ko
|
||||
"деинсталирај",//sr
|
||||
"desinstalar",//pt-pt
|
||||
"desinstalar",//pt-br
|
||||
"desinstalar",//es
|
||||
"desinstalar",//es-419
|
||||
"disinstallare",//it
|
||||
"avinstallere",//nb-NO
|
||||
"odinštalovať",//sk
|
||||
"kaldır",//tr
|
||||
"odinstalovat",//cs
|
||||
"إلغاء التثبيت",//ar
|
||||
"gỡ bỏ",//vi-vn
|
||||
"הסרה"//he
|
||||
};
|
||||
private const string ExeUninstallerSuffix = ".exe";
|
||||
private const string InkUninstallerSuffix = ".lnk";
|
||||
|
||||
static Main()
|
||||
{
|
||||
|
|
@ -60,15 +87,26 @@ namespace Flow.Launcher.Plugin.Program
|
|||
var result = await cache.GetOrCreateAsync(query.Search, async entry =>
|
||||
{
|
||||
var resultList = await Task.Run(() =>
|
||||
_win32s.Cast<IProgram>()
|
||||
.Concat(_uwps)
|
||||
.AsParallel()
|
||||
.WithCancellation(token)
|
||||
.Where(HideUninstallersFilter)
|
||||
.Where(p => p.Enabled)
|
||||
.Select(p => p.Result(query.Search, Context.API))
|
||||
.Where(r => r?.Score > 0)
|
||||
.ToList());
|
||||
{
|
||||
try
|
||||
{
|
||||
return _win32s.Cast<IProgram>()
|
||||
.Concat(_uwps)
|
||||
.AsParallel()
|
||||
.WithCancellation(token)
|
||||
.Where(HideUninstallersFilter)
|
||||
.Where(p => p.Enabled)
|
||||
.Select(p => p.Result(query.Search, Context.API))
|
||||
.Where(r => r?.Score > 0)
|
||||
.ToList();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Log.Debug("|Flow.Launcher.Plugin.Program.Main|Query operation cancelled");
|
||||
return emptyResults;
|
||||
}
|
||||
|
||||
}, token);
|
||||
|
||||
resultList = resultList.Any() ? resultList : emptyResults;
|
||||
|
||||
|
|
@ -85,10 +123,33 @@ namespace Flow.Launcher.Plugin.Program
|
|||
{
|
||||
if (!_settings.HideUninstallers) return true;
|
||||
if (program is not Win32 win32) return true;
|
||||
|
||||
// First check the executable path
|
||||
var fileName = Path.GetFileName(win32.ExecutablePath);
|
||||
return !commonUninstallerNames.Contains(fileName, StringComparer.OrdinalIgnoreCase) &&
|
||||
!(fileName.StartsWith(CommonUninstallerPrefix, StringComparison.OrdinalIgnoreCase) &&
|
||||
fileName.EndsWith(CommonUninstallerSuffix, StringComparison.OrdinalIgnoreCase));
|
||||
// For cases when the uninstaller is named like "uninst.exe"
|
||||
if (commonUninstallerNames.Contains(fileName, StringComparer.OrdinalIgnoreCase)) return false;
|
||||
// For cases when the uninstaller is named like "Uninstall Program Name.exe"
|
||||
foreach (var prefix in commonUninstallerPrefixs)
|
||||
{
|
||||
if (fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
|
||||
fileName.EndsWith(ExeUninstallerSuffix, StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Second check the lnk path
|
||||
if (!string.IsNullOrEmpty(win32.LnkResolvedPath))
|
||||
{
|
||||
var inkFileName = Path.GetFileName(win32.FullPath);
|
||||
// For cases when the uninstaller is named like "Uninstall Program Name.ink"
|
||||
foreach (var prefix in commonUninstallerPrefixs)
|
||||
{
|
||||
if (inkFileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
|
||||
inkFileName.EndsWith(InkUninstallerSuffix, StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task InitAsync(PluginInitContext context)
|
||||
|
|
|
|||
10
Plugins/Flow.Launcher.Plugin.Program/NativeMethods.txt
Normal file
10
Plugins/Flow.Launcher.Plugin.Program/NativeMethods.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
SHGetLocalizedName
|
||||
LoadString
|
||||
LoadLibraryEx
|
||||
FreeLibrary
|
||||
ExpandEnvironmentStrings
|
||||
S_OK
|
||||
SLGP_FLAGS
|
||||
WIN32_FIND_DATAW
|
||||
SLR_FLAGS
|
||||
IShellLinkW
|
||||
|
|
@ -39,14 +39,14 @@ namespace Flow.Launcher.Plugin.Program
|
|||
if (suffixes.Length == 0 && UseCustomSuffixes)
|
||||
{
|
||||
string warning = context.API.GetTranslation("flowlauncher_plugin_program_suffixes_cannot_empty");
|
||||
MessageBox.Show(warning);
|
||||
context.API.ShowMsgBox(warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (protocols.Length == 0 && UseCustomProtocols)
|
||||
{
|
||||
string warning = context.API.GetTranslation("flowlauncher_plugin_protocols_cannot_empty");
|
||||
MessageBox.Show(warning);
|
||||
context.API.ShowMsgBox(warning);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,97 +1,17 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using Accessibility;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Flow.Launcher.Plugin.Program.Logger;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.Shell;
|
||||
using Windows.Win32.Storage.FileSystem;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
{
|
||||
class ShellLinkHelper
|
||||
{
|
||||
[Flags()]
|
||||
public enum SLGP_FLAGS
|
||||
{
|
||||
SLGP_SHORTPATH = 0x1,
|
||||
SLGP_UNCPRIORITY = 0x2,
|
||||
SLGP_RAWPATH = 0x4
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct WIN32_FIND_DATAW
|
||||
{
|
||||
public uint dwFileAttributes;
|
||||
public long ftCreationTime;
|
||||
public long ftLastAccessTime;
|
||||
public long ftLastWriteTime;
|
||||
public uint nFileSizeHigh;
|
||||
public uint nFileSizeLow;
|
||||
public uint dwReserved0;
|
||||
public uint dwReserved1;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string cFileName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
|
||||
public string cAlternateFileName;
|
||||
}
|
||||
|
||||
[Flags()]
|
||||
public enum SLR_FLAGS
|
||||
{
|
||||
SLR_NO_UI = 0x1,
|
||||
SLR_ANY_MATCH = 0x2,
|
||||
SLR_UPDATE = 0x4,
|
||||
SLR_NOUPDATE = 0x8,
|
||||
SLR_NOSEARCH = 0x10,
|
||||
SLR_NOTRACK = 0x20,
|
||||
SLR_NOLINKINFO = 0x40,
|
||||
SLR_INVOKE_MSI = 0x80
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW
|
||||
/// The IShellLink interface allows Shell links to be created, modified, and resolved
|
||||
[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")]
|
||||
interface IShellLinkW
|
||||
{
|
||||
/// <summary>Retrieves the path and file name of a Shell link object</summary>
|
||||
void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
|
||||
/// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
|
||||
void GetIDList(out IntPtr ppidl);
|
||||
/// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary>
|
||||
void SetIDList(IntPtr pidl);
|
||||
/// <summary>Retrieves the description string for a Shell link object</summary>
|
||||
void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
||||
/// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary>
|
||||
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||
/// <summary>Retrieves the name of the working directory for a Shell link object</summary>
|
||||
void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
||||
/// <summary>Sets the name of the working directory for a Shell link object</summary>
|
||||
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||
/// <summary>Retrieves the command-line arguments associated with a Shell link object</summary>
|
||||
void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
|
||||
/// <summary>Sets the command-line arguments for a Shell link object</summary>
|
||||
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
||||
/// <summary>Retrieves the hot key for a Shell link object</summary>
|
||||
void GetHotkey(out short pwHotkey);
|
||||
/// <summary>Sets a hot key for a Shell link object</summary>
|
||||
void SetHotkey(short wHotkey);
|
||||
/// <summary>Retrieves the show command for a Shell link object</summary>
|
||||
void GetShowCmd(out int piShowCmd);
|
||||
/// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary>
|
||||
void SetShowCmd(int iShowCmd);
|
||||
/// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary>
|
||||
void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath,
|
||||
int cchIconPath, out int piIcon);
|
||||
/// <summary>Sets the location (path and index) of the icon for a Shell link object</summary>
|
||||
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||
/// <summary>Sets the relative path to the Shell link object</summary>
|
||||
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
||||
/// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary>
|
||||
void Resolve(ref Accessibility._RemotableHandle hwnd, SLR_FLAGS fFlags);
|
||||
/// <summary>Sets the path and file name of a Shell link object</summary>
|
||||
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||
}
|
||||
|
||||
[ComImport(), Guid("00021401-0000-0000-C000-000000000046")]
|
||||
public class ShellLink
|
||||
{
|
||||
|
|
@ -102,29 +22,43 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
public string arguments = string.Empty;
|
||||
|
||||
// Retrieve the target path using Shell Link
|
||||
public string retrieveTargetPath(string path)
|
||||
public unsafe string retrieveTargetPath(string path)
|
||||
{
|
||||
var link = new ShellLink();
|
||||
const int STGM_READ = 0;
|
||||
((IPersistFile)link).Load(path, STGM_READ);
|
||||
var hwnd = new _RemotableHandle();
|
||||
((IShellLinkW)link).Resolve(ref hwnd, 0);
|
||||
var hwnd = new HWND(IntPtr.Zero);
|
||||
((IShellLinkW)link).Resolve(hwnd, 0);
|
||||
|
||||
const int MAX_PATH = 260;
|
||||
StringBuilder buffer = new StringBuilder(MAX_PATH);
|
||||
Span<char> buffer = stackalloc char[MAX_PATH];
|
||||
|
||||
var data = new WIN32_FIND_DATAW();
|
||||
((IShellLinkW)link).GetPath(buffer, buffer.Capacity, ref data, SLGP_FLAGS.SLGP_SHORTPATH);
|
||||
var target = buffer.ToString();
|
||||
var target = string.Empty;
|
||||
try
|
||||
{
|
||||
fixed (char* bufferPtr = buffer)
|
||||
{
|
||||
((IShellLinkW)link).GetPath((PWSTR)bufferPtr, MAX_PATH, &data, (uint)SLGP_FLAGS.SLGP_SHORTPATH);
|
||||
target = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString();
|
||||
}
|
||||
}
|
||||
catch (COMException e)
|
||||
{
|
||||
ProgramLogger.LogException($"|IShellLinkW|retrieveTargetPath|{path}" +
|
||||
"|Error occurred while getting program arguments", e);
|
||||
}
|
||||
|
||||
// To set the app description
|
||||
if (!String.IsNullOrEmpty(target))
|
||||
if (!string.IsNullOrEmpty(target))
|
||||
{
|
||||
try
|
||||
{
|
||||
buffer = new StringBuilder(MAX_PATH);
|
||||
((IShellLinkW)link).GetDescription(buffer, MAX_PATH);
|
||||
description = buffer.ToString();
|
||||
fixed (char* bufferPtr = buffer)
|
||||
{
|
||||
((IShellLinkW)link).GetDescription(bufferPtr, MAX_PATH);
|
||||
description = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString();
|
||||
}
|
||||
}
|
||||
catch (COMException e)
|
||||
{
|
||||
|
|
@ -134,15 +68,17 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
e);
|
||||
}
|
||||
|
||||
buffer.Clear();
|
||||
((IShellLinkW)link).GetArguments(buffer, MAX_PATH);
|
||||
arguments = buffer.ToString();
|
||||
fixed (char* bufferPtr = buffer)
|
||||
{
|
||||
((IShellLinkW)link).GetArguments(bufferPtr, MAX_PATH);
|
||||
arguments = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// To release unmanaged memory
|
||||
Marshal.ReleaseComObject(link);
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.LibraryLoader;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
{
|
||||
|
|
@ -13,51 +14,41 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
/// </summary>
|
||||
public static class ShellLocalization
|
||||
{
|
||||
internal const uint DONTRESOLVEDLLREFERENCES = 0x00000001;
|
||||
internal const uint LOADLIBRARYASDATAFILE = 0x00000002;
|
||||
|
||||
[DllImport("shell32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
|
||||
internal static extern int SHGetLocalizedName(string pszPath, StringBuilder pszResModule, ref int cch, out int pidsRes);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "LoadStringW", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
|
||||
internal static extern int LoadString(IntPtr hModule, int resourceID, StringBuilder resourceValue, int len);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, EntryPoint = "LoadLibraryExW")]
|
||||
internal static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);
|
||||
|
||||
[DllImport("kernel32.dll", ExactSpelling = true)]
|
||||
internal static extern int FreeLibrary(IntPtr hModule);
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "ExpandEnvironmentStringsW", CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
internal static extern uint ExpandEnvironmentStrings(string lpSrc, StringBuilder lpDst, int nSize);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the localized name of a shell item.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the shell item (e. g. shortcut 'File Explorer.lnk').</param>
|
||||
/// <returns>The localized name as string or <see cref="string.Empty"/>.</returns>
|
||||
public static string GetLocalizedName(string path)
|
||||
public static unsafe string GetLocalizedName(string path)
|
||||
{
|
||||
StringBuilder resourcePath = new StringBuilder(1024);
|
||||
StringBuilder localizedName = new StringBuilder(1024);
|
||||
int len, id;
|
||||
len = resourcePath.Capacity;
|
||||
const int capacity = 1024;
|
||||
Span<char> buffer = new char[capacity];
|
||||
|
||||
// If there is no resource to localize a file name the method returns a non zero value.
|
||||
if (SHGetLocalizedName(path, resourcePath, ref len, out id) == 0)
|
||||
fixed (char* bufferPtr = buffer)
|
||||
{
|
||||
_ = ExpandEnvironmentStrings(resourcePath.ToString(), resourcePath, resourcePath.Capacity);
|
||||
IntPtr hMod = LoadLibraryEx(resourcePath.ToString(), IntPtr.Zero, DONTRESOLVEDLLREFERENCES | LOADLIBRARYASDATAFILE);
|
||||
if (hMod != IntPtr.Zero)
|
||||
var result = PInvoke.SHGetLocalizedName(path, bufferPtr, capacity, out var id);
|
||||
if (result != HRESULT.S_OK)
|
||||
{
|
||||
if (LoadString(hMod, id, localizedName, localizedName.Capacity) != 0)
|
||||
{
|
||||
string lString = localizedName.ToString();
|
||||
_ = FreeLibrary(hMod);
|
||||
return lString;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
_ = FreeLibrary(hMod);
|
||||
var resourcePathStr = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString();
|
||||
_ = PInvoke.ExpandEnvironmentStrings(resourcePathStr, bufferPtr, capacity);
|
||||
using var handle = PInvoke.LoadLibraryEx(resourcePathStr,
|
||||
LOAD_LIBRARY_FLAGS.DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_FLAGS.LOAD_LIBRARY_AS_DATAFILE);
|
||||
if (handle.IsInvalid)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// not sure about the behavior of Pinvoke.LoadString, so we clear the buffer before using it (so it must be a null-terminated string)
|
||||
buffer.Clear();
|
||||
|
||||
if (PInvoke.LoadString(handle, (uint)id, bufferPtr, capacity) != 0)
|
||||
{
|
||||
var lString = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString();
|
||||
return lString;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
if (selectedProgramSource == null)
|
||||
{
|
||||
string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
|
||||
MessageBox.Show(msg);
|
||||
context.API.ShowMsgBox(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -283,7 +283,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
if (selectedItems.Count == 0)
|
||||
{
|
||||
string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
|
||||
MessageBox.Show(msg);
|
||||
context.API.ShowMsgBox(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -292,7 +292,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
var msg = string.Format(
|
||||
context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"));
|
||||
|
||||
if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
if (context.API.ShowMsgBox(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,4 +57,11 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,19 +2,16 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Shutdown;
|
||||
using Application = System.Windows.Application;
|
||||
using Control = System.Windows.Controls.Control;
|
||||
using FormsApplication = System.Windows.Forms.Application;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Sys
|
||||
{
|
||||
|
|
@ -23,33 +20,6 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
private PluginInitContext context;
|
||||
private Dictionary<string, string> KeywordTitleMappings = new Dictionary<string, string>();
|
||||
|
||||
#region DllImport
|
||||
|
||||
internal const int EWX_LOGOFF = 0x00000000;
|
||||
internal const int EWX_SHUTDOWN = 0x00000001;
|
||||
internal const int EWX_REBOOT = 0x00000002;
|
||||
internal const int EWX_FORCE = 0x00000004;
|
||||
internal const int EWX_POWEROFF = 0x00000008;
|
||||
internal const int EWX_FORCEIFHUNG = 0x00000010;
|
||||
|
||||
[DllImport("user32")]
|
||||
private static extern bool ExitWindowsEx(uint uFlags, uint dwReason);
|
||||
|
||||
[DllImport("user32")]
|
||||
private static extern void LockWorkStation();
|
||||
|
||||
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern uint SHEmptyRecycleBin(IntPtr hWnd, uint dwFlags);
|
||||
|
||||
// http://www.pinvoke.net/default.aspx/Enums/HRESULT.html
|
||||
private enum HRESULT : uint
|
||||
{
|
||||
S_FALSE = 0x0001,
|
||||
S_OK = 0x0000
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
var results = Commands();
|
||||
|
|
@ -133,6 +103,9 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
private List<Result> Commands()
|
||||
{
|
||||
var results = new List<Result>();
|
||||
var logPath = Path.Combine(DataLocation.DataDirectory(), "Logs", Constant.Version);
|
||||
var userDataPath = DataLocation.DataDirectory();
|
||||
var recycleBinFolder = "shell:RecycleBinFolder";
|
||||
results.AddRange(new[]
|
||||
{
|
||||
new Result
|
||||
|
|
@ -143,7 +116,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
IcoPath = "Images\\shutdown.png",
|
||||
Action = c =>
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
var result = context.API.ShowMsgBox(
|
||||
context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_shutdown_computer"),
|
||||
context.API.GetTranslation("flowlauncher_plugin_sys_shutdown_computer"),
|
||||
MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||
|
|
@ -163,7 +136,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
IcoPath = "Images\\restart.png",
|
||||
Action = c =>
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
var result = context.API.ShowMsgBox(
|
||||
context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer"),
|
||||
context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
|
||||
MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||
|
|
@ -183,7 +156,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
IcoPath = "Images\\restart_advanced.png",
|
||||
Action = c =>
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
var result = context.API.ShowMsgBox(
|
||||
context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_restart_computer_advanced"),
|
||||
context.API.GetTranslation("flowlauncher_plugin_sys_restart_computer"),
|
||||
MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||
|
|
@ -202,13 +175,13 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
IcoPath = "Images\\logoff.png",
|
||||
Action = c =>
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
var result = context.API.ShowMsgBox(
|
||||
context.API.GetTranslation("flowlauncher_plugin_sys_dlgtext_logoff_computer"),
|
||||
context.API.GetTranslation("flowlauncher_plugin_sys_log_off"),
|
||||
MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
ExitWindowsEx(EWX_LOGOFF, 0);
|
||||
PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_LOGOFF, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -221,7 +194,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
IcoPath = "Images\\lock.png",
|
||||
Action = c =>
|
||||
{
|
||||
LockWorkStation();
|
||||
PInvoke.LockWorkStation();
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
|
@ -231,7 +204,7 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_sleep"),
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xec46"),
|
||||
IcoPath = "Images\\sleep.png",
|
||||
Action = c => FormsApplication.SetSuspendState(PowerState.Suspend, false, false)
|
||||
Action = c => PInvoke.SetSuspendState(false, false, false)
|
||||
},
|
||||
new Result
|
||||
{
|
||||
|
|
@ -276,11 +249,13 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
// http://www.pinvoke.net/default.aspx/shell32/SHEmptyRecycleBin.html
|
||||
// FYI, couldn't find documentation for this but if the recycle bin is already empty, it will return -2147418113 (0x8000FFFF (E_UNEXPECTED))
|
||||
// 0 for nothing
|
||||
var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, 0);
|
||||
if (result != (uint) HRESULT.S_OK && result != (uint) 0x8000FFFF)
|
||||
var result = PInvoke.SHEmptyRecycleBin(new(), string.Empty, 0);
|
||||
if (result != HRESULT.S_OK && result != HRESULT.E_UNEXPECTED)
|
||||
{
|
||||
MessageBox.Show($"Error emptying recycle bin, error code: {result}\n" +
|
||||
"please refer to https://msdn.microsoft.com/en-us/library/windows/desktop/aa378137",
|
||||
context.API.ShowMsgBox("Failed to empty the recycle bin. This might happen if:\n" +
|
||||
"- A file in the recycle bin is in use\n" +
|
||||
"- You don't have permission to delete some items\n" +
|
||||
"Please close any applications that might be using these files and try again.",
|
||||
"Error",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
|
|
@ -294,10 +269,11 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_openrecyclebin"),
|
||||
IcoPath = "Images\\openrecyclebin.png",
|
||||
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe74d"),
|
||||
CopyText = recycleBinFolder,
|
||||
Action = c =>
|
||||
{
|
||||
{
|
||||
System.Diagnostics.Process.Start("explorer", "shell:RecycleBinFolder");
|
||||
System.Diagnostics.Process.Start("explorer", recycleBinFolder);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -386,9 +362,10 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
Title = "Open Log Location",
|
||||
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_log_location"),
|
||||
IcoPath = "Images\\app.png",
|
||||
CopyText = logPath,
|
||||
AutoCompleteText = logPath,
|
||||
Action = c =>
|
||||
{
|
||||
var logPath = Path.Combine(DataLocation.DataDirectory(), "Logs", Constant.Version);
|
||||
context.API.OpenDirectory(logPath);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -398,6 +375,8 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
Title = "Flow Launcher Tips",
|
||||
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_docs_tips"),
|
||||
IcoPath = "Images\\app.png",
|
||||
CopyText = Constant.Documentation,
|
||||
AutoCompleteText = Constant.Documentation,
|
||||
Action = c =>
|
||||
{
|
||||
context.API.OpenUrl(Constant.Documentation);
|
||||
|
|
@ -409,9 +388,11 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
Title = "Flow Launcher UserData Folder",
|
||||
SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_open_userdata_location"),
|
||||
IcoPath = "Images\\app.png",
|
||||
CopyText = userDataPath,
|
||||
AutoCompleteText = userDataPath,
|
||||
Action = c =>
|
||||
{
|
||||
context.API.OpenDirectory(DataLocation.DataDirectory());
|
||||
context.API.OpenDirectory(userDataPath);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
6
Plugins/Flow.Launcher.Plugin.Sys/NativeMethods.txt
Normal file
6
Plugins/Flow.Launcher.Plugin.Sys/NativeMethods.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
ExitWindowsEx
|
||||
LockWorkStation
|
||||
SHEmptyRecycleBin
|
||||
S_OK
|
||||
E_UNEXPECTED
|
||||
SetSuspendState
|
||||
BIN
Plugins/Flow.Launcher.Plugin.WebSearch/Images/copylink.png
Normal file
BIN
Plugins/Flow.Launcher.Plugin.WebSearch/Images/copylink.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -31,7 +31,8 @@
|
|||
Thus, the generic formula for a search on Netflix is https://www.netflix.com/search?q={q}
|
||||
</system:String>
|
||||
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_copyurl_title">Copy URL</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_copyurl_subtitle">Copy search URL to clipboard</system:String>
|
||||
|
||||
<!-- web search edit -->
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_title">Title</system:String>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@
|
|||
<system:String x:Key="flowlauncher_plugin_websearch_copyurl_title">Скопировать URL-адрес</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_copyurl_subtitle">Скопировать URL поиска в буфер обмена</system:String>
|
||||
|
||||
|
||||
<!-- web search edit -->
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_title">Title</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_websearch_enable">Состояние</system:String>
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ using Flow.Launcher.Plugin.SharedCommands;
|
|||
|
||||
namespace Flow.Launcher.Plugin.WebSearch
|
||||
{
|
||||
public class Main : IAsyncPlugin, ISettingProvider, IPluginI18n, IResultUpdated
|
||||
public class Main : IAsyncPlugin, ISettingProvider, IPluginI18n, IResultUpdated, IContextMenu
|
||||
{
|
||||
private PluginInitContext _context;
|
||||
internal static PluginInitContext _context;
|
||||
|
||||
private Settings _settings;
|
||||
private SettingsViewModel _viewModel;
|
||||
|
|
@ -76,7 +76,8 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
_context.API.OpenUrl(searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword)));
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
ContextData = searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword)),
|
||||
};
|
||||
|
||||
results.Add(result);
|
||||
|
|
@ -139,11 +140,31 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
_context.API.OpenUrl(searchSource.Url.Replace("{q}", Uri.EscapeDataString(o)));
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
ContextData = searchSource.Url.Replace("{q}", Uri.EscapeDataString(o)),
|
||||
});
|
||||
return resultsFromSuggestion;
|
||||
}
|
||||
|
||||
public List<Result> LoadContextMenus(Result selected)
|
||||
{
|
||||
if (selected?.ContextData == null || selected.ContextData is not string) return new List<Result>();
|
||||
return new List<Result>() {
|
||||
new Result
|
||||
{
|
||||
Title = _context.API.GetTranslation("flowlauncher_plugin_websearch_copyurl_title"),
|
||||
SubTitle = _context.API.GetTranslation("flowlauncher_plugin_websearch_copyurl_subtitle"),
|
||||
IcoPath = "Images/copylink.png",
|
||||
Action = c =>
|
||||
{
|
||||
_context.API.CopyToClipboard(selected.ContextData as string);
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public Task InitAsync(PluginInitContext context)
|
||||
{
|
||||
return Task.Run(Init);
|
||||
|
|
|
|||
|
|
@ -55,17 +55,17 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
if (string.IsNullOrEmpty(_searchSource.Title))
|
||||
{
|
||||
var warning = _api.GetTranslation("flowlauncher_plugin_websearch_input_title");
|
||||
MessageBox.Show(warning);
|
||||
_context.API.ShowMsgBox(warning);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(_searchSource.Url))
|
||||
{
|
||||
var warning = _api.GetTranslation("flowlauncher_plugin_websearch_input_url");
|
||||
MessageBox.Show(warning);
|
||||
_context.API.ShowMsgBox(warning);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(_searchSource.ActionKeyword))
|
||||
{
|
||||
var warning = _api.GetTranslation("flowlauncher_plugin_websearch_input_action_keyword");
|
||||
MessageBox.Show(warning);
|
||||
_context.API.ShowMsgBox(warning);
|
||||
}
|
||||
else if (_action == Action.Add)
|
||||
{
|
||||
|
|
@ -92,7 +92,7 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
else
|
||||
{
|
||||
var warning = _api.GetTranslation("newActionKeywordsHasBeenAssigned");
|
||||
MessageBox.Show(warning);
|
||||
_context.API.ShowMsgBox(warning);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
else
|
||||
{
|
||||
var warning = _api.GetTranslation("newActionKeywordsHasBeenAssigned");
|
||||
MessageBox.Show(warning);
|
||||
_context.API.ShowMsgBox(warning);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(selectedNewIconImageFullPath))
|
||||
|
|
@ -138,7 +138,7 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
if (!string.IsNullOrEmpty(selectedNewIconImageFullPath))
|
||||
{
|
||||
if (_viewModel.ShouldProvideHint(selectedNewIconImageFullPath))
|
||||
MessageBox.Show(_api.GetTranslation("flowlauncher_plugin_websearch_iconpath_hint"));
|
||||
_context.API.ShowMsgBox(_api.GetTranslation("flowlauncher_plugin_websearch_iconpath_hint"));
|
||||
|
||||
imgPreviewIcon.Source = await _viewModel.LoadPreviewIconAsync(selectedNewIconImageFullPath);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
#if DEBUG
|
||||
throw;
|
||||
#else
|
||||
MessageBox.Show(string.Format("Copying the selected image file to {0} has failed, changes will now be reverted", destinationFileNameFullPath));
|
||||
Main._context.API.ShowMsgBox(string.Format("Copying the selected image file to {0} has failed, changes will now be reverted", destinationFileNameFullPath));
|
||||
UpdateIconAttributes(selectedSearchSource, fullPathToOriginalImage);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Windows;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using System.ComponentModel;
|
||||
|
|
@ -36,7 +36,7 @@ namespace Flow.Launcher.Plugin.WebSearch
|
|||
var warning = _context.API.GetTranslation("flowlauncher_plugin_websearch_delete_warning");
|
||||
var formated = string.Format(warning, selected.Title);
|
||||
|
||||
var result = MessageBox.Show(formated, string.Empty, MessageBoxButton.YesNo);
|
||||
var result = _context.API.ShowMsgBox(formated, string.Empty, MessageBoxButton.YesNo);
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
var id = _context.CurrentPluginMetadata.ID;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue