diff --git a/src/settings-ui/Settings.UI.Library/HotkeyConflicts/ModuleHotkeyData.cs b/src/settings-ui/Settings.UI.Library/HotkeyConflicts/ModuleHotkeyData.cs
index 2b8de94846..a52ae9d47f 100644
--- a/src/settings-ui/Settings.UI.Library/HotkeyConflicts/ModuleHotkeyData.cs
+++ b/src/settings-ui/Settings.UI.Library/HotkeyConflicts/ModuleHotkeyData.cs
@@ -4,20 +4,75 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts
{
- public class ModuleHotkeyData
+ public class ModuleHotkeyData : INotifyPropertyChanged
{
- public string ModuleName { get; set; }
+ private string _moduleName;
+ private string _hotkeyName;
+ private HotkeySettings _hotkeySettings;
+ private bool _isSystemConflict;
- public string HotkeyName { get; set; }
+ public event PropertyChangedEventHandler PropertyChanged;
- public HotkeySettings HotkeySettings { get; set; }
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
- public bool IsSystemConflict { get; set; }
+ public string ModuleName
+ {
+ get => _moduleName;
+ set
+ {
+ if (_moduleName != value)
+ {
+ _moduleName = value;
+ }
+ }
+ }
+
+ public string HotkeyName
+ {
+ get => _hotkeyName;
+ set
+ {
+ if (_hotkeyName != value)
+ {
+ _hotkeyName = value;
+ }
+ }
+ }
+
+ public HotkeySettings HotkeySettings
+ {
+ get => _hotkeySettings;
+ set
+ {
+ if (_hotkeySettings != value)
+ {
+ _hotkeySettings = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public bool IsSystemConflict
+ {
+ get => _isSystemConflict;
+ set
+ {
+ if (_isSystemConflict != value)
+ {
+ _isSystemConflict = value;
+ }
+ }
+ }
}
}
diff --git a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj
index b880154c44..1c9cc28ff6 100644
--- a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj
+++ b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj
@@ -24,7 +24,6 @@
-
@@ -135,9 +134,6 @@
Always
-
- MSBuild:Compile
-
MSBuild:Compile
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs
index 3b7d6bc5ed..d1c4ea256d 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs
@@ -106,32 +106,18 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
Visibility = HasConflicts ? Visibility.Visible : Visibility.Collapsed;
}
- private async void ShortcutConflictBtn_Click(object sender, RoutedEventArgs e)
+ private void ShortcutConflictBtn_Click(object sender, RoutedEventArgs e)
{
if (AllHotkeyConflictsData == null || !HasConflicts)
{
return;
}
- var contentControl = new ShortcutConflictDialogContentControl
- {
- ConflictsData = AllHotkeyConflictsData,
- };
+ // Create and show the new window instead of dialog
+ var conflictWindow = new ShortcutConflictWindow();
- var conflictDialog = new ContentDialog
- {
- Content = contentControl,
- XamlRoot = this.XamlRoot,
- RequestedTheme = this.ActualTheme,
- };
-
- // Handle navigation request to close dialog
- contentControl.DialogCloseRequested += (s, args) =>
- {
- conflictDialog.Hide();
- };
-
- await conflictDialog.ShowAsync();
+ // Show the window
+ conflictWindow.Activate();
}
}
}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictDialogContentControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictDialogContentControl.xaml
deleted file mode 100644
index cd2cd33288..0000000000
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictDialogContentControl.xaml
+++ /dev/null
@@ -1,198 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictDialogContentControl.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictDialogContentControl.xaml.cs
deleted file mode 100644
index e13ac61ec6..0000000000
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictDialogContentControl.xaml.cs
+++ /dev/null
@@ -1,269 +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.Collections.Generic;
-using System.ComponentModel;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices.WindowsRuntime;
-using CommunityToolkit.WinUI.Controls;
-using Microsoft.PowerToys.Settings.UI.Helpers;
-using Microsoft.PowerToys.Settings.UI.Library;
-using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
-using Microsoft.PowerToys.Settings.UI.Services;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Controls.Primitives;
-using Microsoft.UI.Xaml.Data;
-using Microsoft.UI.Xaml.Input;
-using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Navigation;
-using Windows.Foundation;
-using Windows.Foundation.Collections;
-
-namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard
-{
- public sealed partial class ShortcutConflictDialogContentControl : UserControl, INotifyPropertyChanged
- {
- public static readonly DependencyProperty ConflictsDataProperty =
- DependencyProperty.Register(
- nameof(ConflictsData),
- typeof(AllHotkeyConflictsData),
- typeof(ShortcutConflictDialogContentControl),
- new PropertyMetadata(null, OnConflictsDataChanged));
-
- public AllHotkeyConflictsData ConflictsData
- {
- get => (AllHotkeyConflictsData)GetValue(ConflictsDataProperty);
- set => SetValue(ConflictsDataProperty, value);
- }
-
- public List ConflictItems { get; private set; } = new List();
-
- // Event to close the dialog when navigation occurs
- public event EventHandler DialogCloseRequested;
-
- private static void OnConflictsDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- if (d is ShortcutConflictDialogContentControl content)
- {
- content.UpdateConflictItems();
- }
- }
-
- public ShortcutConflictDialogContentControl()
- {
- InitializeComponent();
- DataContext = this;
- }
-
- private void UpdateConflictItems()
- {
- var items = new List();
-
- if (ConflictsData?.InAppConflicts != null)
- {
- foreach (var conflict in ConflictsData.InAppConflicts)
- {
- // Ensure each module has HotkeySettings for the ShortcutControl
- foreach (var module in conflict.Modules)
- {
- if (module.HotkeySettings == null)
- {
- // Create HotkeySettings from the conflict hotkey data
- module.HotkeySettings = ConvertToHotkeySettings(conflict.Hotkey, module.HotkeyName, module.ModuleName, false);
- }
-
- // Mark as having conflict and set conflict properties
- module.HotkeySettings.HasConflict = true;
- module.HotkeySettings.IsSystemConflict = false;
- module.HotkeySettings.ConflictDescription = GetConflictDescription(conflict, module, false);
- module.IsSystemConflict = false; // In-app conflicts are not system conflicts
- }
- }
-
- items.AddRange(ConflictsData.InAppConflicts);
- }
-
- if (ConflictsData?.SystemConflicts != null)
- {
- foreach (var conflict in ConflictsData.SystemConflicts)
- {
- // Ensure each module has HotkeySettings for the ShortcutControl
- foreach (var module in conflict.Modules)
- {
- if (module.HotkeySettings == null)
- {
- // Create HotkeySettings from the conflict hotkey data
- module.HotkeySettings = ConvertToHotkeySettings(conflict.Hotkey, module.HotkeyName, module.ModuleName, true);
- }
-
- // Mark as having conflict and set conflict properties
- module.HotkeySettings.HasConflict = true;
- module.HotkeySettings.IsSystemConflict = true;
- module.HotkeySettings.ConflictDescription = GetConflictDescription(conflict, module, true);
- module.IsSystemConflict = true; // System conflicts
- }
- }
-
- items.AddRange(ConflictsData.SystemConflicts);
- }
-
- ConflictItems = items;
- OnPropertyChanged(nameof(ConflictItems));
- }
-
- private HotkeySettings ConvertToHotkeySettings(HotkeyData hotkeyData, string hotkeyName, string moduleName, bool isSystemConflict)
- {
- // Convert HotkeyData to HotkeySettings using actual data from hotkeyData
- return new HotkeySettings(
- win: hotkeyData.Win,
- ctrl: hotkeyData.Ctrl,
- alt: hotkeyData.Alt,
- shift: hotkeyData.Shift,
- code: hotkeyData.Key,
- hotkeyName: hotkeyName,
- ownerModuleName: moduleName,
- hasConflict: true) // Always set to true since this is a conflict dialog
- {
- IsSystemConflict = isSystemConflict,
- };
- }
-
- private void SettingsCard_Loaded(object sender, RoutedEventArgs e)
- {
- if (sender is SettingsCard card && card.DataContext is ModuleHotkeyData moduleData)
- {
- var iconPath = GetModuleIconPath(moduleData.ModuleName);
- card.HeaderIcon = new BitmapIcon
- {
- UriSource = new Uri(iconPath),
- ShowAsMonochrome = false,
- };
- }
- }
-
- private string GetModuleIconPath(string moduleName)
- {
- return moduleName?.ToLowerInvariant() switch
- {
- "advancedpaste" => "ms-appx:///Assets/Settings/Icons/AdvancedPaste.png",
- "alwaysontop" => "ms-appx:///Assets/Settings/Icons/AlwaysOnTop.png",
- "awake" => "ms-appx:///Assets/Settings/Icons/Awake.png",
- "cmdpal" => "ms-appx:///Assets/Settings/Icons/CmdPal.png",
- "colorpicker" => "ms-appx:///Assets/Settings/Icons/ColorPicker.png",
- "cropandlock" => "ms-appx:///Assets/Settings/Icons/CropAndLock.png",
- "environmentvariables" => "ms-appx:///Assets/Settings/Icons/EnvironmentVariables.png",
- "fancyzones" => "ms-appx:///Assets/Settings/Icons/FancyZones.png",
- "filelocksmith" => "ms-appx:///Assets/Settings/Icons/FileLocksmith.png",
- "findmymouse" => "ms-appx:///Assets/Settings/Icons/FindMyMouse.png",
- "hosts" => "ms-appx:///Assets/Settings/Icons/Hosts.png",
- "imageresizer" => "ms-appx:///Assets/Settings/Icons/ImageResizer.png",
- "keyboardmanager" => "ms-appx:///Assets/Settings/Icons/KeyboardManager.png",
- "measuretool" => "ms-appx:///Assets/Settings/Icons/ScreenRuler.png",
- "mousehighlighter" => "ms-appx:///Assets/Settings/Icons/MouseHighlighter.png",
- "mousejump" => "ms-appx:///Assets/Settings/Icons/MouseJump.png",
- "mousepointer" => "ms-appx:///Assets/Settings/Icons/MouseCrosshairs.png",
- "mousepointeraccessibility" => "ms-appx:///Assets/Settings/Icons/MouseCrosshairs.png",
- "mousepointercrosshairs" => "ms-appx:///Assets/Settings/Icons/MouseCrosshairs.png",
- "mousewithoutborders" => "ms-appx:///Assets/Settings/Icons/MouseWithoutBorders.png",
- "newplus" => "ms-appx:///Assets/Settings/Icons/NewPlus.png",
- "peek" => "ms-appx:///Assets/Settings/Icons/Peek.png",
- "poweraccent" => "ms-appx:///Assets/Settings/Icons/QuickAccent.png",
- "powerlauncher" => "ms-appx:///Assets/Settings/Icons/PowerToysRun.png",
- "powerocr" => "ms-appx:///Assets/Settings/Icons/TextExtractor.png",
- "powerpreview" => "ms-appx:///Assets/Settings/Icons/PowerPreview.png",
- "powerrename" => "ms-appx:///Assets/Settings/Icons/PowerRename.png",
- "registrypreview" => "ms-appx:///Assets/Settings/Icons/RegistryPreview.png",
- "shortcutguide" => "ms-appx:///Assets/Settings/Icons/ShortcutGuide.png",
- "workspaces" => "ms-appx:///Assets/Settings/Icons/Workspaces.png",
- "zoomit" => "ms-appx:///Assets/Settings/Icons/ZoomIt.png",
- _ => "ms-appx:///Assets/Settings/Icons/PowerToys.png",
- };
- }
-
- private string GetConflictDescription(HotkeyConflictGroupData conflict, ModuleHotkeyData currentModule, bool isSystemConflict)
- {
- if (isSystemConflict)
- {
- return "Conflicts with system shortcut";
- }
-
- // For in-app conflicts, list other conflicting modules
- var otherModules = conflict.Modules
- .Where(m => m.ModuleName != currentModule.ModuleName)
- .Select(m => m.ModuleName)
- .ToList();
-
- if (otherModules.Count == 1)
- {
- return $"Conflicts with {otherModules[0]}";
- }
- else if (otherModules.Count > 1)
- {
- return $"Conflicts with: {string.Join(", ", otherModules)}";
- }
-
- return "Shortcut conflict detected";
- }
-
- private void SettingsCard_Click(object sender, RoutedEventArgs e)
- {
- if (sender is SettingsCard settingsCard && settingsCard.DataContext is ModuleHotkeyData moduleData)
- {
- var moduleName = moduleData.ModuleName;
-
- // Navigate to the module's settings page
- if (ModuleNavigationHelper.NavigateToModulePage(moduleName))
- {
- // Successfully navigated, close the dialog
- DialogCloseRequested?.Invoke(this, EventArgs.Empty);
- }
- else
- {
- // If navigation fails, try to handle special cases
- HandleSpecialModuleNavigation(moduleName);
- }
- }
- }
-
- private void HandleSpecialModuleNavigation(string moduleName)
- {
- // Handle special cases for modules that might have different navigation logic
- switch (moduleName?.ToLowerInvariant())
- {
- case "mouse highlighter":
- case "mouse jump":
- case "mouse pointer crosshairs":
- case "find my mouse":
- // These are all part of MouseUtils
- if (ModuleNavigationHelper.NavigateToModulePage("MouseHighlighter"))
- {
- DialogCloseRequested?.Invoke(this, EventArgs.Empty);
- }
-
- break;
-
- case "system":
- case "windows":
- // System conflicts - cannot navigate to a specific page
- // Show a message or do nothing
- break;
-
- default:
- // Try a fallback navigation or show an error message
- System.Diagnostics.Debug.WriteLine($"Could not navigate to settings page for module: {moduleName}");
- break;
- }
- }
-
- public event PropertyChangedEventHandler PropertyChanged;
-
- private void OnPropertyChanged(string propertyName)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
- }
-}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml
new file mode 100644
index 0000000000..be10b0cac4
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml.cs
new file mode 100644
index 0000000000..aa25f4a735
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml.cs
@@ -0,0 +1,121 @@
+// 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 CommunityToolkit.WinUI.Controls;
+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.ViewModels;
+using Microsoft.PowerToys.Settings.UI.Views;
+using Microsoft.UI.Windowing;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Windows.Graphics;
+
+namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard
+{
+ public sealed partial class ShortcutConflictWindow : Window
+ {
+ public ShortcutConflictViewModel DataContext { get; }
+
+ public ShortcutConflictViewModel ViewModel { get; private set; }
+
+ public ShortcutConflictWindow()
+ {
+ var settingsUtils = new SettingsUtils();
+ ViewModel = new ShortcutConflictViewModel(
+ settingsUtils,
+ SettingsRepository.GetInstance(settingsUtils),
+ ShellPage.SendDefaultIPCMessage);
+
+ DataContext = ViewModel;
+ InitializeComponent();
+
+ // Set window size using AppWindow API
+ this.AppWindow.Resize(new SizeInt32(900, 1200));
+
+ // Set window properties
+ this.AppWindow.SetIcon("Assets/Settings/Icons/PowerToys.ico");
+ this.AppWindow.TitleBar.ExtendsContentIntoTitleBar = true;
+ this.AppWindow.TitleBar.ButtonBackgroundColor = Microsoft.UI.Colors.Transparent;
+ this.AppWindow.TitleBar.ButtonInactiveBackgroundColor = Microsoft.UI.Colors.Transparent;
+
+ // Center the window on screen
+ this.CenterOnScreen();
+
+ ViewModel.OnPageLoaded();
+
+ Closed += (s, e) => ViewModel?.Dispose();
+ }
+
+ private void CenterOnScreen()
+ {
+ var displayArea = DisplayArea.GetFromWindowId(this.AppWindow.Id, DisplayAreaFallback.Nearest);
+ if (displayArea != null)
+ {
+ var windowSize = this.AppWindow.Size;
+ var centeredPosition = new PointInt32
+ {
+ X = (displayArea.WorkArea.Width - windowSize.Width) / 2,
+ Y = (displayArea.WorkArea.Height - windowSize.Height) / 2,
+ };
+ this.AppWindow.Move(centeredPosition);
+ }
+ }
+
+ private void SettingsCard_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is SettingsCard settingsCard &&
+ settingsCard.DataContext is ModuleHotkeyData moduleData)
+ {
+ var moduleName = moduleData.ModuleName;
+
+ // Navigate to the module's settings page
+ if (ModuleNavigationHelper.NavigateToModulePage(moduleName))
+ {
+ this.Close();
+ }
+ }
+ }
+
+ private void SettingsCard_Loaded(object sender, RoutedEventArgs e)
+ {
+ if (sender is SettingsCard card && card.DataContext is ModuleHotkeyData moduleData)
+ {
+ var iconPath = GetModuleIconPath(moduleData.ModuleName);
+ card.HeaderIcon = new BitmapIcon
+ {
+ UriSource = new Uri(iconPath),
+ ShowAsMonochrome = false,
+ };
+ }
+ }
+
+ private string GetModuleIconPath(string moduleName)
+ {
+ return moduleName?.ToLowerInvariant() switch
+ {
+ "advancedpaste" => "ms-appx:///Assets/Settings/Icons/AdvancedPaste.png",
+ "alwaysontop" => "ms-appx:///Assets/Settings/Icons/AlwaysOnTop.png",
+ "colorpicker" => "ms-appx:///Assets/Settings/Icons/ColorPicker.png",
+ "cropandlock" => "ms-appx:///Assets/Settings/Icons/CropAndLock.png",
+ "fancyzones" => "ms-appx:///Assets/Settings/Icons/FancyZones.png",
+ "mousehighlighter" => "ms-appx:///Assets/Settings/Icons/MouseHighlighter.png",
+ "mousepointercrosshairs" => "ms-appx:///Assets/Settings/Icons/MouseCrosshairs.png",
+ "findmymouse" => "ms-appx:///Assets/Settings/Icons/FindMyMouse.png",
+ "mousejump" => "ms-appx:///Assets/Settings/Icons/MouseJump.png",
+ "peek" => "ms-appx:///Assets/Settings/Icons/Peek.png",
+ "powerlauncher" => "ms-appx:///Assets/Settings/Icons/PowerToysRun.png",
+ "measuretool" => "ms-appx:///Assets/Settings/Icons/ScreenRuler.png",
+ "shortcutguide" => "ms-appx:///Assets/Settings/Icons/ShortcutGuide.png",
+ "powerocr" => "ms-appx:///Assets/Settings/Icons/TextExtractor.png",
+ "workspaces" => "ms-appx:///Assets/Settings/Icons/Workspaces.png",
+ "cmdpal" => "ms-appx:///Assets/Settings/Icons/CmdPal.png",
+ _ => "ms-appx:///Assets/Settings/Icons/PowerToys.png",
+ };
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs
index 615bd79dda..2c7c3acebd 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs
@@ -665,7 +665,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
private void SetKeys()
{
- var keys = HotkeySettings.GetKeysList();
+ var keys = HotkeySettings?.GetKeysList();
if (keys != null && keys.Count > 0)
{
diff --git a/src/settings-ui/Settings.UI/ViewModels/ShortcutConflictViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ShortcutConflictViewModel.cs
new file mode 100644
index 0000000000..2499643042
--- /dev/null
+++ b/src/settings-ui/Settings.UI/ViewModels/ShortcutConflictViewModel.cs
@@ -0,0 +1,980 @@
+// 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.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Windows.Threading;
+using Microsoft.PowerToys.Settings.UI.Controls;
+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.Views;
+
+namespace Microsoft.PowerToys.Settings.UI.ViewModels
+{
+ public class ShortcutConflictViewModel : PageViewModelBase, IDisposable
+ {
+ private readonly ISettingsUtils _settingsUtils;
+ private readonly ISettingsRepository _generalSettingsRepository;
+ private readonly Dictionary _moduleViewModels = new();
+ private readonly Dictionary> _viewModelFactories = new();
+ private readonly Dictionary _originalSettings = new();
+
+ private AllHotkeyConflictsData _conflictsData = new();
+ private ObservableCollection _conflictItems = new();
+ private bool _hasModifications;
+ private bool _hasConflicts;
+
+ private Dispatcher dispatcher;
+
+ public ShortcutConflictViewModel(
+ ISettingsUtils settingsUtils,
+ ISettingsRepository settingsRepository,
+ Func ipcMSGCallBackFunc)
+ : base(ipcMSGCallBackFunc)
+ {
+ dispatcher = Dispatcher.CurrentDispatcher;
+ _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
+ _generalSettingsRepository = settingsRepository ?? throw new ArgumentNullException(nameof(settingsRepository));
+
+ SendConfigMSG = ipcMSGCallBackFunc;
+
+ InitializeViewModelFactories();
+ }
+
+ public AllHotkeyConflictsData ConflictsData
+ {
+ get => _conflictsData;
+ set
+ {
+ if (Set(ref _conflictsData, value))
+ {
+ UpdateConflictItems();
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public ObservableCollection ConflictItems
+ {
+ get => _conflictItems;
+ private set => Set(ref _conflictItems, value);
+ }
+
+ public bool HasModifications
+ {
+ get => _hasModifications;
+ private set => Set(ref _hasModifications, value);
+ }
+
+ public bool HasConflicts
+ {
+ get => _hasConflicts;
+ private set => Set(ref _hasConflicts, value);
+ }
+
+ protected override string ModuleName => "ShortcutConflicts";
+
+ private Func SendConfigMSG { get; }
+
+ private void InitializeViewModelFactories()
+ {
+ try
+ {
+ _viewModelFactories["advancedpaste"] = () => new AdvancedPasteViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ _viewModelFactories["alwaysontop"] = () => new AlwaysOnTopViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ _viewModelFactories["colorpicker"] = () => new ColorPickerViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ _viewModelFactories["cropandlock"] = () => new CropAndLockViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ _viewModelFactories["measuretool"] = () => new MeasureToolViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ _viewModelFactories["shortcutguide"] = () => new ShortcutGuideViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ _viewModelFactories["powerocr"] = () => new PowerOcrViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ _viewModelFactories["workspaces"] = () => new WorkspacesViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ _viewModelFactories["peek"] = () => new PeekViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SendConfigMSG,
+ Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread());
+
+ _viewModelFactories["mouseutils"] = () => new MouseUtilsViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SettingsRepository.GetInstance(_settingsUtils),
+ SettingsRepository.GetInstance(_settingsUtils),
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ /*_viewModelFactories["fancyzones"] = () => new FancyZonesViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ _viewModelFactories["powerlauncher"] = () => new PowerLauncherViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SettingsRepository.GetInstance(_settingsUtils),
+ SendConfigMSG);
+
+ _viewModelFactories["cmdpal"] = () => new CmdPalViewModel(
+ _settingsUtils,
+ _generalSettingsRepository,
+ SendConfigMSG);*/
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error initializing ViewModel factories: {ex.Message}");
+ }
+ }
+
+ private PageViewModelBase GetOrCreateViewModel(string moduleKey)
+ {
+ if (!_moduleViewModels.TryGetValue(moduleKey, out var viewModel))
+ {
+ if (_viewModelFactories.TryGetValue(moduleKey, out var factory))
+ {
+ try
+ {
+ viewModel = factory();
+ _moduleViewModels[moduleKey] = viewModel;
+
+ System.Diagnostics.Debug.WriteLine($"Lazy-loaded ViewModel for module: {moduleKey}");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error creating ViewModel for {moduleKey}: {ex.Message}");
+ return null;
+ }
+ }
+ else
+ {
+ System.Diagnostics.Debug.WriteLine($"No factory found for module: {moduleKey}");
+ return null;
+ }
+ }
+
+ return viewModel;
+ }
+
+ protected override void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e)
+ {
+ dispatcher.BeginInvoke(() =>
+ {
+ ConflictsData = e.Conflicts ?? new AllHotkeyConflictsData();
+ });
+ }
+
+ private void UpdateConflictItems()
+ {
+ var items = new ObservableCollection();
+ _originalSettings.Clear();
+
+ if (ConflictsData?.InAppConflicts != null)
+ {
+ foreach (var conflict in ConflictsData.InAppConflicts)
+ {
+ ProcessConflictGroup(conflict, false);
+ items.Add(conflict);
+ }
+ }
+
+ if (ConflictsData?.SystemConflicts != null)
+ {
+ foreach (var conflict in ConflictsData.SystemConflicts)
+ {
+ ProcessConflictGroup(conflict, true);
+ items.Add(conflict);
+ }
+ }
+
+ ConflictItems = items;
+ HasConflicts = items.Count > 0;
+ OnPropertyChanged(nameof(ConflictItems));
+ }
+
+ private void ProcessConflictGroup(HotkeyConflictGroupData conflict, bool isSystemConflict)
+ {
+ foreach (var module in conflict.Modules)
+ {
+ module.PropertyChanged += OnModuleHotkeyDataPropertyChanged;
+
+ module.HotkeySettings = GetHotkeySettingsFromViewModel(module.ModuleName, module.HotkeyName);
+
+ if (module.HotkeySettings != null)
+ {
+ // Store original settings for rollback
+ var key = $"{module.ModuleName}_{module.HotkeyName}";
+ _originalSettings[key] = module.HotkeySettings with { };
+
+ // Set conflict properties
+ module.HotkeySettings.HasConflict = true;
+ module.HotkeySettings.IsSystemConflict = isSystemConflict;
+ module.HotkeySettings.ConflictDescription = GetConflictDescription(conflict, module, isSystemConflict);
+ }
+
+ module.IsSystemConflict = isSystemConflict;
+ }
+ }
+
+ private void OnModuleHotkeyDataPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (sender is ModuleHotkeyData moduleData && e.PropertyName == nameof(ModuleHotkeyData.HotkeySettings))
+ {
+ var key = $"{moduleData.ModuleName}_{moduleData.HotkeyName}";
+
+ UpdateModuleViewModelHotkeySettings(moduleData.ModuleName, moduleData.HotkeyName, moduleData.HotkeySettings);
+ }
+ }
+
+ private void UpdateModuleViewModelHotkeySettings(string moduleName, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ try
+ {
+ var moduleKey = GetModuleKey(moduleName);
+ var viewModel = GetOrCreateViewModel(moduleKey);
+ if (viewModel == null)
+ {
+ System.Diagnostics.Debug.WriteLine($"Failed to get or create ViewModel for {moduleName}");
+ return;
+ }
+
+ switch (moduleKey)
+ {
+ case "advancedpaste":
+ UpdateAdvancedPasteHotkeySettings(viewModel as AdvancedPasteViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "alwaysontop":
+ UpdateAlwaysOnTopHotkeySettings(viewModel as AlwaysOnTopViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "colorpicker":
+ UpdateColorPickerHotkeySettings(viewModel as ColorPickerViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "cropandlock":
+ UpdateCropAndLockHotkeySettings(viewModel as CropAndLockViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "fancyzones":
+ UpdateFancyZonesHotkeySettings(viewModel as FancyZonesViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "measuretool":
+ UpdateMeasureToolHotkeySettings(viewModel as MeasureToolViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "shortcutguide":
+ UpdateShortcutGuideHotkeySettings(viewModel as ShortcutGuideViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "powerocr":
+ UpdatePowerOcrHotkeySettings(viewModel as PowerOcrViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "workspaces":
+ UpdateWorkspacesHotkeySettings(viewModel as WorkspacesViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "peek":
+ UpdatePeekHotkeySettings(viewModel as PeekViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "powerlauncher":
+ UpdatePowerLauncherHotkeySettings(viewModel as PowerLauncherViewModel, hotkeyName, newHotkeySettings);
+ break;
+ case "mouseutils":
+ UpdateMouseUtilsHotkeySettings(viewModel as MouseUtilsViewModel, moduleName, hotkeyName, newHotkeySettings);
+ break;
+ default:
+ System.Diagnostics.Debug.WriteLine($"Unknown module key: {moduleKey}");
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error updating hotkey settings for {moduleName}.{hotkeyName}: {ex.Message}");
+ }
+ }
+
+ // Update methods for each module
+ private void UpdateAdvancedPasteHotkeySettings(AdvancedPasteViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ switch (hotkeyName?.ToLowerInvariant())
+ {
+ case "advancedpasteui" or "advancedpasteuishortcut" or "activation_shortcut":
+ if (!AreHotkeySettingsEqual(viewModel.AdvancedPasteUIShortcut, newHotkeySettings))
+ {
+ viewModel.AdvancedPasteUIShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste AdvancedPasteUIShortcut");
+ }
+
+ break;
+
+ case "pasteasplaintext" or "pasteasplaintextshortcut":
+ if (!AreHotkeySettingsEqual(viewModel.PasteAsPlainTextShortcut, newHotkeySettings))
+ {
+ viewModel.PasteAsPlainTextShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste PasteAsPlainTextShortcut");
+ }
+
+ break;
+
+ case "pasteasmarkdown" or "pasteasmarkdownshortcut":
+ if (!AreHotkeySettingsEqual(viewModel.PasteAsMarkdownShortcut, newHotkeySettings))
+ {
+ viewModel.PasteAsMarkdownShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste PasteAsMarkdownShortcut");
+ }
+
+ break;
+
+ case "pasteasjson" or "pasteasjsonshortcut":
+ if (!AreHotkeySettingsEqual(viewModel.PasteAsJsonShortcut, newHotkeySettings))
+ {
+ viewModel.PasteAsJsonShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste PasteAsJsonShortcut");
+ }
+
+ break;
+
+ case "imagetotext" or "imagetotextshortcut":
+ if (viewModel.AdditionalActions?.ImageToText != null &&
+ !AreHotkeySettingsEqual(viewModel.AdditionalActions.ImageToText.Shortcut, newHotkeySettings))
+ {
+ viewModel.AdditionalActions.ImageToText.Shortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste ImageToText shortcut");
+ }
+
+ break;
+
+ case "pasteastxtfile" or "pasteastxtfileshortcut":
+ if (viewModel.AdditionalActions?.PasteAsFile?.PasteAsTxtFile != null &&
+ !AreHotkeySettingsEqual(viewModel.AdditionalActions.PasteAsFile.PasteAsTxtFile.Shortcut, newHotkeySettings))
+ {
+ viewModel.AdditionalActions.PasteAsFile.PasteAsTxtFile.Shortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste PasteAsTxtFile shortcut");
+ }
+
+ break;
+
+ case "pasteaspngfile" or "pasteaspngfileshortcut":
+ if (viewModel.AdditionalActions?.PasteAsFile?.PasteAsPngFile != null &&
+ !AreHotkeySettingsEqual(viewModel.AdditionalActions.PasteAsFile.PasteAsPngFile.Shortcut, newHotkeySettings))
+ {
+ viewModel.AdditionalActions.PasteAsFile.PasteAsPngFile.Shortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste PasteAsPngFile shortcut");
+ }
+
+ break;
+
+ case "pasteashtmlfile" or "pasteashtmlfileshortcut":
+ if (viewModel.AdditionalActions?.PasteAsFile?.PasteAsHtmlFile != null &&
+ !AreHotkeySettingsEqual(viewModel.AdditionalActions.PasteAsFile.PasteAsHtmlFile.Shortcut, newHotkeySettings))
+ {
+ viewModel.AdditionalActions.PasteAsFile.PasteAsHtmlFile.Shortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste PasteAsHtmlFile shortcut");
+ }
+
+ break;
+
+ case "transcodetomp3" or "transcodetomp3shortcut":
+ if (viewModel.AdditionalActions?.Transcode?.TranscodeToMp3 != null &&
+ !AreHotkeySettingsEqual(viewModel.AdditionalActions.Transcode.TranscodeToMp3.Shortcut, newHotkeySettings))
+ {
+ viewModel.AdditionalActions.Transcode.TranscodeToMp3.Shortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste TranscodeToMp3 shortcut");
+ }
+
+ break;
+
+ case "transcodetomp4" or "transcodetomp4shortcut":
+ if (viewModel.AdditionalActions?.Transcode?.TranscodeToMp4 != null &&
+ !AreHotkeySettingsEqual(viewModel.AdditionalActions.Transcode.TranscodeToMp4.Shortcut, newHotkeySettings))
+ {
+ viewModel.AdditionalActions.Transcode.TranscodeToMp4.Shortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste TranscodeToMp4 shortcut");
+ }
+
+ break;
+
+ case var customActionName when customActionName.StartsWith("customaction_", StringComparison.OrdinalIgnoreCase):
+ var parts = customActionName.Split('_');
+ if (parts.Length == 2 && int.TryParse(parts[1], out int customActionId))
+ {
+ var customAction = viewModel.CustomActions?.FirstOrDefault(ca => ca.Id == customActionId);
+ if (customAction != null && !AreHotkeySettingsEqual(customAction.Shortcut, newHotkeySettings))
+ {
+ customAction.Shortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AdvancedPaste CustomAction_{customActionId} shortcut");
+ }
+ }
+
+ break;
+
+ default:
+ System.Diagnostics.Debug.WriteLine($"Unknown AdvancedPaste hotkey name: {hotkeyName}");
+ break;
+ }
+ }
+
+ private void UpdateAlwaysOnTopHotkeySettings(AlwaysOnTopViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // AlwaysOnTop module only has one hotkey setting
+ if (!AreHotkeySettingsEqual(viewModel.Hotkey, newHotkeySettings))
+ {
+ viewModel.Hotkey = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated AlwaysOnTop hotkey settings");
+ }
+ }
+
+ private void UpdateColorPickerHotkeySettings(ColorPickerViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // ColorPicker module only has one activation shortcut
+ if (!AreHotkeySettingsEqual(viewModel.ActivationShortcut, newHotkeySettings))
+ {
+ viewModel.ActivationShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated ColorPicker hotkey settings");
+ }
+ }
+
+ private void UpdateCropAndLockHotkeySettings(CropAndLockViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // Update based on hotkey name for CropAndLock module
+ switch (hotkeyName?.ToLowerInvariant())
+ {
+ case "thumbnail" or "thumbnailhotkey":
+ if (!AreHotkeySettingsEqual(viewModel.ThumbnailActivationShortcut, newHotkeySettings))
+ {
+ viewModel.ThumbnailActivationShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated CropAndLock ThumbnailActivationShortcut");
+ }
+
+ break;
+
+ case "reparent" or "reparenthotkey":
+ if (!AreHotkeySettingsEqual(viewModel.ReparentActivationShortcut, newHotkeySettings))
+ {
+ viewModel.ReparentActivationShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated CropAndLock ReparentActivationShortcut");
+ }
+
+ break;
+
+ default:
+ System.Diagnostics.Debug.WriteLine($"Unknown CropAndLock hotkey name: {hotkeyName}");
+ break;
+ }
+ }
+
+ private void UpdateFancyZonesHotkeySettings(FancyZonesViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // FancyZones module only has one editor hotkey
+ if (!AreHotkeySettingsEqual(viewModel.EditorHotkey, newHotkeySettings))
+ {
+ viewModel.EditorHotkey = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated FancyZones EditorHotkey");
+ }
+ }
+
+ private void UpdateMeasureToolHotkeySettings(MeasureToolViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // MeasureTool module only has one activation shortcut
+ if (!AreHotkeySettingsEqual(viewModel.ActivationShortcut, newHotkeySettings))
+ {
+ viewModel.ActivationShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated MeasureTool ActivationShortcut");
+ }
+ }
+
+ private void UpdateShortcutGuideHotkeySettings(ShortcutGuideViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // ShortcutGuide module only has one shortcut to open the guide
+ if (!AreHotkeySettingsEqual(viewModel.OpenShortcutGuide, newHotkeySettings))
+ {
+ viewModel.OpenShortcutGuide = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated ShortcutGuide OpenShortcutGuide");
+ }
+ }
+
+ private void UpdatePowerOcrHotkeySettings(PowerOcrViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // PowerOCR module only has one activation shortcut
+ if (!AreHotkeySettingsEqual(viewModel.ActivationShortcut, newHotkeySettings))
+ {
+ viewModel.ActivationShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated PowerOCR ActivationShortcut");
+ }
+ }
+
+ private void UpdateWorkspacesHotkeySettings(WorkspacesViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // Workspaces module only has one hotkey
+ if (!AreHotkeySettingsEqual(viewModel.Hotkey, newHotkeySettings))
+ {
+ viewModel.Hotkey = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated Workspaces Hotkey");
+ }
+ }
+
+ private void UpdatePeekHotkeySettings(PeekViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // Peek module only has one activation shortcut
+ if (!AreHotkeySettingsEqual(viewModel.ActivationShortcut, newHotkeySettings))
+ {
+ viewModel.ActivationShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated Peek ActivationShortcut");
+ }
+ }
+
+ private void UpdatePowerLauncherHotkeySettings(PowerLauncherViewModel viewModel, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // PowerLauncher module only has one shortcut to open the launcher
+ if (!AreHotkeySettingsEqual(viewModel.OpenPowerLauncher, newHotkeySettings))
+ {
+ viewModel.OpenPowerLauncher = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated PowerLauncher OpenPowerLauncher");
+ }
+ }
+
+ private void UpdateMouseUtilsHotkeySettings(MouseUtilsViewModel viewModel, string moduleName, string hotkeyName, HotkeySettings newHotkeySettings)
+ {
+ if (viewModel == null)
+ {
+ return;
+ }
+
+ // Update based on specific mouse utility module name
+ switch (moduleName?.ToLowerInvariant())
+ {
+ case "mousehighlighter":
+ if (!AreHotkeySettingsEqual(viewModel.MouseHighlighterActivationShortcut, newHotkeySettings))
+ {
+ viewModel.MouseHighlighterActivationShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated MouseUtils MouseHighlighterActivationShortcut");
+ }
+
+ break;
+
+ case "mousejump":
+ if (!AreHotkeySettingsEqual(viewModel.MouseJumpActivationShortcut, newHotkeySettings))
+ {
+ viewModel.MouseJumpActivationShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated MouseUtils MouseJumpActivationShortcut");
+ }
+
+ break;
+
+ case "mousepointercrosshairs":
+ if (!AreHotkeySettingsEqual(viewModel.MousePointerCrosshairsActivationShortcut, newHotkeySettings))
+ {
+ viewModel.MousePointerCrosshairsActivationShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated MouseUtils MousePointerCrosshairsActivationShortcut");
+ }
+
+ break;
+
+ case "findmymouse":
+ if (!AreHotkeySettingsEqual(viewModel.FindMyMouseActivationShortcut, newHotkeySettings))
+ {
+ viewModel.FindMyMouseActivationShortcut = newHotkeySettings;
+ System.Diagnostics.Debug.WriteLine($"Updated MouseUtils FindMyMouseActivationShortcut");
+ }
+
+ break;
+
+ default:
+ System.Diagnostics.Debug.WriteLine($"Unknown MouseUtils module name: {moduleName}");
+ break;
+ }
+ }
+
+ // Helper methods
+ private bool AreHotkeySettingsEqual(HotkeySettings settings1, HotkeySettings settings2)
+ {
+ if (settings1 == null && settings2 == null)
+ {
+ return true;
+ }
+
+ if (settings1 == null || settings2 == null)
+ {
+ return false;
+ }
+
+ return settings1.Win == settings2.Win &&
+ settings1.Ctrl == settings2.Ctrl &&
+ settings1.Alt == settings2.Alt &&
+ settings1.Shift == settings2.Shift &&
+ settings1.Code == settings2.Code;
+ }
+
+ private void UpdateHotkeySettingsProperties(HotkeySettings target, HotkeySettings source)
+ {
+ if (target == null || source == null)
+ {
+ return;
+ }
+
+ target.Win = source.Win;
+ target.Ctrl = source.Ctrl;
+ target.Alt = source.Alt;
+ target.Shift = source.Shift;
+ target.Code = source.Code;
+ target.Key = source.Key;
+ }
+
+ private ModuleHotkeyData FindModuleDataForHotkeySettings(HotkeySettings hotkeySettings)
+ {
+ foreach (var conflictGroup in ConflictItems)
+ {
+ foreach (var module in conflictGroup.Modules)
+ {
+ if (ReferenceEquals(module.HotkeySettings, hotkeySettings))
+ {
+ return module;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private ModuleHotkeyData FindModuleDataByKey(string moduleName, string hotkeyName)
+ {
+ foreach (var conflictGroup in ConflictItems)
+ {
+ foreach (var module in conflictGroup.Modules)
+ {
+ if (module.ModuleName.Equals(moduleName, StringComparison.OrdinalIgnoreCase) &&
+ module.HotkeyName.Equals(hotkeyName, StringComparison.OrdinalIgnoreCase))
+ {
+ return module;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private HotkeySettings GetHotkeySettingsFromViewModel(string moduleName, string hotkeyName)
+ {
+ try
+ {
+ var moduleKey = GetModuleKey(moduleName);
+ var viewModel = GetOrCreateViewModel(moduleKey);
+ if (viewModel == null)
+ {
+ return null;
+ }
+
+ return moduleKey switch
+ {
+ "advancedpaste" => GetAdvancedPasteHotkeySettings(viewModel as AdvancedPasteViewModel, hotkeyName),
+ "alwaysontop" => GetAlwaysOnTopHotkeySettings(viewModel as AlwaysOnTopViewModel, hotkeyName),
+ "colorpicker" => GetColorPickerHotkeySettings(viewModel as ColorPickerViewModel, hotkeyName),
+ "cropandlock" => GetCropAndLockHotkeySettings(viewModel as CropAndLockViewModel, hotkeyName),
+ "fancyzones" => GetFancyZonesHotkeySettings(viewModel as FancyZonesViewModel, hotkeyName),
+ "measuretool" => GetMeasureToolHotkeySettings(viewModel as MeasureToolViewModel, hotkeyName),
+ "shortcutguide" => GetShortcutGuideHotkeySettings(viewModel as ShortcutGuideViewModel, hotkeyName),
+ "powerocr" => GetPowerOcrHotkeySettings(viewModel as PowerOcrViewModel, hotkeyName),
+ "workspaces" => GetWorkspacesHotkeySettings(viewModel as WorkspacesViewModel, hotkeyName),
+ "peek" => GetPeekHotkeySettings(viewModel as PeekViewModel, hotkeyName),
+ "powerlauncher" => GetPowerLauncherHotkeySettings(viewModel as PowerLauncherViewModel, hotkeyName),
+ "mouseutils" => GetMouseUtilsHotkeySettings(viewModel as MouseUtilsViewModel, moduleName, hotkeyName),
+ "cmdpal" => GetCmdPalHotkeySettings(viewModel as CmdPalViewModel, hotkeyName),
+ _ => null,
+ };
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error getting hotkey settings for {moduleName}.{hotkeyName}: {ex.Message}");
+ return null;
+ }
+ }
+
+ private string GetModuleKey(string moduleName)
+ {
+ return moduleName?.ToLowerInvariant() switch
+ {
+ "mousehighlighter" or "mousejump" or "mousepointercrosshairs" or "findmymouse" => "mouseutils",
+ _ => moduleName?.ToLowerInvariant(),
+ };
+ }
+
+ // Get methods that return direct references to ViewModel properties for two-way binding
+ private HotkeySettings GetAdvancedPasteHotkeySettings(AdvancedPasteViewModel viewModel, string hotkeyName)
+ {
+ if (viewModel == null)
+ {
+ return null;
+ }
+
+ return hotkeyName?.ToLowerInvariant() switch
+ {
+ "advancedpasteui" or "advancedpasteuishortcut" or "activation_shortcut" => viewModel.AdvancedPasteUIShortcut,
+ "pasteasplaintext" or "pasteasplaintextshortcut" => viewModel.PasteAsPlainTextShortcut,
+ "pasteasmarkdown" or "pasteasmarkdownshortcut" => viewModel.PasteAsMarkdownShortcut,
+ "pasteasjson" or "pasteasjsonshortcut" => viewModel.PasteAsJsonShortcut,
+ "imagetotext" or "imagetotextshortcut" => GetAdditionalActionShortcut(viewModel, "ImageToText"),
+ "pasteastxtfile" or "pasteastxtfileshortcut" => GetAdditionalActionShortcut(viewModel, "PasteAsTxtFile"),
+ "pasteaspngfile" or "pasteaspngfileshortcut" => GetAdditionalActionShortcut(viewModel, "PasteAsPngFile"),
+ "pasteashtmlfile" or "pasteashtmlfileshortcut" => GetAdditionalActionShortcut(viewModel, "PasteAsHtmlFile"),
+ "transcodetomp3" or "transcodetomp3shortcut" => GetAdditionalActionShortcut(viewModel, "TranscodeToMp3"),
+ "transcodetomp4" or "transcodetomp4shortcut" => GetAdditionalActionShortcut(viewModel, "TranscodeToMp4"),
+ _ when hotkeyName.StartsWith("customaction_", StringComparison.OrdinalIgnoreCase) => GetCustomActionShortcut(viewModel, hotkeyName),
+ _ => null,
+ };
+ }
+
+ private HotkeySettings GetAdditionalActionShortcut(AdvancedPasteViewModel viewModel, string actionName)
+ {
+ if (viewModel?.AdditionalActions == null)
+ {
+ return null;
+ }
+
+ return actionName switch
+ {
+ "ImageToText" => viewModel.AdditionalActions.ImageToText?.Shortcut,
+ "PasteAsTxtFile" => viewModel.AdditionalActions.PasteAsFile?.PasteAsTxtFile?.Shortcut,
+ "PasteAsPngFile" => viewModel.AdditionalActions.PasteAsFile?.PasteAsPngFile?.Shortcut,
+ "PasteAsHtmlFile" => viewModel.AdditionalActions.PasteAsFile?.PasteAsHtmlFile?.Shortcut,
+ "TranscodeToMp3" => viewModel.AdditionalActions.Transcode?.TranscodeToMp3?.Shortcut,
+ "TranscodeToMp4" => viewModel.AdditionalActions.Transcode?.TranscodeToMp4?.Shortcut,
+ _ => null,
+ };
+ }
+
+ private HotkeySettings GetCustomActionShortcut(AdvancedPasteViewModel viewModel, string hotkeyName)
+ {
+ if (viewModel?.CustomActions == null)
+ {
+ return null;
+ }
+
+ var parts = hotkeyName.Split('_');
+ if (parts.Length == 2 && int.TryParse(parts[1], out int customActionId))
+ {
+ var customAction = viewModel.CustomActions.FirstOrDefault(ca => ca.Id == customActionId);
+ return customAction?.Shortcut;
+ }
+
+ return null;
+ }
+
+ private HotkeySettings GetAlwaysOnTopHotkeySettings(AlwaysOnTopViewModel viewModel, string hotkeyName)
+ {
+ return viewModel?.Hotkey;
+ }
+
+ private HotkeySettings GetColorPickerHotkeySettings(ColorPickerViewModel viewModel, string hotkeyName)
+ {
+ return viewModel?.ActivationShortcut;
+ }
+
+ private HotkeySettings GetCropAndLockHotkeySettings(CropAndLockViewModel viewModel, string hotkeyName)
+ {
+ if (viewModel == null)
+ {
+ return null;
+ }
+
+ return hotkeyName?.ToLowerInvariant() switch
+ {
+ "thumbnail" or "thumbnailhotkey" => viewModel.ThumbnailActivationShortcut,
+ "reparent" or "reparenthotkey" => viewModel.ReparentActivationShortcut,
+ _ => null,
+ };
+ }
+
+ private HotkeySettings GetFancyZonesHotkeySettings(FancyZonesViewModel viewModel, string hotkeyName)
+ {
+ return viewModel?.EditorHotkey;
+ }
+
+ private HotkeySettings GetMeasureToolHotkeySettings(MeasureToolViewModel viewModel, string hotkeyName)
+ {
+ return viewModel?.ActivationShortcut;
+ }
+
+ private HotkeySettings GetShortcutGuideHotkeySettings(ShortcutGuideViewModel viewModel, string hotkeyName)
+ {
+ return viewModel?.OpenShortcutGuide;
+ }
+
+ private HotkeySettings GetPowerOcrHotkeySettings(PowerOcrViewModel viewModel, string hotkeyName)
+ {
+ return viewModel?.ActivationShortcut;
+ }
+
+ private HotkeySettings GetWorkspacesHotkeySettings(WorkspacesViewModel viewModel, string hotkeyName)
+ {
+ return viewModel?.Hotkey;
+ }
+
+ private HotkeySettings GetPeekHotkeySettings(PeekViewModel viewModel, string hotkeyName)
+ {
+ return viewModel?.ActivationShortcut;
+ }
+
+ private HotkeySettings GetPowerLauncherHotkeySettings(PowerLauncherViewModel viewModel, string hotkeyName)
+ {
+ return viewModel?.OpenPowerLauncher;
+ }
+
+ private HotkeySettings GetMouseUtilsHotkeySettings(MouseUtilsViewModel viewModel, string moduleName, string hotkeyName)
+ {
+ if (viewModel == null)
+ {
+ return null;
+ }
+
+ return moduleName?.ToLowerInvariant() switch
+ {
+ "mousehighlighter" => viewModel.MouseHighlighterActivationShortcut,
+ "mousejump" => viewModel.MouseJumpActivationShortcut,
+ "mousepointercrosshairs" => viewModel.MousePointerCrosshairsActivationShortcut,
+ "findmymouse" => viewModel.FindMyMouseActivationShortcut,
+ _ => null,
+ };
+ }
+
+ private HotkeySettings GetCmdPalHotkeySettings(CmdPalViewModel viewModel, string hotkeyName)
+ {
+ return viewModel?.Hotkey;
+ }
+
+ private string GetConflictDescription(HotkeyConflictGroupData conflict, ModuleHotkeyData currentModule, bool isSystemConflict)
+ {
+ if (isSystemConflict)
+ {
+ return "Conflicts with system shortcut";
+ }
+
+ var otherModules = conflict.Modules
+ .Where(m => m.ModuleName != currentModule.ModuleName)
+ .Select(m => m.ModuleName)
+ .ToList();
+
+ return otherModules.Count switch
+ {
+ 1 => $"Conflicts with {otherModules[0]}",
+ > 1 => $"Conflicts with: {string.Join(", ", otherModules)}",
+ _ => "Shortcut conflict detected",
+ };
+ }
+
+ public override void Dispose()
+ {
+ // Unsubscribe from property change events
+ foreach (var conflictGroup in ConflictItems)
+ {
+ foreach (var module in conflictGroup.Modules)
+ {
+ module.PropertyChanged -= OnModuleHotkeyDataPropertyChanged;
+ }
+ }
+
+ // Dispose all created module ViewModels
+ foreach (var viewModel in _moduleViewModels.Values)
+ {
+ viewModel?.Dispose();
+ }
+
+ base.Dispose();
+ }
+ }
+}