diff --git a/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs b/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs
new file mode 100644
index 00000000000..448a70d191c
--- /dev/null
+++ b/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace Flow.Launcher.Infrastructure.Hotkey;
+
+///
+/// Interface that you should implement in your settings class to be able to pass it to
+/// Flow.Launcher.HotkeyControlDialog . It allows the dialog to display the hotkeys that have already been
+/// registered, and optionally provide a way to unregister them.
+///
+public interface IHotkeySettings
+{
+ ///
+ /// A list of hotkeys that have already been registered. The dialog will display these hotkeys and provide a way to
+ /// unregister them.
+ ///
+ public List RegisteredHotkeys { get; }
+}
diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs
new file mode 100644
index 00000000000..b6a10fc3075
--- /dev/null
+++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs
@@ -0,0 +1,119 @@
+using System;
+
+namespace Flow.Launcher.Infrastructure.Hotkey;
+
+#nullable enable
+
+///
+/// Represents a hotkey that has been registered. Used in Flow.Launcher.HotkeyControlDialog via
+/// and to display errors if user tries to register a hotkey
+/// that has already been registered, and optionally provides a way to unregister the hotkey.
+///
+public record RegisteredHotkeyData
+{
+ ///
+ /// representation of this hotkey.
+ ///
+ public HotkeyModel Hotkey { get; }
+
+ ///
+ /// String key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey ,
+ /// which represents the string "Reload Plugins Data" in en.xaml
+ ///
+ public string DescriptionResourceKey { get; }
+
+ ///
+ /// Array of values that will replace {0} , {1} , {2} , etc. in the localized string found via
+ /// .
+ ///
+ public object?[] DescriptionFormatVariables { get; } = Array.Empty();
+
+ ///
+ /// An action that, when called, will unregister this hotkey. If it's null , it's assumed that
+ /// this hotkey can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog.
+ ///
+ public Action? RemoveHotkey { get; }
+
+ ///
+ /// Creates an instance of RegisteredHotkeyData . Assumes that the key specified in
+ /// descriptionResourceKey doesn't need any arguments for string.Format . If it does,
+ /// use one of the other constructors.
+ ///
+ ///
+ /// The hotkey this class will represent.
+ /// Example values: F1 , Ctrl+Shift+Enter
+ ///
+ ///
+ /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey ,
+ /// which represents the string "Reload Plugins Data" in en.xaml
+ ///
+ ///
+ /// An action that, when called, will unregister this hotkey. If it's null , it's assumed that this hotkey
+ /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog.
+ ///
+ public RegisteredHotkeyData(string hotkey, string descriptionResourceKey, Action? removeHotkey = null)
+ {
+ Hotkey = new HotkeyModel(hotkey);
+ DescriptionResourceKey = descriptionResourceKey;
+ RemoveHotkey = removeHotkey;
+ }
+
+ ///
+ /// Creates an instance of RegisteredHotkeyData . Assumes that the key specified in
+ /// descriptionResourceKey needs exactly one argument for string.Format .
+ ///
+ ///
+ /// The hotkey this class will represent.
+ /// Example values: F1 , Ctrl+Shift+Enter
+ ///
+ ///
+ /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey ,
+ /// which represents the string "Reload Plugins Data" in en.xaml
+ ///
+ ///
+ /// The value that will replace {0} in the localized string found via description .
+ ///
+ ///
+ /// An action that, when called, will unregister this hotkey. If it's null , it's assumed that this hotkey
+ /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog.
+ ///
+ public RegisteredHotkeyData(
+ string hotkey, string descriptionResourceKey, object? descriptionFormatVariable, Action? removeHotkey = null
+ )
+ {
+ Hotkey = new HotkeyModel(hotkey);
+ DescriptionResourceKey = descriptionResourceKey;
+ DescriptionFormatVariables = new[] { descriptionFormatVariable };
+ RemoveHotkey = removeHotkey;
+ }
+
+ ///
+ /// Creates an instance of RegisteredHotkeyData . Assumes that the key specified in
+ /// needs multiple arguments for string.Format .
+ ///
+ ///
+ /// The hotkey this class will represent.
+ /// Example values: F1 , Ctrl+Shift+Enter
+ ///
+ ///
+ /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey ,
+ /// which represents the string "Reload Plugins Data" in en.xaml
+ ///
+ ///
+ /// Array of values that will replace {0} , {1} , {2} , etc.
+ /// in the localized string found via description .
+ ///
+ ///
+ /// An action that, when called, will unregister this hotkey. If it's null , it's assumed that this hotkey
+ /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog.
+ ///
+ public RegisteredHotkeyData(
+ string hotkey, string descriptionResourceKey, object?[] descriptionFormatVariables, Action? removeHotkey = null
+ )
+ {
+ Hotkey = new HotkeyModel(hotkey);
+ DescriptionResourceKey = descriptionResourceKey;
+ DescriptionFormatVariables = descriptionFormatVariables;
+ RemoveHotkey = removeHotkey;
+ }
+}
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index 343e91a8500..58b1cc467c6 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -4,13 +4,14 @@
using System.Drawing;
using System.Text.Json.Serialization;
using System.Windows;
+using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Infrastructure.UserSettings
{
- public class Settings : BaseModel
+ public class Settings : BaseModel, IHotkeySettings
{
private string language = "en";
private string _theme = Constant.DefaultTheme;
@@ -207,17 +208,17 @@ public string QuerySearchPrecisionString
public double WindowLeft { get; set; }
public double WindowTop { get; set; }
-
+
///
/// Custom left position on selected monitor
///
public double CustomWindowLeft { get; set; } = 0;
-
+
///
/// Custom top position on selected monitor
///
public double CustomWindowTop { get; set; } = 0;
-
+
public int MaxResultsToShow { get; set; } = 5;
public int ActivateTimes { get; set; }
@@ -229,7 +230,7 @@ public string QuerySearchPrecisionString
[JsonIgnore]
public ObservableCollection BuiltinShortcuts { get; set; } = new()
{
- new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText),
+ new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText),
new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath)
};
@@ -253,7 +254,7 @@ public bool HideNotifyIcon
[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor;
-
+
[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchWindowAligns SearchWindowAlign { get; set; } = SearchWindowAligns.Center;
@@ -273,6 +274,82 @@ public bool HideNotifyIcon
// This needs to be loaded last by staying at the bottom
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
+
+ [JsonIgnore]
+ public List RegisteredHotkeys
+ {
+ get
+ {
+ var list = new List
+ {
+ new("Up", "HotkeyLeftRightDesc"),
+ 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($"{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)
+ };
+
+ if(!string.IsNullOrEmpty(Hotkey))
+ list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = ""));
+ if(!string.IsNullOrEmpty(PreviewHotkey))
+ list.Add(new(PreviewHotkey, "previewHotkey", () => PreviewHotkey = ""));
+ if(!string.IsNullOrEmpty(AutoCompleteHotkey))
+ list.Add(new(AutoCompleteHotkey, "autoCompleteHotkey", () => AutoCompleteHotkey = ""));
+ if(!string.IsNullOrEmpty(AutoCompleteHotkey2))
+ list.Add(new(AutoCompleteHotkey2, "autoCompleteHotkey", () => AutoCompleteHotkey2 = ""));
+ if(!string.IsNullOrEmpty(SelectNextItemHotkey))
+ list.Add(new(SelectNextItemHotkey, "SelectNextItemHotkey", () => SelectNextItemHotkey = ""));
+ if(!string.IsNullOrEmpty(SelectNextItemHotkey2))
+ list.Add(new(SelectNextItemHotkey2, "SelectNextItemHotkey", () => SelectNextItemHotkey2 = ""));
+ if(!string.IsNullOrEmpty(SelectPrevItemHotkey))
+ list.Add(new(SelectPrevItemHotkey, "SelectPrevItemHotkey", () => SelectPrevItemHotkey = ""));
+ 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 = ""));
+
+ foreach (var customPluginHotkey in CustomPluginHotkeys)
+ {
+ if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey))
+ list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = ""));
+ }
+
+ return list;
+ }
+ }
}
public enum LastQueryMode
@@ -288,7 +365,7 @@ public enum ColorSchemes
Light,
Dark
}
-
+
public enum SearchWindowScreens
{
RememberLastLaunchLocation,
@@ -297,7 +374,7 @@ public enum SearchWindowScreens
Primary,
Custom
}
-
+
public enum SearchWindowAligns
{
Center,
diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml
index de724ea819a..068afda15b4 100644
--- a/Flow.Launcher/CustomQueryHotkeySetting.xaml
+++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml
@@ -106,6 +106,7 @@
HorizontalAlignment="Left"
VerticalAlignment="Center"
HorizontalContentAlignment="Left"
+ HotkeySettings="{Binding Settings}"
DefaultHotkey="" />
();
+ Settings.CustomPluginHotkeys ??= new ObservableCollection();
var pluginHotkey = new CustomPluginHotkey
{
Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text
};
- _settings.CustomPluginHotkeys.Add(pluginHotkey);
+ Settings.CustomPluginHotkeys.Add(pluginHotkey);
HotKeyMapper.SetCustomQueryHotkey(pluginHotkey);
}
@@ -59,7 +59,7 @@ private void btnAdd_OnClick(object sender, RoutedEventArgs e)
public void UpdateItem(CustomPluginHotkey item)
{
- updateCustomHotkey = _settings.CustomPluginHotkeys.FirstOrDefault(o =>
+ updateCustomHotkey = Settings.CustomPluginHotkeys.FirstOrDefault(o =>
o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey);
if (updateCustomHotkey == null)
{
diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs
index 546b252df95..a42bde7c9cd 100644
--- a/Flow.Launcher/HotkeyControl.xaml.cs
+++ b/Flow.Launcher/HotkeyControl.xaml.cs
@@ -12,6 +12,17 @@ namespace Flow.Launcher
{
public partial class HotkeyControl
{
+ public IHotkeySettings HotkeySettings {
+ get { return (IHotkeySettings)GetValue(HotkeySettingsProperty); }
+ set { SetValue(HotkeySettingsProperty, value); }
+ }
+
+ public static readonly DependencyProperty HotkeySettingsProperty = DependencyProperty.Register(
+ nameof(HotkeySettings),
+ typeof(IHotkeySettings),
+ typeof(HotkeyControl),
+ new PropertyMetadata()
+ );
public string WindowTitle {
get { return (string)GetValue(WindowTitleProperty); }
set { SetValue(WindowTitleProperty, value); }
@@ -122,7 +133,7 @@ private async Task OpenHotkeyDialog()
HotKeyMapper.RemoveHotkey(Hotkey);
}
- var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, WindowTitle);
+ var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, HotkeySettings, WindowTitle);
await dialog.ShowAsync();
switch (dialog.ResultType)
{
diff --git a/Flow.Launcher/HotkeyControlDialog.xaml b/Flow.Launcher/HotkeyControlDialog.xaml
index 322f82366d7..9a5872f4dc1 100644
--- a/Flow.Launcher/HotkeyControlDialog.xaml
+++ b/Flow.Launcher/HotkeyControlDialog.xaml
@@ -82,7 +82,8 @@
-
-
-
-
+
+
+
+
+
+
@@ -121,6 +128,15 @@
Margin="10"
HorizontalAlignment="Center"
Orientation="Horizontal">
+
InternationalizationManager.Instance.GetTranslation("none");
- public HotkeyControlDialog(string hotkey, string defaultHotkey, string windowTitle = "")
+ public HotkeyControlDialog(string hotkey, string defaultHotkey, IHotkeySettings hotkeySettings, string windowTitle = "")
{
WindowTitle = windowTitle switch
{
@@ -36,6 +42,7 @@ public HotkeyControlDialog(string hotkey, string defaultHotkey, string windowTit
};
DefaultHotkey = defaultHotkey;
CurrentHotkey = new HotkeyModel(hotkey);
+ _hotkeySettings = hotkeySettings;
SetKeysToDisplay(CurrentHotkey);
InitializeComponent();
@@ -93,6 +100,7 @@ private void OnPreviewKeyDown(object sender, KeyEventArgs e)
private void SetKeysToDisplay(HotkeyModel? hotkey)
{
+ _overwriteOtherHotkey = null;
KeysToDisplay.Clear();
if (hotkey == null || hotkey == default(HotkeyModel))
@@ -109,19 +117,63 @@ private void SetKeysToDisplay(HotkeyModel? hotkey)
if (tbMsg == null)
return;
+ if (_hotkeySettings.RegisteredHotkeys.FirstOrDefault(v => v.Hotkey == hotkey) is { } registeredHotkeyData)
+ {
+ var description = string.Format(
+ InternationalizationManager.Instance.GetTranslation(registeredHotkeyData.DescriptionResourceKey),
+ registeredHotkeyData.DescriptionFormatVariables
+ );
+ Alert.Visibility = Visibility.Visible;
+ if (registeredHotkeyData.RemoveHotkey is not null)
+ {
+ tbMsg.Text = string.Format(
+ InternationalizationManager.Instance.GetTranslation("hotkeyUnavailableEditable"),
+ description
+ );
+ SaveBtn.IsEnabled = false;
+ SaveBtn.Visibility = Visibility.Collapsed;
+ OverwriteBtn.IsEnabled = true;
+ OverwriteBtn.Visibility = Visibility.Visible;
+ _overwriteOtherHotkey = registeredHotkeyData.RemoveHotkey;
+ }
+ else
+ {
+ tbMsg.Text = string.Format(
+ InternationalizationManager.Instance.GetTranslation("hotkeyUnavailableUneditable"),
+ description
+ );
+ SaveBtn.IsEnabled = false;
+ SaveBtn.Visibility = Visibility.Visible;
+ OverwriteBtn.IsEnabled = false;
+ OverwriteBtn.Visibility = Visibility.Collapsed;
+ }
+ return;
+ }
+
+ OverwriteBtn.IsEnabled = false;
+ OverwriteBtn.Visibility = Visibility.Collapsed;
+
if (!CheckHotkeyAvailability(hotkey.Value, true))
{
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
Alert.Visibility = Visibility.Visible;
SaveBtn.IsEnabled = false;
+ SaveBtn.Visibility = Visibility.Visible;
}
else
{
Alert.Visibility = Visibility.Collapsed;
SaveBtn.IsEnabled = true;
+ SaveBtn.Visibility = Visibility.Visible;
}
}
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) =>
hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
+
+ private void Overwrite(object sender, RoutedEventArgs e)
+ {
+ _overwriteOtherHotkey?.Invoke();
+ Save(sender, e);
+ }
}
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index 3b4f353271b..bda86e481ec 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -311,6 +311,8 @@
Update
Binding Hotkey
Current hotkey is unavailable.
+ This hotkey is reserved for "{0}" and can't be used. Please choose another hotkey.
+ This hotkey is already in use by "{0}". If you press "Overwrite", it will be removed from "{0}".
Press the keys you want to use for this function.
@@ -323,6 +325,7 @@
Save
+ Overwrite
Cancel
Reset
Delete
@@ -395,6 +398,12 @@
Open Setting Window
Reload Plugin Data
+ Select first result
+ Select last result
+ Run current query again
+ Open result
+ Open result #{0}
+
Weather
Weather in Google Result
> ping 8.8.8.8
diff --git a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml
index b60b8e47ede..6c6fcbb625f 100644
--- a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml
+++ b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml
@@ -115,6 +115,7 @@
ChangeHotkey="{Binding SetTogglingHotkeyCommand}"
DefaultHotkey="Alt+Space"
Hotkey="{Binding Settings.Hotkey}"
+ HotkeySettings="{Binding Settings}"
ValidateKeyGesture="True"
WindowTitle="{DynamicResource flowlauncherHotkey}" />
diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml
index 8ccaeccbba4..84b91e312be 100644
--- a/Flow.Launcher/SettingWindow.xaml
+++ b/Flow.Launcher/SettingWindow.xaml
@@ -2663,6 +2663,7 @@
ChangeHotkey="{Binding SetTogglingHotkeyCommand}"
DefaultHotkey="Alt+Space"
Hotkey="{Binding Settings.Hotkey}"
+ HotkeySettings="{Binding Settings}"
ValidateKeyGesture="True"
WindowTitle="{DynamicResource flowlauncherHotkey}" />
@@ -2676,6 +2677,7 @@
@@ -2776,6 +2778,7 @@
@@ -2857,6 +2863,7 @@
@@ -2878,6 +2886,7 @@
@@ -2899,6 +2909,7 @@