Merge remote-tracking branch 'origin/main' into dev/snickler/net10-upgrade

This commit is contained in:
Jeremy Sinclair
2025-12-05 06:22:30 -05:00
17 changed files with 422 additions and 38 deletions

View File

@@ -773,6 +773,7 @@ INITGUID
INITTOLOGFONTSTRUCT
INLINEPREFIX
inlines
Inno
INPC
inproc
INPUTHARDWARE
@@ -1848,6 +1849,7 @@ UNCPRIORITY
UNDNAME
UNICODETEXT
unins
Uninstaller
uninstalls
Uniquifies
unitconverter

View File

@@ -135,8 +135,9 @@ public partial class App : Application
try
{
var winget = new WinGetExtensionCommandsProvider();
var callback = allApps.LookupApp;
winget.SetAllLookup(callback);
winget.SetAllLookup(
query => allApps.LookupAppByPackageFamilyName(query, requireSingleMatch: true),
query => allApps.LookupAppByProductCode(query, requireSingleMatch: true));
services.AddSingleton<ICommandProvider>(winget);
}
catch (Exception ex)

View File

@@ -58,7 +58,7 @@ public class AllAppsCommandProviderTests : AppsTestBase
var provider = new AllAppsCommandProvider(page);
// Act
var result = provider.LookupApp(string.Empty);
var result = provider.LookupAppByDisplayName(string.Empty);
// Assert
Assert.IsNotNull(result);
@@ -77,7 +77,7 @@ public class AllAppsCommandProviderTests : AppsTestBase
await WaitForPageInitializationAsync();
// Act
var result = provider.LookupApp("TestApp");
var result = provider.LookupAppByDisplayName("TestApp");
// Assert
Assert.IsNotNull(result);
@@ -97,7 +97,7 @@ public class AllAppsCommandProviderTests : AppsTestBase
await WaitForPageInitializationAsync();
// Act
var result = provider.LookupApp("NonExistentApp");
var result = provider.LookupAppByDisplayName("NonExistentApp");
// Assert
Assert.IsNull(result);

View File

@@ -4,6 +4,8 @@
using System;
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.State;
using Microsoft.CommandPalette.Extensions;
@@ -66,7 +68,71 @@ public partial class AllAppsCommandProvider : CommandProvider
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();

View File

@@ -29,6 +29,10 @@ public sealed class AppItem
public string AppIdentifier { get; set; } = string.Empty;
public string? PackageFamilyName { get; set; }
public string? FullExecutablePath { get; set; }
public AppItem()
{
}

View File

@@ -40,6 +40,8 @@ public sealed partial class AppListItem : ListItem
public string AppIdentifier => _app.AppIdentifier;
public AppItem App => _app;
public AppListItem(AppItem app, bool useThumbnails, bool isPinned)
{
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 } });
}
#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
IconInfo? heroImage = null;
if (_app.IsPackaged)

View File

@@ -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;
}
}

View File

@@ -558,6 +558,7 @@ public class UWPApplication : IUWPApplication
IsPackaged = true,
Commands = app.GetCommands(),
AppIdentifier = app.GetAppIdentifier(),
PackageFamilyName = app.Package.FamilyName,
};
return item;
}

View File

@@ -1066,6 +1066,7 @@ public partial class Win32Program : IProgram
DirPath = app.Location,
Commands = app.GetCommands(),
AppIdentifier = app.GetAppIdentifier(),
FullExecutablePath = app.FullPath,
};
}
}

View File

@@ -62,7 +62,7 @@ public partial class InstallPackageCommand : InvokableCommand
{
PackageInstallCommandState.Install => Icons.DownloadIcon,
PackageInstallCommandState.Update => Icons.UpdateIcon,
PackageInstallCommandState.Uninstall => Icons.CompletedIcon,
PackageInstallCommandState.Uninstall => Icons.DeleteIcon,
_ => throw new NotImplementedException(),
};
Name = InstallCommandState switch

View File

@@ -194,46 +194,95 @@ public partial class InstallPackageListItem : ListItem
var isInstalled = _package.InstalledVersion is not null;
var installedState = isInstalled ?
(_package.IsUpdateAvailable ?
PackageInstallCommandState.Update : PackageInstallCommandState.Uninstall) :
(_package.IsUpdateAvailable ? PackageInstallCommandState.Update : PackageInstallCommandState.Uninstall) :
PackageInstallCommandState.Install;
// might be an uninstall command
InstallPackageCommand installCommand = new(_package, installedState);
if (isInstalled)
if (_package.InstalledVersion is not null)
{
this.Icon = installCommand.Icon;
this.Command = new NoOpCommand();
#if DEBUG
var installerType = _package.InstalledVersion.GetMetadata(PackageVersionMetadataField.InstallerType);
Subtitle = installerType + " | " + Subtitle;
#endif
List<IContextItem> contextMenu = [];
CommandContextItem uninstallContextItem = new(installCommand)
Command = installCommand;
Icon = installedState switch
{
IsCritical = true,
Icon = Icons.DeleteIcon,
PackageInstallCommandState.Install => Icons.DownloadIcon,
PackageInstallCommandState.Update => Icons.UpdateIcon,
PackageInstallCommandState.Uninstall => Icons.CompletedIcon,
_ => Icons.DownloadIcon,
};
if (WinGetStatics.AppSearchCallback is not null)
TryLocateAndAppendActionForApp(contextMenu);
MoreCommands = contextMenu.ToArray();
}
else
{
_installCommand = new InstallPackageCommand(_package, installedState);
_installCommand.InstallStateChanged += InstallStateChangedHandler;
Command = _installCommand;
Icon = _installCommand.Icon;
}
}
private void TryLocateAndAppendActionForApp(List<IContextItem> contextMenu)
{
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 callback = WinGetStatics.AppSearchCallback;
var installedApp = callback(_package.DefaultInstallVersion is null ? _package.Name : _package.DefaultInstallVersion.DisplayName);
if (installedApp is not null)
var names = _package.InstalledVersion.PackageFamilyNames;
for (var i = 0; i < names.Count; i++)
{
this.Command = installedApp.Command;
contextMenu = [.. installedApp.MoreCommands];
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;
}
}
}
contextMenu.Add(uninstallContextItem);
this.MoreCommands = contextMenu.ToArray();
return;
var lookupByProductCode = WinGetStatics.AppSearchByProductCodeCallback;
if (lookupByProductCode is not null)
{
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);
}
return;
}
}
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to retrieve app context menu items for package '{_package?.Name ?? "Unknown"}'", ex);
}
// didn't find the app
_installCommand = new InstallPackageCommand(_package, installedState);
this.Command = _installCommand;
Icon = _installCommand.Icon;
_installCommand.InstallStateChanged += InstallStateChangedHandler;
}
private void InstallStateChangedHandler(object? sender, InstallPackageCommand e)

View File

@@ -41,5 +41,9 @@ public partial class WinGetExtensionCommandsProvider : CommandProvider
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;
}
}

View File

@@ -34,7 +34,9 @@ internal static class WinGetStatics
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);

View File

@@ -224,7 +224,7 @@ namespace PowerAccent.Core
LetterKey.VK_X => new[] { "ẋ", "×" },
LetterKey.VK_Y => new[] { "ẏ", "ꝡ" },
LetterKey.VK_Z => new[] { "ʒ", "ǯ", "" },
LetterKey.VK_COMMA => new[] { "∙", "₋", "⁻", "", "√" }, // is in VK_MINUS for other languages, but not VK_COMMA, so we add it here.
LetterKey.VK_COMMA => new[] { "∙", "₋", "⁻", "", "√", "‟", "《", "》", "", "〈", "〉", "″", "‴", "⁗" }, // is in VK_MINUS for other languages, but not VK_COMMA, so we add it here.
LetterKey.VK_PERIOD => new[] { "…", "⁝", "\u0300", "\u0301", "\u0302", "\u0303", "\u0304", "\u0308", "\u030B", "\u030C" },
LetterKey.VK_MINUS => new[] { "~", "", "", "", "—", "―", "", "", "⸺", "⸻", "∓" },
LetterKey.VK_SLASH_ => new[] { "÷", "√" },
@@ -302,6 +302,7 @@ namespace PowerAccent.Core
LetterKey.VK_E => new[] { "€" },
LetterKey.VK_S => new[] { "š" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "»", "«" },
_ => Array.Empty<string>(),
};
}
@@ -317,6 +318,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "ü" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_S => new[] { "š" },
LetterKey.VK_COMMA => new[] { "„", "“", "«", "»" },
_ => Array.Empty<string>(),
};
}
@@ -344,6 +346,7 @@ namespace PowerAccent.Core
LetterKey.VK_A => new[] { "ä", "å" },
LetterKey.VK_E => new[] { "€" },
LetterKey.VK_O => new[] { "ö" },
LetterKey.VK_COMMA => new[] { "”", "", "»" },
_ => Array.Empty<string>(),
};
}
@@ -360,6 +363,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ô", "ö", "ó", "ò", "õ", "œ" },
LetterKey.VK_U => new[] { "û", "ù", "ü", "ú" },
LetterKey.VK_Y => new[] { "ÿ", "ý" },
LetterKey.VK_COMMA => new[] { "«", "»", "", "", "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -376,6 +380,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "ú" },
LetterKey.VK_Y => new[] { "ý" },
LetterKey.VK_T => new[] { "þ" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -393,7 +398,7 @@ namespace PowerAccent.Core
LetterKey.VK_N => new[] { "ñ" },
LetterKey.VK_O => new[] { "ó" },
LetterKey.VK_U => new[] { "ú", "ü" },
LetterKey.VK_COMMA => new[] { "¿", "?", "¡", "!" },
LetterKey.VK_COMMA => new[] { "¿", "?", "¡", "!", "«", "»", "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -411,7 +416,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ò", "ó" },
LetterKey.VK_U => new[] { "ù", "ú", "ü" },
LetterKey.VK_L => new[] { "·" },
LetterKey.VK_COMMA => new[] { "¿", "?", "¡", "!" },
LetterKey.VK_COMMA => new[] { "¿", "?", "¡", "!", "«", "»", "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -427,6 +432,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ō" },
LetterKey.VK_S => new[] { "$" },
LetterKey.VK_U => new[] { "ū" },
LetterKey.VK_COMMA => new[] { "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -443,6 +449,7 @@ namespace PowerAccent.Core
LetterKey.VK_N => new[] { "ñ" },
LetterKey.VK_O => new[] { "ó", "ö", "ô" },
LetterKey.VK_U => new[] { "ú", "ü", "û" },
LetterKey.VK_COMMA => new[] { "“", "„", "”", "", ",", "" },
_ => Array.Empty<string>(),
};
}
@@ -469,6 +476,7 @@ namespace PowerAccent.Core
LetterKey.VK_V => new[] { "ü", "ǖ", "ǘ", "ǚ", "ǜ" },
LetterKey.VK_Y => new[] { "¥" },
LetterKey.VK_Z => new[] { "ẑ" },
LetterKey.VK_COMMA => new[] { "“", "”", "", "", "「", "」", "『", "』" },
_ => Array.Empty<string>(),
};
}
@@ -505,6 +513,7 @@ namespace PowerAccent.Core
LetterKey.VK_S => new[] { "ş" },
LetterKey.VK_T => new[] { "₺" },
LetterKey.VK_U => new[] { "ü", "û" },
LetterKey.VK_COMMA => new[] { "“", "”", "", "", "«", "»", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -522,6 +531,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ó" },
LetterKey.VK_S => new[] { "ś" },
LetterKey.VK_Z => new[] { "ż", "ź" },
LetterKey.VK_COMMA => new[] { "„", "”", "", "", "»", "«" },
_ => Array.Empty<string>(),
};
}
@@ -539,7 +549,7 @@ namespace PowerAccent.Core
LetterKey.VK_P => new[] { "π" },
LetterKey.VK_S => new[] { "$" },
LetterKey.VK_U => new[] { "ú" },
LetterKey.VK_COMMA => new[] { "≤", "≥", "≠", "≈", "≙", "±", "₊", "⁺" },
LetterKey.VK_COMMA => new[] { "≤", "≥", "≠", "≈", "≙", "±", "₊", "⁺", "“", "”", "", "", "«", "»" },
_ => Array.Empty<string>(),
};
}
@@ -594,6 +604,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "ú" },
LetterKey.VK_Y => new[] { "ý" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "", "»", "«", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -608,6 +619,7 @@ namespace PowerAccent.Core
LetterKey.VK_I => new[] { "í" },
LetterKey.VK_O => new[] { "ó" },
LetterKey.VK_U => new[] { "ú" },
LetterKey.VK_COMMA => new[] { "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -623,6 +635,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ò" },
LetterKey.VK_P => new[] { "£" },
LetterKey.VK_U => new[] { "ù" },
LetterKey.VK_COMMA => new[] { "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -645,6 +658,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "ů", "ú" },
LetterKey.VK_Y => new[] { "ý" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "", "»", "«", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -659,6 +673,7 @@ namespace PowerAccent.Core
LetterKey.VK_O => new[] { "ö" },
LetterKey.VK_S => new[] { "ß" },
LetterKey.VK_U => new[] { "ü" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "", "»", "«", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -689,6 +704,7 @@ namespace PowerAccent.Core
LetterKey.VK_X => new string[] { "ξ" },
LetterKey.VK_Y => new string[] { "υ" },
LetterKey.VK_Z => new string[] { "ζ" },
LetterKey.VK_COMMA => new[] { "“", "”", "«", "»", },
_ => Array.Empty<string>(),
};
}
@@ -710,7 +726,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "וֹ", "וּ", "װ", "\u05b9" },
LetterKey.VK_X => new[] { "\u05b6", "\u05b1" },
LetterKey.VK_Y => new[] { "ױ" },
LetterKey.VK_COMMA => new[] { "”", "", "״", "׳" },
LetterKey.VK_COMMA => new[] { "”", "", "'", "״", "׳" },
LetterKey.VK_PERIOD => new[] { "\u05ab", "\u05bd", "\u05bf" },
LetterKey.VK_MINUS => new[] { "", "־" },
_ => Array.Empty<string>(),
@@ -727,6 +743,7 @@ namespace PowerAccent.Core
LetterKey.VK_I => new[] { "í" },
LetterKey.VK_O => new[] { "ó", "ő", "ö" },
LetterKey.VK_U => new[] { "ú", "ű", "ü" },
LetterKey.VK_COMMA => new[] { "„", "”", "»", "«" },
_ => Array.Empty<string>(),
};
}
@@ -740,6 +757,7 @@ namespace PowerAccent.Core
LetterKey.VK_I => new[] { "î" },
LetterKey.VK_S => new[] { "ș" },
LetterKey.VK_T => new[] { "ț" },
LetterKey.VK_COMMA => new[] { "„", "”", "«", "»" },
_ => Array.Empty<string>(),
};
}
@@ -754,6 +772,7 @@ namespace PowerAccent.Core
LetterKey.VK_I => new[] { "ì", "í" },
LetterKey.VK_O => new[] { "ò", "ó" },
LetterKey.VK_U => new[] { "ù", "ú" },
LetterKey.VK_COMMA => new[] { "«", "»", "“", "”", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -772,6 +791,7 @@ namespace PowerAccent.Core
LetterKey.VK_R => new[] { "ř" },
LetterKey.VK_S => new[] { "ş" },
LetterKey.VK_U => new[] { "û", "ü" },
LetterKey.VK_COMMA => new[] { "«", "»", "“", "”" },
_ => Array.Empty<string>(),
};
}
@@ -789,6 +809,7 @@ namespace PowerAccent.Core
LetterKey.VK_U => new[] { "û", "ü", "ù", "ú" },
LetterKey.VK_Y => new[] { "ŷ", "ÿ", "ỳ", "ý" },
LetterKey.VK_W => new[] { "ŵ", "ẅ", "ẁ", "ẃ" },
LetterKey.VK_COMMA => new[] { "", "", "“", "“" },
_ => Array.Empty<string>(),
};
}
@@ -801,6 +822,7 @@ namespace PowerAccent.Core
LetterKey.VK_A => new[] { "å", "ä" },
LetterKey.VK_E => new[] { "é" },
LetterKey.VK_O => new[] { "ö" },
LetterKey.VK_COMMA => new[] { "”", "", "»", "«" },
_ => Array.Empty<string>(),
};
}
@@ -814,6 +836,7 @@ namespace PowerAccent.Core
LetterKey.VK_D => new[] { "đ" },
LetterKey.VK_S => new[] { "š" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "", "»", "«", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -838,6 +861,7 @@ namespace PowerAccent.Core
{
LetterKey.VK_E => new[] { "ѐ" },
LetterKey.VK_I => new[] { "ѝ" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -869,6 +893,7 @@ namespace PowerAccent.Core
LetterKey.VK_E => new[] { "€", "é" },
LetterKey.VK_O => new[] { "ø" },
LetterKey.VK_S => new[] { "$" },
LetterKey.VK_COMMA => new[] { "«", "»", ",", "", "", "„", "“" },
_ => Array.Empty<string>(),
};
}
@@ -881,6 +906,7 @@ namespace PowerAccent.Core
LetterKey.VK_A => new[] { "å", "æ" },
LetterKey.VK_E => new[] { "€" },
LetterKey.VK_O => new[] { "ø" },
LetterKey.VK_COMMA => new[] { "»", "«", "“", "”", "", "", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -897,6 +923,7 @@ namespace PowerAccent.Core
LetterKey.VK_S => new[] { "š" },
LetterKey.VK_U => new[] { "ų", "ū" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "", "" },
_ => Array.Empty<string>(),
};
}
@@ -910,6 +937,7 @@ namespace PowerAccent.Core
LetterKey.VK_E => new[] { "€" },
LetterKey.VK_S => new[] { "š" },
LetterKey.VK_Z => new[] { "ž" },
LetterKey.VK_COMMA => new[] { "„", "“", "»", "«" },
_ => Array.Empty<string>(),
};
}

View File

@@ -20,6 +20,7 @@ public partial class PowerAccent : IDisposable
// Keys that show a description (like dashes) when ShowCharacterInfoSetting is 1
private readonly LetterKey[] _letterKeysShowingDescription = new LetterKey[] { LetterKey.VK_O };
private const double ScreenMinPadding = 150;
private bool _visible;
private string[] _characters = Array.Empty<string>();
@@ -332,6 +333,11 @@ public partial class PowerAccent : IDisposable
return Calculation.GetRawCoordinatesFromPosition(position, screen, window);
}
public double GetDisplayMaxWidth()
{
return WindowsFunctions.GetActiveDisplay().Size.Width - ScreenMinPadding;
}
public Position GetToolbarPosition()
{
return _settingService.Position;

View File

@@ -59,6 +59,7 @@ public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChange
_selectedIndex = index;
characters.SelectedIndex = _selectedIndex;
characterName.Text = _powerAccent.CharacterDescriptions[_selectedIndex];
characters.ScrollIntoView(character);
}
private void PowerAccent_OnChangeDisplay(bool isActive, string[] chars)
@@ -73,6 +74,7 @@ public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChange
characters.ItemsSource = chars;
characters.SelectedIndex = _selectedIndex;
this.UpdateLayout(); // Required for filling the actual width/height before positioning.
SetWindowsSize();
SetWindowPosition();
Show();
Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new PowerAccent.Core.Telemetry.PowerAccentShowAccentMenuEvent());
@@ -96,6 +98,11 @@ public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChange
this.Top = position.Y;
}
private void SetWindowsSize()
{
this.characters.MaxWidth = _powerAccent.GetDisplayMaxWidth();
}
protected override void OnClosed(EventArgs e)
{
_powerAccent.SaveUsageInfo();

Binary file not shown.