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 c412fb32f5a..4f16ae81267 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -15,7 +15,7 @@ public class Settings : BaseModel, IHotkeySettings { private string language = Constant.SystemLanguageCode; private string _theme = Constant.DefaultTheme; - public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}"; + public string Hotkey { get; set; } = $"{KeyConstant.LeftAlt} + {KeyConstant.Space}"; public string OpenResultModifiers { get; set; } = KeyConstant.Alt; public string ColorScheme { get; set; } = "System"; public bool ShowOpenResultHotkey { get; set; } = true; @@ -288,27 +288,27 @@ public List RegisteredHotkeys // Customizeable hotkeys if(!string.IsNullOrEmpty(Hotkey)) list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = "")); - if(!string.IsNullOrEmpty(PreviewHotkey)) + if (!string.IsNullOrEmpty(PreviewHotkey)) list.Add(new(PreviewHotkey, "previewHotkey", () => PreviewHotkey = "")); - if(!string.IsNullOrEmpty(AutoCompleteHotkey)) + if (!string.IsNullOrEmpty(AutoCompleteHotkey)) list.Add(new(AutoCompleteHotkey, "autoCompleteHotkey", () => AutoCompleteHotkey = "")); - if(!string.IsNullOrEmpty(AutoCompleteHotkey2)) + if (!string.IsNullOrEmpty(AutoCompleteHotkey2)) list.Add(new(AutoCompleteHotkey2, "autoCompleteHotkey", () => AutoCompleteHotkey2 = "")); - if(!string.IsNullOrEmpty(SelectNextItemHotkey)) + if (!string.IsNullOrEmpty(SelectNextItemHotkey)) list.Add(new(SelectNextItemHotkey, "SelectNextItemHotkey", () => SelectNextItemHotkey = "")); - if(!string.IsNullOrEmpty(SelectNextItemHotkey2)) + if (!string.IsNullOrEmpty(SelectNextItemHotkey2)) list.Add(new(SelectNextItemHotkey2, "SelectNextItemHotkey", () => SelectNextItemHotkey2 = "")); - if(!string.IsNullOrEmpty(SelectPrevItemHotkey)) + if (!string.IsNullOrEmpty(SelectPrevItemHotkey)) list.Add(new(SelectPrevItemHotkey, "SelectPrevItemHotkey", () => SelectPrevItemHotkey = "")); - if(!string.IsNullOrEmpty(SelectPrevItemHotkey2)) + if (!string.IsNullOrEmpty(SelectPrevItemHotkey2)) list.Add(new(SelectPrevItemHotkey2, "SelectPrevItemHotkey", () => SelectPrevItemHotkey2 = "")); - if(!string.IsNullOrEmpty(SettingWindowHotkey)) + if (!string.IsNullOrEmpty(SettingWindowHotkey)) list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = "")); - if(!string.IsNullOrEmpty(OpenContextMenuHotkey)) + if (!string.IsNullOrEmpty(OpenContextMenuHotkey)) list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = "")); - if(!string.IsNullOrEmpty(SelectNextPageHotkey)) + if (!string.IsNullOrEmpty(SelectNextPageHotkey)) list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = "")); - if(!string.IsNullOrEmpty(SelectPrevPageHotkey)) + if (!string.IsNullOrEmpty(SelectPrevPageHotkey)) list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = "")); if (!string.IsNullOrEmpty(CycleHistoryUpHotkey)) list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = "")); diff --git a/Flow.Launcher.Plugin/ActionContext.cs b/Flow.Launcher.Plugin/ActionContext.cs index e31c8e31d8b..a38907a9d75 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 068afda15b4..7815ce9abb9 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml @@ -107,7 +107,8 @@ VerticalAlignment="Center" HorizontalContentAlignment="Left" HotkeySettings="{Binding Settings}" - DefaultHotkey="" /> + DefaultHotkey="" + IsWPFHotkeyControl="False" /> + all @@ -97,7 +98,6 @@ - diff --git a/Flow.Launcher/Helper/HotKeyMapper.cs b/Flow.Launcher/Helper/HotKeyMapper.cs index 8b30b8be1f5..17aed066770 100644 --- a/Flow.Launcher/Helper/HotKeyMapper.cs +++ b/Flow.Launcher/Helper/HotKeyMapper.cs @@ -1,11 +1,11 @@ using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.UserSettings; using System; -using NHotkey; -using NHotkey.Wpf; using Flow.Launcher.Core.Resource; using Flow.Launcher.ViewModel; using Flow.Launcher.Core; +using ChefKeys; +using System.Windows.Input; namespace Flow.Launcher.Helper; @@ -13,56 +13,37 @@ internal static class HotKeyMapper { private static Settings _settings; private static MainViewModel _mainViewModel; - + internal static void Initialize(MainViewModel mainVM) { _mainViewModel = mainVM; _settings = _mainViewModel.Settings; - SetHotkey(_settings.Hotkey, OnToggleHotkey); + ChefKeysManager.RegisterHotkey(_settings.Hotkey, ToggleHotkey); + ChefKeysManager.Start(); + LoadCustomPluginHotkey(); } - internal static void OnToggleHotkey(object sender, HotkeyEventArgs args) + internal static void ToggleHotkey() { if (!_mainViewModel.ShouldIgnoreHotkeys()) _mainViewModel.ToggleFlowLauncher(); } - private static void SetHotkey(string hotkeyStr, EventHandler action) + internal static void RegisterHotkey(string hotkey, string previousHotkey, Action action) { - var hotkey = new HotkeyModel(hotkeyStr); - SetHotkey(hotkey, action); - } - - internal static void SetHotkey(HotkeyModel hotkey, EventHandler action) - { - string hotkeyStr = hotkey.ToString(); - try - { - HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action); - } - catch (Exception) - { - string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr); - string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle"); - MessageBoxEx.Show(errorMsg, errorMsgTitle); - } + ChefKeysManager.RegisterHotkey(hotkey, previousHotkey, action); } - internal static void RemoveHotkey(string hotkeyStr) + internal static void UnregisterHotkey(string hotkey) { - if (!string.IsNullOrEmpty(hotkeyStr)) - { - HotkeyManager.Current.Remove(hotkeyStr); - } + if (!string.IsNullOrEmpty(hotkey)) + ChefKeysManager.UnregisterHotkey(hotkey); } internal static void LoadCustomPluginHotkey() { - if (_settings.CustomPluginHotkeys == null) - return; - foreach (CustomPluginHotkey hotkey in _settings.CustomPluginHotkeys) { SetCustomQueryHotkey(hotkey); @@ -71,7 +52,7 @@ internal static void LoadCustomPluginHotkey() internal static void SetCustomQueryHotkey(CustomPluginHotkey hotkey) { - SetHotkey(hotkey.Hotkey, (s, e) => + ChefKeysManager.RegisterHotkey(hotkey.Hotkey, () => { if (_mainViewModel.ShouldIgnoreHotkeys()) return; @@ -81,22 +62,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 a42bde7c9cd..1fdcc6afb0c 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.ObjectModel; +using System.Globalization; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; @@ -12,6 +13,8 @@ namespace Flow.Launcher { public partial class HotkeyControl { + private HotkeyControlDialog hotkeyControlDialog; + public IHotkeySettings HotkeySettings { get { return (IHotkeySettings)GetValue(HotkeySettingsProperty); } set { SetValue(HotkeySettingsProperty, value); } @@ -71,8 +74,12 @@ private static void OnHotkeyChanged(DependencyObject d, DependencyPropertyChange return; } - hotkeyControl.SetKeysToDisplay(new HotkeyModel(hotkeyControl.Hotkey)); - hotkeyControl.CurrentHotkey = new HotkeyModel(hotkeyControl.Hotkey); + //hotkeyControl.SetKeysToDisplay(new HotkeyModel(hotkeyControl.Hotkey)); + //hotkeyControl.CurrentHotkey = new HotkeyModel(hotkeyControl.Hotkey); + + var hotkeyModel = new HotkeyModel(hotkeyControl.Hotkey); + hotkeyControl.SetKeysToDisplay(hotkeyModel); + hotkeyControl.CurrentHotkey = hotkeyModel; } @@ -103,6 +110,19 @@ public string Hotkey set { SetValue(HotkeyProperty, value); } } + 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(); @@ -111,14 +131,17 @@ public HotkeyControl() SetKeysToDisplay(CurrentHotkey); } - 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 => InternationalizationManager.Instance.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) @@ -128,20 +151,15 @@ public void GetNewHotkey(object sender, RoutedEventArgs e) private async Task OpenHotkeyDialog() { - if (!string.IsNullOrEmpty(Hotkey)) - { - HotKeyMapper.RemoveHotkey(Hotkey); - } - - var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, HotkeySettings, WindowTitle); - await dialog.ShowAsync(); - switch (dialog.ResultType) + hotkeyControlDialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, HotkeySettings, 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(); @@ -149,25 +167,32 @@ private async Task OpenHotkeyDialog() } } - private void SetHotkey(HotkeyModel keyModel, bool triggerValidate = true) { + if (string.IsNullOrEmpty(keyModel.HotkeyRaw)) + return; + if (triggerValidate) { - bool 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); } } @@ -175,16 +200,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; @@ -198,7 +223,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 a7b99f6704b..d149902fad2 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -6,8 +6,9 @@ using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.Hotkey; -using Flow.Launcher.Plugin; using ModernWpf.Controls; +using ChefKeys; +using System.Collections.Generic; namespace Flow.Launcher; @@ -19,7 +20,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 @@ -33,40 +42,68 @@ public enum EResultType public string ResultValue { get; private set; } = string.Empty; public static string EmptyHotkey => InternationalizationManager.Instance.GetTranslation("none"); - public HotkeyControlDialog(string hotkey, string defaultHotkey, IHotkeySettings hotkeySettings, string windowTitle = "") + public HotkeyControlDialog( + string hotkey, + string defaultHotkey, + IHotkeySettings hotkeySettings, + bool isWPFHotkeyControl, + string windowTitle = "") { + this.isWPFHotkeyControl = isWPFHotkeyControl; + WindowTitle = windowTitle switch { "" or null => InternationalizationManager.Instance.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); + _hotkeySettings = hotkeySettings; + SetKeysToDisplay(CurrentHotkey); + clearKeysOnFirstType = true; InitializeComponent(); + + ChefKeysManager.StartMenuEnableBlocking = true; } 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; + ResultType = EResultType.Cancel; Hide(); } private void Save(object sender, RoutedEventArgs routedEventArgs) { + ChefKeysManager.StartMenuEnableBlocking = false; + if (KeysToDisplay.Count == 1 && KeysToDisplay[0] == EmptyHotkey) { ResultType = EResultType.Delete; @@ -74,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(); } @@ -85,17 +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; - SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers(); + if (clearKeysOnFirstType) + { + KeysToDisplay.Clear(); + HotkeyToUpdate.Clear(); + clearKeysOnFirstType = false; + } - var hotkeyModel = new HotkeyModel( - specialKeyState.AltPressed, - specialKeyState.ShiftPressed, - specialKeyState.WinPressed, - specialKeyState.CtrlPressed, - key); + if (ChefKeysManager.StartMenuBlocked && key.ToString() == ChefKeysManager.StartMenuSimulatedKey) + return; - CurrentHotkey = hotkeyModel; - SetKeysToDisplay(CurrentHotkey); + AddKey(key); + + SetKeysToDisplay(HotkeyToUpdate); + } + + private void AddKey(Key key) + { + if (HotkeyToUpdate.GetLastKeySet() == key.ToString()) + return; + + if (MaxKeysLimitReached()) + return; + + HotkeyToUpdate.AddString(key.ToString()); } private void SetKeysToDisplay(HotkeyModel? hotkey) @@ -103,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; @@ -117,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( InternationalizationManager.Instance.GetTranslation(registeredHotkeyData.DescriptionResourceKey), @@ -136,6 +191,7 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) OverwriteBtn.Visibility = Visibility.Visible; _overwriteOtherHotkey = registeredHotkeyData.RemoveHotkey; } + else { tbMsg.Text = string.Format( @@ -153,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 = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable"); Alert.Visibility = Visibility.Visible; @@ -168,8 +228,13 @@ private void SetKeysToDisplay(HotkeyModel? hotkey) } } - private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => - hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); + private static bool CheckHotkeyAvailability(string hotkey) + => HotKeyMapper.CanRegisterHotkey(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/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 4c465d61f52..53c58ba8bfb 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -14,8 +14,6 @@ Plugins: {0} - fail to load and would be disabled, please contact plugin creator for help - Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. - Flow Launcher Could not start {0} Invalid Flow Launcher plugin file format Set as topmost in this query diff --git a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml index 6c6fcbb625f..dfb4a7b9486 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml +++ b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml @@ -113,11 +113,12 @@ + WindowTitle="{DynamicResource flowlauncherHotkey}" + IsWPFHotkeyControl="False" /> diff --git a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs index 7dfb85a8303..bb5124c16e6 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs +++ b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs @@ -27,7 +27,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); } } } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs index 6d8af9a3f62..ef9e484baba 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs @@ -33,7 +33,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] @@ -57,7 +57,7 @@ private void CustomHotkeyDelete() if (result is MessageBoxResult.Yes) { Settings.CustomPluginHotkeys.Remove(item); - HotKeyMapper.RemoveHotkey(item.Hotkey); + HotKeyMapper.UnregisterHotkey(item.Hotkey); } } diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index 22e3960ef19..61c671be9a1 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -33,11 +33,12 @@ Sub="{DynamicResource flowlauncherHotkeyToolTip}"> + WindowTitle="{DynamicResource flowlauncherHotkey}" + IsWPFHotkeyControl="False" />