diff --git a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs index 65652878fc8..ef9e9cf1d9d 100644 --- a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs +++ b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs @@ -455,7 +455,7 @@ private static void InvokeHideDialogJumpWindow() #region Hotkey - public static void OnToggleHotkey(object sender, HotkeyEventArgs args) + public static void ToggleHotkey() { _ = Task.Run(async () => { diff --git a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs index b2a14075581..1d1eee6ea63 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs @@ -37,25 +37,53 @@ private static UnhookWindowsHookExSafeHandle SetHook(HOOKPROC proc, WINDOWS_HOOK public static SpecialKeyState CheckModifiers() { SpecialKeyState state = new SpecialKeyState(); - if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_SHIFT) & 0x8000) != 0) + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_LSHIFT) & 0x8000) != 0) { //SHIFT is pressed + state.LeftShiftPressed = true; state.ShiftPressed = true; } - if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_CONTROL) & 0x8000) != 0) + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_RSHIFT) & 0x8000) != 0) + { + //SHIFT is pressed + state.RightShiftPressed = true; + state.ShiftPressed = true; + } + + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_LCONTROL) & 0x8000) != 0) + { + //CONTROL is pressed + state.LeftCtrlPressed = true; + state.CtrlPressed = true; + } + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_RCONTROL) & 0x8000) != 0) { //CONTROL is pressed + state.RightCtrlPressed = true; state.CtrlPressed = true; } - if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_MENU) & 0x8000) != 0) + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_LMENU) & 0x8000) != 0) { //ALT is pressed + state.LeftAltPressed = true; state.AltPressed = true; } - if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_LWIN) & 0x8000) != 0 || - (PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_RWIN) & 0x8000) != 0) + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_RMENU) & 0x8000) != 0) + { + //ALT is pressed + state.RightAltPressed = true; + state.AltPressed = true; + } + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_LWIN) & 0x8000) != 0) + { + //WIN is pressed + state.LWinPressed = true; + state.WinPressed = true; + } + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_RWIN) & 0x8000) != 0) { //WIN is pressed + state.RWinPressed = true; state.WinPressed = true; } diff --git a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs index 25bc75a56c1..912c8cca555 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -3,64 +3,156 @@ using System.ComponentModel; using System.Linq; using System.Windows.Input; +using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.Hotkey { public record struct HotkeyModel { - public bool Alt { get; set; } - public bool Shift { get; set; } - public bool Win { get; set; } - public bool Ctrl { get; set; } + public string HotkeyRaw { get; set; } = string.Empty; + public string PreviousHotkey { get; set; } = string.Empty; - public Key CharKey { get; set; } = Key.None; + // HotkeyRaw always be without spaces round '+'. WPF Control hotkey string saved to settings will contain spaces. + public HotkeyModel(string hotkey) + { + HotkeyRaw = ToHotkeyRawString(hotkey); + } - private static readonly Dictionary specialSymbolDictionary = new Dictionary + internal void AddString(string key) { - { Key.Space, "Space" }, { Key.Oem3, "~" } - }; + HotkeyRaw = string.IsNullOrEmpty(HotkeyRaw) ? key : HotkeyRaw + "+" + key; + } - public ModifierKeys ModifierKeys + // Display in the form of WPF Control i.e. simplified text e.g. LeftAlt -> Alt + public IEnumerable EnumerateDisplayKeys() => !string.IsNullOrEmpty(HotkeyRaw) ? ToWPFHotkeyString().Split(" + ") : Array.Empty(); + + internal string GetLastKeySet() => !string.IsNullOrEmpty(HotkeyRaw) ? HotkeyRaw.Split('+').Last() : string.Empty; + + internal void Clear() { - get + HotkeyRaw = string.Empty; + PreviousHotkey = string.Empty; + } + + // WPF Control hotkey form i.e. simplified text e.g. LeftAlt+X -> Alt + X, includes space around '+' + public readonly string ToWPFHotkeyString() + { + var hotkey = string.Empty; + + foreach (var key in HotkeyRaw.Split('+')) { - ModifierKeys modifierKeys = ModifierKeys.None; - if (Alt) - { - modifierKeys |= ModifierKeys.Alt; - } + if (!string.IsNullOrEmpty(hotkey)) + hotkey += " + "; - if (Shift) + switch (key) { - modifierKeys |= ModifierKeys.Shift; - } + case "LeftCtrl" or "RightCtrl": + hotkey += "Ctrl"; + break; + case "LeftAlt" or "RightAlt": + hotkey += "Alt"; + break; + case "LeftShift" or "RightShift": + hotkey += "Shift"; + break; + case "LWin" or "RWin": + hotkey += "Win"; + break; - if (Win) - { - modifierKeys |= ModifierKeys.Windows; + default: + hotkey += key; + break; } + } - if (Ctrl) + return hotkey; + } + + // Converts any WPF Control hotkey e.g. Alt + X -> LeftAlt+X + public readonly string ToHotkeyRawString(string wpfHotkey) + { + var hotkey = string.Empty; + + foreach (var key in wpfHotkey.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + if (!string.IsNullOrEmpty(hotkey)) + hotkey += "+"; + + switch (key) { - modifierKeys |= ModifierKeys.Control; - } + case "Ctrl": + hotkey += "LeftCtrl"; + break; + case "Alt": + hotkey += "LeftAlt"; + break; + case "Shift": + hotkey += "LeftShift"; + break; + case "Win": + hotkey += "LWin"; + break; - return modifierKeys; + default: + hotkey += key; + break; + } } - } - public HotkeyModel(string hotkeyString) - { - Parse(hotkeyString); + return hotkey; } - public HotkeyModel(bool alt, bool shift, bool win, bool ctrl, Key key) + public bool Alt { get; set; } + public bool Shift { get; set; } + public bool Win { get; set; } + public bool Ctrl { get; set; } + + public Key CharKey { get; set; } = Key.None; + + /// + /// Validate hotkey for WPF control only + /// + /// Try to validate hotkey as a KeyGesture. + /// + public bool ValidateForWpf(bool validateKeyGestrue = false) { - Alt = alt; - Shift = shift; - Win = win; - Ctrl = ctrl; - CharKey = key; + Parse(HotkeyRaw); + + switch (CharKey) + { + case Key.LeftAlt: + case Key.RightAlt: + case Key.LeftCtrl: + case Key.RightCtrl: + case Key.LeftShift: + case Key.RightShift: + case Key.LWin: + case Key.RWin: + case Key.None: + return false; + default: + if (validateKeyGestrue) + { + try + { + KeyGesture keyGesture = new KeyGesture(CharKey, ModifierKeys); + } + catch (System.Exception e) when + (e is NotSupportedException || e is InvalidEnumArgumentException) + { + return false; + } + } + + if (ModifierKeys == ModifierKeys.None) + { + return !IsPrintableCharacter(CharKey); + } + else + { + return true; + } + } } private void Parse(string hotkeyString) @@ -70,29 +162,38 @@ private void Parse(string hotkeyString) return; } - List keys = hotkeyString.Replace(" ", "").Split('+').ToList(); - if (keys.Contains("Alt")) + List keys = hotkeyString.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); + + if (keys.Contains("Alt") || keys.Contains("LeftAlt") || keys.Contains("RightAlt")) { Alt = true; keys.Remove("Alt"); + keys.Remove("LeftAlt"); + keys.Remove("RightAlt"); } - if (keys.Contains("Shift")) + if (keys.Contains("Shift") || keys.Contains("LeftShift") || keys.Contains("RightShift")) { Shift = true; keys.Remove("Shift"); + keys.Remove("LeftShift"); + keys.Remove("RightShift"); } - if (keys.Contains("Win")) + if (keys.Contains("Win") || keys.Contains("LWin") || keys.Contains("RWin")) { Win = true; keys.Remove("Win"); + keys.Remove("LWin"); + keys.Remove("RWin"); } - if (keys.Contains("Ctrl")) + if (keys.Contains("Ctrl") || keys.Contains("LeftCtrl") || keys.Contains("RightCtrl")) { Ctrl = true; keys.Remove("Ctrl"); + keys.Remove("LeftCtrl"); + keys.Remove("RightCtrl"); } if (keys.Count == 1) @@ -117,82 +218,37 @@ private void Parse(string hotkeyString) } } - public override string ToString() + private static readonly Dictionary specialSymbolDictionary = new Dictionary { - return string.Join(" + ", EnumerateDisplayKeys()); - } + { Key.Space, "Space" }, { Key.Oem3, "~" } + }; - public IEnumerable EnumerateDisplayKeys() + public ModifierKeys ModifierKeys { - if (Ctrl && CharKey is not (Key.LeftCtrl or Key.RightCtrl)) - { - yield return "Ctrl"; - } - - if (Alt && CharKey is not (Key.LeftAlt or Key.RightAlt)) - { - yield return "Alt"; - } - - if (Shift && CharKey is not (Key.LeftShift or Key.RightShift)) + get { - yield return "Shift"; - } + ModifierKeys modifierKeys = ModifierKeys.None; + if (Alt) + { + modifierKeys |= ModifierKeys.Alt; + } - if (Win && CharKey is not (Key.LWin or Key.RWin)) - { - yield return "Win"; - } + if (Shift) + { + modifierKeys |= ModifierKeys.Shift; + } - if (CharKey != Key.None) - { - yield return specialSymbolDictionary.TryGetValue(CharKey, out var value) - ? value - : CharKey.ToString(); - } - } + if (Win) + { + modifierKeys |= ModifierKeys.Windows; + } - /// - /// Validate hotkey - /// - /// Try to validate hotkey as a KeyGesture. - /// - public bool Validate(bool validateKeyGestrue = false) - { - switch (CharKey) - { - case Key.LeftAlt: - case Key.RightAlt: - case Key.LeftCtrl: - case Key.RightCtrl: - case Key.LeftShift: - case Key.RightShift: - case Key.LWin: - case Key.RWin: - case Key.None: - return false; - default: - if (validateKeyGestrue) - { - try - { - KeyGesture keyGesture = new KeyGesture(CharKey, ModifierKeys); - } - catch (System.Exception e) when - (e is NotSupportedException || e is InvalidEnumArgumentException) - { - return false; - } - } + if (Ctrl) + { + modifierKeys |= ModifierKeys.Control; + } - if (ModifierKeys == ModifierKeys.None) - { - return !IsPrintableCharacter(CharKey); - } - else - { - return true; - } + return modifierKeys; } } @@ -224,7 +280,7 @@ private static bool IsPrintableCharacter(Key key) public override int GetHashCode() { - return HashCode.Combine(ModifierKeys, CharKey); + return HotkeyRaw.GetHashCode(); } } } diff --git a/Flow.Launcher.Infrastructure/KeyConstant.cs b/Flow.Launcher.Infrastructure/KeyConstant.cs index 31748517643..0deb4dc2306 100644 --- a/Flow.Launcher.Infrastructure/KeyConstant.cs +++ b/Flow.Launcher.Infrastructure/KeyConstant.cs @@ -2,8 +2,9 @@ { public static class KeyConstant { - public const string Ctrl = nameof(Ctrl); public const string Alt = nameof(Alt); + public const string LeftAlt = nameof(LeftAlt); + public const string Ctrl = nameof(Ctrl); public const string Space = nameof(Space); } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index f70c4559b38..f5b71e19727 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -38,7 +38,7 @@ public void Save() _storage.Save(); } - public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}"; + public string Hotkey { get; set; } = $"{KeyConstant.LeftAlt} + {KeyConstant.Space}"; private string _openResultModifiers = KeyConstant.Alt; public string OpenResultModifiers diff --git a/Flow.Launcher.Plugin/ActionContext.cs b/Flow.Launcher.Plugin/ActionContext.cs index 9e05bbd0617..7ec91285d6c 100644 --- a/Flow.Launcher.Plugin/ActionContext.cs +++ b/Flow.Launcher.Plugin/ActionContext.cs @@ -38,6 +38,14 @@ public class SpecialKeyState /// True if the Windows key is pressed. /// public bool WinPressed { get; set; } + public bool LeftShiftPressed { get; set; } + public bool RightShiftPressed { get; set; } + public bool LeftCtrlPressed { get; set; } + public bool RightCtrlPressed { get; set; } + public bool LeftAltPressed { get; set; } + public bool RightAltPressed { get; set; } + public bool LWinPressed { get; set; } + public bool RWinPressed { get; set; } /// /// Get this object represented as a flag combination. diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml index db99b704a52..7801b656ba0 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml @@ -104,7 +104,8 @@ VerticalAlignment="Center" HorizontalContentAlignment="Left" DefaultHotkey="" - Type="CustomQueryHotkey" /> + Type="CustomQueryHotkey" + IsWPFHotkeyControl="False" /> (); _settings = Ioc.Default.GetService(); - SetHotkey(_settings.Hotkey, OnToggleHotkey); + ChefKeysManager.RegisterHotkey(_settings.Hotkey, ToggleHotkey); if (_settings.EnableDialogJump) { - SetHotkey(_settings.DialogJumpHotkey, DialogJump.OnToggleHotkey); + ChefKeysManager.RegisterHotkey(_settings.DialogJumpHotkey, DialogJump.ToggleHotkey); } - LoadCustomPluginHotkey(); - } + ChefKeysManager.Start(); - internal static void OnToggleHotkey(object sender, HotkeyEventArgs args) - { - if (!_mainViewModel.ShouldIgnoreHotkeys()) - _mainViewModel.ToggleFlowLauncher(); + LoadCustomPluginHotkey(); } - internal static void OnToggleHotkeyWithChefKeys() + internal static void ToggleHotkey() { if (!_mainViewModel.ShouldIgnoreHotkeys()) _mainViewModel.ToggleFlowLauncher(); } - private static void SetHotkey(string hotkeyStr, EventHandler action) - { - var hotkey = new HotkeyModel(hotkeyStr); - SetHotkey(hotkey, action); - } - - private static void SetWithChefKeys(string hotkeyStr) - { - try - { - ChefKeysManager.RegisterHotkey(hotkeyStr, hotkeyStr, OnToggleHotkeyWithChefKeys); - ChefKeysManager.Start(); - } - catch (Exception e) - { - App.API.LogError(ClassName, - string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}", - e.Message, - e.StackTrace)); - string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); - string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); - App.API.ShowMsgBox(errorMsg, errorMsgTitle); - } - } - - internal static void SetHotkey(HotkeyModel hotkey, EventHandler action) + internal static void RegisterHotkey(string hotkey, string previousHotkey, Action action) { - string hotkeyStr = hotkey.ToString(); try { - if (hotkeyStr == "LWin" || hotkeyStr == "RWin") - { - SetWithChefKeys(hotkeyStr); - return; - } - - HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action); + ChefKeysManager.RegisterHotkey(hotkey, previousHotkey, action); } catch (Exception e) { @@ -86,49 +50,35 @@ internal static void SetHotkey(HotkeyModel hotkey, EventHandler string.Format("|HotkeyMapper.SetHotkey|Error registering hotkey {2}: {0} \nStackTrace:{1}", e.Message, e.StackTrace, - hotkeyStr)); - string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkeyStr); + hotkey)); + string errorMsg = string.Format(App.API.GetTranslation("registerHotkeyFailed"), hotkey); string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } } - internal static void RemoveHotkey(string hotkeyStr) + internal static void UnregisterHotkey(string hotkey) { try { - if (hotkeyStr == "LWin" || hotkeyStr == "RWin") - { - RemoveWithChefKeys(hotkeyStr); - return; - } - - if (!string.IsNullOrEmpty(hotkeyStr)) - HotkeyManager.Current.Remove(hotkeyStr); + if (!string.IsNullOrEmpty(hotkey)) + ChefKeysManager.UnregisterHotkey(hotkey); } catch (Exception e) { App.API.LogError(ClassName, - string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}", + string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey {2}: {0} \nStackTrace:{1}", e.Message, - e.StackTrace)); - string errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkeyStr); + e.StackTrace, + hotkey)); + string errorMsg = string.Format(App.API.GetTranslation("unregisterHotkeyFailed"), hotkey); string errorMsgTitle = App.API.GetTranslation("MessageBoxTitle"); App.API.ShowMsgBox(errorMsg, errorMsgTitle); } } - private static void RemoveWithChefKeys(string hotkeyStr) - { - ChefKeysManager.UnregisterHotkey(hotkeyStr); - ChefKeysManager.Stop(); - } - internal static void LoadCustomPluginHotkey() { - if (_settings.CustomPluginHotkeys == null) - return; - foreach (CustomPluginHotkey hotkey in _settings.CustomPluginHotkeys) { SetCustomQueryHotkey(hotkey); @@ -137,7 +87,7 @@ internal static void LoadCustomPluginHotkey() internal static void SetCustomQueryHotkey(CustomPluginHotkey hotkey) { - SetHotkey(hotkey.Hotkey, (s, e) => + ChefKeysManager.RegisterHotkey(hotkey.Hotkey, () => { if (_mainViewModel.ShouldIgnoreHotkeys()) return; @@ -147,22 +97,13 @@ internal static void SetCustomQueryHotkey(CustomPluginHotkey hotkey) }); } - internal static bool CheckAvailability(HotkeyModel currentHotkey) + internal static bool CanRegisterHotkey(string hotkey) { - try - { - HotkeyManager.Current.AddOrReplace("HotkeyAvailabilityTest", currentHotkey.CharKey, currentHotkey.ModifierKeys, (sender, e) => { }); + return ChefKeysManager.CanRegisterHotkey(hotkey); + } - return true; - } - catch - { - } - finally - { - HotkeyManager.Current.Remove("HotkeyAvailabilityTest"); - } + internal static bool CheckHotkeyAvailability(string hotkey) => ChefKeysManager.IsAvailable(hotkey); + + internal static bool CheckHotkeyValid(string hotkey) => ChefKeysManager.IsValidHotkey(hotkey); - return false; - } } diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 89bfde3497a..a36db0d032b 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -1,4 +1,5 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; +using System.Globalization; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; @@ -13,6 +14,8 @@ namespace Flow.Launcher { public partial class HotkeyControl { + private HotkeyControlDialog hotkeyControlDialog; + public string WindowTitle { get { return (string)GetValue(WindowTitleProperty); } set { SetValue(WindowTitleProperty, value); } @@ -61,6 +64,13 @@ private static void OnHotkeyChanged(DependencyObject d, DependencyPropertyChange return; } + //hotkeyControl.SetKeysToDisplay(new HotkeyModel(hotkeyControl.Hotkey)); + //hotkeyControl.CurrentHotkey = new HotkeyModel(hotkeyControl.Hotkey); + + var hotkeyModel = new HotkeyModel(hotkeyControl.Hotkey); + hotkeyControl.SetKeysToDisplay(hotkeyModel); + hotkeyControl.CurrentHotkey = hotkeyModel; + hotkeyControl.RefreshHotkeyInterface(hotkeyControl.Hotkey); } @@ -215,6 +225,19 @@ public string Hotkey } } + public static readonly DependencyProperty IsWPFHotkeyControlProperty = DependencyProperty.Register( + nameof(IsWPFHotkeyControl), + typeof(bool), + typeof(HotkeyControl), + new PropertyMetadata(true) + ); + + public bool IsWPFHotkeyControl + { + get { return (bool)GetValue(IsWPFHotkeyControlProperty); } + set { SetValue(IsWPFHotkeyControlProperty, value); } + } + public HotkeyControl() { InitializeComponent(); @@ -231,14 +254,17 @@ private void RefreshHotkeyInterface(string hotkey) CurrentHotkey = new HotkeyModel(hotkey); } - private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => - hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); + private static bool CheckHotkeyValid(string hotkey) + => HotKeyMapper.CheckHotkeyValid(hotkey); + + private static bool CheckWPFHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) + => hotkey.ValidateForWpf(validateKeyGesture); public string EmptyHotkey => App.API.GetTranslation("none"); public ObservableCollection KeysToDisplay { get; set; } = new(); - public HotkeyModel CurrentHotkey { get; private set; } = new(false, false, false, false, Key.None); + public HotkeyModel CurrentHotkey { get; private set; } = new(); public void GetNewHotkey(object sender, RoutedEventArgs e) { @@ -247,24 +273,15 @@ public void GetNewHotkey(object sender, RoutedEventArgs e) private async Task OpenHotkeyDialogAsync() { - if (!string.IsNullOrEmpty(Hotkey)) - { - HotKeyMapper.RemoveHotkey(Hotkey); - } - - var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, WindowTitle) - { - Owner = Window.GetWindow(this) - }; - - await dialog.ShowAsync(); - switch (dialog.ResultType) + hotkeyControlDialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, IsWPFHotkeyControl, WindowTitle); + await hotkeyControlDialog.ShowAsync(); + switch (hotkeyControlDialog.ResultType) { case HotkeyControlDialog.EResultType.Cancel: - SetHotkey(Hotkey); + //SetHotkey(Hotkey); return; case HotkeyControlDialog.EResultType.Save: - SetHotkey(dialog.ResultValue); + SetHotkey(hotkeyControlDialog.ResultValue); break; case HotkeyControlDialog.EResultType.Delete: Delete(); @@ -274,31 +291,30 @@ private async Task OpenHotkeyDialogAsync() private void SetHotkey(HotkeyModel keyModel, bool triggerValidate = true) { + if (string.IsNullOrEmpty(keyModel.HotkeyRaw)) + return; + if (triggerValidate) { - bool hotkeyAvailable; - // TODO: This is a temporary way to enforce changing only the open flow hotkey to Win, and will be removed by PR #3157 - if (keyModel.ToString() == "LWin" || keyModel.ToString() == "RWin") - { - hotkeyAvailable = true; - } - else - { - hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture); - } + var hotkeyAvailable = IsWPFHotkeyControl + ? CheckWPFHotkeyAvailability(keyModel, ValidateKeyGesture) + : CheckHotkeyValid(keyModel.HotkeyRaw); if (!hotkeyAvailable) - { return; - } - Hotkey = keyModel.ToString(); + Hotkey = keyModel.ToWPFHotkeyString(); SetKeysToDisplay(CurrentHotkey); + + // If exists then will be unregistered, if doesn't no errors will be thrown. + if (IsWPFHotkeyControl) + HotKeyMapper.UnregisterHotkey(keyModel.HotkeyRaw); + ChangeHotkey?.Execute(keyModel); } else { - Hotkey = keyModel.ToString(); + Hotkey = keyModel.ToWPFHotkeyString(); ChangeHotkey?.Execute(keyModel); } } @@ -306,16 +322,16 @@ private void SetHotkey(HotkeyModel keyModel, bool triggerValidate = true) public void Delete() { if (!string.IsNullOrEmpty(Hotkey)) - HotKeyMapper.RemoveHotkey(Hotkey); + HotKeyMapper.UnregisterHotkey(Hotkey); Hotkey = ""; - SetKeysToDisplay(new HotkeyModel(false, false, false, false, Key.None)); + SetKeysToDisplay(new HotkeyModel(Hotkey)); } private void SetKeysToDisplay(HotkeyModel? hotkey) { KeysToDisplay.Clear(); - if (hotkey == null || hotkey == default(HotkeyModel)) + if (hotkey == null || string.IsNullOrEmpty(hotkey.Value.HotkeyRaw)) { KeysToDisplay.Add(EmptyHotkey); return; @@ -329,7 +345,16 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) public void SetHotkey(string? keyStr, bool triggerValidate = true) { - SetHotkey(new HotkeyModel(keyStr), triggerValidate); + if (string.IsNullOrEmpty(keyStr)) + return; + + // index 0 - new hotkey to be added, index 1 - old hotkey to be removed + var hotkeyNewOld = keyStr.Split(":"); + var hotkey = new HotkeyModel(hotkeyNewOld[0]) + { + PreviousHotkey = hotkeyNewOld.Length == 2 ? hotkeyNewOld[1] : hotkeyNewOld[0] + }; + SetHotkey(hotkey, triggerValidate); } } } diff --git a/Flow.Launcher/HotkeyControlDialog.xaml.cs b/Flow.Launcher/HotkeyControlDialog.xaml.cs index c7af8c5b8bb..77ed9558ffb 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -10,6 +10,8 @@ using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using ModernWpf.Controls; +using ChefKeys; +using System.Collections.Generic; namespace Flow.Launcher; @@ -21,7 +23,15 @@ public partial class HotkeyControlDialog : ContentDialog private Action? _overwriteOtherHotkey; private string DefaultHotkey { get; } public string WindowTitle { get; } - public HotkeyModel CurrentHotkey { get; private set; } + + public HotkeyModel CurrentHotkey; + + public HotkeyModel HotkeyToUpdate; + + private bool isWPFHotkeyControl = true; + + private bool clearKeysOnFirstType; + public ObservableCollection KeysToDisplay { get; } = new(); public enum EResultType @@ -35,45 +45,56 @@ public enum EResultType public string ResultValue { get; private set; } = string.Empty; public static string EmptyHotkey => App.API.GetTranslation("none"); - private static bool isOpenFlowHotkey; - - public HotkeyControlDialog(string hotkey, string defaultHotkey, string windowTitle = "") + public HotkeyControlDialog( + string hotkey, + string defaultHotkey, + bool isWPFHotkeyControl, + string windowTitle = "") { + this.isWPFHotkeyControl = isWPFHotkeyControl; + WindowTitle = windowTitle switch { "" or null => App.API.GetTranslation("hotkeyRegTitle"), _ => windowTitle }; DefaultHotkey = defaultHotkey; + CurrentHotkey = new HotkeyModel(hotkey); + // This is a requirement to be set with current hotkey for the WPF hotkey control when saving without any new changes + HotkeyToUpdate = new HotkeyModel(hotkey); + SetKeysToDisplay(CurrentHotkey); + clearKeysOnFirstType = true; InitializeComponent(); - // TODO: This is a temporary way to enforce changing only the open flow hotkey to Win, and will be removed by PR #3157 - isOpenFlowHotkey = _hotkeySettings.RegisteredHotkeys - .Any(x => x.DescriptionResourceKey == "flowlauncherHotkey" - && x.Hotkey.ToString() == hotkey); - ChefKeysManager.StartMenuEnableBlocking = true; - ChefKeysManager.Start(); } private void Reset(object sender, RoutedEventArgs routedEventArgs) { - SetKeysToDisplay(new HotkeyModel(DefaultHotkey)); + HotkeyToUpdate = new HotkeyModel(DefaultHotkey); + SetKeysToDisplay(HotkeyToUpdate); + clearKeysOnFirstType = true; } private void Delete(object sender, RoutedEventArgs routedEventArgs) { + HotkeyToUpdate.Clear(); KeysToDisplay.Clear(); KeysToDisplay.Add(EmptyHotkey); + tbMsg.Text = string.Empty; + SaveBtn.IsEnabled = true; + SaveBtn.Visibility = Visibility.Visible; + OverwriteBtn.IsEnabled = false; + OverwriteBtn.Visibility = Visibility.Collapsed; + Alert.Visibility = Visibility.Collapsed; } private void Cancel(object sender, RoutedEventArgs routedEventArgs) { ChefKeysManager.StartMenuEnableBlocking = false; - ChefKeysManager.Stop(); ResultType = EResultType.Cancel; Hide(); @@ -82,7 +103,6 @@ private void Cancel(object sender, RoutedEventArgs routedEventArgs) private void Save(object sender, RoutedEventArgs routedEventArgs) { ChefKeysManager.StartMenuEnableBlocking = false; - ChefKeysManager.Stop(); if (KeysToDisplay.Count == 1 && KeysToDisplay[0] == EmptyHotkey) { @@ -91,7 +111,9 @@ private void Save(object sender, RoutedEventArgs routedEventArgs) return; } ResultType = EResultType.Save; - ResultValue = string.Join("+", KeysToDisplay); + var newHotkey = string.Join("+", KeysToDisplay); + var oldHotkey = !string.IsNullOrEmpty(CurrentHotkey.HotkeyRaw) ? CurrentHotkey.HotkeyRaw : newHotkey; + ResultValue = string.Format("{0}:{1}", newHotkey, oldHotkey); Hide(); } @@ -102,20 +124,30 @@ private void OnPreviewKeyDown(object sender, KeyEventArgs e) //when alt is pressed, the real key should be e.SystemKey Key key = e.Key == Key.System ? e.SystemKey : e.Key; + if (clearKeysOnFirstType) + { + KeysToDisplay.Clear(); + HotkeyToUpdate.Clear(); + clearKeysOnFirstType = false; + } + if (ChefKeysManager.StartMenuBlocked && key.ToString() == ChefKeysManager.StartMenuSimulatedKey) return; - SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers(); + AddKey(key); + + SetKeysToDisplay(HotkeyToUpdate); + } - var hotkeyModel = new HotkeyModel( - specialKeyState.AltPressed, - specialKeyState.ShiftPressed, - specialKeyState.WinPressed, - specialKeyState.CtrlPressed, - key); + private void AddKey(Key key) + { + if (HotkeyToUpdate.GetLastKeySet() == key.ToString()) + return; - CurrentHotkey = hotkeyModel; - SetKeysToDisplay(CurrentHotkey); + if (MaxKeysLimitReached()) + return; + + HotkeyToUpdate.AddString(key.ToString()); } private void SetKeysToDisplay(HotkeyModel? hotkey) @@ -123,7 +155,7 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) _overwriteOtherHotkey = null; KeysToDisplay.Clear(); - if (hotkey == null || hotkey == default(HotkeyModel)) + if (hotkey is null || string.IsNullOrEmpty(hotkey.Value.HotkeyRaw)) { KeysToDisplay.Add(EmptyHotkey); return; @@ -137,7 +169,10 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) if (tbMsg == null) return; - if (_hotkeySettings.RegisteredHotkeys.FirstOrDefault(v => v.Hotkey == hotkey) is { } registeredHotkeyData) + if (_hotkeySettings.RegisteredHotkeys + .FirstOrDefault(v => v.Hotkey == hotkey + || v.Hotkey.HotkeyRaw == hotkey.Value.HotkeyRaw) + is { } registeredHotkeyData) { var description = string.Format( App.API.GetTranslation(registeredHotkeyData.DescriptionResourceKey), @@ -156,6 +191,7 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) OverwriteBtn.Visibility = Visibility.Visible; _overwriteOtherHotkey = registeredHotkeyData.RemoveHotkey; } + else { tbMsg.Text = string.Format( @@ -173,7 +209,11 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) OverwriteBtn.IsEnabled = false; OverwriteBtn.Visibility = Visibility.Collapsed; - if (!CheckHotkeyAvailability(hotkey.Value, true)) + var isHotkeyAvailable = !isWPFHotkeyControl + ? CheckHotkeyAvailability(hotkey.Value.HotkeyRaw) + : CheckWPFHotkeyAvailability(hotkey.Value, true); + + if (!isHotkeyAvailable) { tbMsg.Text = App.API.GetTranslation("hotkeyUnavailable"); Alert.Visibility = Visibility.Visible; @@ -188,13 +228,13 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) } } - private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) - { - if (isOpenFlowHotkey && (hotkey.ToString() == "LWin" || hotkey.ToString() == "RWin")) - return true; + private static bool CheckHotkeyAvailability(string hotkey) + => HotKeyMapper.CanRegisterHotkey(hotkey); - return hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); - } + private static bool CheckWPFHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) + => hotkey.ValidateForWpf(validateKeyGesture) && HotKeyMapper.CheckHotkeyAvailability(hotkey.HotkeyRaw); + + private bool MaxKeysLimitReached() => isWPFHotkeyControl ? KeysToDisplay.Count == 2 : KeysToDisplay.Count == 4; private void Overwrite(object sender, RoutedEventArgs e) { diff --git a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml index cf0dff9ab37..6bd84e08c26 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml +++ b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml @@ -113,10 +113,11 @@ + WindowTitle="{DynamicResource flowlauncherHotkey}" + IsWPFHotkeyControl="False" /> diff --git a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs index 37767f1285f..e9b5b383c7f 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs +++ b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs @@ -30,7 +30,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e) [RelayCommand] private static void SetTogglingHotkey(HotkeyModel hotkey) { - HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey); + HotKeyMapper.RegisterHotkey(hotkey.HotkeyRaw, hotkey.PreviousHotkey, HotKeyMapper.ToggleHotkey); } public Brush PreviewBackground diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs index b47b53654b4..fe51c232d64 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs @@ -158,11 +158,12 @@ public bool EnableDialogJump DialogJump.SetupDialogJump(value); if (Settings.EnableDialogJump) { - HotKeyMapper.SetHotkey(new(Settings.DialogJumpHotkey), DialogJump.OnToggleHotkey); + // TODO: How can we parse the previousHotkey parameter here? + HotKeyMapper.RegisterHotkey(Settings.DialogJumpHotkey, string.Empty, DialogJump.ToggleHotkey); } else { - HotKeyMapper.RemoveHotkey(Settings.DialogJumpHotkey); + HotKeyMapper.UnregisterHotkey(Settings.DialogJumpHotkey); } } } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs index 9e6a31dc772..d8c176375a3 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -32,7 +32,7 @@ public SettingsPaneHotkeyViewModel(Settings settings) [RelayCommand] private void SetTogglingHotkey(HotkeyModel hotkey) { - HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey); + HotKeyMapper.RegisterHotkey(hotkey.HotkeyRaw, hotkey.PreviousHotkey, HotKeyMapper.ToggleHotkey); } [RelayCommand] @@ -40,7 +40,7 @@ private void SetDialogJumpHotkey(HotkeyModel hotkey) { if (Settings.EnableDialogJump) { - HotKeyMapper.SetHotkey(hotkey, DialogJump.OnToggleHotkey); + HotKeyMapper.RegisterHotkey(hotkey.HotkeyRaw, hotkey.PreviousHotkey, DialogJump.ToggleHotkey); } } @@ -65,7 +65,7 @@ private void CustomHotkeyDelete() if (result is MessageBoxResult.Yes) { Settings.CustomPluginHotkeys.Remove(item); - HotKeyMapper.RemoveHotkey(item.Hotkey); + HotKeyMapper.UnregisterHotkey(item.Hotkey); } } @@ -92,7 +92,7 @@ private void CustomHotkeyEdit() var index = Settings.CustomPluginHotkeys.IndexOf(settingItem); Settings.CustomPluginHotkeys[index] = new CustomPluginHotkey(window.Hotkey, window.ActionKeyword); - HotKeyMapper.RemoveHotkey(settingItem.Hotkey); // remove origin hotkey + HotKeyMapper.UnregisterHotkey(settingItem.Hotkey); // remove origin hotkey HotKeyMapper.SetCustomQueryHotkey(Settings.CustomPluginHotkeys[index]); // set new hotkey } diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index d82d6baa0c2..efff4f87e30 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -33,7 +33,8 @@ Sub="{DynamicResource flowlauncherHotkeyToolTip}"> diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index d492f28c58c..efc956c0c0f 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -2073,6 +2073,8 @@ public void Show() MainWindowVisibilityStatus = true; VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true }); + Application.Current.MainWindow.Focus(); // TEST + // Switch keyboard layout if (StartWithEnglishMode) {