mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 03:07:56 +01:00
Merge remote-tracking branch 'origin/main' into dev/snickler/net10-upgrade
This commit is contained in:
2
.github/actions/spell-check/expect.txt
vendored
2
.github/actions/spell-check/expect.txt
vendored
@@ -773,6 +773,7 @@ INITGUID
|
||||
INITTOLOGFONTSTRUCT
|
||||
INLINEPREFIX
|
||||
inlines
|
||||
Inno
|
||||
INPC
|
||||
inproc
|
||||
INPUTHARDWARE
|
||||
@@ -1848,6 +1849,7 @@ UNCPRIORITY
|
||||
UNDNAME
|
||||
UNICODETEXT
|
||||
unins
|
||||
Uninstaller
|
||||
uninstalls
|
||||
Uniquifies
|
||||
unitconverter
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
Commands = app.GetCommands(),
|
||||
AppIdentifier = app.GetAppIdentifier(),
|
||||
PackageFamilyName = app.Package.FamilyName,
|
||||
};
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -1066,6 +1066,7 @@ public partial class Win32Program : IProgram
|
||||
DirPath = app.Location,
|
||||
Commands = app.GetCommands(),
|
||||
AppIdentifier = app.GetAppIdentifier(),
|
||||
FullExecutablePath = app.FullPath,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
Reference in New Issue
Block a user