add new hotkey support for win and four combo keys

This commit is contained in:
Jeremy 2025-01-01 13:50:42 +11:00
parent 21f22e6a59
commit 27ccd3b24c
14 changed files with 334 additions and 177 deletions

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.Hotkey
{
@ -13,6 +14,9 @@ namespace Flow.Launcher.Infrastructure.Hotkey
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;
private static readonly Dictionary<Key, string> specialSymbolDictionary = new Dictionary<Key, string>
@ -49,18 +53,56 @@ namespace Flow.Launcher.Infrastructure.Hotkey
}
}
public HotkeyModel(string hotkeyString)
// Used for WPF control only
public void SetHotkeyFromString(string hotkeyString)
{
Clear();
Parse(hotkeyString);
HotkeyRaw = ToChefKeysString();
}
public HotkeyModel(bool alt, bool shift, bool win, bool ctrl, Key key)
internal void SetHotkeyFromWPFControl(SpecialKeyState specialKeyState, Key key)
{
Alt = alt;
Shift = shift;
Win = win;
Ctrl = ctrl;
Alt = specialKeyState.AltPressed;
Shift = specialKeyState.ShiftPressed;
Win = specialKeyState.WinPressed;
Ctrl = specialKeyState.CtrlPressed;
CharKey = key;
HotkeyRaw = ToChefKeysString();
PreviousHotkey = string.Empty;
}
public HotkeyModel(string hotkey)
{
SetHotkeyFromString(hotkey);
}
//public HotkeyModel(bool alt, bool shift, bool win, bool ctrl, Key key)
//{
// Alt = alt;
// Shift = shift;
// Win = win;
// Ctrl = ctrl;
// CharKey = key;
//}
// Use for ChefKeys only
internal void AddString(string key)
{
HotkeyRaw = string.IsNullOrEmpty(HotkeyRaw) ? key : HotkeyRaw + "+" + key;
}
internal bool MaxKeysReached() => DisplayKeysRaw().Count() == 4;
internal void Clear()
{
Alt = false;
Shift = false;
Win = false;
Ctrl = false;
HotkeyRaw = string.Empty;
PreviousHotkey = string.Empty;
CharKey = Key.None;
}
private void Parse(string hotkeyString)
@ -71,28 +113,36 @@ namespace Flow.Launcher.Infrastructure.Hotkey
}
List<string> keys = hotkeyString.Replace(" ", "").Split('+').ToList();
if (keys.Contains("Alt"))
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,47 +167,71 @@ namespace Flow.Launcher.Infrastructure.Hotkey
}
}
public override string ToString()
public string ToChefKeysString()
{
return string.Join(" + ", EnumerateDisplayKeys());
var key = string.Join("+", EnumerateDisplayKeys(true));
return key;
}
public IEnumerable<string> EnumerateDisplayKeys()
public IEnumerable<string> DisplayKeysRaw() => !string.IsNullOrEmpty(HotkeyRaw) ? HotkeyRaw.Split('+') : Array.Empty<string>();
public IEnumerable<string> EnumerateDisplayKeys(bool forChefKeys = false)
{
if (Ctrl && CharKey is not (Key.LeftCtrl or Key.RightCtrl))
{
yield return "Ctrl";
yield return GetKeyString("Ctrl", forChefKeys);
}
if (Alt && CharKey is not (Key.LeftAlt or Key.RightAlt))
{
yield return "Alt";
yield return GetKeyString("Alt", forChefKeys);
}
if (Shift && CharKey is not (Key.LeftShift or Key.RightShift))
{
yield return "Shift";
yield return GetKeyString("Shift", forChefKeys);
}
if (Win && CharKey is not (Key.LWin or Key.RWin))
{
yield return "Win";
yield return GetKeyString("Win", forChefKeys);
}
if (CharKey != Key.None)
{
yield return specialSymbolDictionary.TryGetValue(CharKey, out var value)
? value
: CharKey.ToString();
: GetKeyString(CharKey.ToString(), forChefKeys);
}
}
private string GetKeyString(string key, bool convertToChefKeysString)
{
if (!convertToChefKeysString)
return key;
switch (key)
{
case "Alt":
return "LeftAlt";
case "Ctrl":
return "LeftCtrl";
case "Shift":
return "LeftShift";
case "Win":
return "LWin";
default:
return key;
}
}
/// <summary>
/// Validate hotkey
/// Validate hotkey for WPF control only
/// </summary>
/// <param name="validateKeyGestrue">Try to validate hotkey as a KeyGesture.</param>
/// <returns></returns>
public bool Validate(bool validateKeyGestrue = false)
public bool ValidateForWpf(bool validateKeyGestrue = false)
{
switch (CharKey)
{

View file

@ -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);
}
}
}

View file

@ -15,7 +15,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
private string language = "en";
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,32 +288,32 @@ namespace Flow.Launcher.Infrastructure.UserSettings
// 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))
list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = ""));
if(!string.IsNullOrEmpty(OpenContextMenuHotkey))
list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = ""));
if(!string.IsNullOrEmpty(SelectNextPageHotkey))
list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = ""));
if(!string.IsNullOrEmpty(SelectPrevPageHotkey))
list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = ""));
if (!string.IsNullOrEmpty(CycleHistoryUpHotkey))
list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = ""));
if (!string.IsNullOrEmpty(CycleHistoryDownHotkey))
list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = ""));
//if (!string.IsNullOrEmpty(SettingWindowHotkey))
// list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = ""));
//if (!string.IsNullOrEmpty(OpenContextMenuHotkey))
// list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = ""));
//if (!string.IsNullOrEmpty(SelectNextPageHotkey))
// list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = ""));
//if (!string.IsNullOrEmpty(SelectPrevPageHotkey))
// list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = ""));
//if (!string.IsNullOrEmpty(CycleHistoryUpHotkey))
// list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = ""));
//if (!string.IsNullOrEmpty(CycleHistoryDownHotkey))
// list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = ""));
// Custom Query Hotkeys
foreach (var customPluginHotkey in CustomPluginHotkeys)
@ -334,34 +334,34 @@ namespace Flow.Launcher.Infrastructure.UserSettings
new("Down", "HotkeyLeftRightDesc"),
new("Left", "HotkeyUpDownDesc"),
new("Right", "HotkeyUpDownDesc"),
new("Escape", "HotkeyESCDesc"),
new("F5", "ReloadPluginHotkey"),
new("Alt+Home", "HotkeySelectFirstResult"),
new("Alt+End", "HotkeySelectLastResult"),
new("Ctrl+R", "HotkeyRequery"),
new("Ctrl+H", "ToggleHistoryHotkey"),
new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"),
new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"),
new("Ctrl+OemPlus", "QuickHeightHotkey"),
new("Ctrl+OemMinus", "QuickHeightHotkey"),
new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"),
new("Shift+Enter", "OpenContextMenuHotkey"),
new("Enter", "HotkeyRunDesc"),
new("Ctrl+Enter", "OpenContainFolderHotkey"),
new("Alt+Enter", "HotkeyOpenResult"),
new("Ctrl+F12", "ToggleGameModeHotkey"),
new("Ctrl+Shift+C", "CopyFilePathHotkey"),
//new("Escape", "HotkeyESCDesc"),
//new("F5", "ReloadPluginHotkey"),
//new("Alt+Home", "HotkeySelectFirstResult"),
//new("Alt+End", "HotkeySelectLastResult"),
//new("Ctrl+R", "HotkeyRequery"),
//new("Ctrl+H", "ToggleHistoryHotkey"),
//new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"),
//new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"),
//new("Ctrl+OemPlus", "QuickHeightHotkey"),
//new("Ctrl+OemMinus", "QuickHeightHotkey"),
//new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"),
//new("Shift+Enter", "OpenContextMenuHotkey"),
//new("Enter", "HotkeyRunDesc"),
//new("Ctrl+Enter", "OpenContainFolderHotkey"),
//new("Alt+Enter", "HotkeyOpenResult"),
//new("Ctrl+F12", "ToggleGameModeHotkey"),
//new("Ctrl+Shift+C", "CopyFilePathHotkey"),
new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1),
new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2),
new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3),
new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4),
new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5),
new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6),
new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7),
new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8),
new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9),
new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10)
//new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1),
//new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2),
//new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3),
//new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4),
//new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5),
//new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6),
//new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7),
//new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8),
//new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9),
//new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10)
};
}
}

View file

@ -107,7 +107,8 @@
VerticalAlignment="Center"
HorizontalContentAlignment="Left"
HotkeySettings="{Binding Settings}"
DefaultHotkey="" />
DefaultHotkey=""
IsWPFHotkeyControl="False" />
<TextBlock
Grid.Row="1"
Grid.Column="0"

View file

@ -37,7 +37,7 @@ namespace Flow.Launcher
var pluginHotkey = new CustomPluginHotkey
{
Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text
Hotkey = HotkeyControl.CurrentHotkey.HotkeyRaw, ActionKeyword = tbAction.Text
};
Settings.CustomPluginHotkeys.Add(pluginHotkey);
@ -47,9 +47,9 @@ namespace Flow.Launcher
{
var oldHotkey = updateCustomHotkey.Hotkey;
updateCustomHotkey.ActionKeyword = tbAction.Text;
updateCustomHotkey.Hotkey = HotkeyControl.CurrentHotkey.ToString();
updateCustomHotkey.Hotkey = HotkeyControl.CurrentHotkey.HotkeyRaw;
//remove origin hotkey
HotKeyMapper.RemoveHotkey(oldHotkey);
HotKeyMapper.UnregisterHotkey(oldHotkey);
HotKeyMapper.SetCustomQueryHotkey(updateCustomHotkey);
}

View file

@ -83,6 +83,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ChefKeys" Version="0.1.1" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Fody" Version="6.5.4">
<PrivateAssets>all</PrivateAssets>
@ -93,7 +94,6 @@
<!-- ModernWpfUI v0.9.5 introduced WinRT changes that causes Notification platform unavailable error on some machines -->
<!-- https://github.com/Flow-Launcher/Flow.Launcher/issues/1772#issuecomment-1502440801 -->
<PackageReference Include="ModernWpfUI" Version="0.9.4" />
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
<PackageReference Include="SemanticVersioning" Version="3.0.0-beta2" />
<PackageReference Include="VirtualizingWrapPanel" Version="2.1.0" />

View file

@ -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<HotkeyEventArgs> action)
internal static void RegisterHotkey(string hotkey, string previousHotkey, Action action)
{
var hotkey = new HotkeyModel(hotkeyStr);
SetHotkey(hotkey, action);
ChefKeysManager.RegisterHotkey(hotkey, previousHotkey, action);
}
internal static void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> action)
internal static void UnregisterHotkey(string hotkey)
{
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);
}
}
internal static void RemoveHotkey(string hotkeyStr)
{
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 class HotKeyMapper
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 class HotKeyMapper
});
}
internal static bool CheckAvailability(HotkeyModel currentHotkey)
internal static bool CanRegisterHotkey(string hotkey)
{
try
{
HotkeyManager.Current.AddOrReplace("HotkeyAvailabilityTest", currentHotkey.CharKey, currentHotkey.ModifierKeys, (sender, e) => { });
return true;
}
catch
{
}
finally
{
HotkeyManager.Current.Remove("HotkeyAvailabilityTest");
}
return false;
return ChefKeysManager.CanRegisterHotkey(hotkey);
}
internal static bool CheckHotkeyAvailability(string hotkey) => ChefKeysManager.IsAvailable(hotkey);
internal static bool CheckHotkeyValid(string hotkey) => ChefKeysManager.IsValidHotkey(hotkey);
}

View file

@ -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 @@ namespace Flow.Launcher
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 @@ namespace Flow.Launcher
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 @@ namespace Flow.Launcher
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<string> 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 @@ namespace Flow.Launcher
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,33 @@ namespace Flow.Launcher
}
}
private void SetHotkey(HotkeyModel keyModel, bool triggerValidate = true)
{
// WPF hotkey control uses CharKey
if (string.IsNullOrEmpty(keyModel.HotkeyRaw) || string.IsNullOrEmpty(keyModel.CharKey.ToString()))
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.HotkeyRaw;
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.HotkeyRaw;
ChangeHotkey?.Execute(keyModel);
}
}
@ -175,16 +201,16 @@ namespace Flow.Launcher
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 +224,16 @@ namespace Flow.Launcher
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);
}
}
}

View file

@ -6,8 +6,9 @@ using System.Windows.Input;
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<string> KeysToDisplay { get; } = new();
public enum EResultType
@ -33,40 +42,68 @@ public partial class HotkeyControlDialog : ContentDialog
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 @@ public partial class HotkeyControlDialog : ContentDialog
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,29 @@ public partial class HotkeyControlDialog : ContentDialog
//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);
if (!isWPFHotkeyControl)
{
if (HotkeyToUpdate.MaxKeysReached())
return;
HotkeyToUpdate.AddString(key.ToString());
}
else
{
HotkeyToUpdate.SetHotkeyFromWPFControl(GlobalHotkey.CheckModifiers(), key);
}
SetKeysToDisplay(HotkeyToUpdate);
}
private void SetKeysToDisplay(HotkeyModel? hotkey)
@ -103,21 +154,36 @@ public partial class HotkeyControlDialog : ContentDialog
_overwriteOtherHotkey = null;
KeysToDisplay.Clear();
if (hotkey == null || hotkey == default(HotkeyModel))
if (hotkey is null || string.IsNullOrEmpty(hotkey.Value.HotkeyRaw))
{
KeysToDisplay.Add(EmptyHotkey);
return;
}
foreach (var key in hotkey.Value.EnumerateDisplayKeys()!)
IEnumerable<string> enumerateMethod;
if (!isWPFHotkeyControl)
{
KeysToDisplay.Add(key);
foreach (var key in hotkey.Value.DisplayKeysRaw()!)
{
KeysToDisplay.Add(key);
}
}
else
{
foreach (var key in hotkey.Value.EnumerateDisplayKeys()!)
{
KeysToDisplay.Add(key);
}
}
if (tbMsg == null)
return;
if (_hotkeySettings.RegisteredHotkeys.FirstOrDefault(v => v.Hotkey == hotkey) is { } registeredHotkeyData)
if (_hotkeySettings.RegisteredHotkeys
.FirstOrDefault(v => v.Hotkey == hotkey
|| v.Hotkey.ToChefKeysString() == hotkey.Value.HotkeyRaw)
is { } registeredHotkeyData)
{
var description = string.Format(
InternationalizationManager.Instance.GetTranslation(registeredHotkeyData.DescriptionResourceKey),
@ -136,6 +202,7 @@ public partial class HotkeyControlDialog : ContentDialog
OverwriteBtn.Visibility = Visibility.Visible;
_overwriteOtherHotkey = registeredHotkeyData.RemoveHotkey;
}
else
{
tbMsg.Text = string.Format(
@ -153,7 +220,11 @@ public partial class HotkeyControlDialog : ContentDialog
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 +239,11 @@ public partial class HotkeyControlDialog : ContentDialog
}
}
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 void Overwrite(object sender, RoutedEventArgs e)
{

View file

@ -14,8 +14,6 @@
<system:String x:Key="failedToInitializePluginsMessage">Plugins: {0} - fail to load and would be disabled, please contact plugin creator for help</system:String>
<!-- MainWindow -->
<system:String x:Key="registerHotkeyFailed">Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program.</system:String>
<system:String x:Key="MessageBoxTitle">Flow Launcher</system:String>
<system:String x:Key="couldnotStartCmd">Could not start {0}</system:String>
<system:String x:Key="invalidFlowLauncherPluginFileFormat">Invalid Flow Launcher plugin file format</system:String>
<system:String x:Key="setAsTopMostInThisQuery">Set as topmost in this query</system:String>

View file

@ -113,11 +113,12 @@
<flowlauncher:HotkeyControl
Margin="0,8,0,0"
ChangeHotkey="{Binding SetTogglingHotkeyCommand}"
DefaultHotkey="Alt+Space"
DefaultHotkey="LeftAlt+Space"
Hotkey="{Binding Settings.Hotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="True"
WindowTitle="{DynamicResource flowlauncherHotkey}" />
WindowTitle="{DynamicResource flowlauncherHotkey}"
IsWPFHotkeyControl="False" />
</StackPanel>
</StackPanel>

View file

@ -27,7 +27,7 @@ namespace Flow.Launcher.Resources.Pages
[RelayCommand]
private static void SetTogglingHotkey(HotkeyModel hotkey)
{
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
HotKeyMapper.RegisterHotkey(hotkey.HotkeyRaw, hotkey.PreviousHotkey, HotKeyMapper.ToggleHotkey);
}
}
}

View file

@ -33,7 +33,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
[RelayCommand]
private void SetTogglingHotkey(HotkeyModel hotkey)
{
HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
HotKeyMapper.RegisterHotkey(hotkey.HotkeyRaw, hotkey.PreviousHotkey, HotKeyMapper.ToggleHotkey);
}
[RelayCommand]
@ -57,7 +57,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
if (result is MessageBoxResult.Yes)
{
Settings.CustomPluginHotkeys.Remove(item);
HotKeyMapper.RemoveHotkey(item.Hotkey);
HotKeyMapper.UnregisterHotkey(item.Hotkey);
}
}

View file

@ -33,11 +33,12 @@
Sub="{DynamicResource flowlauncherHotkeyToolTip}">
<flowlauncher:HotkeyControl
ChangeHotkey="{Binding SetTogglingHotkeyCommand}"
DefaultHotkey="Alt+Space"
DefaultHotkey="LeftAlt+Space"
Hotkey="{Binding Settings.Hotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="True"
WindowTitle="{DynamicResource flowlauncherHotkey}" />
WindowTitle="{DynamicResource flowlauncherHotkey}"
IsWPFHotkeyControl="False" />
</cc:Card>
<cc:Card