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)