Use IPluginHotkey for explorer plugin

This commit is contained in:
Jack251970 2025-06-25 22:11:42 +08:00
parent 8ed495dc84
commit f7fa647da3
10 changed files with 161 additions and 197 deletions

View file

@ -58,7 +58,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public string OpenHistoryHotkey { get; set; } = $"Ctrl+H";
public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up";
public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down";
public string RenameFileHotkey { get; set; } = $"F2";
private string _language = Constant.SystemLanguageCode;
public string Language
@ -473,14 +472,13 @@ namespace Flow.Launcher.Infrastructure.UserSettings
list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = ""));
if (!string.IsNullOrEmpty(CycleHistoryDownHotkey))
list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = ""));
if (!string.IsNullOrEmpty(RenameFileHotkey))
list.Add(new RegisteredHotkeyData(RenameFileHotkey, "RenameFileHotkey", () => RenameFileHotkey = ""));
// Custom Query Hotkeys
foreach (var customPluginHotkey in CustomPluginHotkeys)
{
if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey))
list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = ""));
}
foreach (var customPluginHotkey in CustomPluginHotkeys)
{
if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey))
list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = ""));
}
return list;
}

View file

@ -111,9 +111,7 @@ namespace Flow.Launcher
SelectPrevItemHotkey,
SelectPrevItemHotkey2,
SelectNextItemHotkey,
SelectNextItemHotkey2,
RenameFileHotkey
SelectNextItemHotkey2
}
// We can initialize settings in static field because it has been constructed in App constuctor
@ -145,8 +143,6 @@ namespace Flow.Launcher
HotkeyType.SelectPrevItemHotkey2 => _settings.SelectPrevItemHotkey2,
HotkeyType.SelectNextItemHotkey => _settings.SelectNextItemHotkey,
HotkeyType.SelectNextItemHotkey2 => _settings.SelectNextItemHotkey2,
HotkeyType.RenameFileHotkey => _settings.RenameFileHotkey,
_ => throw new System.NotImplementedException("Hotkey type not set")
};
}
@ -206,9 +202,6 @@ namespace Flow.Launcher
case HotkeyType.SelectNextItemHotkey2:
_settings.SelectNextItemHotkey2 = value;
break;
case HotkeyType.RenameFileHotkey:
_settings.RenameFileHotkey = value;
break;
default:
throw new System.NotImplementedException("Hotkey type not set");
}
@ -239,7 +232,7 @@ namespace Flow.Launcher
public string EmptyHotkey => App.API.GetTranslation("none");
public ObservableCollection<string> KeysToDisplay { get; set; } = new ObservableCollection<string>();
public ObservableCollection<string> KeysToDisplay { get; set; } = new();
public HotkeyModel CurrentHotkey { get; private set; } = new(false, false, false, false, Key.None);
@ -316,7 +309,6 @@ namespace Flow.Launcher
private void SetKeysToDisplay(HotkeyModel? hotkey)
{
KeysToDisplay.Clear();
if (hotkey == null || hotkey == default(HotkeyModel))
@ -330,8 +322,6 @@ namespace Flow.Launcher
KeysToDisplay.Add(key);
}
}
public void SetHotkey(string? keyStr, bool triggerValidate = true)

View file

@ -557,9 +557,4 @@
<system:String x:Key="FileSize">File Size</system:String>
<system:String x:Key="Created">Created</system:String>
<system:String x:Key="LastModified">Last Modified</system:String>
<system:String x:Key="RenameFileHotkey">Rename a file/directory</system:String>
<system:String x:Key="AreTryingToRenameFile">Are you trying to rename a file?</system:String>
<system:String x:Key="ExplorerNeedsEnabledForRenameFile">The explorer plugin needs to be enabled for the hotkey to rename files to work.</system:String>
</ResourceDictionary>

View file

@ -211,10 +211,6 @@
Key="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding ForwardHistoryCommand}"
Modifiers="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding RenameFileHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding RenameFileCommand}"
Modifiers="{Binding RenameFileHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
</Window.InputBindings>
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">

View file

@ -222,7 +222,7 @@ namespace Flow.Launcher
}
}
}
private static async Task<Exception> RetryActionOnSTAThreadAsync(Action action, int retryCount = 6, int retryDelay = 150)
{
for (var i = 0; i < retryCount; i++)

View file

@ -204,15 +204,6 @@
<cc:HotkeyDisplay Margin="4 0 0 0" Keys="Ctrl+Minus" />
</StackPanel>
</cc:Card>
<cc:Card
Title="{DynamicResource RenameFileHotkey}"
Icon="&#xe70f;"
Type="Inside">
<flowlauncher:HotkeyControl
DefaultHotkey="F2"
Type="RenameFileHotkey"
ValidateKeyGesture="True" />
</cc:Card>
</StackPanel>
</cc:ExCard>

View file

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
@ -24,7 +23,6 @@ using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Storage;
using Microsoft.VisualStudio.Threading;
namespace Flow.Launcher.ViewModel
{
public partial class MainViewModel : BaseModel, ISavable, IDisposable
@ -62,8 +60,6 @@ namespace Flow.Launcher.ViewModel
#endregion
#region Constructor
public MainViewModel()
@ -144,9 +140,6 @@ namespace Flow.Launcher.ViewModel
case nameof(Settings.OpenHistoryHotkey):
OnPropertyChanged(nameof(OpenHistoryHotkey));
break;
case nameof(Settings.RenameFileHotkey):
OnPropertyChanged(nameof(RenameFileHotkey));
break;
}
};
@ -603,7 +596,6 @@ namespace Flow.Launcher.ViewModel
#endregion
#region ViewModel Properties
public Settings Settings { get; }
public string ClockText { get; private set; }
@ -921,76 +913,9 @@ namespace Flow.Launcher.ViewModel
public string OpenHistoryHotkey => VerifyOrSetDefaultHotkey(Settings.OpenHistoryHotkey, "Ctrl+H");
public string CycleHistoryUpHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryUpHotkey, "Alt+Up");
public string CycleHistoryDownHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryDownHotkey, "Alt+Down");
public string RenameFileHotkey => VerifyOrSetDefaultHotkey(Settings.RenameFileHotkey, "F2");
public bool StartWithEnglishMode => Settings.AlwaysStartEn;
#region renamingFiles
private int timesTriedToRenameFileWithExplorerDisabled = 0;
[RelayCommand]
private void RenameFile()
{
// at runtime this is an instance the Flow.Launcher.Plugin.Explorer.Main
var explorerPlugin = GetExplorerPlugin();
if (!(explorerPlugin != null))
{
timesTriedToRenameFileWithExplorerDisabled++;
if (timesTriedToRenameFileWithExplorerDisabled > 3)
{
App.API.ShowMsg(App.API.GetTranslation("AreTryingToRenameFile"), App.API.GetTranslation("ExplorerNeedsEnabledForRenameFile"));
timesTriedToRenameFileWithExplorerDisabled = 0;
}
return;
}
else
{
string path = SelectedResults?.SelectedItem?.Result.SubTitle ?? "";
string name = SelectedResults?.SelectedItem?.Result.Title ?? "";
if (string.IsNullOrWhiteSpace(path) || string.IsNullOrWhiteSpace(name)) return;
ShowRenamingDialog(explorerPlugin, path, name);
}
}
/// <summary>
/// Get an instance of the explorer plugin if it's loaded
/// </summary>
/// <returns>Returns an instance of Flow.Launcher.Plugin.Explorer.Main, if it's not loaded this returns null.</returns>
private IAsyncPlugin GetExplorerPlugin()
{
const string explorerPluginID = "572be03c74c642baae319fc283e561a8";
IEnumerable<PluginPair> explorerPluginMatches = App.API.GetAllPlugins().Where(
plugin => plugin.Metadata.ID == explorerPluginID && plugin.Metadata.Disabled != true) ;
if (explorerPluginMatches.Any())
{
// assuming it's the first plugin as no 2 plugins can be loaded with the same ID
return explorerPluginMatches.First().Plugin;
}
return null;
}
/// <summary>
/// Shows the dialog to rename a file system element.
/// </summary>
/// <param name="explorerPlugin">An instance of the Flow.Launcher.Plugin.Explorer.Main, which is invisible in the current namespace</param>
/// <param name="path">The path of the element</param>
/// <param name="name">The new name</param>
private void ShowRenamingDialog(dynamic explorerPlugin, string path, string name)
{
if (File.Exists(Path.Join(path, name)))
{
explorerPlugin.RenameDialog(new FileInfo(Path.Join(path, name)), App.API);
return;
}
if (Directory.Exists(path))
{
explorerPlugin.RenameDialog(new DirectoryInfo(path), App.API);
return;
}
}
#endregion
#endregion
#region Preview
@ -1086,7 +1011,6 @@ namespace Flow.Launcher.ViewModel
_ = ShowPreviewAsync();
}
}
private async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true)
{

View file

@ -130,6 +130,7 @@
<system:String x:Key="plugin_explorer_show_contextmenu_title">Show Windows Context Menu</system:String>
<system:String x:Key="plugin_explorer_openwith">Open With</system:String>
<system:String x:Key="plugin_explorer_openwith_subtitle">Select a program to open with</system:String>
<system:String x:Key="plugin_explorer_run_as_administrator">Run As Administrator</system:String>
<!-- Special Results -->
<system:String x:Key="plugin_explorer_diskfreespace">{0} free of {1}</system:String>

View file

@ -1,24 +1,26 @@
using Flow.Launcher.Plugin.Explorer.Helper;
using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.Everything;
using Flow.Launcher.Plugin.Explorer.ViewModels;
using Flow.Launcher.Plugin.Explorer.Views;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using Flow.Launcher.Plugin.Explorer.Exceptions;
using Flow.Launcher.Plugin.Explorer.Helper;
using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.Everything;
using Flow.Launcher.Plugin.Explorer.ViewModels;
using Flow.Launcher.Plugin.Explorer.Views;
namespace Flow.Launcher.Plugin.Explorer
{
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n, IPluginHotkey
{
internal static PluginInitContext Context { get; set; }
internal Settings Settings;
private static readonly string ClassName = nameof(Main);
private SettingsViewModel viewModel;
private IContextMenu contextMenu;
@ -56,10 +58,8 @@ namespace Flow.Launcher.Plugin.Explorer
return contextMenu.LoadContextMenus(selectedResult);
}
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
{
try
{
return await searchManager.SearchAsync(query, token);
@ -99,21 +99,7 @@ namespace Flow.Launcher.Plugin.Explorer
{
return Context.API.GetTranslation("plugin_explorer_plugin_description");
}
public void RenameDialog(FileSystemInfo info, IPublicAPI api)
{
if (info == null) throw new ArgumentNullException(nameof(info));
if (api == null) throw new ArgumentNullException(nameof(api));
try
{
new RenameFile(api, info).ShowDialog();
}
catch (Exception ex)
{
api.ShowMsgError(api.GetTranslation("errorTitle"), api.GetTranslation("plugin_explorer_failed_to_open_rename_dialog"));
api.LogException(nameof(Main), $"Failed to open rename dialog: {ex.Message}", ex, nameof(RenameDialog));
}
}
private void FillQuickAccessLinkNames()
{
// Legacy version does not have names for quick access links, so we fill them with the path name.
@ -125,6 +111,144 @@ namespace Flow.Launcher.Plugin.Explorer
}
}
}
public List<BasePluginHotkey> GetPuginHotkeys()
{
return new List<BasePluginHotkey>
{
new SearchWindowPluginHotkey()
{
Id = 0,
Name = Context.API.GetTranslation("plugin_explorer_opencontainingfolder"),
Description = Context.API.GetTranslation("plugin_explorer_opencontainingfolder_subtitle"),
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue838"),
DefaultHotkey = "Ctrl+Enter",
Editable = false,
Action = (r) =>
{
if (r.ContextData is SearchResult record)
{
if (record.Type is ResultType.File)
{
ResultManager.OpenFolder(record.FullPath, record.FullPath);
}
else
{
try
{
Context.API.OpenDirectory(Path.GetDirectoryName(record.FullPath), record.FullPath);
}
catch (Exception e)
{
var message = $"Fail to open file at {record.FullPath}";
Context.API.LogException(ClassName, message, e);
Context.API.ShowMsgBox(e.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
return false;
}
return true;
}
}
return false;
}
},
new SearchWindowPluginHotkey()
{
Id = 1,
Name = Context.API.GetTranslation("plugin_explorer_show_contextmenu_title"),
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue700"),
DefaultHotkey = "Alt+Enter",
Editable = false,
Action = (r) =>
{
if (r.ContextData is SearchResult record && record.Type is not ResultType.Volume)
{
try
{
ResultManager.ShowNativeContextMenu(record.FullPath, record.Type);
}
catch (Exception e)
{
var message = $"Fail to show context menu for {record.FullPath}";
Context.API.LogException(ClassName, message, e);
}
}
return false;
}
},
new SearchWindowPluginHotkey()
{
Id = 2,
Name = Context.API.GetTranslation("plugin_explorer_run_as_administrator"),
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE7EF"),
DefaultHotkey = "Ctrl+Shift+Enter",
Editable = false,
Action = (r) =>
{
if (r.ContextData is SearchResult record)
{
if (record.Type is ResultType.File)
{
var filePath = record.FullPath;
ResultManager.OpenFile(filePath, Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty, true);
}
else
{
try
{
ResultManager.OpenFolder(record.FullPath);
return true;
}
catch (Exception ex)
{
var message = $"Fail to open file at {record.FullPath}";
Context.API.LogException(ClassName, message, ex);
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
return false;
}
}
return true;
}
return false;
}
},
new SearchWindowPluginHotkey()
{
Id = 3,
Name = Context.API.GetTranslation("plugin_explorer_rename_a_file"),
Description = Context.API.GetTranslation("plugin_explorer_rename_subtitle"),
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8ac"),
DefaultHotkey = "F2",
Editable = true,
Action = (r) =>
{
if (r.ContextData is SearchResult record)
{
RenameFile window;
switch (record.Type)
{
case ResultType.Folder:
window = new RenameFile(Context.API, new DirectoryInfo(record.FullPath));
break;
case ResultType.File:
window = new RenameFile(Context.API, new FileInfo(record.FullPath));
break;
default:
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_cannot_rename"));
return false;
}
window.ShowDialog();
return false;
}
return false;
}
}
};
}
}
}

View file

@ -108,40 +108,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
PreviewPanel = new Lazy<UserControl>(() => new PreviewPanel(Settings, path, ResultType.Folder)),
Action = c =>
{
if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt)
{
ShowNativeContextMenu(path, ResultType.Folder);
return false;
}
// open folder
if (c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift))
{
try
{
OpenFolder(path);
return true;
}
catch (Exception ex)
{
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
return false;
}
}
// Open containing folder
if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control)
{
try
{
Context.API.OpenDirectory(Path.GetDirectoryName(path), path);
return true;
}
catch (Exception ex)
{
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
return false;
}
}
// If path search is disabled just open it in file manager
if (Settings.DefaultOpenFolderInFileManager || (!Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled))
{
@ -259,11 +225,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
CopyText = folderPath,
Action = c =>
{
if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt)
{
ShowNativeContextMenu(folderPath, ResultType.Folder);
return false;
}
OpenFolder(folderPath);
return true;
},
@ -296,25 +257,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
PreviewPanel = new Lazy<UserControl>(() => new PreviewPanel(Settings, filePath, ResultType.File)),
Action = c =>
{
if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt)
{
ShowNativeContextMenu(filePath, ResultType.File);
return false;
}
try
{
if (c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift))
{
OpenFile(filePath, Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty, true);
}
else if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control)
{
OpenFolder(filePath, filePath);
}
else
{
OpenFile(filePath, Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty);
}
OpenFile(filePath, Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty);
}
catch (Exception ex)
{
@ -337,13 +282,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return MediaExtensions.Contains(extension.ToLowerInvariant());
}
private static void OpenFile(string filePath, string workingDir = "", bool asAdmin = false)
public static void OpenFile(string filePath, string workingDir = "", bool asAdmin = false)
{
IncrementEverythingRunCounterIfNeeded(filePath);
FilesFolders.OpenFile(filePath, workingDir, asAdmin, (string str) => Context.API.ShowMsgBox(str));
}
private static void OpenFolder(string folderPath, string fileNameOrFilePath = null)
public static void OpenFolder(string folderPath, string fileNameOrFilePath = null)
{
IncrementEverythingRunCounterIfNeeded(folderPath);
Context.API.OpenDirectory(folderPath, fileNameOrFilePath);