File context menu, element preview improvements
This commit is contained in:
@@ -9,11 +9,23 @@ namespace FileTime.App.Core.ViewModels.ItemPreview;
|
|||||||
[ViewModel]
|
[ViewModel]
|
||||||
public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInitable<IElement>
|
public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInitable<IElement>
|
||||||
{
|
{
|
||||||
|
private record EncodingResult(char BinaryChar, string PartialResult);
|
||||||
|
|
||||||
private const int MaxTextPreviewSize = 1024 * 1024;
|
private const int MaxTextPreviewSize = 1024 * 1024;
|
||||||
|
|
||||||
|
private static readonly List<Encoding> _encodings = new()
|
||||||
|
{
|
||||||
|
Encoding.UTF8,
|
||||||
|
Encoding.Unicode,
|
||||||
|
Encoding.ASCII,
|
||||||
|
Encoding.UTF32,
|
||||||
|
Encoding.BigEndianUnicode
|
||||||
|
};
|
||||||
|
|
||||||
public ItemPreviewMode Mode { get; private set; }
|
public ItemPreviewMode Mode { get; private set; }
|
||||||
|
|
||||||
[Property] private string? _textContent;
|
[Property] private string? _textContent;
|
||||||
|
[Property] private string? _textEncoding;
|
||||||
|
|
||||||
public async Task InitAsync(IElement element)
|
public async Task InitAsync(IElement element)
|
||||||
{
|
{
|
||||||
@@ -21,9 +33,17 @@ public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInit
|
|||||||
{
|
{
|
||||||
var content = await element.Provider.GetContentAsync(element, MaxTextPreviewSize);
|
var content = await element.Provider.GetContentAsync(element, MaxTextPreviewSize);
|
||||||
|
|
||||||
TextContent = content is null
|
if (content is null)
|
||||||
? "Could not read any data from file " + element.Name
|
{
|
||||||
: GetNormalizedText(Encoding.UTF8.GetString(content));
|
TextContent = "Could not read any data from file " + element.Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(TextContent, var encoding) = GetNormalizedText(content);
|
||||||
|
TextEncoding = encoding is null
|
||||||
|
? null
|
||||||
|
: $"{encoding.EncodingName} ({encoding.WebName})";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -36,14 +56,58 @@ public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInit
|
|||||||
_ => ItemPreviewMode.Text
|
_ => ItemPreviewMode.Text
|
||||||
};
|
};
|
||||||
|
|
||||||
string GetNormalizedText(string text)
|
(string, Encoding?) GetNormalizedText(byte[] data)
|
||||||
{
|
{
|
||||||
foreach (var c in text)
|
var binaryCharacter = new Dictionary<string, EncodingResult>();
|
||||||
|
foreach (var encoding in _encodings)
|
||||||
{
|
{
|
||||||
if (c < 32 && c != 9 && c != 10 && c != 13) return $"Binary data, contains '{(int) c}'";
|
var text = encoding.GetString(data);
|
||||||
|
var binary = false;
|
||||||
|
for (var i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
var c = text[i];
|
||||||
|
if (c < 32 && c != 9 && c != 10 && c != 13)
|
||||||
|
{
|
||||||
|
binaryCharacter[encoding.EncodingName] =
|
||||||
|
new EncodingResult(
|
||||||
|
c,
|
||||||
|
i == 0
|
||||||
|
? string.Empty
|
||||||
|
: text.Substring(0, i - 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
binary = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binary) continue;
|
||||||
|
|
||||||
|
return (text, encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
var stringBuilder = new StringBuilder();
|
||||||
|
stringBuilder.AppendLine("The following binary characters were found by encodings:");
|
||||||
|
foreach (var binaryByEncoding in binaryCharacter)
|
||||||
|
{
|
||||||
|
stringBuilder.AppendLine(binaryByEncoding.Key + ": " + (int) binaryByEncoding.Value.BinaryChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
var encodingsWithPartialResult = binaryCharacter.Where(e => !string.IsNullOrWhiteSpace(e.Value.PartialResult)).ToList();
|
||||||
|
if (encodingsWithPartialResult.Count > 0)
|
||||||
|
{
|
||||||
|
stringBuilder.AppendLine("The following partial texts could be read by encodings:");
|
||||||
|
foreach (var binaryByEncoding in encodingsWithPartialResult)
|
||||||
|
{
|
||||||
|
var text = binaryByEncoding.Value.PartialResult;
|
||||||
|
|
||||||
|
stringBuilder.AppendLine(binaryByEncoding.Key);
|
||||||
|
stringBuilder.AppendLine(text);
|
||||||
|
stringBuilder.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (stringBuilder.ToString(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,4 +5,5 @@ namespace FileTime.GuiApp.Services;
|
|||||||
public interface IContextMenuProvider
|
public interface IContextMenuProvider
|
||||||
{
|
{
|
||||||
List<object> GetContextMenuForFolder(IContainer container);
|
List<object> GetContextMenuForFolder(IContainer container);
|
||||||
|
List<object> GetContextMenuForFile(IElement element);
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ using System.Globalization;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.Core.Models;
|
||||||
using FileTime.GuiApp.Services;
|
using FileTime.GuiApp.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
@@ -15,12 +16,16 @@ public class ContextMenuGenerator : IValueConverter
|
|||||||
{
|
{
|
||||||
_contextMenuProvider ??= DI.ServiceProvider.GetRequiredService<IContextMenuProvider>();
|
_contextMenuProvider ??= DI.ServiceProvider.GetRequiredService<IContextMenuProvider>();
|
||||||
|
|
||||||
if (value is IContainerViewModel containerViewModel)
|
if (value is IContainerViewModel {Container: { } container})
|
||||||
{
|
{
|
||||||
return _contextMenuProvider.GetContextMenuForFolder(containerViewModel.Container);
|
return _contextMenuProvider.GetContextMenuForFolder(container);
|
||||||
|
}
|
||||||
|
else if (value is IElementViewModel {Element: { } element})
|
||||||
|
{
|
||||||
|
return _contextMenuProvider.GetContextMenuForFile(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new object[] { new MenuItem() { Header = "asd" } };
|
return new object[] {new MenuItem {Header = "asd"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.GuiApp.Helper;
|
using FileTime.GuiApp.Helper;
|
||||||
@@ -24,19 +25,20 @@ public static class WindowsSystemIconHelper
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
public static ImagePath GetImagePathByIconPath(string path)
|
public static ImagePath GetImagePathByIconPath(string path)
|
||||||
{
|
{
|
||||||
var environemntVariables = Environment.GetEnvironmentVariables();
|
var environmentVariables = Environment.GetEnvironmentVariables();
|
||||||
foreach (var keyo in environemntVariables.Keys)
|
foreach (var keyObject in environmentVariables.Keys)
|
||||||
{
|
{
|
||||||
if (keyo is string key && environemntVariables[key] is string value)
|
if (keyObject is string key && environmentVariables[key] is string value)
|
||||||
{
|
{
|
||||||
path = path.Replace($"%{key}%", value);
|
path = path.Replace($"%{key}%", value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts = path.Split(',');
|
var parts = path.Split(',');
|
||||||
(var parsedResourceId, var path2) = parts.Length >= 2 && long.TryParse(parts[^1], out var id)
|
var (parsedResourceId, path2) = parts.Length >= 2 && long.TryParse(parts[^1], out var id)
|
||||||
? (id, NormalizePath(string.Join(',', parts[..^1])))
|
? (id, NormalizePath(string.Join(',', parts[..^1])))
|
||||||
: (0, NormalizePath(path));
|
: (0, NormalizePath(path));
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,9 @@ public class LinuxContextMenuProvider : IContextMenuProvider
|
|||||||
{
|
{
|
||||||
return new List<object>();
|
return new List<object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<object> GetContextMenuForFile(IElement element)
|
||||||
|
{
|
||||||
|
return new List<object>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
@@ -10,6 +11,7 @@ using Microsoft.Win32;
|
|||||||
|
|
||||||
namespace FileTime.GuiApp.Services;
|
namespace FileTime.GuiApp.Services;
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
public class WindowsContextMenuProvider : IContextMenuProvider
|
public class WindowsContextMenuProvider : IContextMenuProvider
|
||||||
{
|
{
|
||||||
public List<object> GetContextMenuForFolder(IContainer container)
|
public List<object> GetContextMenuForFolder(IContainer container)
|
||||||
@@ -17,17 +19,15 @@ public class WindowsContextMenuProvider : IContextMenuProvider
|
|||||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) throw new NotSupportedException();
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) throw new NotSupportedException();
|
||||||
|
|
||||||
var menuItems = new List<object>();
|
var menuItems = new List<object>();
|
||||||
|
if (container.Provider is not ILocalContentProvider) return menuItems;
|
||||||
|
|
||||||
if (container.Provider is ILocalContentProvider)
|
using var directoryKey = Registry.ClassesRoot.OpenSubKey("Directory");
|
||||||
{
|
ProcessRegistryKeyForContainer(directoryKey, menuItems, container!.NativePath!.Path);
|
||||||
using var directoryKey = Registry.ClassesRoot.OpenSubKey("Directory");
|
|
||||||
ProcessRegistryKey(directoryKey, menuItems, container!.NativePath!.Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return menuItems;
|
return menuItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessRegistryKey(RegistryKey? contextMenuContainer, List<object> menuItems, string folderPath)
|
private void ProcessRegistryKeyForContainer(RegistryKey? contextMenuContainer, List<object> menuItems, string folderPath)
|
||||||
{
|
{
|
||||||
using var shell = contextMenuContainer?.OpenSubKey("shell");
|
using var shell = contextMenuContainer?.OpenSubKey("shell");
|
||||||
if (shell == null) return;
|
if (shell == null) return;
|
||||||
@@ -36,119 +36,253 @@ public class WindowsContextMenuProvider : IContextMenuProvider
|
|||||||
|
|
||||||
foreach (var shellKey in shellSubKeys.Select(k => shell.OpenSubKey(k)).OfType<RegistryKey>())
|
foreach (var shellKey in shellSubKeys.Select(k => shell.OpenSubKey(k)).OfType<RegistryKey>())
|
||||||
{
|
{
|
||||||
var textBase = shellKey.GetValue(null) as string ?? shellKey.GetValue("MUIVerb") as string;
|
var displayTextBase =
|
||||||
|
shellKey.GetValue(null) as string
|
||||||
|
?? shellKey.GetValue("MUIVerb") as string
|
||||||
|
?? shellKey.Name.Split('\\').Last();
|
||||||
|
|
||||||
if (textBase == null) continue;
|
string? displayText = null;
|
||||||
|
displayText = displayTextBase.StartsWith("@")
|
||||||
|
? ResolveText(displayTextBase)
|
||||||
|
: displayTextBase;
|
||||||
|
|
||||||
string? text = null;
|
if (displayText is null) continue;
|
||||||
if (textBase.StartsWith("@"))
|
|
||||||
|
displayText = displayText.Replace("&", "");
|
||||||
|
|
||||||
|
var image = shellKey.GetValue("Icon") is string iconPath
|
||||||
|
? ResolveImage(iconPath)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
using var commandKey = shellKey.OpenSubKey("command");
|
||||||
|
if (commandKey?.GetValueNames().Contains("DelegateExecute") ?? false) continue;
|
||||||
|
|
||||||
|
if (GetCommandKey(shellKey, commandKey) is { } commandString)
|
||||||
{
|
{
|
||||||
var parts = textBase[1..].Split(',');
|
var item = new MenuItem {Header = displayText, Icon = image};
|
||||||
if (parts.Length == 2 && long.TryParse(parts[1], out var parsedResourceId))
|
item.Click += (o, e) => HandleStartCommandMenuItemClick(folderPath, commandString);
|
||||||
{
|
menuItems.Add(item);
|
||||||
if (parsedResourceId < 0) parsedResourceId *= -1;
|
|
||||||
|
|
||||||
text = NativeMethodHelpers.GetStringResource(string.Join(',', parts[..^1]), (uint) parsedResourceId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else if (shellKey.GetValue("ExtendedSubCommandsKey") is string extendedCommands)
|
||||||
{
|
{
|
||||||
text = textBase;
|
var rootMenuItems = new List<object>();
|
||||||
|
|
||||||
|
ProcessRegistryKeyForContainer(Registry.ClassesRoot.OpenSubKey(extendedCommands), rootMenuItems, folderPath);
|
||||||
|
|
||||||
|
if (rootMenuItems.Count == 0) continue;
|
||||||
|
|
||||||
|
var rootMenu = new MenuItem {Header = displayText, Icon = image};
|
||||||
|
foreach (var item in rootMenuItems)
|
||||||
|
{
|
||||||
|
rootMenu.Items.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.Add(rootMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static string? ResolveText(string textBase)
|
||||||
|
{
|
||||||
|
var parts = textBase[1..].Split(',');
|
||||||
|
if (parts.Length == 2 && long.TryParse(parts[1], out var parsedResourceId))
|
||||||
|
{
|
||||||
|
if (parsedResourceId < 0) parsedResourceId *= -1;
|
||||||
|
|
||||||
|
return NativeMethodHelpers.GetStringResource(string.Join(',', parts[..^1]), (uint) parsedResourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text != null)
|
return null;
|
||||||
{
|
}
|
||||||
text = text.Replace("&", "");
|
|
||||||
|
|
||||||
object? image = null;
|
static string? GetCommandKey(RegistryKey shellKey, RegistryKey? commandKey)
|
||||||
try
|
{
|
||||||
{
|
return
|
||||||
if (shellKey.GetValue("Icon") is string iconPath)
|
shellKey.GetSubKeyNames().Contains("command")
|
||||||
{
|
&& commandKey?.GetValue(null) is string commandString
|
||||||
var imagePath = WindowsSystemIconHelper.GetImagePathByIconPath(iconPath);
|
? commandString
|
||||||
if (imagePath.Type == Models.ImagePathType.Raw)
|
: null;
|
||||||
{
|
|
||||||
image = new Image()
|
|
||||||
{
|
|
||||||
Source = (IImage) imagePath.Image!
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
using var commandKey = shellKey.OpenSubKey("command");
|
|
||||||
if (shellKey.GetSubKeyNames().Contains("command") && commandKey?.GetValue(null) is string commandString)
|
|
||||||
{
|
|
||||||
var item = new MenuItem() {Header = text, Icon = image};
|
|
||||||
item.Click += (o, e) => MenuItemClick(folderPath, commandString);
|
|
||||||
menuItems.Add(item);
|
|
||||||
}
|
|
||||||
else if (shellKey.GetValue("ExtendedSubCommandsKey") is string extendedCommands)
|
|
||||||
{
|
|
||||||
var rootMenuItems = new List<object>();
|
|
||||||
|
|
||||||
ProcessRegistryKey(Registry.ClassesRoot.OpenSubKey(extendedCommands), rootMenuItems, folderPath);
|
|
||||||
|
|
||||||
var rootMenu = new MenuItem {Header = text, Icon = image};
|
|
||||||
foreach (var item in rootMenuItems)
|
|
||||||
{
|
|
||||||
rootMenu.Items.Add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
menuItems.Add(rootMenu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MenuItemClick(string folderPath, string commandString)
|
public List<object> GetContextMenuForFile(IElement element)
|
||||||
{
|
{
|
||||||
var commandPartsWithoutAp = commandString.Split('\"').ToList();
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) throw new NotSupportedException();
|
||||||
var commandParts = new List<List<string>>();
|
|
||||||
|
|
||||||
for (var i = 0; i < commandPartsWithoutAp.Count; i++)
|
var menuItems = new List<object>();
|
||||||
|
|
||||||
|
if (element.Provider is not ILocalContentProvider) return menuItems;
|
||||||
|
|
||||||
|
var extension = element.Name.Split('.').LastOrDefault();
|
||||||
|
if (extension is null) return menuItems;
|
||||||
|
|
||||||
|
using var extensionKey = Registry.ClassesRoot.OpenSubKey("." + extension);
|
||||||
|
ProcessRegistryKeyForElement(extensionKey, menuItems, element!.NativePath!.Path);
|
||||||
|
|
||||||
|
return menuItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessRegistryKeyForElement(RegistryKey? extensionKey, List<object> menuItems, string path)
|
||||||
|
{
|
||||||
|
var openWithItems = GetElementOpenWithItems(extensionKey, path);
|
||||||
|
if (openWithItems.Count > 0)
|
||||||
{
|
{
|
||||||
if (i % 2 == 0)
|
var openWithMenuItem = new MenuItem {Header = "Open with"};
|
||||||
|
foreach (var openWithItem in openWithItems)
|
||||||
{
|
{
|
||||||
commandParts.Add(commandPartsWithoutAp[i].Split(' ').ToList());
|
openWithMenuItem.Items.Add(openWithItem);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
commandParts.Add(new List<string> {commandPartsWithoutAp[i]});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menuItems.Add(openWithMenuItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<object> GetElementOpenWithItems(RegistryKey? extensionKey, string path)
|
||||||
|
{
|
||||||
|
List<object> menuItems = new();
|
||||||
|
var openWithProgIds = extensionKey?.OpenSubKey("OpenWithProgids");
|
||||||
|
if (openWithProgIds is null) return menuItems;
|
||||||
|
|
||||||
|
foreach (var valueName in openWithProgIds.GetValueNames())
|
||||||
|
{
|
||||||
|
var programRegistryKey = Registry.ClassesRoot.OpenSubKey(valueName);
|
||||||
|
if (programRegistryKey is null) continue;
|
||||||
|
|
||||||
|
var programOpenKey = programRegistryKey.OpenSubKey("shell")?.OpenSubKey("open");
|
||||||
|
if (programOpenKey is null) continue;
|
||||||
|
|
||||||
|
|
||||||
|
//Try get display name
|
||||||
|
var displayText = GetDisplayText(programRegistryKey, programOpenKey);
|
||||||
|
|
||||||
|
//Try get executable path
|
||||||
|
var programCommandKey = programOpenKey.OpenSubKey("command");
|
||||||
|
if (programCommandKey?.GetValue(null) is not string command) continue;
|
||||||
|
|
||||||
|
var commandParts = ChopCommand(command);
|
||||||
|
|
||||||
|
var (executable, _) = TryGetExecutablePath(commandParts);
|
||||||
|
|
||||||
|
if (executable is null) continue;
|
||||||
|
|
||||||
|
displayText ??= Registry.ClassesRoot
|
||||||
|
.OpenSubKey("Local Settings")
|
||||||
|
?.OpenSubKey("Software")
|
||||||
|
?.OpenSubKey("Microsoft")
|
||||||
|
?.OpenSubKey("Windows")
|
||||||
|
?.OpenSubKey("Shell")
|
||||||
|
?.OpenSubKey("MuiCache")
|
||||||
|
?.GetValue(executable + ".FriendlyAppName") as string;
|
||||||
|
|
||||||
|
if (displayText is null) continue;
|
||||||
|
|
||||||
|
var menuItem = new MenuItem {Header = displayText, Icon = ResolveImage(executable)};
|
||||||
|
menuItem.Click +=
|
||||||
|
(_, _) => HandleStartCommandMenuItemClick(
|
||||||
|
path,
|
||||||
|
command,
|
||||||
|
commandParts: commandParts);
|
||||||
|
menuItems.Add(menuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < commandParts.Count; i++)
|
return menuItems;
|
||||||
{
|
|
||||||
for (var i2 = 0; i2 < commandParts[i].Count; i2++)
|
|
||||||
{
|
|
||||||
commandParts[i][i2] = commandParts[i][i2].Replace("%1", folderPath).Replace("%V", folderPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var commandPartsWithoutEmpty = commandParts.SelectMany(c => c).Where(c => !string.IsNullOrWhiteSpace(c)).ToList();
|
static string? GetDisplayText(RegistryKey programRegistryKey, RegistryKey programOpenKey)
|
||||||
|
{
|
||||||
|
if (programRegistryKey.GetValue("FriendlyAppName") is string rootFriendAppName)
|
||||||
|
return rootFriendAppName;
|
||||||
|
|
||||||
|
if (programOpenKey.GetValue("FriendlyAppName") is string openFriendAppName) return openFriendAppName;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleStartCommandMenuItemClick(
|
||||||
|
string placeholderValue,
|
||||||
|
string commandString,
|
||||||
|
List<List<string>>? commandParts = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
commandParts ??= ChopCommand(commandString);
|
||||||
|
|
||||||
|
ReplacePlaceholders(commandParts, placeholderValue);
|
||||||
|
|
||||||
|
var commandPartsWithoutEmpty = commandParts
|
||||||
|
.SelectMany(c => c)
|
||||||
|
.Where(c => !string.IsNullOrWhiteSpace(c))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (commandPartsWithoutEmpty.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
if (commandPartsWithoutEmpty.Count == 1)
|
if (commandPartsWithoutEmpty.Count == 1)
|
||||||
{
|
{
|
||||||
Process.Start(commandPartsWithoutEmpty[0]);
|
Process.Start(commandPartsWithoutEmpty[0]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (commandPartsWithoutEmpty.Count > 1)
|
|
||||||
|
|
||||||
|
if (commandParts[0].Count > 0)
|
||||||
{
|
{
|
||||||
var paramStartIndex1 = -1;
|
var (executable, lastExecutablePart) = TryGetExecutablePath(commandParts);
|
||||||
var paramStartIndex2 = -1;
|
|
||||||
|
if (executable is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (paramStartX, paramStartY) = GetPositionInArrayFromBySkipping(commandParts, 1, 0, lastExecutablePart);
|
||||||
|
var arguments = SumList(commandParts, paramStartX, paramStartY);
|
||||||
|
|
||||||
|
using var process = new Process();
|
||||||
|
process.StartInfo.FileName = executable;
|
||||||
|
process.StartInfo.Arguments = arguments.TrimStart();
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//TODO: error message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (argumentsStartIndexX, argumentsStartIndexY) = FindArgumentStartPosition(commandParts, commandPartsWithoutEmpty);
|
||||||
|
var arguments2 = SumList(commandParts, argumentsStartIndexX, argumentsStartIndexY);
|
||||||
|
using var process2 = new Process();
|
||||||
|
process2.StartInfo.FileName = commandPartsWithoutEmpty[0];
|
||||||
|
process2.StartInfo.Arguments = arguments2;
|
||||||
|
process2.Start();
|
||||||
|
|
||||||
|
static void ReplacePlaceholders(List<List<string>> commandParts, string placeholderValue)
|
||||||
|
{
|
||||||
|
foreach (var commandPart in commandParts)
|
||||||
|
{
|
||||||
|
for (var i2 = 0; i2 < commandPart.Count; i2++)
|
||||||
|
{
|
||||||
|
commandPart[i2] = commandPart[i2]
|
||||||
|
.Replace("%1", placeholderValue)
|
||||||
|
.Replace("%V", placeholderValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static (int argumentsStartIndexX, int argumentsStartIndexY) FindArgumentStartPosition(
|
||||||
|
List<List<string>> commandParts,
|
||||||
|
List<string> commandPartsWithoutEmpty)
|
||||||
|
{
|
||||||
|
var argumentsStartIndexX = -1;
|
||||||
|
var argumentsStartIndexY = -1;
|
||||||
var found = false;
|
var found = false;
|
||||||
|
|
||||||
for (var x = 0; x < commandParts.Count && paramStartIndex1 == -1; x++)
|
for (var x = 0; x < commandParts.Count && argumentsStartIndexX == -1; x++)
|
||||||
{
|
{
|
||||||
for (var y = 0; y < commandParts[x].Count; y++)
|
for (var y = 0; y < commandParts[x].Count; y++)
|
||||||
{
|
{
|
||||||
if (found)
|
if (found)
|
||||||
{
|
{
|
||||||
paramStartIndex1 = x;
|
argumentsStartIndexX = x;
|
||||||
paramStartIndex2 = y;
|
argumentsStartIndexY = y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,49 +293,102 @@ public class WindowsContextMenuProvider : IContextMenuProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var arguments = SumList(commandParts, paramStartIndex1, paramStartIndex2);
|
return (argumentsStartIndexX, argumentsStartIndexY);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
static (int positionX, int positionY) GetPositionInArrayFromBySkipping(
|
||||||
|
List<List<string>> data,
|
||||||
|
int skip,
|
||||||
|
int startX = 0,
|
||||||
|
int startY = 0
|
||||||
|
)
|
||||||
|
{
|
||||||
|
int skipping = 0;
|
||||||
|
var x = startX;
|
||||||
|
var y = startY;
|
||||||
|
|
||||||
|
var resultX = -1;
|
||||||
|
var resultY = -1;
|
||||||
|
for (; x < data.Count && resultX == -1; x++, y = 0)
|
||||||
{
|
{
|
||||||
using var process = new Process();
|
for (; y < data[x].Count; y++)
|
||||||
process.StartInfo.FileName = commandPartsWithoutEmpty[0];
|
|
||||||
process.StartInfo.Arguments = arguments;
|
|
||||||
process.Start();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
if (commandParts[0].Count > 0)
|
|
||||||
{
|
{
|
||||||
var executable = "";
|
if (skipping == skip)
|
||||||
var lastExecutablePart = 0;
|
|
||||||
for (lastExecutablePart = 0; !File.Exists(executable) && lastExecutablePart < commandParts[0].Count; lastExecutablePart++)
|
|
||||||
{
|
{
|
||||||
executable += (lastExecutablePart == 0 ? "" : " ") + commandParts[0][lastExecutablePart];
|
resultX = x;
|
||||||
|
resultY = y;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastExecutablePart--;
|
skipping++;
|
||||||
|
|
||||||
if (File.Exists(executable))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var (paramStartX, paramStartY) = GetCoordinatesFrom(commandParts, 1, 0, lastExecutablePart);
|
|
||||||
arguments = SumList(commandParts, paramStartX, paramStartY);
|
|
||||||
|
|
||||||
using var process = new Process();
|
|
||||||
process.StartInfo.FileName = executable;
|
|
||||||
process.StartInfo.Arguments = arguments;
|
|
||||||
process.Start();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
//TODO: error message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//TODO: ELSE error message
|
}
|
||||||
|
|
||||||
|
return (resultX, resultY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<List<string>> ChopCommand(string commandString)
|
||||||
|
{
|
||||||
|
var commandPartsWithoutQuotationMark = commandString.Split('\"').ToList();
|
||||||
|
var commandParts = new List<List<string>>();
|
||||||
|
|
||||||
|
for (var i = 0; i < commandPartsWithoutQuotationMark.Count; i++)
|
||||||
|
{
|
||||||
|
if (i % 2 == 0)
|
||||||
|
{
|
||||||
|
commandParts.Add(commandPartsWithoutQuotationMark[i].Split(' ').ToList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
commandParts.Add(new List<string> {commandPartsWithoutQuotationMark[i]});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return commandParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static (string? executable, int lastExecutablePart) TryGetExecutablePath(List<List<string>> commandParts)
|
||||||
|
{
|
||||||
|
var executable = "";
|
||||||
|
var lastExecutablePart = 0;
|
||||||
|
|
||||||
|
//Note: If the first block is empty, we will use the second one
|
||||||
|
//Note: This can happen (or rather, the common case) when the command starts with ", for example
|
||||||
|
// `"c:\...\...\xyz.exe" someParam` (without the ``)
|
||||||
|
var executableIndex = commandParts[0].Count == 0 || commandParts[0].All(string.IsNullOrWhiteSpace)
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
for (; !File.Exists(executable) && lastExecutablePart < commandParts[executableIndex].Count; lastExecutablePart++)
|
||||||
|
{
|
||||||
|
executable += (lastExecutablePart == 0 ? "" : " ") + commandParts[executableIndex][lastExecutablePart];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (executableIndex == 1) lastExecutablePart += commandParts[0].Count;
|
||||||
|
|
||||||
|
lastExecutablePart--;
|
||||||
|
|
||||||
|
return (File.Exists(executable) ? executable : null, lastExecutablePart);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Image? ResolveImage(string iconPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var imagePath = WindowsSystemIconHelper.GetImagePathByIconPath(iconPath);
|
||||||
|
if (imagePath.Type != Models.ImagePathType.Raw) return null;
|
||||||
|
|
||||||
|
return new Image
|
||||||
|
{
|
||||||
|
Source = (IImage) imagePath.Image!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string SumList(List<List<string>> data, int paramStartIndex1, int paramStartIndex2)
|
private static string SumList(List<List<string>> data, int paramStartIndex1, int paramStartIndex2)
|
||||||
@@ -224,30 +411,4 @@ public class WindowsContextMenuProvider : IContextMenuProvider
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (int, int) GetCoordinatesFrom(List<List<string>> data, int skip, int startX = 0, int startY = 0)
|
|
||||||
{
|
|
||||||
int skipping = 0;
|
|
||||||
var x = startX;
|
|
||||||
var y = startY;
|
|
||||||
|
|
||||||
var resultX = -1;
|
|
||||||
var resultY = -1;
|
|
||||||
for (; x < data.Count && resultX == -1; x++, y = 0)
|
|
||||||
{
|
|
||||||
for (; y < data[x].Count; y++)
|
|
||||||
{
|
|
||||||
if (skipping == skip)
|
|
||||||
{
|
|
||||||
resultX = x;
|
|
||||||
resultY = y;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
skipping++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (resultX, resultY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -553,12 +553,19 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Empty}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}"
|
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Empty}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}"
|
||||||
Text="Empty" />
|
Text="Empty" />
|
||||||
<ScrollViewer IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Text}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}">
|
<Grid IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Text}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}" RowDefinitions="*, Auto">
|
||||||
<TextBox
|
<ScrollViewer>
|
||||||
IsReadOnly="True"
|
<TextBox
|
||||||
Text="{Binding ItemPreviewService.ItemPreview^.TextContent}"
|
IsReadOnly="True"
|
||||||
|
Text="{Binding ItemPreviewService.ItemPreview^.TextContent}"
|
||||||
|
x:CompileBindings="False" />
|
||||||
|
</ScrollViewer>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="5"
|
||||||
|
Text="{Binding ItemPreviewService.ItemPreview^.TextEncoding, StringFormat=Encoding: {0}}"
|
||||||
x:CompileBindings="False" />
|
x:CompileBindings="False" />
|
||||||
</ScrollViewer>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
Reference in New Issue
Block a user