diff --git a/AutoDarkModeApp/Strings/en-us/Resources.resw b/AutoDarkModeApp/Strings/en-us/Resources.resw index 9c0600c6d..fb59bed1d 100644 --- a/AutoDarkModeApp/Strings/en-us/Resources.resw +++ b/AutoDarkModeApp/Strings/en-us/Resources.resw @@ -370,6 +370,15 @@ Would you like to create an issue on the Auto Dark Mode repository? Hotkeys + + Duplicate Hotkey + + + This hotkey is already used by "{0}". Please choose a different key combination. + + + Invalid hotkey combination. Please include at least one non-modifier key (e.g., a letter, number, or function key). + Time in minutes before the system is considered idle diff --git a/AutoDarkModeApp/UserControls/ShortcutDialogContentControl.xaml b/AutoDarkModeApp/UserControls/ShortcutDialogContentControl.xaml index 24a267fb3..c4d167e7c 100644 --- a/AutoDarkModeApp/UserControls/ShortcutDialogContentControl.xaml +++ b/AutoDarkModeApp/UserControls/ShortcutDialogContentControl.xaml @@ -10,6 +10,13 @@ + + ? ValidateHotkey { get; set; } + private KeyboardHook? _keyboardHook; private bool _isCapturing; + public void ShowError(string message) + { + ErrorMessage = message; + IsErrorVisible = true; + } + + public void HideError() + { + IsErrorVisible = false; + ErrorMessage = null; + } + public void LoadFromKeyValue(string? hotkeyValue) { if (string.IsNullOrWhiteSpace(hotkeyValue)) @@ -86,13 +109,19 @@ private void OnKeyboardEvent(object? sender, KeyboardHookEventArgs e) } List displayParts = []; - if (isCtrl) displayParts.Add("Ctrl"); - if (isShift) displayParts.Add("Shift"); - if (isAlt) displayParts.Add("Alt"); - if (isWin) displayParts.Add("Win"); + if (isCtrl) + displayParts.Add("Ctrl"); + if (isShift) + displayParts.Add("Shift"); + if (isAlt) + displayParts.Add("Alt"); + if (isWin) + displayParts.Add("Win"); var key = (VirtualKey)e.VirtualKeyCode; - if (!IsModifierKey(key)) + HasNonModifierKey = !IsModifierKey(key); + + if (HasNonModifierKey) { displayParts.Add(HotkeyStringConverter.GetKeyDisplayName(key)); } @@ -101,6 +130,23 @@ private void OnKeyboardEvent(object? sender, KeyboardHookEventArgs e) { CapturedHotkeys = string.Join(" + ", displayParts); HotkeyCombination = displayParts.Select(p => new SingleHotkeyDataObject { Key = p }).ToList(); + + if (ValidateHotkey is not null) + { + var (isDuplicate, conflictingName) = ValidateHotkey(CapturedHotkeys); + if (isDuplicate) + { + ShowError(string.Format(ResourceExtensions.GetLocalized("HotkeyDuplicateErrorMessage"), conflictingName)); + } + else + { + HideError(); + } + } + else + { + HideError(); + } }); e.Handled = true; diff --git a/AutoDarkModeApp/ViewModels/HotkeysViewModel.cs b/AutoDarkModeApp/ViewModels/HotkeysViewModel.cs index 4590b539f..334bcbbfe 100644 --- a/AutoDarkModeApp/ViewModels/HotkeysViewModel.cs +++ b/AutoDarkModeApp/ViewModels/HotkeysViewModel.cs @@ -102,6 +102,47 @@ public void UpdateHotkeyValue(string tag, string? value) SafeSaveBuilder(); } + public (bool isDuplicate, string? conflictingName) IsDuplicateHotkey(string currentTag, string? newKeys) + { + if (string.IsNullOrWhiteSpace(newKeys)) + { + return (false, null); + } + + var normalizedNewKeys = newKeys.Trim(); + + var allHotkeys = new[] + { + (Tag: "ForceLight", Keys: _builder.Config.Hotkeys.ForceLight, Name: "ForceLight"), + (Tag: "ForceDark", Keys: _builder.Config.Hotkeys.ForceDark, Name: "ForceDark"), + (Tag: "StopForcing", Keys: _builder.Config.Hotkeys.NoForce, Name: "StopForcing"), + (Tag: "ToggleTheme", Keys: _builder.Config.Hotkeys.ToggleTheme, Name: "ToggleTheme"), + (Tag: "AutomaticThemeSwitch", Keys: _builder.Config.Hotkeys.ToggleAutoThemeSwitch, Name: "AutomaticThemeSwitch"), + (Tag: "PauseAutoThemeSwitching", Keys: _builder.Config.Hotkeys.TogglePostpone, Name: "PauseAutoThemeSwitching"), + }; + + foreach (var hotkey in allHotkeys) + { + if (hotkey.Tag == currentTag) + { + continue; + } + + if (string.IsNullOrWhiteSpace(hotkey.Keys)) + { + continue; + } + + if (hotkey.Keys.Trim().Equals(normalizedNewKeys, StringComparison.OrdinalIgnoreCase)) + { + var displayName = HotkeysCollection?.FirstOrDefault(h => h.Tag == hotkey.Tag)?.DisplayName ?? hotkey.Name; + return (true, displayName); + } + } + + return (false, null); + } + public HotkeysViewModel(IErrorService errorService) { _dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); diff --git a/AutoDarkModeApp/Views/HotkeysPage.xaml.cs b/AutoDarkModeApp/Views/HotkeysPage.xaml.cs index c47763547..0ec7bd751 100644 --- a/AutoDarkModeApp/Views/HotkeysPage.xaml.cs +++ b/AutoDarkModeApp/Views/HotkeysPage.xaml.cs @@ -24,6 +24,7 @@ private async void EditHotkeysButton_Click(object sender, RoutedEventArgs e) var keyValue = ViewModel.GetHotkeyValue(hotkeyData.Tag); var dialogContent = new ShortcutDialogContentControl(); + dialogContent.ValidateHotkey = (newKeys) => ViewModel.IsDuplicateHotkey(hotkeyData.Tag, newKeys); if (keyValue is not null) { @@ -42,18 +43,32 @@ private async void EditHotkeysButton_Click(object sender, RoutedEventArgs e) Content = dialogContent, }; + shortcutDialog.Closing += (dialog, args) => + { + if (args.Result == ContentDialogResult.Primary) + { + if (!dialogContent.HasNonModifierKey) + { + dialogContent.ShowError("HotkeyInvalidErrorMessage".GetLocalized()); + } + if (dialogContent.IsErrorVisible) + { + args.Cancel = true; + } + } + }; + var result = await shortcutDialog.ShowAsync(); if (result == ContentDialogResult.Secondary) { dialogContent.HotkeyCombination?.Clear(); dialogContent.CapturedHotkeys = null; } - else if (result != ContentDialogResult.Primary) + + if (result == ContentDialogResult.Primary || result == ContentDialogResult.Secondary) { - return; + ViewModel.UpdateHotkeyValue(hotkeyData.Tag, dialogContent.CapturedHotkeys); } - - ViewModel.UpdateHotkeyValue(hotkeyData.Tag, dialogContent.CapturedHotkeys); } private async void SaveSettingsButton_Click(object sender, RoutedEventArgs e)