mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Merge remote-tracking branch 'origin/main' into dev/vanzue/cmdpal-pt
This commit is contained in:
2
.github/actions/spell-check/expect.txt
vendored
2
.github/actions/spell-check/expect.txt
vendored
@@ -774,6 +774,7 @@ INITGUID
|
|||||||
INITTOLOGFONTSTRUCT
|
INITTOLOGFONTSTRUCT
|
||||||
INLINEPREFIX
|
INLINEPREFIX
|
||||||
inlines
|
inlines
|
||||||
|
Inno
|
||||||
INPC
|
INPC
|
||||||
inproc
|
inproc
|
||||||
INPUTHARDWARE
|
INPUTHARDWARE
|
||||||
@@ -1849,6 +1850,7 @@ UNCPRIORITY
|
|||||||
UNDNAME
|
UNDNAME
|
||||||
UNICODETEXT
|
UNICODETEXT
|
||||||
unins
|
unins
|
||||||
|
Uninstaller
|
||||||
uninstalls
|
uninstalls
|
||||||
Uniquifies
|
Uniquifies
|
||||||
unitconverter
|
unitconverter
|
||||||
|
|||||||
@@ -135,8 +135,9 @@ public partial class App : Application
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var winget = new WinGetExtensionCommandsProvider();
|
var winget = new WinGetExtensionCommandsProvider();
|
||||||
var callback = allApps.LookupApp;
|
winget.SetAllLookup(
|
||||||
winget.SetAllLookup(callback);
|
query => allApps.LookupAppByPackageFamilyName(query, requireSingleMatch: true),
|
||||||
|
query => allApps.LookupAppByProductCode(query, requireSingleMatch: true));
|
||||||
services.AddSingleton<ICommandProvider>(winget);
|
services.AddSingleton<ICommandProvider>(winget);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class AllAppsCommandProviderTests : AppsTestBase
|
|||||||
var provider = new AllAppsCommandProvider(page);
|
var provider = new AllAppsCommandProvider(page);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = provider.LookupApp(string.Empty);
|
var result = provider.LookupAppByDisplayName(string.Empty);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsNotNull(result);
|
Assert.IsNotNull(result);
|
||||||
@@ -77,7 +77,7 @@ public class AllAppsCommandProviderTests : AppsTestBase
|
|||||||
await WaitForPageInitializationAsync();
|
await WaitForPageInitializationAsync();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = provider.LookupApp("TestApp");
|
var result = provider.LookupAppByDisplayName("TestApp");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsNotNull(result);
|
Assert.IsNotNull(result);
|
||||||
@@ -97,7 +97,7 @@ public class AllAppsCommandProviderTests : AppsTestBase
|
|||||||
await WaitForPageInitializationAsync();
|
await WaitForPageInitializationAsync();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = provider.LookupApp("NonExistentApp");
|
var result = provider.LookupAppByDisplayName("NonExistentApp");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsNull(result);
|
Assert.IsNull(result);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||||
|
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||||
using Microsoft.CmdPal.Ext.Apps.State;
|
using Microsoft.CmdPal.Ext.Apps.State;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
@@ -66,7 +68,71 @@ public partial class AllAppsCommandProvider : CommandProvider
|
|||||||
|
|
||||||
public override ICommandItem[] TopLevelCommands() => [_listItem, .. _page.GetPinnedApps()];
|
public override ICommandItem[] TopLevelCommands() => [_listItem, .. _page.GetPinnedApps()];
|
||||||
|
|
||||||
public ICommandItem? LookupApp(string displayName)
|
public ICommandItem? LookupAppByPackageFamilyName(string packageFamilyName, bool requireSingleMatch)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(packageFamilyName))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = _page.GetItems();
|
||||||
|
List<ICommandItem> matches = [];
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item is AppListItem appItem && string.Equals(packageFamilyName, appItem.App.PackageFamilyName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
matches.Add(item);
|
||||||
|
if (!requireSingleMatch)
|
||||||
|
{
|
||||||
|
// Return early if we don't require uniqueness.
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requireSingleMatch && matches.Count == 1 ? matches[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommandItem? LookupAppByProductCode(string productCode, bool requireSingleMatch)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(productCode))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UninstallRegistryAppLocator.TryGetInstallInfo(productCode, out _, out var candidates) || candidates.Count <= 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = _page.GetItems();
|
||||||
|
List<ICommandItem> matches = [];
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item is not AppListItem appListItem || string.IsNullOrEmpty(appListItem.App.FullExecutablePath))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var candidate in candidates)
|
||||||
|
{
|
||||||
|
if (string.Equals(appListItem.App.FullExecutablePath, candidate, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
matches.Add(item);
|
||||||
|
if (!requireSingleMatch)
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requireSingleMatch && matches.Count == 1 ? matches[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommandItem? LookupAppByDisplayName(string displayName)
|
||||||
{
|
{
|
||||||
var items = _page.GetItems();
|
var items = _page.GetItems();
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ public sealed class AppItem
|
|||||||
|
|
||||||
public string AppIdentifier { get; set; } = string.Empty;
|
public string AppIdentifier { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? PackageFamilyName { get; set; }
|
||||||
|
|
||||||
|
public string? FullExecutablePath { get; set; }
|
||||||
|
|
||||||
public AppItem()
|
public AppItem()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ public sealed partial class AppListItem : ListItem
|
|||||||
|
|
||||||
public string AppIdentifier => _app.AppIdentifier;
|
public string AppIdentifier => _app.AppIdentifier;
|
||||||
|
|
||||||
|
public AppItem App => _app;
|
||||||
|
|
||||||
public AppListItem(AppItem app, bool useThumbnails, bool isPinned)
|
public AppListItem(AppItem app, bool useThumbnails, bool isPinned)
|
||||||
{
|
{
|
||||||
Command = _appCommand = new AppCommand(app);
|
Command = _appCommand = new AppCommand(app);
|
||||||
@@ -82,6 +84,12 @@ public sealed partial class AppListItem : ListItem
|
|||||||
metadata.Add(new DetailsElement() { Key = "Path", Data = new DetailsLink() { Text = _app.ExePath } });
|
metadata.Add(new DetailsElement() { Key = "Path", Data = new DetailsLink() { Text = _app.ExePath } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
metadata.Add(new DetailsElement() { Key = "[DEBUG] AppIdentifier", Data = new DetailsLink() { Text = _app.AppIdentifier } });
|
||||||
|
metadata.Add(new DetailsElement() { Key = "[DEBUG] ExePath", Data = new DetailsLink() { Text = _app.ExePath } });
|
||||||
|
metadata.Add(new DetailsElement() { Key = "[DEBUG] IcoPath", Data = new DetailsLink() { Text = _app.IcoPath } });
|
||||||
|
#endif
|
||||||
|
|
||||||
// Icon
|
// Icon
|
||||||
IconInfo? heroImage = null;
|
IconInfo? heroImage = null;
|
||||||
if (_app.IsPackaged)
|
if (_app.IsPackaged)
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||||
|
|
||||||
|
internal static class UninstallRegistryAppLocator
|
||||||
|
{
|
||||||
|
private static readonly string[] UninstallBaseKeys =
|
||||||
|
[
|
||||||
|
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
|
||||||
|
@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to find install directory and a list of plausible main EXEs from an uninstall key
|
||||||
|
/// (e.g. Inno Setup keys like "{guid}_is1").
|
||||||
|
/// <paramref name="exeCandidates"/> may be empty if we couldn't pick any safe EXEs.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Returns true if the uninstall key is found and an install directory is resolved.
|
||||||
|
/// </returns>
|
||||||
|
public static bool TryGetInstallInfo(
|
||||||
|
string uninstallKeyName,
|
||||||
|
out string? installDir,
|
||||||
|
out IReadOnlyList<string> exeCandidates,
|
||||||
|
string? expectedExeName = null)
|
||||||
|
{
|
||||||
|
installDir = null;
|
||||||
|
exeCandidates = [];
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(uninstallKeyName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Key name must not be null or empty.", nameof(uninstallKeyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstallKeyName = uninstallKeyName.Trim();
|
||||||
|
|
||||||
|
foreach (var baseKeyPath in UninstallBaseKeys)
|
||||||
|
{
|
||||||
|
// HKLM
|
||||||
|
using (var key = Registry.LocalMachine.OpenSubKey($"{baseKeyPath}\\{uninstallKeyName}"))
|
||||||
|
{
|
||||||
|
if (TryFromUninstallKey(key, expectedExeName, out installDir, out exeCandidates))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HKCU
|
||||||
|
using (var key = Registry.CurrentUser.OpenSubKey($"{baseKeyPath}\\{uninstallKeyName}"))
|
||||||
|
{
|
||||||
|
if (TryFromUninstallKey(key, expectedExeName, out installDir, out exeCandidates))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryFromUninstallKey(
|
||||||
|
RegistryKey? key,
|
||||||
|
string? expectedExeName,
|
||||||
|
out string? installDir,
|
||||||
|
out IReadOnlyList<string> exeCandidates)
|
||||||
|
{
|
||||||
|
installDir = null;
|
||||||
|
exeCandidates = [];
|
||||||
|
|
||||||
|
if (key is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var location = (key.GetValue("InstallLocation") as string)?.Trim('"', ' ', '\t');
|
||||||
|
if (string.IsNullOrEmpty(location))
|
||||||
|
{
|
||||||
|
location = (key.GetValue("Inno Setup: App Path") as string)?.Trim('"', ' ', '\t');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(location))
|
||||||
|
{
|
||||||
|
var uninstall = key.GetValue("UninstallString") as string;
|
||||||
|
var uninsExe = ExtractFirstPath(uninstall);
|
||||||
|
if (!string.IsNullOrEmpty(uninsExe))
|
||||||
|
{
|
||||||
|
var dir = Path.GetDirectoryName(uninsExe);
|
||||||
|
if (!string.IsNullOrEmpty(dir) && Directory.Exists(dir))
|
||||||
|
{
|
||||||
|
location = dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(location) || !Directory.Exists(location))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
installDir = location;
|
||||||
|
|
||||||
|
// Collect safe EXE candidates; may be empty if ambiguous or only uninstall exes exist.
|
||||||
|
exeCandidates = GetExeCandidates(location, expectedExeName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> GetExeCandidates(string root, string? expectedExeName)
|
||||||
|
{
|
||||||
|
// Look at root and a "bin" subfolder (very common pattern)
|
||||||
|
var allExes = Directory.EnumerateFiles(root, "*.exe", SearchOption.TopDirectoryOnly)
|
||||||
|
.Concat(GetBinExes(root))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (allExes.Length == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new List<string>();
|
||||||
|
|
||||||
|
// 1) Exact match on expected exe name (if provided), ignoring case, and not uninstall/setup-like.
|
||||||
|
if (!string.IsNullOrWhiteSpace(expectedExeName))
|
||||||
|
{
|
||||||
|
foreach (var exe in allExes)
|
||||||
|
{
|
||||||
|
if (string.Equals(Path.GetFileName(exe), expectedExeName, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!LooksLikeUninstallerOrSetup(exe))
|
||||||
|
{
|
||||||
|
result.Add(exe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) All other non-uninstall/setup exes
|
||||||
|
foreach (var exe in allExes)
|
||||||
|
{
|
||||||
|
if (LooksLikeUninstallerOrSetup(exe))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip ones already added as expectedExeName matches
|
||||||
|
if (result.Contains(exe, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(exe);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) We intentionally do NOT add uninstall/setup/update exes here.
|
||||||
|
// If you ever want them, you can add a separate API to expose them.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetBinExes(string root)
|
||||||
|
{
|
||||||
|
var bin = Path.Combine(root, "bin");
|
||||||
|
return !Directory.Exists(bin)
|
||||||
|
? []
|
||||||
|
: Directory.EnumerateFiles(bin, "*.exe", SearchOption.TopDirectoryOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool LooksLikeUninstallerOrSetup(string path)
|
||||||
|
{
|
||||||
|
var name = Path.GetFileName(path);
|
||||||
|
return name.StartsWith("unins", StringComparison.OrdinalIgnoreCase) // e.g. Inno: unins000.exe
|
||||||
|
|| name.Contains("setup", StringComparison.OrdinalIgnoreCase) // setup.exe
|
||||||
|
|| name.Contains("installer", StringComparison.OrdinalIgnoreCase) // installer.exe / MyAppInstaller.exe
|
||||||
|
|| name.Contains("update", StringComparison.OrdinalIgnoreCase); // updater/updater.exe
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ExtractFirstPath(string? commandLine)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(commandLine))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
commandLine = commandLine.Trim();
|
||||||
|
|
||||||
|
if (commandLine.StartsWith('"'))
|
||||||
|
{
|
||||||
|
var endQuote = commandLine.IndexOf('"', 1);
|
||||||
|
if (endQuote > 1)
|
||||||
|
{
|
||||||
|
return commandLine[1..endQuote];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstSpace = commandLine.IndexOf(' ');
|
||||||
|
var candidate = firstSpace > 0 ? commandLine[..firstSpace] : commandLine;
|
||||||
|
candidate = candidate.Trim('"');
|
||||||
|
return candidate.Length > 0 ? candidate : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -558,6 +558,7 @@ public class UWPApplication : IUWPApplication
|
|||||||
IsPackaged = true,
|
IsPackaged = true,
|
||||||
Commands = app.GetCommands(),
|
Commands = app.GetCommands(),
|
||||||
AppIdentifier = app.GetAppIdentifier(),
|
AppIdentifier = app.GetAppIdentifier(),
|
||||||
|
PackageFamilyName = app.Package.FamilyName,
|
||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1065,6 +1065,7 @@ public class Win32Program : IProgram
|
|||||||
DirPath = app.Location,
|
DirPath = app.Location,
|
||||||
Commands = app.GetCommands(),
|
Commands = app.GetCommands(),
|
||||||
AppIdentifier = app.GetAppIdentifier(),
|
AppIdentifier = app.GetAppIdentifier(),
|
||||||
|
FullExecutablePath = app.FullPath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public partial class InstallPackageCommand : InvokableCommand
|
|||||||
{
|
{
|
||||||
PackageInstallCommandState.Install => Icons.DownloadIcon,
|
PackageInstallCommandState.Install => Icons.DownloadIcon,
|
||||||
PackageInstallCommandState.Update => Icons.UpdateIcon,
|
PackageInstallCommandState.Update => Icons.UpdateIcon,
|
||||||
PackageInstallCommandState.Uninstall => Icons.CompletedIcon,
|
PackageInstallCommandState.Uninstall => Icons.DeleteIcon,
|
||||||
_ => throw new NotImplementedException(),
|
_ => throw new NotImplementedException(),
|
||||||
};
|
};
|
||||||
Name = InstallCommandState switch
|
Name = InstallCommandState switch
|
||||||
|
|||||||
@@ -194,46 +194,95 @@ public partial class InstallPackageListItem : ListItem
|
|||||||
var isInstalled = _package.InstalledVersion is not null;
|
var isInstalled = _package.InstalledVersion is not null;
|
||||||
|
|
||||||
var installedState = isInstalled ?
|
var installedState = isInstalled ?
|
||||||
(_package.IsUpdateAvailable ?
|
(_package.IsUpdateAvailable ? PackageInstallCommandState.Update : PackageInstallCommandState.Uninstall) :
|
||||||
PackageInstallCommandState.Update : PackageInstallCommandState.Uninstall) :
|
|
||||||
PackageInstallCommandState.Install;
|
PackageInstallCommandState.Install;
|
||||||
|
|
||||||
// might be an uninstall command
|
// might be an uninstall command
|
||||||
InstallPackageCommand installCommand = new(_package, installedState);
|
InstallPackageCommand installCommand = new(_package, installedState);
|
||||||
|
|
||||||
if (isInstalled)
|
if (_package.InstalledVersion is not null)
|
||||||
{
|
{
|
||||||
this.Icon = installCommand.Icon;
|
#if DEBUG
|
||||||
this.Command = new NoOpCommand();
|
var installerType = _package.InstalledVersion.GetMetadata(PackageVersionMetadataField.InstallerType);
|
||||||
|
Subtitle = installerType + " | " + Subtitle;
|
||||||
|
#endif
|
||||||
|
|
||||||
List<IContextItem> contextMenu = [];
|
List<IContextItem> contextMenu = [];
|
||||||
CommandContextItem uninstallContextItem = new(installCommand)
|
Command = installCommand;
|
||||||
|
Icon = installedState switch
|
||||||
{
|
{
|
||||||
IsCritical = true,
|
PackageInstallCommandState.Install => Icons.DownloadIcon,
|
||||||
Icon = Icons.DeleteIcon,
|
PackageInstallCommandState.Update => Icons.UpdateIcon,
|
||||||
|
PackageInstallCommandState.Uninstall => Icons.CompletedIcon,
|
||||||
|
_ => Icons.DownloadIcon,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (WinGetStatics.AppSearchCallback is not null)
|
TryLocateAndAppendActionForApp(contextMenu);
|
||||||
|
|
||||||
|
MoreCommands = contextMenu.ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var callback = WinGetStatics.AppSearchCallback;
|
_installCommand = new InstallPackageCommand(_package, installedState);
|
||||||
var installedApp = callback(_package.DefaultInstallVersion is null ? _package.Name : _package.DefaultInstallVersion.DisplayName);
|
_installCommand.InstallStateChanged += InstallStateChangedHandler;
|
||||||
if (installedApp is not null)
|
Command = _installCommand;
|
||||||
{
|
Icon = _installCommand.Icon;
|
||||||
this.Command = installedApp.Command;
|
|
||||||
contextMenu = [.. installedApp.MoreCommands];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contextMenu.Add(uninstallContextItem);
|
private void TryLocateAndAppendActionForApp(List<IContextItem> contextMenu)
|
||||||
this.MoreCommands = contextMenu.ToArray();
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Let's try to connect it to an installed app if possible
|
||||||
|
// This is a bit of dark magic, since there's no direct link between
|
||||||
|
// WinGet packages and installed apps.
|
||||||
|
var lookupByPackageName = WinGetStatics.AppSearchByPackageFamilyNameCallback;
|
||||||
|
if (lookupByPackageName is not null)
|
||||||
|
{
|
||||||
|
var names = _package.InstalledVersion.PackageFamilyNames;
|
||||||
|
for (var i = 0; i < names.Count; i++)
|
||||||
|
{
|
||||||
|
var installedAppByPfn = lookupByPackageName(names[i]);
|
||||||
|
if (installedAppByPfn is not null)
|
||||||
|
{
|
||||||
|
contextMenu.Add(new Separator());
|
||||||
|
contextMenu.Add(new CommandContextItem(installedAppByPfn.Command));
|
||||||
|
foreach (var item in installedAppByPfn.MoreCommands)
|
||||||
|
{
|
||||||
|
contextMenu.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// didn't find the app
|
var lookupByProductCode = WinGetStatics.AppSearchByProductCodeCallback;
|
||||||
_installCommand = new InstallPackageCommand(_package, installedState);
|
if (lookupByProductCode is not null)
|
||||||
this.Command = _installCommand;
|
{
|
||||||
|
var productCodes = _package.InstalledVersion.ProductCodes;
|
||||||
|
for (var i = 0; i < productCodes.Count; i++)
|
||||||
|
{
|
||||||
|
var installedAppByProductCode = lookupByProductCode(productCodes[i]);
|
||||||
|
if (installedAppByProductCode is not null)
|
||||||
|
{
|
||||||
|
contextMenu.Add(new Separator());
|
||||||
|
contextMenu.Add(new CommandContextItem(installedAppByProductCode.Command));
|
||||||
|
foreach (var item in installedAppByProductCode.MoreCommands)
|
||||||
|
{
|
||||||
|
contextMenu.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
Icon = _installCommand.Icon;
|
return;
|
||||||
_installCommand.InstallStateChanged += InstallStateChangedHandler;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Failed to retrieve app context menu items for package '{_package?.Name ?? "Unknown"}'", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InstallStateChangedHandler(object? sender, InstallPackageCommand e)
|
private void InstallStateChangedHandler(object? sender, InstallPackageCommand e)
|
||||||
|
|||||||
@@ -41,5 +41,9 @@ public partial class WinGetExtensionCommandsProvider : CommandProvider
|
|||||||
|
|
||||||
public override void InitializeWithHost(IExtensionHost host) => WinGetExtensionHost.Instance.Initialize(host);
|
public override void InitializeWithHost(IExtensionHost host) => WinGetExtensionHost.Instance.Initialize(host);
|
||||||
|
|
||||||
public void SetAllLookup(Func<string, ICommandItem?> callback) => WinGetStatics.AppSearchCallback = callback;
|
public void SetAllLookup(Func<string, ICommandItem?> lookupByPackageName, Func<string, ICommandItem?> lookupByProductCode)
|
||||||
|
{
|
||||||
|
WinGetStatics.AppSearchByPackageFamilyNameCallback = lookupByPackageName;
|
||||||
|
WinGetStatics.AppSearchByProductCodeCallback = lookupByProductCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ internal static class WinGetStatics
|
|||||||
|
|
||||||
private static readonly StatusMessage _errorMessage = new() { State = MessageState.Error };
|
private static readonly StatusMessage _errorMessage = new() { State = MessageState.Error };
|
||||||
|
|
||||||
public static Func<string, ICommandItem?>? AppSearchCallback { get; set; }
|
public static Func<string, ICommandItem?>? AppSearchByPackageFamilyNameCallback { get; set; }
|
||||||
|
|
||||||
|
public static Func<string, ICommandItem?>? AppSearchByProductCodeCallback { get; set; }
|
||||||
|
|
||||||
private static readonly CompositeFormat CreateCatalogErrorMessage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_create_catalog_error);
|
private static readonly CompositeFormat CreateCatalogErrorMessage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_create_catalog_error);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user