Fixed shortcut key monitoring

This commit is contained in:
Thorsten Sommer 2026-01-22 18:21:13 +01:00
parent 18fa36c32b
commit 7ff53ebcae
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
2 changed files with 52 additions and 66 deletions

View File

@ -12,44 +12,37 @@
@T("Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused.") @T("Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused.")
</MudJustifiedText> </MudJustifiedText>
<MudPaper Class="pa-4 mb-3 d-flex align-center justify-center shortcut-capture-area" <MudFocusTrap DefaultFocus="DefaultFocus.FirstChild">
Style="min-height: 80px; cursor: pointer;" <MudTextField
Elevation="2" @ref="@this.inputField"
@onclick="@this.FocusInput"> T="string"
@* Hidden input to capture keyboard events *@ Text="@this.ShowText"
<input type="text" Variant="Variant.Outlined"
@ref="this.hiddenInput" Label="@T("Define a shortcut")"
@onkeydown="@this.HandleKeyDown" Placeholder="@T("Press a key combination...")"
style="position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none;" Adornment="Adornment.Start"
autocomplete="off" AdornmentIcon="@Icons.Material.Filled.Keyboard"
readonly /> Immediate="@true"
@if (string.IsNullOrWhiteSpace(this.currentShortcut)) TextUpdateSuppression="false"
{ OnKeyDown="@this.HandleKeyDown"
<MudText Typo="Typo.h6" Color="Color.Secondary"> OnBlur="@this.HandleBlur"
@T("Press a key combination...") UserAttributes="@USER_INPUT_ATTRIBUTES"
</MudText> AutoFocus="true"
} KeyDownPreventDefault="true"
else KeyUpPreventDefault="true"
{ HelperText="@T("Supported modifiers: Ctrl/Cmd, Shift, Alt.")"
<MudText Typo="Typo.h5" Color="Color.Primary"> Class="me-3"/>
@this.GetDisplayShortcut() </MudFocusTrap>
</MudText>
}
</MudPaper>
@if (!string.IsNullOrWhiteSpace(this.validationMessage)) @if (!string.IsNullOrWhiteSpace(this.validationMessage))
{ {
<MudAlert Severity="@this.validationSeverity" Class="mb-3"> <MudAlert Severity="@this.validationSeverity" Variant="Variant.Filled" Class="mb-3">
@this.validationMessage @this.validationMessage
</MudAlert> </MudAlert>
} }
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="mb-2">
@T("Supported modifiers: Ctrl/Cmd, Shift, Alt. Example: Ctrl+Shift+R")
</MudText>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<MudButton OnClick="@this.ClearShortcut" Variant="Variant.Text" Color="Color.Warning" StartIcon="@Icons.Material.Filled.Clear"> <MudButton OnClick="@this.ClearShortcut" Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Clear">
@T("Clear Shortcut") @T("Clear Shortcut")
</MudButton> </MudButton>
<MudSpacer/> <MudSpacer/>

View File

@ -29,44 +29,42 @@ public partial class ShortcutDialog : MSGComponentBase
[Parameter] [Parameter]
public string ShortcutName { get; set; } = string.Empty; public string ShortcutName { get; set; } = string.Empty;
private ElementReference hiddenInput; private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
private string currentShortcut = string.Empty; private string currentShortcut = string.Empty;
private string validationMessage = string.Empty; private string validationMessage = string.Empty;
private Severity validationSeverity = Severity.Info; private Severity validationSeverity = Severity.Info;
private bool hasValidationError; private bool hasValidationError;
// Current key state //
// Current key state:
//
private bool hasCtrl; private bool hasCtrl;
private bool hasShift; private bool hasShift;
private bool hasAlt; private bool hasAlt;
private bool hasMeta; private bool hasMeta;
private string? currentKey; private string? currentKey;
private MudTextField<string>? inputField;
private bool isFirstRender = true;
#region Overrides of ComponentBase #region Overrides of ComponentBase
protected override void OnInitialized() protected override async Task OnInitializedAsync()
{ {
base.OnInitialized(); await base.OnInitializedAsync();
// Configure the spellchecking for the user input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
this.currentShortcut = this.InitialShortcut; this.currentShortcut = this.InitialShortcut;
this.ParseExistingShortcut(); this.ParseExistingShortcut();
} }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
// Auto-focus the hidden input when the dialog opens
if (this.isFirstRender)
{
this.isFirstRender = false;
await this.hiddenInput.FocusAsync();
}
}
#endregion #endregion
private string ShowText => string.IsNullOrWhiteSpace(this.currentShortcut)
? T("Press a key combination...")
: this.GetDisplayShortcut();
private void ParseExistingShortcut() private void ParseExistingShortcut()
{ {
if (string.IsNullOrWhiteSpace(this.currentShortcut)) if (string.IsNullOrWhiteSpace(this.currentShortcut))
@ -107,15 +105,9 @@ public partial class ShortcutDialog : MSGComponentBase
} }
} }
private async Task FocusInput()
{
// Focus the hidden input to capture keyboard events
await this.hiddenInput.FocusAsync();
}
private async Task HandleKeyDown(KeyboardEventArgs e) private async Task HandleKeyDown(KeyboardEventArgs e)
{ {
// Ignore pure modifier key presses // Ignore pure modifier key presses:
if (IsModifierKey(e.Code)) if (IsModifierKey(e.Code))
{ {
this.UpdateModifiers(e); this.UpdateModifiers(e);
@ -124,10 +116,9 @@ public partial class ShortcutDialog : MSGComponentBase
return; return;
} }
// Update modifiers
this.UpdateModifiers(e); this.UpdateModifiers(e);
// Get the key // Get the key:
this.currentKey = TranslateKeyCode(e.Code); this.currentKey = TranslateKeyCode(e.Code);
// Validate: must have at least one modifier + a key // Validate: must have at least one modifier + a key
@ -140,11 +131,10 @@ public partial class ShortcutDialog : MSGComponentBase
return; return;
} }
// Build the shortcut string
this.UpdateShortcutString(); this.UpdateShortcutString();
// Validate the shortcut
await this.ValidateShortcut(); await this.ValidateShortcut();
this.StateHasChanged();
} }
private void UpdateModifiers(KeyboardEventArgs e) private void UpdateModifiers(KeyboardEventArgs e)
@ -213,10 +203,7 @@ public partial class ShortcutDialog : MSGComponentBase
private string GetDisplayShortcut() private string GetDisplayShortcut()
{ {
if (string.IsNullOrWhiteSpace(this.currentShortcut)) // Convert internal format to display format:
return string.Empty;
// Convert internal format to display format
return this.currentShortcut return this.currentShortcut
.Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl") .Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl")
.Replace("CommandOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl"); .Replace("CommandOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl");
@ -376,4 +363,10 @@ public partial class ShortcutDialog : MSGComponentBase
// Default: return as-is // Default: return as-is
_ => code, _ => code,
}; };
private void HandleBlur()
{
// Re-focus the input field to keep capturing keys:
this.inputField?.FocusAsync();
}
} }