mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 02:06:36 +02:00
[AdvancedPaste] Custom Actions (#34395)
* [AdvancedPaste] Custom Actions * Renamed pipe name to make spellcheck happy * Improved settings page for Custom Actions * UI improvements, disabled standard paste actions when no clipboard text, update clipboard text and gpo state every second * Bug fixes, single query/prompt box, Ctrl+num shortcuts for custom actions, error box * Spellcheck issue * Bug fixes and used Advanced Paste Window as wait indicator for keyboard shortcuts * Improvements to PromptBox, incluing show error message as tooltip * Refactoring * Fixed issue where ESC sometimes didn't close paste window * Update src/settings-ui/Settings.UI/Strings/en-us/Resources.resw Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> --------- Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Settings;
|
||||
using AdvancedPaste.ViewModels;
|
||||
@@ -14,6 +18,7 @@ using Microsoft.UI.Xaml;
|
||||
using Windows.Graphics;
|
||||
using WinUIEx;
|
||||
using static AdvancedPaste.Helpers.NativeMethods;
|
||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
@@ -26,12 +31,13 @@ namespace AdvancedPaste
|
||||
{
|
||||
public IHost Host { get; private set; }
|
||||
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private readonly OptionsViewModel viewModel;
|
||||
|
||||
private MainWindow window;
|
||||
|
||||
private nint windowHwnd;
|
||||
|
||||
private OptionsViewModel viewModel;
|
||||
|
||||
private bool disposedValue;
|
||||
|
||||
/// <summary>
|
||||
@@ -74,7 +80,7 @@ namespace AdvancedPaste
|
||||
/// Invoked when the application is launched.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
var cmdArgs = Environment.GetCommandLineArgs();
|
||||
if (cmdArgs?.Length > 1)
|
||||
@@ -88,9 +94,44 @@ namespace AdvancedPaste
|
||||
}
|
||||
}
|
||||
|
||||
NativeEventWaiter.WaitForEventLoop(PowerToys.Interop.Constants.ShowAdvancedPasteSharedEvent(), OnAdvancedPasteHotkey);
|
||||
NativeEventWaiter.WaitForEventLoop(PowerToys.Interop.Constants.AdvancedPasteMarkdownEvent(), OnAdvancedPasteMarkdownHotkey);
|
||||
NativeEventWaiter.WaitForEventLoop(PowerToys.Interop.Constants.AdvancedPasteJsonEvent(), OnAdvancedPasteJsonHotkey);
|
||||
if (cmdArgs?.Length > 2)
|
||||
{
|
||||
ProcessNamedPipe(cmdArgs[2]);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNamedPipe(string pipeName)
|
||||
{
|
||||
void OnMessage(string message) => _dispatcherQueue.TryEnqueue(() => OnNamedPipeMessage(message));
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var connectTimeout = TimeSpan.FromSeconds(10);
|
||||
await NamedPipeProcessor.ProcessNamedPipeAsync(pipeName, connectTimeout, OnMessage, CancellationToken.None);
|
||||
});
|
||||
}
|
||||
|
||||
private void OnNamedPipeMessage(string message)
|
||||
{
|
||||
var messageParts = message.Split();
|
||||
var messageType = messageParts.First();
|
||||
|
||||
if (messageType == PowerToys.Interop.Constants.AdvancedPasteShowUIMessage())
|
||||
{
|
||||
OnAdvancedPasteHotkey();
|
||||
}
|
||||
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteMarkdownMessage())
|
||||
{
|
||||
OnAdvancedPasteMarkdownHotkey();
|
||||
}
|
||||
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteJsonMessage())
|
||||
{
|
||||
OnAdvancedPasteJsonHotkey();
|
||||
}
|
||||
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteCustomActionMessage())
|
||||
{
|
||||
OnAdvancedPasteCustomActionHotkey(messageParts);
|
||||
}
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
@@ -100,17 +141,43 @@ namespace AdvancedPaste
|
||||
|
||||
private void OnAdvancedPasteJsonHotkey()
|
||||
{
|
||||
viewModel.GetClipboardData();
|
||||
viewModel.ReadClipboard();
|
||||
viewModel.ToJsonFunction(true);
|
||||
}
|
||||
|
||||
private void OnAdvancedPasteMarkdownHotkey()
|
||||
{
|
||||
viewModel.GetClipboardData();
|
||||
viewModel.ReadClipboard();
|
||||
viewModel.ToMarkdownFunction(true);
|
||||
}
|
||||
|
||||
private void OnAdvancedPasteHotkey()
|
||||
{
|
||||
ShowWindow();
|
||||
}
|
||||
|
||||
private void OnAdvancedPasteCustomActionHotkey(string[] messageParts)
|
||||
{
|
||||
if (messageParts.Length != 2)
|
||||
{
|
||||
Logger.LogWarning("Unexpected custom action message");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!int.TryParse(messageParts[1], CultureInfo.InvariantCulture, out int customActionId))
|
||||
{
|
||||
Logger.LogWarning($"Unexpected custom action message id {messageParts[1]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowWindow();
|
||||
viewModel.ReadClipboard();
|
||||
viewModel.ExecuteCustomActionWithPaste(customActionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowWindow()
|
||||
{
|
||||
viewModel.OnShow();
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:converters="using:AdvancedPaste.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:AdvancedPaste.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
@@ -323,7 +324,12 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<tkconverters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<tkconverters:BoolToVisibilityConverter
|
||||
x:Key="BoolToInvertedVisibilityConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
<converters:CountToVisibilityConverter x:Key="CountToVisibilityConverter" />
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid x:Name="PromptBoxGrid" Loaded="Grid_Loaded">
|
||||
@@ -340,13 +346,12 @@
|
||||
x:Name="InputTxtBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
x:FieldModifier="public"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsClipboardDataText, Mode=OneWay}"
|
||||
KeyDown="InputTxtBox_KeyDown"
|
||||
PlaceholderText="{x:Bind ViewModel.InputTxtBoxPlaceholderText, Mode=OneWay}"
|
||||
Style="{StaticResource CustomTextBoxStyle}"
|
||||
TabIndex="0"
|
||||
Text="{x:Bind Prompt, Mode=TwoWay}"
|
||||
TextChanging="InputTxtBox_TextChanging">
|
||||
Text="{x:Bind ViewModel.Query, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="InputTxtBoxTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
@@ -531,48 +536,63 @@
|
||||
<animations:OffsetAnimation Duration="0:0:1" />
|
||||
</animations:Implicit.Animations>
|
||||
</Button>-->
|
||||
<Button
|
||||
x:Name="SendBtn"
|
||||
x:Uid="SendButtonAutomation"
|
||||
<Grid
|
||||
Width="32"
|
||||
Height="32"
|
||||
Margin="0,0,4,0"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Stretch"
|
||||
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
|
||||
Command="{x:Bind GenerateCustomCommand}"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=16}"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
TabIndex="1"
|
||||
Visibility="Collapsed">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="SendBtnToolTip" TextWrapping="WrapWholeWords" />
|
||||
</ToolTipService.ToolTip>
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:ScaleAnimation
|
||||
From="0.4"
|
||||
To="1"
|
||||
Duration="0:0:0.167" />
|
||||
<animations:OpacityAnimation
|
||||
From="0.0"
|
||||
To="1.0"
|
||||
Duration="0:0:0.167" />
|
||||
</animations:Implicit.ShowAnimations>
|
||||
<animations:Implicit.HideAnimations>
|
||||
<animations:ScaleAnimation
|
||||
From="1"
|
||||
To="0.4"
|
||||
Duration="0:0:0.167" />
|
||||
<animations:OpacityAnimation
|
||||
From="1.0"
|
||||
To="0.0"
|
||||
Duration="0:0:0.167" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
</Button>
|
||||
<!--</StackPanel>-->
|
||||
VerticalAlignment="Stretch">
|
||||
<Button
|
||||
x:Name="SendBtn"
|
||||
x:Uid="SendButtonAutomation"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
|
||||
Command="{x:Bind GenerateCustomCommand}"
|
||||
Content="{ui:FontIcon Glyph=,
|
||||
FontSize=16}"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
TabIndex="1"
|
||||
Visibility="{x:Bind ViewModel.Query.Length, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="SendBtnToolTip" TextWrapping="WrapWholeWords" />
|
||||
</ToolTipService.ToolTip>
|
||||
<animations:Implicit.ShowAnimations>
|
||||
<animations:ScaleAnimation
|
||||
From="0.4"
|
||||
To="1"
|
||||
Duration="0:0:0.167" />
|
||||
<animations:OpacityAnimation
|
||||
From="0.0"
|
||||
To="1.0"
|
||||
Duration="0:0:0.167" />
|
||||
</animations:Implicit.ShowAnimations>
|
||||
<animations:Implicit.HideAnimations>
|
||||
<animations:ScaleAnimation
|
||||
From="1"
|
||||
To="0.4"
|
||||
Duration="0:0:0.167" />
|
||||
<animations:OpacityAnimation
|
||||
From="1.0"
|
||||
To="0.0"
|
||||
Duration="0:0:0.167" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
</Button>
|
||||
<!-- Transparent overlay to show tooltip -->
|
||||
<Grid
|
||||
x:Name="SendBtnOverlay"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
Visibility="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind ViewModel.GeneralErrorText}" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</local:AnimatedContentControl>
|
||||
<ContentPresenter
|
||||
@@ -618,7 +638,7 @@
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.InputTxtBoxErrorText, Mode=OneWay}" />
|
||||
Text="{x:Bind ViewModel.ApiErrorText, Mode=OneWay}" />
|
||||
<HyperlinkButton
|
||||
x:Uid="SettingsBtn"
|
||||
Grid.Column="1"
|
||||
@@ -635,7 +655,6 @@
|
||||
<animations:OpacityAnimation To="0.0" Duration="0:0:0.167" />
|
||||
</animations:Implicit.HideAnimations>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="DefaultState" />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -18,23 +19,14 @@ namespace AdvancedPaste.Controls
|
||||
{
|
||||
public sealed partial class PromptBox : Microsoft.UI.Xaml.Controls.UserControl
|
||||
{
|
||||
// Minimum time to show spinner when generating custom format using forcePasteCustom
|
||||
private static readonly TimeSpan MinTaskTime = TimeSpan.FromSeconds(2);
|
||||
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private readonly IUserSettings _userSettings;
|
||||
|
||||
public static readonly DependencyProperty PromptProperty = DependencyProperty.Register(
|
||||
nameof(Prompt),
|
||||
typeof(string),
|
||||
typeof(PromptBox),
|
||||
new PropertyMetadata(defaultValue: string.Empty));
|
||||
|
||||
public OptionsViewModel ViewModel { get; private set; }
|
||||
|
||||
public string Prompt
|
||||
{
|
||||
get => (string)GetValue(PromptProperty);
|
||||
set => SetValue(PromptProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(
|
||||
nameof(PlaceholderText),
|
||||
typeof(string),
|
||||
@@ -66,6 +58,7 @@ namespace AdvancedPaste.Controls
|
||||
_userSettings = App.GetService<IUserSettings>();
|
||||
|
||||
ViewModel = App.GetService<OptionsViewModel>();
|
||||
ViewModel.CustomActionActivated += (_, e) => GenerateCustom(e.ForcePasteCustom);
|
||||
}
|
||||
|
||||
private void Grid_Loaded(object sender, RoutedEventArgs e)
|
||||
@@ -74,27 +67,30 @@ namespace AdvancedPaste.Controls
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void GenerateCustom()
|
||||
private void GenerateCustom() => GenerateCustom(false);
|
||||
|
||||
private void GenerateCustom(bool forcePasteCustom)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
VisualStateManager.GoToState(this, "LoadingState", true);
|
||||
string inputInstructions = InputTxtBox.Text;
|
||||
string inputInstructions = ViewModel.Query;
|
||||
ViewModel.SaveQuery(inputInstructions);
|
||||
|
||||
var customFormatTask = ViewModel.GenerateCustomFunction(inputInstructions);
|
||||
|
||||
customFormatTask.ContinueWith(
|
||||
t =>
|
||||
var delayTask = forcePasteCustom ? Task.Delay(MinTaskTime) : Task.CompletedTask;
|
||||
Task.WhenAll(customFormatTask, delayTask)
|
||||
.ContinueWith(
|
||||
_ =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
ViewModel.CustomFormatResult = t.Result;
|
||||
ViewModel.CustomFormatResult = customFormatTask.Result;
|
||||
|
||||
if (ViewModel.ApiRequestStatus == (int)HttpStatusCode.OK)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "DefaultState", true);
|
||||
if (_userSettings.ShowCustomPreview)
|
||||
if (_userSettings.ShowCustomPreview && !forcePasteCustom)
|
||||
{
|
||||
PreviewGrid.Width = InputTxtBox.ActualWidth;
|
||||
PreviewFlyout.ShowAt(InputTxtBox);
|
||||
@@ -130,14 +126,9 @@ namespace AdvancedPaste.Controls
|
||||
ClipboardHelper.SetClipboardTextContent(lastQuery.ClipboardData);
|
||||
}
|
||||
|
||||
private void InputTxtBox_TextChanging(Microsoft.UI.Xaml.Controls.TextBox sender, TextBoxTextChangingEventArgs args)
|
||||
{
|
||||
SendBtn.Visibility = InputTxtBox.Text.Length > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void InputTxtBox_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == Windows.System.VirtualKey.Enter && InputTxtBox.Text.Length > 0)
|
||||
if (e.Key == Windows.System.VirtualKey.Enter && InputTxtBox.Text.Length > 0 && ViewModel.IsCustomAIEnabled)
|
||||
{
|
||||
GenerateCustom();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace AdvancedPaste.Converters;
|
||||
|
||||
public sealed class CountToDoubleConverter : IValueConverter
|
||||
{
|
||||
public double ValueIfZero { get; set; }
|
||||
|
||||
public double ValueIfNonZero { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
bool hasCount = ((value is int intValue) && intValue > 0) || (value is IEnumerable collection && collection.GetEnumerator().MoveNext());
|
||||
|
||||
return hasCount ? ValueIfNonZero : ValueIfZero;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace AdvancedPaste.Converters;
|
||||
|
||||
public sealed class CountToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
bool hasCount = ((value is int intValue) && intValue > 0) || (value is IEnumerable collection && collection.GetEnumerator().MoveNext());
|
||||
|
||||
if (targetType == typeof(Visibility))
|
||||
{
|
||||
return hasCount ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
else if (targetType == typeof(bool))
|
||||
{
|
||||
return hasCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(targetType));
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace AdvancedPaste.Converters
|
||||
{
|
||||
public sealed class ListViewIndexConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var presenter = value as ListViewItemPresenter;
|
||||
var item = VisualTreeHelper.GetParent(presenter) as ListViewItem;
|
||||
|
||||
var listView = ItemsControl.ItemsControlFromItemContainer(item);
|
||||
int index = listView.IndexFromContainer(item) + 1;
|
||||
#pragma warning disable CA1305 // Specify IFormatProvider
|
||||
return index.ToString();
|
||||
#pragma warning restore CA1305 // Specify IFormatProvider
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,18 @@ namespace AdvancedPaste
|
||||
|
||||
_userSettings = App.GetService<IUserSettings>();
|
||||
|
||||
var baseHeight = MinHeight;
|
||||
|
||||
void UpdateHeight()
|
||||
{
|
||||
var trimmedCustomActionCount = Math.Min(_userSettings.CustomActions.Count, 5);
|
||||
Height = MinHeight = baseHeight + (trimmedCustomActionCount * 40);
|
||||
}
|
||||
|
||||
UpdateHeight();
|
||||
|
||||
_userSettings.CustomActions.CollectionChanged += (_, _) => UpdateHeight();
|
||||
|
||||
AppWindow.SetIcon("Assets/AdvancedPaste/AdvancedPaste.ico");
|
||||
this.ExtendsContentIntoTitleBar = true;
|
||||
this.SetTitleBar(titleBar);
|
||||
|
||||
@@ -5,14 +5,21 @@
|
||||
xmlns:controls="using:AdvancedPaste.Controls"
|
||||
xmlns:converters="using:AdvancedPaste.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:library="using:Microsoft.PowerToys.Settings.UI.Library"
|
||||
xmlns:local="using:AdvancedPaste.Models"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
KeyDown="Page_KeyDown"
|
||||
KeyboardAcceleratorPlacementMode="Hidden"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<converters:ListViewIndexConverter x:Name="listViewIndexConverter" />
|
||||
<tkconverters:BoolToVisibilityConverter x:Name="BoolToVisibilityConverter" />
|
||||
<converters:CountToVisibilityConverter x:Name="countToVisibilityConverter" />
|
||||
<converters:CountToDoubleConverter
|
||||
x:Name="customActionsCountToMinHeightConverter"
|
||||
ValueIfNonZero="40"
|
||||
ValueIfZero="0" />
|
||||
<Style
|
||||
x:Key="PaddingLessFlyoutPresenterStyle"
|
||||
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
|
||||
@@ -21,6 +28,38 @@
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<DataTemplate x:Key="PasteFormatTemplate" x:DataType="local:PasteFormat">
|
||||
<Grid>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="{x:Bind ToolTip}" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="26" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FontIcon
|
||||
Margin="0,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontSize="16"
|
||||
Glyph="{x:Bind IconGlyph}" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
x:Phase="1"
|
||||
Text="{x:Bind Name}" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Margin="0,0,8,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind ShortcutText, Mode=OneWay}"
|
||||
Visibility="{x:Bind ShortcutText.Length, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Page.Resources>
|
||||
<Page.KeyboardAccelerators>
|
||||
<KeyboardAccelerator Key="Escape" Invoked="KeyboardAccelerator_Invoked" />
|
||||
@@ -36,6 +75,30 @@
|
||||
Key="Number3"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number4"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number5"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number6"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number7"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number8"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
<KeyboardAccelerator
|
||||
Key="Number9"
|
||||
Invoked="KeyboardAccelerator_Invoked"
|
||||
Modifiers="Control" />
|
||||
</Page.KeyboardAccelerators>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -103,73 +166,55 @@
|
||||
BorderThickness="0,1,0,0"
|
||||
RowSpacing="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" MinHeight="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource customActionsCountToMinHeightConverter}}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ListView
|
||||
x:Name="PasteOptionsListView"
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Bottom"
|
||||
IsEnabled="{x:Bind ViewModel.IsClipboardDataText, Mode=OneWay}"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="PasteOptionsListView_ItemClick"
|
||||
ItemClick="ListView_Click"
|
||||
ItemContainerTransitions="{x:Null}"
|
||||
ItemsSource="{x:Bind pasteFormats, Mode=OneWay}"
|
||||
ItemTemplate="{StaticResource PasteFormatTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
|
||||
SelectionMode="None"
|
||||
TabIndex="1">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:PasteFormat">
|
||||
<Grid>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock>
|
||||
<Run Text="{x:Bind Name}" />
|
||||
<Run Text="(" /><Run Text="Ctrl" /><Run Text="+" /><Run Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource listViewIndexConverter}}" /><Run Text=")" />
|
||||
</TextBlock>
|
||||
</ToolTipService.ToolTip>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="26" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Viewbox
|
||||
x:Name="IconHolderBox"
|
||||
MaxWidth="16"
|
||||
MaxHeight="16"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<ContentPresenter
|
||||
x:Name="IconHolder"
|
||||
x:Phase="2"
|
||||
Content="{x:Bind Icon}" />
|
||||
</Viewbox>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
x:Phase="1"
|
||||
Text="{x:Bind Name}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Margin="0,0,8,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Run Text="Ctrl" /><Run Text="+" /><Run Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource listViewIndexConverter}}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
TabIndex="1" />
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
Visibility="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
|
||||
|
||||
<ListView
|
||||
x:Name="CustomActionsListView"
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Top"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomAIEnabled, Mode=OneWay}"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ListView_Click"
|
||||
ItemContainerTransitions="{x:Null}"
|
||||
ItemTemplate="{StaticResource PasteFormatTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Auto"
|
||||
SelectionMode="None"
|
||||
TabIndex="2" />
|
||||
|
||||
<Rectangle
|
||||
Grid.Row="3"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
<!-- x:Uid="ClipboardHistoryButton" -->
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Grid.Row="4"
|
||||
Height="32"
|
||||
Margin="4,0,4,4"
|
||||
Padding="{StaticResource ButtonPadding}"
|
||||
|
||||
@@ -6,7 +6,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Models;
|
||||
@@ -25,7 +24,6 @@ namespace AdvancedPaste.Pages
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
private readonly ObservableCollection<ClipboardItem> clipboardHistory;
|
||||
private readonly ObservableCollection<PasteFormat> pasteFormats;
|
||||
private readonly Microsoft.UI.Dispatching.DispatcherQueue _dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
public OptionsViewModel ViewModel { get; private set; }
|
||||
@@ -34,13 +32,6 @@ namespace AdvancedPaste.Pages
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
pasteFormats =
|
||||
[
|
||||
new PasteFormat { Icon = new FontIcon() { Glyph = "\uE8E9" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsPlainText"), Format = PasteFormats.PlainText },
|
||||
new PasteFormat { Icon = new FontIcon() { Glyph = "\ue8a5" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsMarkdown"), Format = PasteFormats.Markdown },
|
||||
new PasteFormat { Icon = new FontIcon() { Glyph = "\uE943" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsJson"), Format = PasteFormats.Json },
|
||||
];
|
||||
|
||||
ViewModel = App.GetService<OptionsViewModel>();
|
||||
|
||||
clipboardHistory = new ObservableCollection<ClipboardItem>();
|
||||
@@ -121,6 +112,8 @@ namespace AdvancedPaste.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private static MainWindow GetMainWindow() => (App.Current as App)?.GetMainWindow();
|
||||
|
||||
private void ClipboardHistoryItemDeleteButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
@@ -135,83 +128,40 @@ namespace AdvancedPaste.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private void PasteAsPlain()
|
||||
{
|
||||
ViewModel.ToPlainTextFunction();
|
||||
}
|
||||
|
||||
private void PasteAsMarkdown()
|
||||
{
|
||||
ViewModel.ToMarkdownFunction();
|
||||
}
|
||||
|
||||
private void PasteAsJson()
|
||||
{
|
||||
ViewModel.ToJsonFunction();
|
||||
}
|
||||
|
||||
private void PasteOptionsListView_ItemClick(object sender, ItemClickEventArgs e)
|
||||
private void ListView_Click(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is PasteFormat format)
|
||||
{
|
||||
switch (format.Format)
|
||||
{
|
||||
case PasteFormats.PlainText:
|
||||
{
|
||||
PasteAsPlain();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.PlainText));
|
||||
break;
|
||||
}
|
||||
|
||||
case PasteFormats.Markdown:
|
||||
{
|
||||
PasteAsMarkdown();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.Markdown));
|
||||
break;
|
||||
}
|
||||
|
||||
case PasteFormats.Json:
|
||||
{
|
||||
PasteAsJson();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.Json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
ViewModel.ExecutePasteFormat(format);
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyboardAccelerator_Invoked(Microsoft.UI.Xaml.Input.KeyboardAccelerator sender, Microsoft.UI.Xaml.Input.KeyboardAcceleratorInvokedEventArgs args)
|
||||
{
|
||||
if (GetMainWindow()?.Visible is false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogTrace();
|
||||
|
||||
switch (sender.Key)
|
||||
{
|
||||
case VirtualKey.Escape:
|
||||
{
|
||||
(App.Current as App).GetMainWindow().Close();
|
||||
break;
|
||||
}
|
||||
GetMainWindow()?.Close();
|
||||
break;
|
||||
|
||||
case VirtualKey.Number1:
|
||||
{
|
||||
PasteAsPlain();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.PlainText));
|
||||
break;
|
||||
}
|
||||
|
||||
case VirtualKey.Number2:
|
||||
{
|
||||
PasteAsMarkdown();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.Markdown));
|
||||
break;
|
||||
}
|
||||
|
||||
case VirtualKey.Number3:
|
||||
{
|
||||
PasteAsJson();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.Json));
|
||||
break;
|
||||
}
|
||||
case VirtualKey.Number4:
|
||||
case VirtualKey.Number5:
|
||||
case VirtualKey.Number6:
|
||||
case VirtualKey.Number7:
|
||||
case VirtualKey.Number8:
|
||||
case VirtualKey.Number9:
|
||||
ViewModel.ExecutePasteFormat(sender.Key);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
@@ -222,7 +172,7 @@ namespace AdvancedPaste.Pages
|
||||
{
|
||||
if (e.Key == VirtualKey.Escape)
|
||||
{
|
||||
(App.Current as App).GetMainWindow().Close();
|
||||
GetMainWindow()?.Close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace AdvancedPaste.Settings
|
||||
{
|
||||
public interface IUserSettings
|
||||
@@ -11,5 +14,7 @@ namespace AdvancedPaste.Settings
|
||||
public bool SendPasteKeyCombination { get; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; }
|
||||
|
||||
public ObservableCollection<AdvancedPasteCustomAction> CustomActions { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AdvancedPaste.Helpers;
|
||||
|
||||
public static class NamedPipeProcessor
|
||||
{
|
||||
public static async Task ProcessNamedPipeAsync(string pipeName, TimeSpan connectTimeout, Action<string> messageHandler, CancellationToken cancellationToken)
|
||||
{
|
||||
using NamedPipeClientStream pipeClient = new(".", pipeName, PipeDirection.In);
|
||||
|
||||
await pipeClient.ConnectAsync(connectTimeout, cancellationToken);
|
||||
|
||||
using StreamReader streamReader = new(pipeClient, Encoding.Unicode);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var message = await streamReader.ReadLineAsync(cancellationToken);
|
||||
|
||||
if (message != null)
|
||||
{
|
||||
messageHandler(message);
|
||||
}
|
||||
|
||||
var intraMessageDelay = TimeSpan.FromMilliseconds(10);
|
||||
await Task.Delay(intraMessageDelay, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace AdvancedPaste.Helpers
|
||||
{
|
||||
public static class NativeEventWaiter
|
||||
{
|
||||
public static void WaitForEventLoop(string eventName, Action callback)
|
||||
{
|
||||
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
new Thread(() =>
|
||||
{
|
||||
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
while (true)
|
||||
{
|
||||
if (eventHandle.WaitOne())
|
||||
{
|
||||
dispatcherQueue.TryEnqueue(() => callback());
|
||||
}
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,37 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
|
||||
namespace AdvancedPaste.Settings
|
||||
{
|
||||
internal sealed class UserSettings : IUserSettings
|
||||
internal sealed class UserSettings : IUserSettings, IDisposable
|
||||
{
|
||||
private readonly SettingsUtils _settingsUtils;
|
||||
private readonly TaskScheduler _taskScheduler;
|
||||
private readonly IFileSystemWatcher _watcher;
|
||||
private readonly object _loadingSettingsLock = new object();
|
||||
private readonly object _loadingSettingsLock = new();
|
||||
|
||||
private const string AdvancedPasteModuleName = "AdvancedPaste";
|
||||
private const int MaxNumberOfRetry = 5;
|
||||
|
||||
private bool _disposedValue;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
public bool ShowCustomPreview { get; private set; }
|
||||
|
||||
public bool SendPasteKeyCombination { get; private set; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; private set; }
|
||||
|
||||
public ObservableCollection<AdvancedPasteCustomAction> CustomActions { get; private set; }
|
||||
|
||||
public UserSettings()
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
@@ -33,10 +41,25 @@ namespace AdvancedPaste.Settings
|
||||
ShowCustomPreview = true;
|
||||
SendPasteKeyCombination = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
CustomActions = [];
|
||||
|
||||
_taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||
|
||||
LoadSettingsFromJson();
|
||||
|
||||
_watcher = Helper.GetFileWatcher(AdvancedPasteModuleName, "settings.json", () => LoadSettingsFromJson());
|
||||
_watcher = Helper.GetFileWatcher(AdvancedPasteModuleName, "settings.json", OnSettingsFileChanged);
|
||||
}
|
||||
|
||||
private void OnSettingsFileChanged()
|
||||
{
|
||||
lock (_loadingSettingsLock)
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
Task.Delay(TimeSpan.FromMilliseconds(500))
|
||||
.ContinueWith(_ => LoadSettingsFromJson(), _cancellationTokenSource.Token, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSettingsFromJson()
|
||||
@@ -62,9 +85,25 @@ namespace AdvancedPaste.Settings
|
||||
var settings = _settingsUtils.GetSettingsOrDefault<AdvancedPasteSettings>(AdvancedPasteModuleName);
|
||||
if (settings != null)
|
||||
{
|
||||
ShowCustomPreview = settings.Properties.ShowCustomPreview;
|
||||
SendPasteKeyCombination = settings.Properties.SendPasteKeyCombination;
|
||||
CloseAfterLosingFocus = settings.Properties.CloseAfterLosingFocus;
|
||||
void UpdateSettings()
|
||||
{
|
||||
ShowCustomPreview = settings.Properties.ShowCustomPreview;
|
||||
SendPasteKeyCombination = settings.Properties.SendPasteKeyCombination;
|
||||
CloseAfterLosingFocus = settings.Properties.CloseAfterLosingFocus;
|
||||
|
||||
CustomActions.Clear();
|
||||
foreach (var customAction in settings.Properties.CustomActions.Value)
|
||||
{
|
||||
if (customAction.IsShown && customAction.IsValid)
|
||||
{
|
||||
CustomActions.Add(customAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Task.Factory
|
||||
.StartNew(UpdateSettings, CancellationToken.None, TaskCreationOptions.None, _taskScheduler)
|
||||
.Wait();
|
||||
}
|
||||
|
||||
retry = false;
|
||||
@@ -82,5 +121,30 @@ namespace AdvancedPaste.Settings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_cancellationTokenSource.Dispose();
|
||||
_watcher.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
~UserSettings()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace AdvancedPaste.Models;
|
||||
|
||||
public sealed class CustomActionActivatedEventArgs(string text, bool forcePasteCustom) : EventArgs
|
||||
{
|
||||
public string Text { get; private set; } = text;
|
||||
|
||||
public bool ForcePasteCustom { get; private set; } = forcePasteCustom;
|
||||
}
|
||||
@@ -2,16 +2,38 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace AdvancedPaste.Models
|
||||
namespace AdvancedPaste.Models;
|
||||
|
||||
public partial class PasteFormat : ObservableObject
|
||||
{
|
||||
public class PasteFormat
|
||||
[ObservableProperty]
|
||||
private string _shortcutText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _toolTip = string.Empty;
|
||||
|
||||
public PasteFormat()
|
||||
{
|
||||
public IconElement Icon { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public PasteFormats Format { get; set; }
|
||||
}
|
||||
|
||||
public PasteFormat(AdvancedPasteCustomAction customAction, string shortcutText)
|
||||
{
|
||||
IconGlyph = "\uE945";
|
||||
Name = customAction.Name;
|
||||
Prompt = customAction.Prompt;
|
||||
Format = PasteFormats.Custom;
|
||||
ShortcutText = shortcutText;
|
||||
ToolTip = customAction.Prompt;
|
||||
}
|
||||
|
||||
public string IconGlyph { get; init; }
|
||||
|
||||
public string Name { get; init; }
|
||||
|
||||
public PasteFormats Format { get; init; }
|
||||
|
||||
public string Prompt { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -59,10 +59,7 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root"
|
||||
xmlns=""
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
@@ -228,4 +225,7 @@
|
||||
<data name="OpenAIGpoDisabled" xml:space="preserve">
|
||||
<value>To custom with AI is disabled by your organization</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="CtrlKey" xml:space="preserve">
|
||||
<value>Ctrl</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
@@ -15,49 +16,79 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Win32;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.System;
|
||||
using WinUIEx;
|
||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
|
||||
namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
public partial class OptionsViewModel : ObservableObject
|
||||
public partial class OptionsViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private readonly DispatcherTimer _clipboardTimer;
|
||||
private readonly IUserSettings _userSettings;
|
||||
|
||||
private App app = App.Current as App;
|
||||
|
||||
private AICompletionsHelper aiHelper;
|
||||
private readonly AICompletionsHelper aiHelper;
|
||||
private readonly App app = App.Current as App;
|
||||
private readonly PasteFormat[] _allStandardPasteFormats;
|
||||
|
||||
public DataPackageView ClipboardData { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
|
||||
[NotifyPropertyChangedFor(nameof(GeneralErrorText))]
|
||||
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
|
||||
private bool _isClipboardDataText;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
|
||||
private bool _isCustomAIEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _clipboardHistoryEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxErrorText))]
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxPlaceholderText))]
|
||||
[NotifyPropertyChangedFor(nameof(GeneralErrorText))]
|
||||
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
|
||||
private bool _isAllowedByGPO;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ApiErrorText))]
|
||||
private int _apiRequestStatus;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _query = string.Empty;
|
||||
|
||||
private bool _pasteFormatsDirty;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsCustomAIEnabled))]
|
||||
private bool _isCustomAIEnabledOverride = false;
|
||||
|
||||
public ObservableCollection<PasteFormat> StandardPasteFormats { get; } = [];
|
||||
|
||||
public ObservableCollection<PasteFormat> CustomActionPasteFormats { get; } = [];
|
||||
|
||||
public bool IsCustomAIEnabled => IsCustomAIEnabledOverride || IsCustomAIEnabledCore;
|
||||
|
||||
private bool IsCustomAIEnabledCore => IsAllowedByGPO && IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
|
||||
public event EventHandler<CustomActionActivatedEventArgs> CustomActionActivated;
|
||||
|
||||
public OptionsViewModel(IUserSettings userSettings)
|
||||
{
|
||||
aiHelper = new AICompletionsHelper();
|
||||
_userSettings = userSettings;
|
||||
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
|
||||
ApiRequestStatus = (int)HttpStatusCode.OK;
|
||||
|
||||
_allStandardPasteFormats =
|
||||
[
|
||||
new PasteFormat { IconGlyph = "\uE8E9", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsPlainText"), Format = PasteFormats.PlainText },
|
||||
new PasteFormat { IconGlyph = "\ue8a5", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsMarkdown"), Format = PasteFormats.Markdown },
|
||||
new PasteFormat { IconGlyph = "\uE943", Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsJson"), Format = PasteFormats.Json },
|
||||
];
|
||||
|
||||
GeneratedResponses = new ObservableCollection<string>();
|
||||
GeneratedResponses.CollectionChanged += (s, e) =>
|
||||
{
|
||||
@@ -66,10 +97,87 @@ namespace AdvancedPaste.ViewModels
|
||||
};
|
||||
|
||||
ClipboardHistoryEnabled = IsClipboardHistoryEnabled();
|
||||
GetClipboardData();
|
||||
ReadClipboard();
|
||||
_clipboardTimer = new() { Interval = TimeSpan.FromSeconds(1) };
|
||||
_clipboardTimer.Tick += ClipboardTimer_Tick;
|
||||
_clipboardTimer.Start();
|
||||
|
||||
RefreshPasteFormats();
|
||||
_userSettings.CustomActions.CollectionChanged += (_, _) => EnqueueRefreshPasteFormats();
|
||||
PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(Query))
|
||||
{
|
||||
EnqueueRefreshPasteFormats();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void GetClipboardData()
|
||||
private void ClipboardTimer_Tick(object sender, object e)
|
||||
{
|
||||
if (app.GetMainWindow()?.Visible is true)
|
||||
{
|
||||
ReadClipboard();
|
||||
UpdateAllowedByGPO();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnqueueRefreshPasteFormats()
|
||||
{
|
||||
if (_pasteFormatsDirty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pasteFormatsDirty = true;
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
RefreshPasteFormats();
|
||||
_pasteFormatsDirty = false;
|
||||
});
|
||||
}
|
||||
|
||||
private void RefreshPasteFormats()
|
||||
{
|
||||
bool Filter(string text) => text.Contains(Query, StringComparison.CurrentCultureIgnoreCase);
|
||||
|
||||
var ctrlString = ResourceLoaderInstance.ResourceLoader.GetString("CtrlKey");
|
||||
int shortcutNum = 0;
|
||||
|
||||
string GetNextShortcutText()
|
||||
{
|
||||
shortcutNum++;
|
||||
return shortcutNum <= 9 ? $"{ctrlString}+{shortcutNum}" : string.Empty;
|
||||
}
|
||||
|
||||
StandardPasteFormats.Clear();
|
||||
foreach (var format in _allStandardPasteFormats)
|
||||
{
|
||||
if (Filter(format.Name))
|
||||
{
|
||||
format.ShortcutText = GetNextShortcutText();
|
||||
format.ToolTip = $"{format.Name} ({format.ShortcutText})";
|
||||
StandardPasteFormats.Add(format);
|
||||
}
|
||||
}
|
||||
|
||||
CustomActionPasteFormats.Clear();
|
||||
foreach (var customAction in _userSettings.CustomActions)
|
||||
{
|
||||
if (Filter(customAction.Name) || Filter(customAction.Prompt))
|
||||
{
|
||||
CustomActionPasteFormats.Add(new PasteFormat(customAction, GetNextShortcutText()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clipboardTimer.Stop();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void ReadClipboard()
|
||||
{
|
||||
ClipboardData = Clipboard.GetContent();
|
||||
IsClipboardDataText = ClipboardData.Contains(StandardDataFormats.Text);
|
||||
@@ -77,14 +185,10 @@ namespace AdvancedPaste.ViewModels
|
||||
|
||||
public void OnShow()
|
||||
{
|
||||
GetClipboardData();
|
||||
ReadClipboard();
|
||||
UpdateAllowedByGPO();
|
||||
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
{
|
||||
IsCustomAIEnabled = false;
|
||||
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
|
||||
}
|
||||
else
|
||||
if (IsAllowedByGPO)
|
||||
{
|
||||
var openAIKey = AICompletionsHelper.LoadOpenAIKey();
|
||||
var currentKey = aiHelper.GetKey();
|
||||
@@ -104,19 +208,30 @@ namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
app.GetMainWindow().FinishLoading(aiHelper.IsAIEnabled);
|
||||
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
OnPropertyChanged(nameof(GeneralErrorText));
|
||||
OnPropertyChanged(nameof(IsCustomAIEnabled));
|
||||
});
|
||||
},
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardHistoryEnabled = IsClipboardHistoryEnabled();
|
||||
GeneratedResponses.Clear();
|
||||
|
||||
_dispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
// Work-around for ListViews being disabled but sometimes not appearing grayed out.
|
||||
// It appears that this is sometimes only triggered by a change event. This
|
||||
// work-around sometimes still doesn't work, but it's better than not having it.
|
||||
await Task.Delay(5);
|
||||
IsClipboardDataText = true;
|
||||
IsCustomAIEnabledOverride = true;
|
||||
|
||||
await Task.Delay(150);
|
||||
ReadClipboard();
|
||||
IsCustomAIEnabledOverride = false;
|
||||
});
|
||||
}
|
||||
|
||||
// List to store generated responses
|
||||
@@ -152,47 +267,44 @@ namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
app.GetMainWindow().ClearInputText();
|
||||
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIGpoDisabled");
|
||||
}
|
||||
else if (!aiHelper.IsAIEnabled)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAINotConfigured");
|
||||
}
|
||||
else if (!IsClipboardDataText)
|
||||
return IsClipboardDataText ? ResourceLoaderInstance.ResourceLoader.GetString("CustomFormatTextBox/PlaceholderText") : GeneralErrorText;
|
||||
}
|
||||
}
|
||||
|
||||
public string GeneralErrorText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsClipboardDataText)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("ClipboardDataTypeMismatchWarning");
|
||||
}
|
||||
|
||||
if (!IsAllowedByGPO)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIGpoDisabled");
|
||||
}
|
||||
|
||||
if (!aiHelper.IsAIEnabled)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAINotConfigured");
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("CustomFormatTextBox/PlaceholderText");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string InputTxtBoxErrorText
|
||||
public string ApiErrorText
|
||||
{
|
||||
get
|
||||
get => (HttpStatusCode)ApiRequestStatus switch
|
||||
{
|
||||
if (ApiRequestStatus != (int)HttpStatusCode.OK)
|
||||
{
|
||||
if (ApiRequestStatus == (int)HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyTooManyRequests");
|
||||
}
|
||||
else if (ApiRequestStatus == (int)HttpStatusCode.Unauthorized)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyUnauthorized");
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyError") + ApiRequestStatus.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
HttpStatusCode.TooManyRequests => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyTooManyRequests"),
|
||||
HttpStatusCode.Unauthorized => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyUnauthorized"),
|
||||
HttpStatusCode.OK => string.Empty,
|
||||
_ => ResourceLoaderInstance.ResourceLoader.GetString("OpenAIApiKeyError") + ApiRequestStatus.ToString(CultureInfo.InvariantCulture),
|
||||
};
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -201,7 +313,12 @@ namespace AdvancedPaste.ViewModels
|
||||
[RelayCommand]
|
||||
public void PasteCustom()
|
||||
{
|
||||
PasteCustomFunction(GeneratedResponses[CurrentResponseIndex]);
|
||||
var text = GeneratedResponses.ElementAtOrDefault(CurrentResponseIndex);
|
||||
|
||||
if (text != null)
|
||||
{
|
||||
PasteCustomFunction(text);
|
||||
}
|
||||
}
|
||||
|
||||
// Command to select the previous custom format
|
||||
@@ -306,6 +423,59 @@ namespace AdvancedPaste.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
internal void ExecutePasteFormat(VirtualKey key)
|
||||
{
|
||||
var index = key - VirtualKey.Number1;
|
||||
var pasteFormat = StandardPasteFormats.ElementAtOrDefault(index) ?? CustomActionPasteFormats.ElementAtOrDefault(index - StandardPasteFormats.Count);
|
||||
|
||||
if (pasteFormat != null)
|
||||
{
|
||||
ExecutePasteFormat(pasteFormat);
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(pasteFormat.Format));
|
||||
}
|
||||
}
|
||||
|
||||
internal void ExecutePasteFormat(PasteFormat pasteFormat)
|
||||
{
|
||||
if (!IsClipboardDataText || (pasteFormat.Format == PasteFormats.Custom && !IsCustomAIEnabledCore))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pasteFormat.Format)
|
||||
{
|
||||
case PasteFormats.PlainText:
|
||||
ToPlainTextFunction();
|
||||
break;
|
||||
|
||||
case PasteFormats.Markdown:
|
||||
ToMarkdownFunction();
|
||||
break;
|
||||
|
||||
case PasteFormats.Json:
|
||||
ToJsonFunction();
|
||||
break;
|
||||
|
||||
case PasteFormats.Custom:
|
||||
Query = pasteFormat.Prompt;
|
||||
CustomActionActivated?.Invoke(this, new CustomActionActivatedEventArgs(pasteFormat.Prompt, false));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal void ExecuteCustomActionWithPaste(int customActionId)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
var customAction = _userSettings.CustomActions.FirstOrDefault(customAction => customAction.Id == customActionId);
|
||||
|
||||
if (customAction != null)
|
||||
{
|
||||
Query = customAction.Prompt;
|
||||
CustomActionActivated?.Invoke(this, new CustomActionActivatedEventArgs(customAction.Prompt, true));
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<string> GenerateCustomFunction(string inputInstructions)
|
||||
{
|
||||
Logger.LogTrace();
|
||||
@@ -315,7 +485,7 @@ namespace AdvancedPaste.ViewModels
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (ClipboardData == null || !ClipboardData.Contains(StandardDataFormats.Text))
|
||||
if (!IsClipboardDataText)
|
||||
{
|
||||
Logger.LogWarning("Clipboard does not contain text data");
|
||||
return string.Empty;
|
||||
@@ -416,5 +586,10 @@ namespace AdvancedPaste.ViewModels
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAllowedByGPO()
|
||||
{
|
||||
IsAllowedByGPO = PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
|
||||
#include <atlfile.h>
|
||||
#include <atlstr.h>
|
||||
#include <vector>
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
@@ -35,6 +39,9 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lp
|
||||
namespace
|
||||
{
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
const wchar_t JSON_KEY_CUSTOM_ACTIONS[] = L"custom-actions";
|
||||
const wchar_t JSON_KEY_SHORTCUT[] = L"shortcut";
|
||||
const wchar_t JSON_KEY_ID[] = L"id";
|
||||
const wchar_t JSON_KEY_WIN[] = L"win";
|
||||
const wchar_t JSON_KEY_ALT[] = L"alt";
|
||||
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
|
||||
@@ -60,33 +67,30 @@ private:
|
||||
|
||||
HANDLE m_hProcess;
|
||||
|
||||
std::thread create_pipe_thread;
|
||||
std::unique_ptr<CAtlFile> m_write_pipe;
|
||||
|
||||
// Time to wait for process to close after sending WM_CLOSE signal
|
||||
static const int MAX_WAIT_MILLISEC = 10000;
|
||||
static const constexpr int MAX_WAIT_MILLISEC = 10000;
|
||||
|
||||
static const constexpr int NUM_DEFAULT_HOTKEYS = 4;
|
||||
|
||||
Hotkey m_paste_as_plain_hotkey = { .win = true, .ctrl = true, .shift = false, .alt = true, .key = 'V' };
|
||||
Hotkey m_advanced_paste_ui_hotkey = { .win = true, .ctrl = false, .shift = true, .alt = false, .key = 'V' };
|
||||
Hotkey m_paste_as_markdown_hotkey{};
|
||||
Hotkey m_paste_as_json_hotkey{};
|
||||
|
||||
std::vector<Hotkey> m_custom_action_hotkeys;
|
||||
std::vector<int> m_custom_action_ids;
|
||||
|
||||
bool m_preview_custom_format_output = true;
|
||||
|
||||
// Handle to event used to invoke AdvancedPaste
|
||||
HANDLE m_hShowUIEvent;
|
||||
HANDLE m_hPasteMarkdownEvent;
|
||||
HANDLE m_hPasteJsonEvent;
|
||||
|
||||
Hotkey parse_single_hotkey(const wchar_t* hotkey, const winrt::Windows::Data::Json::JsonObject& settingsObject)
|
||||
Hotkey parse_single_hotkey(const wchar_t* keyName, const winrt::Windows::Data::Json::JsonObject& settingsObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
Hotkey _temp_paste_as_plain;
|
||||
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(hotkey);
|
||||
_temp_paste_as_plain.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
_temp_paste_as_plain.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
_temp_paste_as_plain.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
_temp_paste_as_plain.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
_temp_paste_as_plain.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
return _temp_paste_as_plain;
|
||||
const auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(keyName);
|
||||
return parse_single_hotkey(jsonHotkeyObject);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -96,6 +100,38 @@ private:
|
||||
return {};
|
||||
}
|
||||
|
||||
static Hotkey parse_single_hotkey(const winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
Hotkey hotkey;
|
||||
hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
return hotkey;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Failed to initialize AdvancedPaste shortcut from settings. Value will keep unchanged.");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static json::JsonObject to_json_object(const Hotkey& hotkey)
|
||||
{
|
||||
json::JsonObject jsonObject;
|
||||
jsonObject.SetNamedValue(JSON_KEY_WIN, json::value(hotkey.win));
|
||||
jsonObject.SetNamedValue(JSON_KEY_ALT, json::value(hotkey.alt));
|
||||
jsonObject.SetNamedValue(JSON_KEY_SHIFT, json::value(hotkey.shift));
|
||||
jsonObject.SetNamedValue(JSON_KEY_CTRL, json::value(hotkey.ctrl));
|
||||
jsonObject.SetNamedValue(JSON_KEY_CODE, json::value(hotkey.key));
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
bool migrate_data_and_remove_data_file(Hotkey& old_paste_as_plain_hotkey)
|
||||
{
|
||||
const wchar_t OLD_JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
|
||||
@@ -131,7 +167,7 @@ private:
|
||||
{
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
|
||||
// Migrate Paste As PLain text shortcut
|
||||
// Migrate Paste As Plain text shortcut
|
||||
Hotkey old_paste_as_plain_hotkey;
|
||||
bool old_data_migrated = migrate_data_and_remove_data_file(old_paste_as_plain_hotkey);
|
||||
if (old_data_migrated)
|
||||
@@ -139,12 +175,7 @@ private:
|
||||
m_paste_as_plain_hotkey = old_paste_as_plain_hotkey;
|
||||
|
||||
// override settings file
|
||||
json::JsonObject new_hotkey_value;
|
||||
new_hotkey_value.SetNamedValue(JSON_KEY_WIN, json::value(old_paste_as_plain_hotkey.win));
|
||||
new_hotkey_value.SetNamedValue(JSON_KEY_ALT, json::value(old_paste_as_plain_hotkey.alt));
|
||||
new_hotkey_value.SetNamedValue(JSON_KEY_SHIFT, json::value(old_paste_as_plain_hotkey.shift));
|
||||
new_hotkey_value.SetNamedValue(JSON_KEY_CTRL, json::value(old_paste_as_plain_hotkey.ctrl));
|
||||
new_hotkey_value.SetNamedValue(JSON_KEY_CODE, json::value(old_paste_as_plain_hotkey.key));
|
||||
const auto new_hotkey_value = to_json_object(old_paste_as_plain_hotkey);
|
||||
|
||||
if (!settingsObject.HasKey(JSON_KEY_PROPERTIES))
|
||||
{
|
||||
@@ -153,13 +184,7 @@ private:
|
||||
|
||||
settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).SetNamedValue(JSON_KEY_PASTE_AS_PLAIN_HOTKEY, new_hotkey_value);
|
||||
|
||||
|
||||
json::JsonObject ui_hotkey;
|
||||
ui_hotkey.SetNamedValue(JSON_KEY_WIN, json::value(m_advanced_paste_ui_hotkey.win));
|
||||
ui_hotkey.SetNamedValue(JSON_KEY_ALT, json::value(m_advanced_paste_ui_hotkey.alt));
|
||||
ui_hotkey.SetNamedValue(JSON_KEY_SHIFT, json::value(m_advanced_paste_ui_hotkey.shift));
|
||||
ui_hotkey.SetNamedValue(JSON_KEY_CTRL, json::value(m_advanced_paste_ui_hotkey.ctrl));
|
||||
ui_hotkey.SetNamedValue(JSON_KEY_CODE, json::value(m_advanced_paste_ui_hotkey.key));
|
||||
const auto ui_hotkey = to_json_object(m_advanced_paste_ui_hotkey);
|
||||
settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).SetNamedValue(JSON_KEY_ADVANCED_PASTE_UI_HOTKEY, ui_hotkey);
|
||||
|
||||
settings.save_to_settings_file();
|
||||
@@ -168,40 +193,53 @@ private:
|
||||
{
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_PASTE_AS_PLAIN_HOTKEY))
|
||||
const std::array<std::pair<Hotkey*, LPCWSTR>, NUM_DEFAULT_HOTKEYS> defaultHotkeys{
|
||||
{ { &m_paste_as_plain_hotkey, JSON_KEY_PASTE_AS_PLAIN_HOTKEY },
|
||||
{ &m_advanced_paste_ui_hotkey, JSON_KEY_ADVANCED_PASTE_UI_HOTKEY },
|
||||
{ &m_paste_as_markdown_hotkey, JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY },
|
||||
{ &m_paste_as_json_hotkey, JSON_KEY_PASTE_AS_JSON_HOTKEY } }
|
||||
};
|
||||
|
||||
for (auto& [hotkey, keyName] : defaultHotkeys)
|
||||
{
|
||||
m_paste_as_plain_hotkey = parse_single_hotkey(JSON_KEY_PASTE_AS_PLAIN_HOTKEY, settingsObject);
|
||||
*hotkey = parse_single_hotkey(keyName, settingsObject);
|
||||
}
|
||||
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_ADVANCED_PASTE_UI_HOTKEY))
|
||||
|
||||
m_custom_action_hotkeys.clear();
|
||||
m_custom_action_ids.clear();
|
||||
|
||||
if (settingsObject.HasKey(JSON_KEY_PROPERTIES))
|
||||
{
|
||||
m_advanced_paste_ui_hotkey = parse_single_hotkey(JSON_KEY_ADVANCED_PASTE_UI_HOTKEY, settingsObject);
|
||||
}
|
||||
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY))
|
||||
{
|
||||
m_paste_as_markdown_hotkey = parse_single_hotkey(JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY, settingsObject);
|
||||
}
|
||||
if (settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_PASTE_AS_JSON_HOTKEY))
|
||||
{
|
||||
m_paste_as_json_hotkey = parse_single_hotkey(JSON_KEY_PASTE_AS_JSON_HOTKEY, settingsObject);
|
||||
const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
|
||||
|
||||
if (propertiesObject.HasKey(JSON_KEY_CUSTOM_ACTIONS))
|
||||
{
|
||||
const auto customActions = propertiesObject.GetNamedObject(JSON_KEY_CUSTOM_ACTIONS).GetNamedArray(JSON_KEY_VALUE);
|
||||
|
||||
for (const auto& customAction : customActions)
|
||||
{
|
||||
const auto object = customAction.GetObjectW();
|
||||
|
||||
m_custom_action_hotkeys.push_back(parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT)));
|
||||
m_custom_action_ids.push_back(static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_process_running()
|
||||
bool is_process_running() const
|
||||
{
|
||||
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
void launch_process(const std::wstring& arg = L"")
|
||||
void launch_process(const std::wstring& pipe_name)
|
||||
{
|
||||
Logger::trace(L"Starting AdvancedPaste process");
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
const unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
|
||||
std::wstring executable_args = L"";
|
||||
executable_args.append(std::to_wstring(powertoys_pid));
|
||||
|
||||
executable_args += L" " + arg;
|
||||
const auto executable_args = std::format(L"{} {}", std::to_wstring(powertoys_pid), pipe_name);
|
||||
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
|
||||
@@ -221,6 +259,55 @@ private:
|
||||
m_hProcess = sei.hProcess;
|
||||
}
|
||||
|
||||
|
||||
std::optional<std::wstring> get_pipe_name(const std::wstring& prefix) const
|
||||
{
|
||||
UUID temp_uuid;
|
||||
wchar_t* uuid_chars = nullptr;
|
||||
if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS)
|
||||
{
|
||||
const auto val = get_last_error_message(GetLastError());
|
||||
Logger::error(L"UuidCreate cannot create guid. {}", val.has_value() ? val.value() : L"");
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (UuidToString(&temp_uuid, reinterpret_cast<RPC_WSTR*>(&uuid_chars)) != RPC_S_OK)
|
||||
{
|
||||
const auto val = get_last_error_message(GetLastError());
|
||||
Logger::error(L"UuidToString cannot convert to string. {}", val.has_value() ? val.value() : L"");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto pipe_name = std::format(L"{}{}", prefix, std::wstring(uuid_chars));
|
||||
RpcStringFree(reinterpret_cast<RPC_WSTR*>(&uuid_chars));
|
||||
|
||||
return pipe_name;
|
||||
}
|
||||
|
||||
void launch_process_and_named_pipe()
|
||||
{
|
||||
const auto pipe_name = get_pipe_name(L"powertoys_advanced_paste_");
|
||||
|
||||
if (!pipe_name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
create_pipe_thread = std::thread([&] { start_named_pipe_server(pipe_name.value()); });
|
||||
launch_process(pipe_name.value());
|
||||
create_pipe_thread.join();
|
||||
}
|
||||
|
||||
void send_named_pipe_message(const std::wstring& message_type, const std::wstring& message_arg = L"")
|
||||
{
|
||||
if (m_write_pipe)
|
||||
{
|
||||
const auto message = message_arg.empty() ? std::format(L"{}\r\n", message_type) : std::format(L"{} {}\r\n", message_type, message_arg);
|
||||
|
||||
const CString file_name(message.c_str());
|
||||
m_write_pipe->Write(file_name, file_name.GetLength() * sizeof(TCHAR));
|
||||
}
|
||||
}
|
||||
|
||||
// Load the settings file.
|
||||
void init_settings()
|
||||
{
|
||||
@@ -258,7 +345,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void try_inject_modifier_key_restore(std::vector<INPUT> &inputs, short modifier)
|
||||
void try_inject_modifier_key_restore(std::vector<INPUT>& inputs, short modifier)
|
||||
{
|
||||
// Most significant bit is set if key is down
|
||||
if ((GetAsyncKeyState(static_cast<int>(modifier)) & 0x8000) != 0)
|
||||
@@ -487,15 +574,54 @@ private:
|
||||
EnumWindows(enum_windows, (LPARAM)m_hProcess);
|
||||
}
|
||||
|
||||
HRESULT start_named_pipe_server(const std::wstring& pipe_name)
|
||||
{
|
||||
const constexpr DWORD BUFSIZE = 4096 * 4;
|
||||
|
||||
const auto full_pipe_name = std::format(L"\\\\.\\pipe\\{}", pipe_name);
|
||||
|
||||
const auto hPipe = CreateNamedPipe(
|
||||
full_pipe_name.c_str(), // pipe name
|
||||
PIPE_ACCESS_OUTBOUND, // write access
|
||||
PIPE_TYPE_MESSAGE | // message type pipe
|
||||
PIPE_READMODE_MESSAGE | // message-read mode
|
||||
PIPE_WAIT, // blocking mode
|
||||
1, // max. instances
|
||||
BUFSIZE, // output buffer size
|
||||
0, // input buffer size
|
||||
0, // client time-out
|
||||
NULL); // default security attribute
|
||||
|
||||
if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// This call blocks until a client process connects to the pipe
|
||||
BOOL connected = ConnectNamedPipe(hPipe, NULL);
|
||||
if (!connected)
|
||||
{
|
||||
if (GetLastError() == ERROR_PIPE_CONNECTED)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseHandle(hPipe);
|
||||
}
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
m_write_pipe = std::make_unique<CAtlFile>(hPipe);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
public:
|
||||
AdvancedPaste()
|
||||
{
|
||||
app_name = GET_RESOURCE_STRING(IDS_ADVANCED_PASTE_NAME);
|
||||
app_key = AdvancedPasteConstants::ModuleKey;
|
||||
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "AdvancedPaste");
|
||||
m_hShowUIEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_ADVANCED_PASTE_SHARED_EVENT);
|
||||
m_hPasteMarkdownEvent = CreateDefaultEvent(CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_EVENT);
|
||||
m_hPasteJsonEvent = CreateDefaultEvent(CommonSharedConstants::ADVANCED_PASTE_JSON_EVENT);
|
||||
init_settings();
|
||||
}
|
||||
|
||||
@@ -559,7 +685,7 @@ public:
|
||||
|
||||
parse_hotkeys(values);
|
||||
|
||||
auto settingsObject = values.get_raw_json();
|
||||
const auto settingsObject = values.get_raw_json();
|
||||
if (settingsObject.GetView().Size() && settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).HasKey(JSON_KEY_SHOW_CUSTOM_PREVIEW))
|
||||
{
|
||||
m_preview_custom_format_output = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE);
|
||||
@@ -567,10 +693,10 @@ public:
|
||||
|
||||
// order of args matter
|
||||
Trace::AdvancedPaste_SettingsTelemetry(m_paste_as_plain_hotkey,
|
||||
m_advanced_paste_ui_hotkey,
|
||||
m_paste_as_markdown_hotkey,
|
||||
m_paste_as_json_hotkey,
|
||||
m_preview_custom_format_output);
|
||||
m_advanced_paste_ui_hotkey,
|
||||
m_paste_as_markdown_hotkey,
|
||||
m_paste_as_json_hotkey,
|
||||
m_preview_custom_format_output);
|
||||
|
||||
// If you don't need to do any custom processing of the settings, proceed
|
||||
// to persists the values calling:
|
||||
@@ -588,12 +714,9 @@ public:
|
||||
{
|
||||
Logger::trace("AdvancedPaste::enable()");
|
||||
Trace::AdvancedPaste_Enable(true);
|
||||
ResetEvent(m_hShowUIEvent);
|
||||
ResetEvent(m_hPasteMarkdownEvent);
|
||||
ResetEvent(m_hPasteJsonEvent);
|
||||
m_enabled = true;
|
||||
|
||||
launch_process();
|
||||
launch_process_and_named_pipe();
|
||||
};
|
||||
|
||||
virtual void disable()
|
||||
@@ -601,9 +724,8 @@ public:
|
||||
Logger::trace("AdvancedPaste::disable()");
|
||||
if (m_enabled)
|
||||
{
|
||||
ResetEvent(m_hShowUIEvent);
|
||||
ResetEvent(m_hPasteMarkdownEvent);
|
||||
ResetEvent(m_hPasteJsonEvent);
|
||||
m_write_pipe = nullptr;
|
||||
|
||||
TerminateProcess(m_hProcess, 1);
|
||||
Trace::AdvancedPaste_Enable(false);
|
||||
|
||||
@@ -622,13 +744,14 @@ public:
|
||||
if (!is_process_running())
|
||||
{
|
||||
Logger::trace(L"Launching new process");
|
||||
launch_process();
|
||||
launch_process_and_named_pipe();
|
||||
|
||||
Trace::AdvancedPaste_Invoked(L"AdvancedPasteUI");
|
||||
}
|
||||
|
||||
// hotkeyId in same order as set by get_hotkeys
|
||||
if (hotkeyId == 0) { // m_paste_as_plain_hotkey
|
||||
if (hotkeyId == 0)
|
||||
{ // m_paste_as_plain_hotkey
|
||||
Logger::trace(L"Paste as plain text hotkey pressed");
|
||||
|
||||
std::thread([=]() {
|
||||
@@ -641,21 +764,36 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hotkeyId == 1) { // m_advanced_paste_ui_hotkey
|
||||
if (hotkeyId == 1)
|
||||
{ // m_advanced_paste_ui_hotkey
|
||||
Logger::trace(L"Setting start up event");
|
||||
|
||||
bring_process_to_front();
|
||||
SetEvent(m_hShowUIEvent);
|
||||
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_MESSAGE);
|
||||
return true;
|
||||
}
|
||||
if (hotkeyId == 2) { // m_paste_as_markdown_hotkey
|
||||
if (hotkeyId == 2)
|
||||
{ // m_paste_as_markdown_hotkey
|
||||
Logger::trace(L"Starting paste as markdown directly");
|
||||
SetEvent(m_hPasteMarkdownEvent);
|
||||
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_MESSAGE);
|
||||
return true;
|
||||
}
|
||||
if (hotkeyId == 3) { // m_paste_as_json_hotkey
|
||||
if (hotkeyId == 3)
|
||||
{ // m_paste_as_json_hotkey
|
||||
Logger::trace(L"Starting paste as json directly");
|
||||
SetEvent(m_hPasteJsonEvent);
|
||||
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_JSON_MESSAGE);
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto custom_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
|
||||
|
||||
if (custom_action_index < m_custom_action_ids.size())
|
||||
{
|
||||
const auto id = m_custom_action_ids.at(custom_action_index);
|
||||
|
||||
Logger::trace(L"Starting custom action id={}", id);
|
||||
|
||||
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE, std::to_wstring(id));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -665,14 +803,20 @@ public:
|
||||
|
||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||
{
|
||||
if (hotkeys && buffer_size >= 4)
|
||||
const size_t num_hotkeys = NUM_DEFAULT_HOTKEYS + m_custom_action_hotkeys.size();
|
||||
|
||||
if (hotkeys && buffer_size >= num_hotkeys)
|
||||
{
|
||||
hotkeys[0] = m_paste_as_plain_hotkey;
|
||||
hotkeys[1] = m_advanced_paste_ui_hotkey;
|
||||
hotkeys[2] = m_paste_as_markdown_hotkey;
|
||||
hotkeys[3] = m_paste_as_json_hotkey;
|
||||
const std::array default_hotkeys = { m_paste_as_plain_hotkey,
|
||||
m_advanced_paste_ui_hotkey,
|
||||
m_paste_as_markdown_hotkey,
|
||||
m_paste_as_json_hotkey };
|
||||
|
||||
std::copy(default_hotkeys.begin(), default_hotkeys.end(), hotkeys);
|
||||
std::copy(m_custom_action_hotkeys.begin(), m_custom_action_hotkeys.end(), hotkeys + NUM_DEFAULT_HOTKEYS);
|
||||
}
|
||||
return 4;
|
||||
|
||||
return num_hotkeys;
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override
|
||||
|
||||
Reference in New Issue
Block a user