mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
493 lines
22 KiB
C#
493 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using Flow.Launcher.Plugin.SharedCommands;
|
|
using Flow.Launcher.Plugin.Explorer.Search;
|
|
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
|
|
using Flow.Launcher.Plugin.Explorer.Helper;
|
|
using Flow.Launcher.Plugin.Explorer.ViewModels;
|
|
|
|
namespace Flow.Launcher.Plugin.Explorer
|
|
{
|
|
internal class ContextMenu : IContextMenu
|
|
{
|
|
private static readonly string ClassName = nameof(ContextMenu);
|
|
|
|
private PluginInitContext Context { get; set; }
|
|
|
|
private Settings Settings { get; set; }
|
|
|
|
public ContextMenu(PluginInitContext context, Settings settings)
|
|
{
|
|
Context = context;
|
|
Settings = settings;
|
|
}
|
|
|
|
public List<Result> LoadContextMenus(Result selectedResult)
|
|
{
|
|
var contextMenus = new List<Result>();
|
|
if (selectedResult.ContextData is SearchResult record)
|
|
{
|
|
if (record.Type == ResultType.File && !string.IsNullOrEmpty(Settings.EditorPath))
|
|
contextMenus.Add(CreateOpenWithEditorResult(record, Settings.EditorPath));
|
|
|
|
if ((record.Type == ResultType.Folder || record.Type == ResultType.Volume) && !string.IsNullOrEmpty(Settings.FolderEditorPath))
|
|
contextMenus.Add(CreateOpenWithEditorResult(record, Settings.FolderEditorPath));
|
|
|
|
if (record.Type == ResultType.Folder)
|
|
{
|
|
contextMenus.Add(CreateOpenWithShellResult(record));
|
|
if (record.WindowsIndexed)
|
|
{
|
|
contextMenus.Add(CreateAddToIndexSearchExclusionListResult(record));
|
|
}
|
|
}
|
|
|
|
contextMenus.Add(CreateOpenContainingFolderResult(record));
|
|
|
|
if (record.Type == ResultType.File)
|
|
{
|
|
contextMenus.Add(CreateOpenWithMenu(record));
|
|
}
|
|
|
|
if (record.WindowsIndexed)
|
|
{
|
|
contextMenus.Add(CreateOpenWindowsIndexingOptions());
|
|
}
|
|
|
|
var icoPath = (record.Type == ResultType.File) ? Constants.FileImagePath : Constants.FolderImagePath;
|
|
bool isFile = record.Type == ResultType.File;
|
|
|
|
if (Settings.QuickAccessLinks.All(x => !x.Path.Equals(record.FullPath, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
contextMenus.Add(new Result
|
|
{
|
|
Title = Localize.plugin_explorer_add_to_quickaccess_title(),
|
|
SubTitle = Localize.plugin_explorer_add_to_quickaccess_subtitle(),
|
|
Action = (context) =>
|
|
{
|
|
Settings.QuickAccessLinks.Add(new AccessLink
|
|
{
|
|
Name = record.FullPath.GetPathName(),
|
|
Path = record.FullPath,
|
|
Type = record.Type
|
|
});
|
|
|
|
Context.API.ShowMsg(Localize.plugin_explorer_addfilefoldersuccess(),
|
|
Localize.plugin_explorer_addfilefoldersuccess_detail(),
|
|
Constants.ExplorerIconImageFullPath);
|
|
|
|
return true;
|
|
},
|
|
SubTitleToolTip = Localize.plugin_explorer_contextmenu_titletooltip(),
|
|
TitleToolTip = Localize.plugin_explorer_contextmenu_titletooltip(),
|
|
IcoPath = Constants.QuickAccessImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue718"),
|
|
});
|
|
}
|
|
else
|
|
{
|
|
contextMenus.Add(new Result
|
|
{
|
|
Title = Localize.plugin_explorer_remove_from_quickaccess_title(),
|
|
SubTitle = Localize.plugin_explorer_remove_from_quickaccess_subtitle(),
|
|
Action = (context) =>
|
|
{
|
|
Settings.QuickAccessLinks.Remove(Settings.QuickAccessLinks.FirstOrDefault(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase)));
|
|
|
|
Context.API.ShowMsg(Localize.plugin_explorer_removefilefoldersuccess(),
|
|
Localize.plugin_explorer_removefilefoldersuccess_detail(),
|
|
Constants.ExplorerIconImageFullPath);
|
|
|
|
return true;
|
|
},
|
|
SubTitleToolTip = Localize.plugin_explorer_contextmenu_remove_titletooltip(),
|
|
TitleToolTip = Localize.plugin_explorer_contextmenu_remove_titletooltip(),
|
|
IcoPath = Constants.RemoveQuickAccessImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uecc9")
|
|
});
|
|
}
|
|
|
|
contextMenus.Add(new Result
|
|
{
|
|
Title = Localize.plugin_explorer_copypath(),
|
|
SubTitle = Localize.plugin_explorer_copypath_subtitle(),
|
|
Action = _ =>
|
|
{
|
|
try
|
|
{
|
|
Context.API.CopyToClipboard(record.FullPath);
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException("Fail to set text in clipboard", e);
|
|
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_text());
|
|
return false;
|
|
}
|
|
},
|
|
IcoPath = Constants.CopyImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8")
|
|
});
|
|
|
|
contextMenus.Add(new Result
|
|
{
|
|
Title = Localize.plugin_explorer_copyname(),
|
|
SubTitle = Localize.plugin_explorer_copyname_subtitle(),
|
|
Action = _ =>
|
|
{
|
|
try
|
|
{
|
|
Context.API.CopyToClipboard(Path.GetFileName(record.FullPath));
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException("Fail to set text in clipboard", e);
|
|
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_text());
|
|
return false;
|
|
}
|
|
},
|
|
IcoPath = Constants.CopyImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8")
|
|
});
|
|
|
|
contextMenus.Add(new Result
|
|
{
|
|
Title = Localize.plugin_explorer_copyfilefolder(),
|
|
SubTitle = isFile ? Localize.plugin_explorer_copyfile_subtitle(): Localize.plugin_explorer_copyfolder_subtitle(),
|
|
Action = _ =>
|
|
{
|
|
try
|
|
{
|
|
Context.API.CopyToClipboard(record.FullPath, directCopy: true);
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException($"Fail to set file/folder in clipboard", e);
|
|
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_files());
|
|
return false;
|
|
}
|
|
},
|
|
IcoPath = icoPath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uf12b")
|
|
});
|
|
|
|
if (record.Type is ResultType.File or ResultType.Folder)
|
|
contextMenus.Add(new Result
|
|
{
|
|
Title = Localize.plugin_explorer_deletefilefolder(),
|
|
SubTitle = isFile ? Localize.plugin_explorer_deletefile_subtitle(): Localize.plugin_explorer_deletefolder_subtitle(),
|
|
Action = (context) =>
|
|
{
|
|
try
|
|
{
|
|
if (Context.API.ShowMsgBox(
|
|
Localize.plugin_explorer_delete_folder_link(record.FullPath),
|
|
Localize.plugin_explorer_deletefilefolder(),
|
|
MessageBoxButton.OKCancel,
|
|
MessageBoxImage.Warning)
|
|
== MessageBoxResult.Cancel)
|
|
return false;
|
|
|
|
if (isFile)
|
|
File.Delete(record.FullPath);
|
|
else
|
|
Directory.Delete(record.FullPath, true);
|
|
|
|
_ = Task.Run(() =>
|
|
{
|
|
Context.API.ShowMsg(Localize.plugin_explorer_deletefilefoldersuccess(),
|
|
Localize.plugin_explorer_deletefilefoldersuccess_detail(record.FullPath),
|
|
Constants.ExplorerIconImageFullPath);
|
|
});
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException($"Fail to delete {record.FullPath}", e);
|
|
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_delete(record.FullPath));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
IcoPath = Constants.DeleteFileFolderImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue74d")
|
|
});
|
|
|
|
if (record.Type is not ResultType.Volume)
|
|
{
|
|
contextMenus.Add(new Result()
|
|
{
|
|
Title = Localize.plugin_explorer_show_contextmenu_title(),
|
|
IcoPath = Constants.ShowContextMenuImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue700"),
|
|
Action = _ =>
|
|
{
|
|
if (record.Type is ResultType.Volume)
|
|
return false;
|
|
|
|
ResultManager.ShowNativeContextMenu(record.FullPath, record.Type);
|
|
|
|
return false;
|
|
},
|
|
});
|
|
}
|
|
|
|
if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath))
|
|
contextMenus.Add(new Result
|
|
{
|
|
Title = Localize.plugin_explorer_runasdifferentuser(),
|
|
SubTitle = Localize.plugin_explorer_runasdifferentuser_subtitle(),
|
|
Action = (context) =>
|
|
{
|
|
try
|
|
{
|
|
_ = Task.Run(() => ShellCommand.RunAsDifferentUser(record.FullPath.SetProcessStartInfo()));
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
Context.API.ShowMsgError(
|
|
Localize.plugin_explorer_plugin_name(),
|
|
Localize.plugin_explorer_file_not_found(e.Message));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
IcoPath = Constants.DifferentUserIconImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue748"),
|
|
});
|
|
|
|
if (record.Type is ResultType.File or ResultType.Folder && Settings.ShowInlinedWindowsContextMenu)
|
|
{
|
|
var includedItems = Settings
|
|
.WindowsContextMenuIncludedItems
|
|
.Replace("\r", "")
|
|
.Split("\n")
|
|
.Where(v => !string.IsNullOrWhiteSpace(v))
|
|
.ToArray();
|
|
var excludedItems = Settings
|
|
.WindowsContextMenuExcludedItems
|
|
.Replace("\r", "")
|
|
.Split("\n")
|
|
.Where(v => !string.IsNullOrWhiteSpace(v))
|
|
.ToArray();
|
|
var menuItems = ShellContextMenuDisplayHelper
|
|
.GetContextMenuWithIcons(record.FullPath)
|
|
.Where(contextMenuItem =>
|
|
(includedItems.Length == 0 || includedItems.Any(filter =>
|
|
contextMenuItem.Label.Contains(filter, StringComparison.OrdinalIgnoreCase)
|
|
)) &&
|
|
(excludedItems.Length == 0 || !excludedItems.Any(filter =>
|
|
contextMenuItem.Label.Contains(filter, StringComparison.OrdinalIgnoreCase)
|
|
))
|
|
);
|
|
foreach (var menuItem in menuItems)
|
|
{
|
|
contextMenus.Add(new Result
|
|
{
|
|
Title = menuItem.Label,
|
|
Icon = () => menuItem.Icon,
|
|
Action = _ =>
|
|
{
|
|
ShellContextMenuDisplayHelper.ExecuteContextMenuItem(record.FullPath, menuItem.CommandId);
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return contextMenus;
|
|
}
|
|
|
|
private Result CreateOpenContainingFolderResult(SearchResult record)
|
|
{
|
|
return new Result
|
|
{
|
|
Title = Localize.plugin_explorer_opencontainingfolder(),
|
|
SubTitle = Localize.plugin_explorer_opencontainingfolder_subtitle(),
|
|
Action = _ =>
|
|
{
|
|
try
|
|
{
|
|
Context.API.OpenDirectory(Path.GetDirectoryName(record.FullPath), record.FullPath);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogException($"Fail to open file at {record.FullPath}", e);
|
|
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_open(record.FullPath));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
IcoPath = Constants.FolderImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue838")
|
|
};
|
|
}
|
|
|
|
private Result CreateOpenWithEditorResult(SearchResult record, string editorPath)
|
|
{
|
|
var name = $"{Localize.plugin_explorer_openwitheditor()} {Path.GetFileNameWithoutExtension(editorPath)}";
|
|
|
|
return new Result
|
|
{
|
|
Title = name,
|
|
Action = _ =>
|
|
{
|
|
try
|
|
{
|
|
Process.Start(new ProcessStartInfo()
|
|
{
|
|
FileName = editorPath,
|
|
ArgumentList = { record.FullPath }
|
|
});
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
var message = Localize.plugin_explorer_openwitheditor_error(record.FullPath, Path.GetFileNameWithoutExtension(editorPath), editorPath);
|
|
LogException(message, e);
|
|
Context.API.ShowMsgError(message);
|
|
return false;
|
|
}
|
|
},
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue70f"),
|
|
IcoPath = Constants.FileImagePath
|
|
};
|
|
}
|
|
|
|
private Result CreateOpenWithShellResult(SearchResult record)
|
|
{
|
|
string shellPath = Settings.ShellPath;
|
|
|
|
var name = $"{Localize.plugin_explorer_openwithshell()} {Path.GetFileNameWithoutExtension(shellPath)}";
|
|
|
|
return new Result
|
|
{
|
|
Title = name,
|
|
Action = _ =>
|
|
{
|
|
try
|
|
{
|
|
Process.Start(new ProcessStartInfo()
|
|
{
|
|
FileName = shellPath, WorkingDirectory = record.FullPath
|
|
});
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
var message = Localize.plugin_explorer_openwithshell_error(record.FullPath, Path.GetFileNameWithoutExtension(shellPath), shellPath);
|
|
LogException(message, e);
|
|
Context.API.ShowMsgError(message);
|
|
return false;
|
|
}
|
|
},
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue756"),
|
|
IcoPath = Constants.FolderImagePath
|
|
};
|
|
}
|
|
|
|
private Result CreateAddToIndexSearchExclusionListResult(SearchResult record)
|
|
{
|
|
return new Result
|
|
{
|
|
Title = Localize.plugin_explorer_excludefromindexsearch(),
|
|
SubTitle = Localize.plugin_explorer_path()+ " " + record.FullPath,
|
|
Action = c_ =>
|
|
{
|
|
if (!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase)))
|
|
Settings.IndexSearchExcludedSubdirectoryPaths.Add(new AccessLink
|
|
{
|
|
Path = record.FullPath
|
|
});
|
|
|
|
_ = Task.Run(() =>
|
|
{
|
|
Context.API.ShowMsg(Localize.plugin_explorer_excludedfromindexsearch_msg(),
|
|
Localize.plugin_explorer_path()+
|
|
" " + record.FullPath, Constants.ExplorerIconImageFullPath);
|
|
|
|
// so the new path can be persisted to storage and not wait till next ViewModel save.
|
|
Context.API.SaveAppAllSettings();
|
|
});
|
|
|
|
return false;
|
|
},
|
|
IcoPath = Constants.ExcludeFromIndexImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uf140"),
|
|
};
|
|
}
|
|
|
|
private Result CreateOpenWindowsIndexingOptions()
|
|
{
|
|
return new Result
|
|
{
|
|
Title = Localize.plugin_explorer_openindexingoptions(),
|
|
SubTitle = Localize.plugin_explorer_openindexingoptions_subtitle(),
|
|
Action = _ =>
|
|
{
|
|
try
|
|
{
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = "control.exe",
|
|
UseShellExecute = true,
|
|
Arguments = "srchadmin.dll"
|
|
};
|
|
|
|
Process.Start(psi);
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
var message = Localize.plugin_explorer_openindexingoptions_errormsg();
|
|
LogException(message, e);
|
|
Context.API.ShowMsgError(message);
|
|
return false;
|
|
}
|
|
},
|
|
IcoPath = Constants.IndexingOptionsIconImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue773"),
|
|
};
|
|
}
|
|
|
|
private static Result CreateOpenWithMenu(SearchResult record)
|
|
{
|
|
return new Result
|
|
{
|
|
Title = Localize.plugin_explorer_openwith(),
|
|
SubTitle = Localize.plugin_explorer_openwith_subtitle(),
|
|
Action = _ =>
|
|
{
|
|
Process.Start("rundll32.exe", $"{Path.Combine(Environment.SystemDirectory, "shell32.dll")},OpenAs_RunDLL {record.FullPath}");
|
|
return true;
|
|
},
|
|
IcoPath = Constants.ShowContextMenuImagePath,
|
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue7ac"),
|
|
};
|
|
}
|
|
|
|
private void LogException(string message, Exception e)
|
|
{
|
|
Context.API.LogException(ClassName, message, e);
|
|
}
|
|
|
|
private static bool CanRunAsDifferentUser(string path)
|
|
{
|
|
return Path.GetExtension(path) switch
|
|
{
|
|
".exe" or ".bat" or ".msi" => true,
|
|
_ => false,
|
|
};
|
|
}
|
|
}
|
|
}
|