ignore holtkey conflict (#41729)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This PR implements functionality to ignore specific hotkey conflicts in
PowerToys settings. The primary purpose is to allow users to suppress
individual shortcut conflict warnings if they find their configurations
work correctly despite the detected conflicts.


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #41544
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
- Added hotkey conflict ignore functionality with user-controllable
settings
- Updated shortcut control UI to support ignore states and clearer
conflict messaging
- Enhanced conflict detection to respect ignored shortcuts when counting
conflicts

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
Shawn Yuan
2025-09-29 08:53:07 +08:00
committed by GitHub
parent b026bf5be2
commit 8d4ed04f1a
28 changed files with 1203 additions and 274 deletions

View File

@@ -0,0 +1,45 @@
// 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.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Controls;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public partial class BoolToKeyVisualStateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool b && parameter is string param)
{
if (b && param == "Warning")
{
return State.Warning;
}
else if (b && param == "Error")
{
return State.Error;
}
else
{
return State.Normal;
}
}
else
{
return State.Normal;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,229 @@
// 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.Generic;
using System.Linq;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Views;
namespace Microsoft.PowerToys.Settings.UI.Helpers
{
/// <summary>
/// Static helper class to manage and check hotkey conflict ignore settings
/// </summary>
public static class HotkeyConflictIgnoreHelper
{
private static readonly ISettingsRepository<GeneralSettings> _generalSettingsRepository;
private static readonly ISettingsUtils _settingsUtils;
static HotkeyConflictIgnoreHelper()
{
_settingsUtils = new SettingsUtils();
_generalSettingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
}
/// <summary>
/// Ensures ignored conflict properties are initialized
/// </summary>
private static void EnsureInitialized()
{
var settings = _generalSettingsRepository.SettingsConfig;
if (settings.IgnoredConflictProperties == null)
{
settings.IgnoredConflictProperties = new ShortcutConflictProperties();
SaveSettings();
}
}
/// <summary>
/// Checks if a specific hotkey setting is configured to ignore conflicts
/// </summary>
/// <param name="hotkeySettings">The hotkey settings to check</param>
/// <returns>True if the hotkey is set to ignore conflicts, false otherwise</returns>
public static bool IsIgnoringConflicts(HotkeySettings hotkeySettings)
{
if (hotkeySettings == null)
{
return false;
}
try
{
EnsureInitialized();
var settings = _generalSettingsRepository.SettingsConfig;
return settings.IgnoredConflictProperties.IgnoredShortcuts
.Any(h => AreHotkeySettingsEqual(h, hotkeySettings));
}
catch (Exception ex)
{
Logger.LogError($"Error checking if hotkey is ignoring conflicts: {ex.Message}");
return false;
}
}
/// <summary>
/// Adds a hotkey setting to the ignored shortcuts list
/// </summary>
/// <param name="hotkeySettings">The hotkey settings to add to the ignored list</param>
/// <returns>True if successfully added, false if it was already ignored or on error</returns>
public static bool AddToIgnoredList(HotkeySettings hotkeySettings)
{
if (hotkeySettings == null)
{
return false;
}
try
{
EnsureInitialized();
var settings = _generalSettingsRepository.SettingsConfig;
// Check if already ignored (avoid duplicates)
if (IsIgnoringConflicts(hotkeySettings))
{
Logger.LogInfo($"Hotkey already in ignored list: {hotkeySettings}");
return false;
}
// Add to ignored list
settings.IgnoredConflictProperties.IgnoredShortcuts.Add(hotkeySettings);
SaveSettings();
Logger.LogInfo($"Added hotkey to ignored list: {hotkeySettings}");
return true;
}
catch (Exception ex)
{
Logger.LogError($"Error adding hotkey to ignored list: {ex.Message}");
return false;
}
}
/// <summary>
/// Removes a hotkey setting from the ignored shortcuts list
/// </summary>
/// <param name="hotkeySettings">The hotkey settings to remove from the ignored list</param>
/// <returns>True if successfully removed, false if it wasn't in the list or on error</returns>
public static bool RemoveFromIgnoredList(HotkeySettings hotkeySettings)
{
if (hotkeySettings == null)
{
return false;
}
try
{
EnsureInitialized();
var settings = _generalSettingsRepository.SettingsConfig;
var ignoredShortcut = settings.IgnoredConflictProperties.IgnoredShortcuts
.FirstOrDefault(h => AreHotkeySettingsEqual(h, hotkeySettings));
if (ignoredShortcut != null)
{
settings.IgnoredConflictProperties.IgnoredShortcuts.Remove(ignoredShortcut);
SaveSettings();
Logger.LogInfo($"Removed hotkey from ignored list: {ignoredShortcut}");
return true;
}
Logger.LogInfo($"Hotkey not found in ignored list: {hotkeySettings}");
return false;
}
catch (Exception ex)
{
Logger.LogError($"Error removing hotkey from ignored list: {ex.Message}");
return false;
}
}
/// <summary>
/// Gets all hotkey settings that are currently being ignored
/// </summary>
/// <returns>List of ignored hotkey settings</returns>
public static List<HotkeySettings> GetAllIgnoredShortcuts()
{
try
{
EnsureInitialized();
var settings = _generalSettingsRepository.SettingsConfig;
return new List<HotkeySettings>(settings.IgnoredConflictProperties.IgnoredShortcuts);
}
catch (Exception ex)
{
Logger.LogError($"Error getting ignored shortcuts: {ex.Message}");
return new List<HotkeySettings>();
}
}
/// <summary>
/// Clears all ignored shortcuts from the list
/// </summary>
/// <returns>True if successfully cleared, false on error</returns>
public static bool ClearAllIgnoredShortcuts()
{
try
{
EnsureInitialized();
var settings = _generalSettingsRepository.SettingsConfig;
var count = settings.IgnoredConflictProperties.IgnoredShortcuts.Count;
settings.IgnoredConflictProperties.IgnoredShortcuts.Clear();
SaveSettings();
Logger.LogInfo($"Cleared all {count} ignored shortcuts");
return true;
}
catch (Exception ex)
{
Logger.LogError($"Error clearing ignored shortcuts: {ex.Message}");
return false;
}
}
/// <summary>
/// Compares two HotkeySettings for equality
/// </summary>
/// <param name="hotkey1">First hotkey settings</param>
/// <param name="hotkey2">Second hotkey settings</param>
/// <returns>True if they represent the same shortcut, false otherwise</returns>
private static bool AreHotkeySettingsEqual(HotkeySettings hotkey1, HotkeySettings hotkey2)
{
if (hotkey1 == null || hotkey2 == null)
{
return false;
}
return hotkey1.Win == hotkey2.Win &&
hotkey1.Ctrl == hotkey2.Ctrl &&
hotkey1.Alt == hotkey2.Alt &&
hotkey1.Shift == hotkey2.Shift &&
hotkey1.Code == hotkey2.Code;
}
/// <summary>
/// Saves the general settings using PowerToys standard settings persistence
/// </summary>
private static void SaveSettings()
{
try
{
var settings = _generalSettingsRepository.SettingsConfig;
// Send IPC message to notify runner of changes (this is thread-safe)
var outgoing = new OutGoingGeneralSettings(settings);
ShellPage.SendDefaultIPCMessage(outgoing.ToString());
ShellPage.ShellHandler?.SignalGeneralDataUpdate();
}
catch (Exception ex)
{
Logger.LogError($"Error saving shortcut conflict settings: {ex.Message}");
Logger.LogError($"Stack trace: {ex.StackTrace}");
throw;
}
}
}
}

View File

@@ -33,6 +33,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
[JsonSerializable(typeof(PowerOcrSettings))]
[JsonSerializable(typeof(PowerOcrSettings))]
[JsonSerializable(typeof(RegistryPreviewSettings))]
[JsonSerializable(typeof(ShortcutConflictProperties))]
[JsonSerializable(typeof(ShortcutGuideSettings))]
[JsonSerializable(typeof(WINDOWPLACEMENT))]
[JsonSerializable(typeof(WorkspacesSettings))]

View File

@@ -8,7 +8,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Visibility="{x:Bind HasConflicts, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid>
<Button Click="ShortcutConflictBtn_Click" Style="{StaticResource SubtleButtonStyle}">
<Grid ColumnSpacing="16">
<Grid.ColumnDefinitions>
@@ -16,10 +16,10 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<FontIcon
x:Name="Icon"
AutomationProperties.AccessibilityView="Raw"
FontSize="20"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
Glyph="&#xE814;" />
Glyph="&#xEDA7;" />
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock x:Uid="ShortcutConflictControl_Title" FontWeight="SemiBold" />
<TextBlock
@@ -29,5 +29,16 @@
</StackPanel>
</Grid>
</Button>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ConflictsStateGroup">
<VisualState x:Name="NoConflictState" />
<VisualState x:Name="ConflictState">
<VisualState.Setters>
<Setter Target="Icon.Glyph" Value="&#xE814;" />
<Setter Target="Icon.Foreground" Value="{ThemeResource SystemFillColorCautionBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</UserControl>

View File

@@ -47,12 +47,24 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
int count = 0;
if (AllHotkeyConflictsData.InAppConflicts != null)
{
count += AllHotkeyConflictsData.InAppConflicts.Count;
foreach (var inAppConflict in AllHotkeyConflictsData.InAppConflicts)
{
if (!inAppConflict.ConflictIgnored)
{
count++;
}
}
}
if (AllHotkeyConflictsData.SystemConflicts != null)
{
count += AllHotkeyConflictsData.SystemConflicts.Count;
foreach (var systemConflict in AllHotkeyConflictsData.SystemConflicts)
{
if (!systemConflict.ConflictIgnored)
{
count++;
}
}
}
return count;
@@ -95,7 +107,14 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
OnPropertyChanged(nameof(HasConflicts));
// Update visibility based on conflict count
Visibility = HasConflicts ? Visibility.Visible : Visibility.Collapsed;
if (HasConflicts)
{
VisualStateManager.GoToState(this, "ConflictState", true);
}
else
{
VisualStateManager.GoToState(this, "NoConflictState", true);
}
if (!_telemetryEventSent && HasConflicts)
{
@@ -119,13 +138,12 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
InitializeComponent();
DataContext = this;
// Initially hide the control if no conflicts
Visibility = HasConflicts ? Visibility.Visible : Visibility.Collapsed;
UpdateProperties();
}
private void ShortcutConflictBtn_Click(object sender, RoutedEventArgs e)
{
if (AllHotkeyConflictsData == null || !HasConflicts)
if (AllHotkeyConflictsData == null)
{
return;
}

View File

@@ -53,34 +53,22 @@
</Grid.RowDefinitions>
<!-- Title Bar Area -->
<Grid
x:Name="titleBar"
Height="48"
ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0" />
<ColumnDefinition x:Name="IconColumn" Width="Auto" />
<ColumnDefinition x:Name="TitleColumn" Width="Auto" />
<ColumnDefinition x:Name="RightPaddingColumn" Width="0" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="1"
Width="16"
Height="16"
VerticalAlignment="Center"
Source="/Assets/Settings/icon.ico" />
<TextBlock
x:Uid="ShortcutConflictWindow_TitleTxt"
Grid.Column="2"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" />
</Grid>
<TitleBar x:Name="titleBar" x:Uid="ShortcutConflictWindow_TitleTxt">
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
<TitleBar.LeftHeader>
<ImageIcon
Height="16"
Margin="16,0,0,0"
Source="/Assets/Settings/icon.ico" />
</TitleBar.LeftHeader>
</TitleBar>
<!-- Description text -->
<TextBlock
x:Uid="ShortcutConflictWindow_Description"
Grid.Row="1"
Margin="16,24,16,24"
Margin="16,8,16,24"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource BodyTextBlockStyle}"
TextWrapping="Wrap" />
@@ -97,22 +85,40 @@
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="hotkeyConflicts:HotkeyConflictGroupData">
<StackPanel Orientation="Vertical">
<StackPanel
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{StaticResource OverlayCornerRadius}"
Orientation="Vertical">
<!-- Hotkey Header -->
<controls:ShortcutWithTextLabelControl
x:Uid="ShortcutConflictWindow_ModulesUsingShortcut"
Margin="0,0,0,8"
FontWeight="SemiBold"
Keys="{x:Bind Hotkey.GetKeysList()}"
LabelPlacement="Before" />
<Grid Margin="16,12,16,12">
<controls:ShortcutWithTextLabelControl
x:Uid="ShortcutConflictWindow_ModulesUsingShortcut"
VerticalAlignment="Center"
FontWeight="SemiBold"
Keys="{x:Bind Hotkey.GetKeysList()}"
LabelPlacement="Before" />
<CheckBox
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="OnIgnoreConflictClicked"
Content="Ignore shortcut"
IsChecked="{x:Bind ConflictIgnored, Mode=OneWay}" />
</Grid>
<!-- PowerToys Module Cards -->
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind Modules}">
<ItemsControl
Grid.Row="1"
IsEnabled="{x:Bind ConflictVisible, Mode=OneWay}"
ItemsSource="{x:Bind Modules, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="hotkeyConflicts:ModuleHotkeyData">
<tkcontrols:SettingsCard
Margin="0,0,0,4"
Background="Transparent"
BorderThickness="0,1,0,0"
Click="SettingsCard_Click"
CornerRadius="0"
Description="{x:Bind DisplayName}"
Header="{x:Bind Header}"
IsClickEnabled="True">
@@ -137,15 +143,15 @@
<tkcontrols:SettingsCard
x:Name="SystemConflictCard"
x:Uid="ShortcutConflictWindow_SystemCard"
Visibility="{x:Bind IsSystemConflict}">
Background="Transparent"
BorderThickness="0,1,0,0"
CornerRadius="0"
IsEnabled="{x:Bind ShouldShowSysConflict, Mode=OneWay}">
<tkcontrols:SettingsCard.HeaderIcon>
<PathIcon Data="M9 20H0V11H9V20ZM20 20H11V11H20V20ZM9 9H0V0H9V9ZM20 9H11V0H20V9Z" Foreground="{ThemeResource WindowsLogoGradient}" />
</tkcontrols:SettingsCard.HeaderIcon>
<!-- System shortcut message -->
<TextBlock
x:Uid="ShortcutConflictWindow_SystemShortcutMessage"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<HyperlinkButton x:Uid="ShortcutConflictWindow_SystemShortcutLink" NavigateUri="https://support.microsoft.com/windows/keyboard-shortcuts-in-windows-dcc61a57-8ff0-cffe-9796-cb9706c75eec" />
</tkcontrols:SettingsCard>
</StackPanel>
</DataTemplate>

View File

@@ -14,6 +14,7 @@ using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Graphics;
using WinUIEx;
@@ -21,8 +22,6 @@ namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard
{
public sealed partial class ShortcutConflictWindow : WindowEx
{
public ShortcutConflictViewModel DataContext { get; }
public ShortcutConflictViewModel ViewModel { get; private set; }
public ShortcutConflictWindow()
@@ -33,14 +32,17 @@ namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),
ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
// Set DataContext on the root Grid instead of the Window
RootGrid.DataContext = ViewModel;
this.Activated += Window_Activated_SetIcon;
// Set localized window title
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
this.ExtendsContentIntoTitleBar = true;
ExtendsContentIntoTitleBar = true;
SetTitleBar(titleBar);
this.Title = resourceLoader.GetString("ShortcutConflictWindow_Title");
this.CenterOnScreen();
@@ -74,6 +76,54 @@ namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard
}
}
private void OnIgnoreConflictClicked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox && checkBox.DataContext is HotkeyConflictGroupData conflictGroup)
{
// The Click event only fires from user interaction, not programmatic changes
if (checkBox.IsChecked == true)
{
IgnoreConflictGroup(conflictGroup);
}
else
{
UnignoreConflictGroup(conflictGroup);
}
}
}
private void IgnoreConflictGroup(HotkeyConflictGroupData conflictGroup)
{
try
{
// Ignore all hotkey settings in this conflict group
if (conflictGroup.Modules != null)
{
HotkeySettings hotkey = new(conflictGroup.Hotkey.Win, conflictGroup.Hotkey.Ctrl, conflictGroup.Hotkey.Alt, conflictGroup.Hotkey.Shift, conflictGroup.Hotkey.Key);
ViewModel.IgnoreShortcut(hotkey);
}
}
catch
{
}
}
private void UnignoreConflictGroup(HotkeyConflictGroupData conflictGroup)
{
try
{
// Unignore all hotkey settings in this conflict group
if (conflictGroup.Modules != null)
{
HotkeySettings hotkey = new(conflictGroup.Hotkey.Win, conflictGroup.Hotkey.Ctrl, conflictGroup.Hotkey.Alt, conflictGroup.Hotkey.Shift, conflictGroup.Hotkey.Key);
ViewModel.UnignoreShortcut(hotkey);
}
}
catch
{
}
}
private void WindowEx_Closed(object sender, WindowEventArgs args)
{
ViewModel?.Dispose();
@@ -82,10 +132,7 @@ namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard
private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args)
{
// Set window icon
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd);
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
appWindow.SetIcon("Assets\\Settings\\icon.ico");
AppWindow.SetIcon("Assets\\Settings\\icon.ico");
}
}
}

View File

@@ -63,10 +63,18 @@
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Target="KeyHolder.BorderThickness" Value="2" />
<Setter Target="KeyHolder.BorderThickness" Value="1" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Warning">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCautionBackgroundBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCautionBrush}" />
<Setter Target="KeyHolder.BorderThickness" Value="1" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCautionBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
@@ -120,6 +128,11 @@
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Warning">
<VisualState.Setters>
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCautionBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
@@ -177,10 +190,18 @@
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Target="KeyHolder.BorderThickness" Value="2" />
<Setter Target="KeyHolder.BorderThickness" Value="1" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Warning">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCautionBackgroundBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCautionBrush}" />
<Setter Target="KeyHolder.BorderThickness" Value="1" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCautionBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>

View File

@@ -12,12 +12,14 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
[TemplateVisualState(Name = NormalState, GroupName = "CommonStates")]
[TemplateVisualState(Name = DisabledState, GroupName = "CommonStates")]
[TemplateVisualState(Name = InvalidState, GroupName = "CommonStates")]
[TemplateVisualState(Name = WarningState, GroupName = "CommonStates")]
public sealed partial class KeyVisual : Control
{
private const string KeyPresenter = "KeyPresenter";
private const string NormalState = "Normal";
private const string DisabledState = "Disabled";
private const string InvalidState = "Invalid";
private const string WarningState = "Warning";
private KeyCharPresenter _keyPresenter;
public object Content
@@ -28,13 +30,13 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged));
public bool IsInvalid
public State State
{
get => (bool)GetValue(IsInvalidProperty);
set => SetValue(IsInvalidProperty, value);
get => (State)GetValue(StateProperty);
set => SetValue(StateProperty, value);
}
public static readonly DependencyProperty IsInvalidProperty = DependencyProperty.Register(nameof(IsInvalid), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnIsInvalidChanged));
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(State), typeof(KeyVisual), new PropertyMetadata(State.Normal, OnStateChanged));
public bool RenderKeyAsGlyph
{
@@ -64,7 +66,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
((KeyVisual)d).SetVisualStates();
}
private static void OnIsInvalidChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
private static void OnStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((KeyVisual)d).SetVisualStates();
}
@@ -73,10 +75,14 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
if (this != null)
{
if (IsInvalid)
if (State == State.Error)
{
VisualStateManager.GoToState(this, InvalidState, true);
}
else if (State == State.Warning)
{
VisualStateManager.GoToState(this, WarningState, true);
}
else if (!IsEnabled)
{
VisualStateManager.GoToState(this, DisabledState, true);
@@ -177,4 +183,11 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
SetVisualStates();
}
}
public enum State
{
Normal,
Error,
Warning,
}
}

View File

@@ -6,11 +6,14 @@
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
x:Name="LayoutRoot"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
<UserControl.Resources>
<converters:BoolToKeyVisualStateConverter x:Key="BoolToKeyVisualStateConverter" />
</UserControl.Resources>
<Grid HorizontalAlignment="Right">
<Button
x:Name="EditButton"
@@ -40,8 +43,8 @@
Content="{Binding}"
CornerRadius="{StaticResource ControlCornerRadius}"
FontWeight="SemiBold"
IsInvalid="{Binding ElementName=LayoutRoot, Path=HasConflict}"
IsTabStop="False"
State="{Binding ElementName=LayoutRoot, Path=KeyVisualShouldShowConflict, Mode=OneWay, Converter={StaticResource BoolToKeyVisualStateConverter}, ConverterParameter=Warning}"
Style="{StaticResource AccentKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
@@ -49,6 +52,7 @@
<StackPanel
x:Name="PlaceholderPanel"
Padding="8,4"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{StaticResource ControlCornerRadius}"
Orientation="Horizontal"
@@ -62,13 +66,15 @@
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
<FontIcon
<controls:IsEnabledTextBlock
Margin="0,0,4,0"
VerticalAlignment="Center"
AutomationProperties.AccessibilityView="Raw"
AutomationProperties.Name=""
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="14"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE70F;" />
Text="&#xE70F;" />
</StackPanel>
</Button>
<VisualStateManager.VisualStateGroups>

View File

@@ -12,6 +12,7 @@ using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml;
@@ -51,6 +52,8 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
public static readonly DependencyProperty AllowDisableProperty = DependencyProperty.Register("AllowDisable", typeof(bool), typeof(ShortcutControl), new PropertyMetadata(false, OnAllowDisableChanged));
public static readonly DependencyProperty HasConflictProperty = DependencyProperty.Register("HasConflict", typeof(bool), typeof(ShortcutControl), new PropertyMetadata(false, OnHasConflictChanged));
public static readonly DependencyProperty TooltipProperty = DependencyProperty.Register("Tooltip", typeof(string), typeof(ShortcutControl), new PropertyMetadata(null, OnTooltipChanged));
public static readonly DependencyProperty KeyVisualShouldShowConflictProperty = DependencyProperty.Register("KeyVisualShouldShowConflict", typeof(bool), typeof(ShortcutControl), new PropertyMetadata(false));
public static readonly DependencyProperty IgnoreConflictProperty = DependencyProperty.Register("IgnoreConflict", typeof(bool), typeof(ShortcutControl), new PropertyMetadata(false));
// Dependency property to track the source/context of the ShortcutControl
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(ShortcutControlSource), typeof(ShortcutControl), new PropertyMetadata(ShortcutControlSource.SettingsPage));
@@ -161,6 +164,18 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set => SetValue(TooltipProperty, value);
}
public bool KeyVisualShouldShowConflict
{
get => (bool)GetValue(KeyVisualShouldShowConflictProperty);
set => SetValue(KeyVisualShouldShowConflictProperty, value);
}
public bool IgnoreConflict
{
get => (bool)GetValue(IgnoreConflictProperty);
set => SetValue(IgnoreConflictProperty, value);
}
public ShortcutControlSource Source
{
get => (ShortcutControlSource)GetValue(SourceProperty);
@@ -241,6 +256,8 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
// Update the ShortcutControl's conflict properties from HotkeySettings
HasConflict = hotkeySettings.HasConflict;
Tooltip = hotkeySettings.HasConflict ? hotkeySettings.ConflictDescription : null;
IgnoreConflict = HotkeyConflictIgnoreHelper.IsIgnoringConflicts(hotkeySettings);
KeyVisualShouldShowConflict = !IgnoreConflict && HasConflict;
}
else
{
@@ -257,6 +274,10 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
this.Unloaded += ShortcutControl_Unloaded;
this.Loaded += ShortcutControl_Loaded;
c.ResetClick += C_ResetClick;
c.ClearClick += C_ClearClick;
c.LearnMoreClick += C_LearnMoreClick;
// We create the Dialog in C# because doing it in XAML is giving WinUI/XAML Island bugs when using dark theme.
shortcutDialog = new ContentDialog
{
@@ -264,11 +285,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
Title = resourceLoader.GetString("Activation_Shortcut_Title"),
Content = c,
PrimaryButtonText = resourceLoader.GetString("Activation_Shortcut_Save"),
SecondaryButtonText = resourceLoader.GetString("Activation_Shortcut_Reset"),
CloseButtonText = resourceLoader.GetString("Activation_Shortcut_Cancel"),
DefaultButton = ContentDialogButton.Primary,
};
shortcutDialog.SecondaryButtonClick += ShortcutDialog_Reset;
shortcutDialog.RightTapped += ShortcutDialog_Disable;
AutomationProperties.SetName(EditButton, resourceLoader.GetString("Activation_Shortcut_Title"));
@@ -276,6 +295,16 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
OnAllowDisableChanged(this, null);
}
private void C_LearnMoreClick(object sender, RoutedEventArgs e)
{
// Close the current shortcut dialog
shortcutDialog.Hide();
// Create and show the ShortcutConflictWindow
var conflictWindow = new ShortcutConflictWindow();
conflictWindow.Activate();
}
private void UpdateKeyVisualStyles()
{
if (PreviewKeysControl?.ItemsSource != null)
@@ -305,6 +334,8 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
shortcutDialog.Opened -= ShortcutDialog_Opened;
shortcutDialog.Closing -= ShortcutDialog_Closing;
c.LearnMoreClick -= C_LearnMoreClick;
if (App.GetSettingsWindow() != null)
{
App.GetSettingsWindow().Activated -= ShortcutDialog_SettingsWindow_Activated;
@@ -510,6 +541,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
else
{
EnableKeys();
if (lastValidSettings.IsValid())
{
if (string.Equals(lastValidSettings.ToString(), hotkeySettings.ToString(), StringComparison.OrdinalIgnoreCase))
@@ -578,16 +610,12 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
shortcutDialog.IsPrimaryButtonEnabled = true;
c.IsError = false;
// WarningLabel.Style = (Style)App.Current.Resources["SecondaryTextStyle"];
}
private void DisableKeys()
{
shortcutDialog.IsPrimaryButtonEnabled = false;
c.IsError = true;
// WarningLabel.Style = (Style)App.Current.Resources["SecondaryWarningTextStyle"];
}
private void Hotkey_KeyUp(int key)
@@ -648,6 +676,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
c.Keys = null;
c.Keys = HotkeySettings.GetKeysList();
c.IgnoreConflict = IgnoreConflict;
c.HasConflict = hotkeySettings.HasConflict;
c.ConflictMessage = hotkeySettings.ConflictDescription;
@@ -660,7 +689,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
await shortcutDialog.ShowAsync();
}
private void ShortcutDialog_Reset(ContentDialog sender, ContentDialogButtonClickEventArgs args)
private void C_ResetClick(object sender, RoutedEventArgs e)
{
hotkeySettings = null;
@@ -674,6 +703,20 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
GlobalHotkeyConflictManager.Instance?.RequestAllConflicts();
}
private void C_ClearClick(object sender, RoutedEventArgs e)
{
hotkeySettings = new HotkeySettings();
SetValue(HotkeySettingsProperty, hotkeySettings);
SetKeys();
lastValidSettings = hotkeySettings;
shortcutDialog.Hide();
// Send RequestAllConflicts IPC to update the UI after changed hotkey settings.
GlobalHotkeyConflictManager.Instance?.RequestAllConflicts();
}
private void ShortcutDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (ComboIsValid(lastValidSettings))
@@ -728,7 +771,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
args.Handled = true;
if (args.WindowActivationState != WindowActivationState.Deactivated && (hook == null || hook.GetDisposedState() == true))
{
// If the PT settings window gets focussed/activated again, we enable the keyboard hook to catch the keyboard input.
// If the PT settings window gets focused/activated again, we enable the keyboard hook to catch the keyboard input.
hook = new HotkeySettingsControlHook(Hotkey_KeyDown, Hotkey_KeyUp, Hotkey_IsActive, FilterAccessibleKeyboardEvents);
}
else if (args.WindowActivationState == WindowActivationState.Deactivated && hook != null && hook.GetDisposedState() == false)
@@ -742,6 +785,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
private void ShortcutDialog_Closing(ContentDialog sender, ContentDialogClosingEventArgs args)
{
_isActive = false;
lastValidSettings = hotkeySettings;
}
private void Dispose(bool disposing)

View File

@@ -3,78 +3,332 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:tk7controls="using:CommunityToolkit.WinUI.Controls"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
x:Name="ShortcutContentControl"
mc:Ignorable="d">
<Grid MinWidth="498" MinHeight="220">
<UserControl.Resources>
<converters:BoolToKeyVisualStateConverter x:Key="BoolToKeyVisualStateConverter" />
<Style x:Key="CondensedInfoBarStyle" TargetType="InfoBar">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{ThemeResource InfoBarBorderBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource InfoBarBorderThickness}" />
<Setter Property="AutomationProperties.LandmarkType" Value="Custom" />
<Setter Property="AutomationProperties.IsDialog" Value="True" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="InfoBar">
<Border
x:Name="ContentRoot"
VerticalAlignment="Top"
Background="{ThemeResource InfoBarInformationalSeverityBackgroundBrush}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<!-- Background is used here so that it overrides the severity status color if set. -->
<Grid
MinHeight="0"
Padding="8"
HorizontalAlignment="Stretch"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<!-- Icon -->
<ColumnDefinition Width="*" />
<!-- Title, message, and action -->
<ColumnDefinition Width="Auto" />
<!-- Close button -->
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid
x:Name="StandardIconArea"
Margin="0,0,8,0"
Visibility="Collapsed">
<TextBlock
x:Name="IconBackground"
Grid.Column="0"
VerticalAlignment="Top"
AutomationProperties.AccessibilityView="Raw"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="{StaticResource InfoBarIconFontSize}"
Foreground="{ThemeResource InfoBarInformationalSeverityIconBackground}"
Text="{StaticResource InfoBarIconBackgroundGlyph}" />
<TextBlock
x:Name="StandardIcon"
Grid.Column="0"
VerticalAlignment="Top"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="{StaticResource InfoBarIconFontSize}"
Foreground="{ThemeResource InfoBarInformationalSeverityIconForeground}"
Text="{StaticResource InfoBarInformationalIconGlyph}" />
</Grid>
<Viewbox
x:Name="UserIconBox"
Grid.Column="0"
MaxWidth="{ThemeResource InfoBarIconFontSize}"
MaxHeight="{ThemeResource InfoBarIconFontSize}"
Margin="0"
VerticalAlignment="Top"
Child="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.IconElement}"
Visibility="Collapsed" />
<InfoBarPanel
Grid.Column="1"
Margin="0,0,0,0"
HorizontalOrientationPadding="0"
VerticalOrientationPadding="0">
<TextBlock
x:Name="Title"
Margin="0,-1,0,0"
FontSize="{StaticResource InfoBarTitleFontSize}"
FontWeight="{StaticResource InfoBarTitleFontWeight}"
Foreground="{ThemeResource InfoBarTitleForeground}"
InfoBarPanel.HorizontalOrientationMargin="0,0,8,0"
InfoBarPanel.VerticalOrientationMargin="0,8,0,0"
Text="{TemplateBinding Title}"
TextWrapping="WrapWholeWords" />
<TextBlock
x:Name="Message"
Margin="0,-1,0,0"
FontSize="{StaticResource InfoBarMessageFontSize}"
FontWeight="{StaticResource InfoBarMessageFontWeight}"
Foreground="{ThemeResource InfoBarMessageForeground}"
InfoBarPanel.HorizontalOrientationMargin="0"
InfoBarPanel.VerticalOrientationMargin="0"
Text="{TemplateBinding Message}"
TextWrapping="WrapWholeWords" />
<ContentPresenter
Content="{TemplateBinding ActionButton}"
InfoBarPanel.HorizontalOrientationMargin="16,-2,0,0"
InfoBarPanel.VerticalOrientationMargin="0,8,0,0" />
</InfoBarPanel>
<ContentPresenter
x:Name="ContentArea"
Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SeverityLevels">
<VisualState x:Name="Informational" />
<VisualState x:Name="Error">
<VisualState.Setters>
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarErrorSeverityBackgroundBrush}" />
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarErrorSeverityIconBackground}" />
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarErrorIconGlyph}" />
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarErrorSeverityIconForeground}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Warning">
<VisualState.Setters>
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarWarningSeverityBackgroundBrush}" />
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarWarningSeverityIconBackground}" />
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarWarningIconGlyph}" />
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarWarningSeverityIconForeground}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Success">
<VisualState.Setters>
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarSuccessSeverityBackgroundBrush}" />
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarSuccessSeverityIconBackground}" />
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarSuccessIconGlyph}" />
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarSuccessSeverityIconForeground}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="IconStates">
<VisualState x:Name="StandardIconVisible">
<VisualState.Setters>
<Setter Target="UserIconBox.Visibility" Value="Collapsed" />
<Setter Target="StandardIconArea.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="UserIconVisible">
<VisualState.Setters>
<Setter Target="UserIconBox.Visibility" Value="Visible" />
<Setter Target="StandardIconArea.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NoIconVisible" />
</VisualStateGroup>
<VisualStateGroup>
<VisualState x:Name="CloseButtonVisible" />
<VisualState x:Name="CloseButtonCollapsed" />
</VisualStateGroup>
<VisualStateGroup x:Name="InfoBarVisibility">
<VisualState x:Name="InfoBarVisible" />
<VisualState x:Name="InfoBarCollapsed">
<VisualState.Setters>
<Setter Target="ContentRoot.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup>
<VisualState x:Name="ForegroundNotSet" />
<VisualState x:Name="ForegroundSet">
<VisualState.Setters>
<Setter Target="Title.Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}" />
<Setter Target="Message.Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup>
<VisualState x:Name="BannerContent" />
<VisualState x:Name="NoBannerContent">
<VisualState.Setters>
<Setter Target="ContentArea.(Grid.Row)" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid
MinWidth="498"
MinHeight="220"
RowSpacing="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition MinHeight="110" />
<RowDefinition Height="Auto" MinHeight="104" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl
x:Name="KeysControl"
<tk7controls:MarkdownTextBlock x:Uid="InvalidShortcutWarningLabel" Background="Transparent" />
<Grid
Grid.Row="1"
Height="56"
Margin="0,64,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Center"
ItemsSource="{x:Bind Keys, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
Padding="20,16"
AutomationProperties.AccessibilityView="Raw"
Content="{Binding}"
CornerRadius="{StaticResource ControlCornerRadius}"
FontSize="16"
FontWeight="SemiBold"
IsInvalid="{Binding ElementName=ShortcutContentControl, Path=IsError, Mode=OneWay}"
IsTabStop="False"
Style="{StaticResource AccentKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Margin="0,16,0,0"
Background="{ThemeResource SolidBackgroundFillColorTertiaryBrush}"
CornerRadius="{StaticResource OverlayCornerRadius}">
<ItemsControl
x:Name="KeysControl"
Height="56"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
ItemsSource="{x:Bind Keys, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
Padding="20,16"
AutomationProperties.AccessibilityView="Raw"
Content="{Binding}"
CornerRadius="{StaticResource ControlCornerRadius}"
FontSize="16"
FontWeight="SemiBold"
IsTabStop="False"
State="{Binding ElementName=ShortcutContentControl, Path=IsError, Mode=OneWay, Converter={StaticResource BoolToKeyVisualStateConverter}, ConverterParameter=Error}"
Style="{StaticResource AccentKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextAlignment="Center"
Visibility="{x:Bind Keys.Count, Mode=OneWay, Converter={StaticResource DoubleToInvertedVisibilityConverter}}" />
</Grid>
<StackPanel
Grid.Row="2"
Margin="0,24,0,0"
VerticalAlignment="Top"
Orientation="Vertical"
Spacing="8">
<Grid Height="62">
<InfoBar
x:Uid="InvalidShortcut"
IsClosable="False"
IsOpen="{Binding ElementName=ShortcutContentControl, Path=IsError, Mode=OneWay}"
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=IsError, Mode=OneWay}"
Severity="Error" />
<InfoBar
x:Uid="WarningShortcutAltGr"
IsClosable="False"
IsOpen="{Binding ElementName=ShortcutContentControl, Path=IsWarningAltGr, Mode=OneWay}"
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=IsWarningAltGr, Mode=OneWay}"
Severity="Warning" />
<InfoBar
x:Uid="WarningShortcutConflict"
IsClosable="False"
IsOpen="{Binding ElementName=ShortcutContentControl, Path=HasConflict, Mode=OneWay}"
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=HasConflict, Mode=OneWay}"
Message="{Binding ElementName=ShortcutContentControl, Path=ConflictMessage, Mode=OneWay}"
Severity="Warning" />
</Grid>
<tkcontrols:MarkdownTextBlock
x:Uid="InvalidShortcutWarningLabel"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="12">
<HyperlinkButton
x:Name="ResetBtn"
x:Uid="Shortcut_ResetBtn"
Click="ResetBtn_Click">
<ToolTipService.ToolTip>
<TextBlock x:Uid="Shortcut_ResetToolTip" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="10" Glyph="&#xE777;" />
<TextBlock
x:Uid="Shortcut_Reset"
Margin="0,-1,0,0"
VerticalAlignment="Center"
FontSize="12" />
</StackPanel>
</HyperlinkButton>
<HyperlinkButton
x:Name="ClearBtn"
x:Uid="Shortcut_ClearBtn"
Click="ClearBtn_Click"
Visibility="{x:Bind Keys.Count, Mode=OneWay, Converter={StaticResource DoubleToVisibilityConverter}}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="Shortcut_ClearToolTip" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="12" Glyph="&#xE894;" />
<TextBlock
x:Uid="Shortcut_Clear"
Margin="0,-1,0,0"
VerticalAlignment="Center"
FontSize="12" />
</StackPanel>
</HyperlinkButton>
</StackPanel>
<Grid Grid.Row="3" Margin="0,12,0,0">
<InfoBar
x:Uid="InvalidShortcut"
BorderThickness="0"
IsClosable="False"
IsOpen="{Binding ElementName=ShortcutContentControl, Path=IsError, Mode=OneWay}"
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=IsError, Mode=OneWay}"
Severity="Error"
Style="{StaticResource CondensedInfoBarStyle}" />
<InfoBar
x:Uid="WarningShortcutAltGr"
BorderThickness="0"
IsClosable="False"
IsOpen="{Binding ElementName=ShortcutContentControl, Path=IsWarningAltGr, Mode=OneWay}"
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=IsWarningAltGr, Mode=OneWay}"
Severity="Warning"
Style="{StaticResource CondensedInfoBarStyle}" />
<InfoBar
BorderThickness="0"
IsClosable="False"
IsOpen="{Binding ElementName=ShortcutContentControl, Path=ShouldShowConflict, Mode=OneWay}"
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=ShouldShowConflict, Mode=OneWay}"
Message="{Binding ElementName=ShortcutContentControl, Path=ConflictMessage, Mode=OneWay}"
Severity="Warning"
Style="{StaticResource CondensedInfoBarStyle}" />
<InfoBar
x:Uid="WarningPotentialShortcutConflict"
BorderThickness="0"
IsClosable="False"
IsOpen="{Binding ElementName=ShortcutContentControl, Path=ShouldShowPotentialConflict, Mode=OneWay}"
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=ShouldShowPotentialConflict, Mode=OneWay}"
Message="{Binding ElementName=ShortcutContentControl, Path=ConflictMessage, Mode=OneWay}"
Severity="Warning"
Style="{StaticResource CondensedInfoBarStyle}">
<InfoBar.ActionButton>
<HyperlinkButton
x:Uid="Shortcut_Conflict_LearnMore"
Padding="0"
Click="LearnMoreBtn_Click" />
</InfoBar.ActionButton>
</InfoBar>
</Grid>
</Grid>
</UserControl>

View File

@@ -2,8 +2,10 @@
// 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.Generic;
using System.Diagnostics.Eventing.Reader;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -14,8 +16,22 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
public static readonly DependencyProperty KeysProperty = DependencyProperty.Register("Keys", typeof(List<object>), typeof(ShortcutDialogContentControl), new PropertyMetadata(default(string)));
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register("IsError", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false));
public static readonly DependencyProperty IsWarningAltGrProperty = DependencyProperty.Register("IsWarningAltGr", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false));
public static readonly DependencyProperty HasConflictProperty = DependencyProperty.Register("HasConflict", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false));
public static readonly DependencyProperty HasConflictProperty = DependencyProperty.Register("HasConflict", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false, OnConflictPropertyChanged));
public static readonly DependencyProperty ConflictMessageProperty = DependencyProperty.Register("ConflictMessage", typeof(string), typeof(ShortcutDialogContentControl), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty IgnoreConflictProperty = DependencyProperty.Register("IgnoreConflict", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false, OnIgnoreConflictChanged));
public static readonly DependencyProperty ShouldShowConflictProperty = DependencyProperty.Register("ShouldShowConflict", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false));
public static readonly DependencyProperty ShouldShowPotentialConflictProperty = DependencyProperty.Register("ShouldShowPotentialConflict", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false));
public event EventHandler<bool> IgnoreConflictChanged;
public event RoutedEventHandler LearnMoreClick;
public bool IgnoreConflict
{
get => (bool)GetValue(IgnoreConflictProperty);
set => SetValue(IgnoreConflictProperty, value);
}
public bool HasConflict
{
@@ -29,9 +45,22 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set => SetValue(ConflictMessageProperty, value);
}
public bool ShouldShowConflict
{
get => (bool)GetValue(ShouldShowConflictProperty);
private set => SetValue(ShouldShowConflictProperty, value);
}
public bool ShouldShowPotentialConflict
{
get => (bool)GetValue(ShouldShowPotentialConflictProperty);
private set => SetValue(ShouldShowPotentialConflictProperty, value);
}
public ShortcutDialogContentControl()
{
this.InitializeComponent();
UpdateShouldShowConflict();
}
public List<object> Keys
@@ -51,5 +80,54 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
get => (bool)GetValue(IsWarningAltGrProperty);
set => SetValue(IsWarningAltGrProperty, value);
}
public event RoutedEventHandler ResetClick;
public event RoutedEventHandler ClearClick;
private static void OnIgnoreConflictChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as ShortcutDialogContentControl;
if (control == null)
{
return;
}
control.UpdateShouldShowConflict();
control.IgnoreConflictChanged?.Invoke(control, (bool)e.NewValue);
}
private static void OnConflictPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as ShortcutDialogContentControl;
if (control == null)
{
return;
}
control.UpdateShouldShowConflict();
}
private void UpdateShouldShowConflict()
{
ShouldShowConflict = !IgnoreConflict && HasConflict;
ShouldShowPotentialConflict = IgnoreConflict && HasConflict;
}
private void ResetBtn_Click(object sender, RoutedEventArgs e)
{
ResetClick?.Invoke(this, new RoutedEventArgs());
}
private void ClearBtn_Click(object sender, RoutedEventArgs e)
{
ClearClick?.Invoke(this, new RoutedEventArgs());
}
private void LearnMoreBtn_Click(object sender, RoutedEventArgs e)
{
LearnMoreClick?.Invoke(this, new RoutedEventArgs());
}
}
}

View File

@@ -14,7 +14,6 @@ using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
@@ -29,6 +28,8 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData();
private Windows.ApplicationModel.Resources.ResourceLoader resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
private int _conflictCount;
public bool EnableDataDiagnostics
{
get
@@ -60,6 +61,9 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
if (_allHotkeyConflictsData != value)
{
_allHotkeyConflictsData = value;
UpdateConflictCount();
OnPropertyChanged(nameof(AllHotkeyConflictsData));
OnPropertyChanged(nameof(ConflictCount));
OnPropertyChanged(nameof(ConflictText));
@@ -71,28 +75,43 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
}
}
public int ConflictCount
public int ConflictCount => _conflictCount;
private void UpdateConflictCount()
{
get
int count = 0;
if (AllHotkeyConflictsData == null)
{
if (AllHotkeyConflictsData == null)
{
return 0;
}
int count = 0;
if (AllHotkeyConflictsData.InAppConflicts != null)
{
count += AllHotkeyConflictsData.InAppConflicts.Count;
}
if (AllHotkeyConflictsData.SystemConflicts != null)
{
count += AllHotkeyConflictsData.SystemConflicts.Count;
}
return count;
_conflictCount = count;
}
if (AllHotkeyConflictsData.InAppConflicts != null)
{
foreach (var inAppConflict in AllHotkeyConflictsData.InAppConflicts)
{
var hotkey = inAppConflict.Hotkey;
var hotkeySettings = new HotkeySettings(hotkey.Win, hotkey.Ctrl, hotkey.Alt, hotkey.Shift, hotkey.Key);
if (!HotkeyConflictIgnoreHelper.IsIgnoringConflicts(hotkeySettings))
{
count++;
}
}
}
if (AllHotkeyConflictsData.SystemConflicts != null)
{
foreach (var systemConflict in AllHotkeyConflictsData.SystemConflicts)
{
var hotkey = systemConflict.Hotkey;
var hotkeySettings = new HotkeySettings(hotkey.Win, hotkey.Ctrl, hotkey.Alt, hotkey.Shift, hotkey.Key);
if (!HotkeyConflictIgnoreHelper.IsIgnoringConflicts(hotkeySettings))
{
count++;
}
}
}
_conflictCount = count;
}
public string ConflictText

View File

@@ -16,7 +16,9 @@ using CommunityToolkit.WinUI.Controls;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.SerializationContext;
@@ -39,6 +41,8 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
public bool ShowDataDiagnosticsInfoBar => GetShowDataDiagnosticsInfoBar();
private int _conflictCount;
public AllHotkeyConflictsData AllHotkeyConflictsData
{
get => _allHotkeyConflictsData;
@@ -47,34 +51,48 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
if (_allHotkeyConflictsData != value)
{
_allHotkeyConflictsData = value;
UpdateConflictCount();
OnPropertyChanged(nameof(AllHotkeyConflictsData));
OnPropertyChanged(nameof(HasConflicts));
}
}
}
public bool HasConflicts
public bool HasConflicts => _conflictCount > 0;
private void UpdateConflictCount()
{
get
int count = 0;
if (AllHotkeyConflictsData == null)
{
if (AllHotkeyConflictsData == null)
{
return false;
}
int count = 0;
if (AllHotkeyConflictsData.InAppConflicts != null)
{
count += AllHotkeyConflictsData.InAppConflicts.Count;
}
if (AllHotkeyConflictsData.SystemConflicts != null)
{
count += AllHotkeyConflictsData.SystemConflicts.Count;
}
return count > 0;
_conflictCount = count;
}
if (AllHotkeyConflictsData.InAppConflicts != null)
{
foreach (var inAppConflict in AllHotkeyConflictsData.InAppConflicts)
{
if (!inAppConflict.ConflictIgnored)
{
count++;
}
}
}
if (AllHotkeyConflictsData.SystemConflicts != null)
{
foreach (var systemConflict in AllHotkeyConflictsData.SystemConflicts)
{
if (!systemConflict.ConflictIgnored)
{
count++;
}
}
}
_conflictCount = count;
}
public event PropertyChangedEventHandler PropertyChanged;
@@ -100,6 +118,21 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
this.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
{
var allConflictData = e.Conflicts;
foreach (var inAppConflict in allConflictData.InAppConflicts)
{
var hotkey = inAppConflict.Hotkey;
var hotkeySetting = new HotkeySettings(hotkey.Win, hotkey.Ctrl, hotkey.Alt, hotkey.Shift, hotkey.Key);
inAppConflict.ConflictIgnored = HotkeyConflictIgnoreHelper.IsIgnoringConflicts(hotkeySetting);
}
foreach (var systemConflict in allConflictData.SystemConflicts)
{
var hotkey = systemConflict.Hotkey;
var hotkeySetting = new HotkeySettings(hotkey.Win, hotkey.Ctrl, hotkey.Alt, hotkey.Shift, hotkey.Key);
systemConflict.ConflictIgnored = HotkeyConflictIgnoreHelper.IsIgnoringConflicts(hotkeySetting);
}
AllHotkeyConflictsData = e.Conflicts ?? new AllHotkeyConflictsData();
});
}

View File

@@ -2667,23 +2667,20 @@ From there, simply click on one of the supported files in the File Explorer and
<value>Press a combination of keys to change this shortcut.
Right-click to remove the key combination, thereby deactivating the shortcut.</value>
</data>
<data name="Activation_Shortcut_Reset" xml:space="preserve">
<value>Reset</value>
</data>
<data name="Activation_Shortcut_Save" xml:space="preserve">
<value>Save</value>
</data>
<data name="Activation_Shortcut_Title" xml:space="preserve">
<value>Activation shortcut</value>
</data>
<data name="InvalidShortcut.Title" xml:space="preserve">
<data name="InvalidShortcut.Message" xml:space="preserve">
<value>Invalid shortcut</value>
</data>
<data name="InvalidShortcutWarningLabel.Text" xml:space="preserve">
<value>Only shortcuts that start with **Windows key**, **Ctrl**, **Alt** or **Shift** are valid.</value>
<value>A shortcut should start with **Windows key**, **Ctrl**, **Alt** or **Shift**.</value>
<comment>The ** sequences are used for text formatting of the key names. Don't remove them on translation.</comment>
</data>
<data name="WarningShortcutAltGr.Title" xml:space="preserve">
<data name="WarningShortcutAltGr.Message" xml:space="preserve">
<value>Possible shortcut interference with Alt Gr</value>
<comment>Alt Gr refers to the right alt key on some international keyboards</comment>
</data>
@@ -2691,8 +2688,8 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<value>Shortcuts with **Ctrl** and **Alt** may remove functionality from some international keyboards, because **Ctrl** + **Alt** = **Alt Gr** in those keyboards.</value>
<comment>The ** sequences are used for text formatting of the key names. Don't remove them on translation.</comment>
</data>
<data name="WarningShortcutConflict.Title" xml:space="preserve">
<value>Shortcut conflict</value>
<data name="WarningPotentialShortcutConflict.Message" xml:space="preserve">
<value>This shortcut has a potential conflict, but the warning is ignored.</value>
</data>
<data name="WarningShortcutConflict.ToolTipService.ToolTip" xml:space="preserve">
<value>A conflict has been detected for this shortcut.</value>
@@ -5256,23 +5253,23 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="ShortcutConflictWindow_Title" xml:space="preserve">
<value>PowerToys shortcut conflicts</value>
</data>
<data name="ShortcutConflictWindow_TitleTxt.Text" xml:space="preserve">
<data name="ShortcutConflictWindow_TitleTxt.Title" xml:space="preserve">
<value>PowerToys shortcut conflicts</value>
</data>
<data name="ShortcutConflictWindow_Description.Text" xml:space="preserve">
<value>Conflicting shortcuts may cause unexpected behavior. Edit them here or go to the module settings to update them.</value>
<value>If any shortcut conflicts are detected, theyll appear below. Conflicts can happen between PowerToys utilities or Windows system shortcuts, and may cause unexpected behavior. If everything works as expected, you can safely ignore the conflict.</value>
</data>
<data name="ShortcutConflictWindow_ModulesUsingShortcut.Text" xml:space="preserve">
<value>Conflicts found for</value>
</data>
<data name="ShortcutConflictWindow_SystemCard.Header" xml:space="preserve">
<value>System</value>
<value>System shortcut</value>
</data>
<data name="ShortcutConflictWindow_SystemCard.Description" xml:space="preserve">
<value>Windows system shortcut</value>
<value>This shortcut is reserved by Windows and can't be reassigned.</value>
</data>
<data name="ShortcutConflictWindow_SystemShortcutMessage.Text" xml:space="preserve">
<value>This shortcut can't be changed.</value>
<data name="ShortcutConflictWindow_SystemShortcutLink.Content" xml:space="preserve">
<value>See all Windows shortcuts</value>
</data>
<data name="ShortcutConflictWindow_SystemShortcutTooltip.Content" xml:space="preserve">
<value>This shortcut is used by Windows and can't be changed.</value>
@@ -5312,4 +5309,31 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="UtilitiesHeader.Title" xml:space="preserve">
<value>Utilities</value>
</data>
<data name="DismissConflictBtn.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Dismiss</value>
</data>
<data name="DismissText.Text" xml:space="preserve">
<value>Dismiss</value>
</data>
<data name="Shortcut_ResetBtn.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Reset shortcut</value>
</data>
<data name="Shortcut_ResetToolTip.Text" xml:space="preserve">
<value>Reset to the default shortcut</value>
</data>
<data name="Shortcut_Reset.Text" xml:space="preserve">
<value>Reset</value>
</data>
<data name="Shortcut_ClearBtn.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Clear shortcut</value>
</data>
<data name="Shortcut_ClearToolTip.Text" xml:space="preserve">
<value>Clear and unassign this shortcut</value>
</data>
<data name="Shortcut_Clear.Text" xml:space="preserve">
<value>Clear</value>
</data>
<data name="Shortcut_Conflict_LearnMore.Content" xml:space="preserve">
<value>Learn more</value>
</data>
</root>

View File

@@ -128,14 +128,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
if (value != _hotkey)
{
if (value == null || value.IsEmpty())
{
_hotkey = AlwaysOnTopProperties.DefaultHotkeyValue;
}
else
{
_hotkey = value;
}
_hotkey = value ?? AlwaysOnTopProperties.DefaultHotkeyValue;
Settings.Properties.Hotkey.Value = _hotkey;
NotifyPropertyChanged();

View File

@@ -29,7 +29,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
protected override string ModuleName => "Dashboard";
private const string JsonFileType = ".json";
private Dispatcher dispatcher;
public Func<string, int> SendConfigMSG { get; }
@@ -88,6 +87,21 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
dispatcher.BeginInvoke(() =>
{
var allConflictData = e.Conflicts;
foreach (var inAppConflict in allConflictData.InAppConflicts)
{
var hotkey = inAppConflict.Hotkey;
var hotkeySetting = new HotkeySettings(hotkey.Win, hotkey.Ctrl, hotkey.Alt, hotkey.Shift, hotkey.Key);
inAppConflict.ConflictIgnored = HotkeyConflictIgnoreHelper.IsIgnoringConflicts(hotkeySetting);
}
foreach (var systemConflict in allConflictData.SystemConflicts)
{
var hotkey = systemConflict.Hotkey;
var hotkeySetting = new HotkeySettings(hotkey.Win, hotkey.Ctrl, hotkey.Alt, hotkey.Shift, hotkey.Key);
systemConflict.ConflictIgnored = HotkeyConflictIgnoreHelper.IsIgnoringConflicts(hotkeySetting);
}
AllHotkeyConflictsData = e.Conflicts ?? new AllHotkeyConflictsData();
});
}

View File

@@ -776,7 +776,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
if (value != _editorHotkey)
{
if (value == null || value.IsEmpty())
if (value == null)
{
_editorHotkey = FZConfigProperties.DefaultEditorHotkeyValue;
}
@@ -822,7 +822,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
if (value != _nextTabHotkey)
{
if (value == null || value.IsEmpty())
if (value == null)
{
_nextTabHotkey = FZConfigProperties.DefaultNextTabHotkeyValue;
}
@@ -848,7 +848,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
if (value != _prevTabHotkey)
{
if (value == null || value.IsEmpty())
if (value == null)
{
_prevTabHotkey = FZConfigProperties.DefaultPrevTabHotkeyValue;
}

View File

@@ -12,12 +12,10 @@ using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Windows;
using System.Windows.Threading;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.SerializationContext;
@@ -70,6 +68,36 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
protected override string ModuleName => "ShortcutConflictsWindow";
/// <summary>
/// Ignore a specific HotkeySettings
/// </summary>
/// <param name="hotkeySettings">The HotkeySettings to ignore</param>
public void IgnoreShortcut(HotkeySettings hotkeySettings)
{
if (hotkeySettings == null)
{
return;
}
HotkeyConflictIgnoreHelper.AddToIgnoredList(hotkeySettings);
GlobalHotkeyConflictManager.Instance?.RequestAllConflicts();
}
/// <summary>
/// Remove a HotkeySettings from the ignored list
/// </summary>
/// <param name="hotkeySettings">The HotkeySettings to unignore</param>
public void UnignoreShortcut(HotkeySettings hotkeySettings)
{
if (hotkeySettings == null)
{
return;
}
HotkeyConflictIgnoreHelper.RemoveFromIgnoredList(hotkeySettings);
GlobalHotkeyConflictManager.Instance?.RequestAllConflicts();
}
private IHotkeyConfig GetModuleSettings(string moduleKey)
{
try
@@ -120,20 +148,24 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
foreach (var conflict in conflicts)
{
ProcessConflictGroup(conflict, isSystemConflict);
HotkeySettings hotkey = new(conflict.Hotkey.Win, conflict.Hotkey.Ctrl, conflict.Hotkey.Alt, conflict.Hotkey.Shift, conflict.Hotkey.Key);
var isIgnored = HotkeyConflictIgnoreHelper.IsIgnoringConflicts(hotkey);
conflict.ConflictIgnored = isIgnored;
ProcessConflictGroup(conflict, isSystemConflict, isIgnored);
items.Add(conflict);
}
}
private void ProcessConflictGroup(HotkeyConflictGroupData conflict, bool isSystemConflict)
private void ProcessConflictGroup(HotkeyConflictGroupData conflict, bool isSystemConflict, bool isIgnored)
{
foreach (var module in conflict.Modules)
{
SetupModuleData(module, isSystemConflict);
SetupModuleData(module, isSystemConflict, isIgnored);
}
}
private void SetupModuleData(ModuleHotkeyData module, bool isSystemConflict)
private void SetupModuleData(ModuleHotkeyData module, bool isSystemConflict, bool isIgnored)
{
try
{
@@ -220,55 +252,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
private void SaveModuleSettingsAndNotify(string moduleName)
{
try
{
var settings = GetModuleSettings(moduleName);
if (settings is ISettingsConfig settingsConfig)
{
// No need to save settings here, the runner will call module interface to save it
// SaveSettingsToFile(settings);
// Send IPC notification using the same format as other ViewModels
SendConfigMSG(settingsConfig, moduleName);
System.Diagnostics.Debug.WriteLine($"Saved settings and sent IPC notification for module: {moduleName}");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error saving settings and notifying for {moduleName}: {ex.Message}");
}
}
private void SaveSettingsToFile(IHotkeyConfig settings)
{
try
{
// Get the repository for this settings type using reflection
var settingsType = settings.GetType();
var repositoryMethod = typeof(SettingsFactory).GetMethod("GetRepository");
if (repositoryMethod != null)
{
var genericMethod = repositoryMethod.MakeGenericMethod(settingsType);
var repository = genericMethod.Invoke(_settingsFactory, null);
if (repository != null)
{
var saveMethod = repository.GetType().GetMethod("SaveSettingsToFile");
saveMethod?.Invoke(repository, null);
System.Diagnostics.Debug.WriteLine($"Saved settings to file for type: {settingsType.Name}");
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error saving settings to file: {ex.Message}");
}
}
/// <summary>
/// Sends IPC notification using the same format as other ViewModels
/// </summary>

View File

@@ -127,7 +127,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
if (value != _hotkey)
{
if (value == null || value.IsEmpty())
if (value == null)
{
_hotkey = WorkspacesProperties.DefaultHotkeyValue;
}