mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Users can now pin/unpin apps from the top of AllApps extension (#40544)
Users can now use Ctrl+P to toggle pinning and unpinning applications from the top of the All Apps extension. This pinned status persists through restarts & reboots. https://github.com/user-attachments/assets/86895a38-7312-438a-9409-b50a85979d12 Reference: #40543 --------- Co-authored-by: Mike Griese <migrie@microsoft.com>
This commit is contained in:
@@ -2,8 +2,12 @@
|
||||
// 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.Linq;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.State;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -29,9 +33,12 @@ public partial class AllAppsCommandProvider : CommandProvider
|
||||
Subtitle = Resources.search_installed_apps,
|
||||
MoreCommands = [new CommandContextItem(AllAppsSettings.Instance.Settings.SettingsPage)],
|
||||
};
|
||||
|
||||
// Subscribe to pin state changes to refresh the command provider
|
||||
PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => [_listItem];
|
||||
public override ICommandItem[] TopLevelCommands() => [_listItem, ..Page.GetPinnedApps()];
|
||||
|
||||
public ICommandItem? LookupApp(string displayName)
|
||||
{
|
||||
@@ -62,4 +69,9 @@ public partial class AllAppsCommandProvider : CommandProvider
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnPinStateChanged(object? sender, System.EventArgs e)
|
||||
{
|
||||
RaiseItemsChanged(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,20 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
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;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -18,16 +24,22 @@ namespace Microsoft.CmdPal.Ext.Apps;
|
||||
public sealed partial class AllAppsPage : ListPage
|
||||
{
|
||||
private readonly Lock _listLock = new();
|
||||
private AppListItem[] allAppsSection = [];
|
||||
|
||||
private AppItem[] allApps = [];
|
||||
private AppListItem[] unpinnedApps = [];
|
||||
private AppListItem[] pinnedApps = [];
|
||||
|
||||
public AllAppsPage()
|
||||
{
|
||||
this.Name = Resources.all_apps;
|
||||
this.Icon = IconHelpers.FromRelativePath("Assets\\AllApps.svg");
|
||||
this.Icon = Icons.AllAppsIcon;
|
||||
this.ShowDetails = true;
|
||||
this.IsLoading = true;
|
||||
this.PlaceholderText = Resources.search_installed_apps_placeholder;
|
||||
|
||||
// Subscribe to pin state changes to refresh the command provider
|
||||
PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
lock (_listLock)
|
||||
@@ -37,89 +49,139 @@ public sealed partial class AllAppsPage : ListPage
|
||||
});
|
||||
}
|
||||
|
||||
internal AppListItem[] GetPinnedApps()
|
||||
{
|
||||
BuildListItems();
|
||||
return pinnedApps;
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
if (allAppsSection.Length == 0 || AppCache.Instance.Value.ShouldReload())
|
||||
{
|
||||
lock (_listLock)
|
||||
{
|
||||
BuildListItems();
|
||||
}
|
||||
}
|
||||
|
||||
return allAppsSection;
|
||||
// Build or update the list if needed
|
||||
BuildListItems();
|
||||
return pinnedApps.Concat(unpinnedApps).ToArray();
|
||||
}
|
||||
|
||||
private void BuildListItems()
|
||||
{
|
||||
this.IsLoading = true;
|
||||
if (allApps.Length == 0 || AppCache.Instance.Value.ShouldReload())
|
||||
{
|
||||
lock (_listLock)
|
||||
{
|
||||
this.IsLoading = true;
|
||||
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
|
||||
var apps = GetPrograms();
|
||||
var apps = GetPrograms();
|
||||
this.allApps = apps.AllApps;
|
||||
this.pinnedApps = apps.PinnedItems;
|
||||
this.unpinnedApps = apps.UnpinnedItems;
|
||||
|
||||
this.allAppsSection = apps
|
||||
.Select((app) => new AppListItem(app, true))
|
||||
.ToArray();
|
||||
this.IsLoading = false;
|
||||
|
||||
this.IsLoading = false;
|
||||
AppCache.Instance.Value.ResetReloadFlag();
|
||||
|
||||
AppCache.Instance.Value.ResetReloadFlag();
|
||||
|
||||
stopwatch.Stop();
|
||||
Logger.LogTrace($"{nameof(AllAppsPage)}.{nameof(BuildListItems)} took: {stopwatch.ElapsedMilliseconds} ms");
|
||||
stopwatch.Stop();
|
||||
Logger.LogTrace($"{nameof(AllAppsPage)}.{nameof(BuildListItems)} took: {stopwatch.ElapsedMilliseconds} ms");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal List<AppItem> GetPrograms()
|
||||
private AppItem[] GetAllApps()
|
||||
{
|
||||
var uwpResults = AppCache.Instance.Value.UWPs
|
||||
.Where((application) => application.Enabled)
|
||||
.Select(UwpToAppItem);
|
||||
.Where((application) => application.Enabled)
|
||||
.Select(app => app.ToAppItem());
|
||||
|
||||
var win32Results = AppCache.Instance.Value.Win32s
|
||||
.Where((application) => application.Enabled && application.Valid)
|
||||
.Select(app =>
|
||||
{
|
||||
var icoPath = string.IsNullOrEmpty(app.IcoPath) ?
|
||||
(app.AppType == Win32Program.ApplicationType.InternetShortcutApplication ?
|
||||
app.IcoPath :
|
||||
app.FullPath) :
|
||||
app.IcoPath;
|
||||
.Select(app => app.ToAppItem());
|
||||
|
||||
// icoPath = icoPath.EndsWith(".lnk", System.StringComparison.InvariantCultureIgnoreCase) ? (icoPath + ",0") : icoPath;
|
||||
icoPath = icoPath.EndsWith(".lnk", System.StringComparison.InvariantCultureIgnoreCase) ?
|
||||
app.FullPath :
|
||||
icoPath;
|
||||
return new AppItem()
|
||||
{
|
||||
Name = app.Name,
|
||||
Subtitle = app.Description,
|
||||
Type = app.Type(),
|
||||
IcoPath = icoPath,
|
||||
ExePath = !string.IsNullOrEmpty(app.LnkFilePath) ? app.LnkFilePath : app.FullPath,
|
||||
DirPath = app.Location,
|
||||
Commands = app.GetCommands(),
|
||||
};
|
||||
});
|
||||
|
||||
return uwpResults.Concat(win32Results).OrderBy(app => app.Name).ToList();
|
||||
var allApps = uwpResults.Concat(win32Results).ToArray();
|
||||
return allApps;
|
||||
}
|
||||
|
||||
private AppItem UwpToAppItem(UWPApplication app)
|
||||
internal (AppItem[] AllApps, AppListItem[] PinnedItems, AppListItem[] UnpinnedItems) GetPrograms()
|
||||
{
|
||||
var iconPath = app.LogoType != LogoType.Error ? app.LogoPath : string.Empty;
|
||||
var item = new AppItem()
|
||||
var allApps = GetAllApps();
|
||||
var pinned = new List<AppListItem>();
|
||||
var unpinned = new List<AppListItem>();
|
||||
|
||||
foreach (var app in allApps)
|
||||
{
|
||||
Name = app.Name,
|
||||
Subtitle = app.Description,
|
||||
Type = UWPApplication.Type(),
|
||||
IcoPath = iconPath,
|
||||
DirPath = app.Location,
|
||||
UserModelId = app.UserModelId,
|
||||
IsPackaged = true,
|
||||
Commands = app.GetCommands(),
|
||||
};
|
||||
return item;
|
||||
var isPinned = PinnedAppsManager.Instance.IsAppPinned(app.AppIdentifier);
|
||||
var appListItem = new AppListItem(app, true, isPinned);
|
||||
|
||||
if (isPinned)
|
||||
{
|
||||
appListItem.Tags = appListItem.Tags
|
||||
.Concat([new Tag() { Icon = Icons.PinIcon }])
|
||||
.ToArray();
|
||||
pinned.Add(appListItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
unpinned.Add(appListItem);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
allApps
|
||||
.ToArray(),
|
||||
pinned
|
||||
.OrderBy(app => app.Title)
|
||||
.ToArray(),
|
||||
unpinned
|
||||
.OrderBy(app => app.Title)
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
private void OnPinStateChanged(object? sender, PinStateChangedEventArgs e)
|
||||
{
|
||||
/*
|
||||
* Rebuilding all the lists is pretty expensive.
|
||||
* So, instead, we'll just compare pinned items to move existing
|
||||
* items between the two lists.
|
||||
*/
|
||||
var existingAppItem = allApps.FirstOrDefault(f => f.AppIdentifier == e.AppIdentifier);
|
||||
|
||||
if (existingAppItem != null)
|
||||
{
|
||||
var appListItem = new AppListItem(existingAppItem, true, e.IsPinned);
|
||||
|
||||
if (e.IsPinned)
|
||||
{
|
||||
// Remove it from the unpinned apps array
|
||||
this.unpinnedApps = this.unpinnedApps
|
||||
.Where(app => app.AppIdentifier != existingAppItem.AppIdentifier)
|
||||
.OrderBy(app => app.Title)
|
||||
.ToArray();
|
||||
|
||||
var newPinned = this.pinnedApps.ToList();
|
||||
newPinned.Add(appListItem);
|
||||
|
||||
this.pinnedApps = newPinned
|
||||
.OrderBy(app => app.Title)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove it from the pinned apps array
|
||||
this.pinnedApps = this.pinnedApps
|
||||
.Where(app => app.AppIdentifier != existingAppItem.AppIdentifier)
|
||||
.OrderBy(app => app.Title)
|
||||
.ToArray();
|
||||
|
||||
var newUnpinned = this.unpinnedApps.ToList();
|
||||
newUnpinned.Add(appListItem);
|
||||
|
||||
this.unpinnedApps = newUnpinned
|
||||
.OrderBy(app => app.Title)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
RaiseItemsChanged(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps;
|
||||
@@ -26,7 +27,9 @@ internal sealed class AppItem
|
||||
|
||||
public bool IsPackaged { get; set; }
|
||||
|
||||
public List<CommandContextItem>? Commands { get; set; }
|
||||
public List<IContextItem>? Commands { get; set; }
|
||||
|
||||
public string AppIdentifier { get; set; } = string.Empty;
|
||||
|
||||
public AppItem()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Storage.Streams;
|
||||
@@ -23,14 +24,17 @@ internal sealed partial class AppListItem : ListItem
|
||||
|
||||
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
|
||||
|
||||
public AppListItem(AppItem app, bool useThumbnails)
|
||||
public string AppIdentifier => _app.AppIdentifier;
|
||||
|
||||
public AppListItem(AppItem app, bool useThumbnails, bool isPinned)
|
||||
: base(new AppCommand(app))
|
||||
{
|
||||
_app = app;
|
||||
Title = app.Name;
|
||||
Subtitle = app.Subtitle;
|
||||
Tags = [_appTag];
|
||||
MoreCommands = _app.Commands!.ToArray();
|
||||
|
||||
MoreCommands = AddPinCommands(_app.Commands!, isPinned);
|
||||
|
||||
_details = new Lazy<Details>(() =>
|
||||
{
|
||||
@@ -121,4 +125,37 @@ internal sealed partial class AppListItem : ListItem
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private IContextItem[] AddPinCommands(List<IContextItem> commands, bool isPinned)
|
||||
{
|
||||
var newCommands = new List<IContextItem>();
|
||||
newCommands.AddRange(commands);
|
||||
|
||||
newCommands.Add(new SeparatorContextItem());
|
||||
|
||||
// 0x50 = P
|
||||
// Full key chord would be Ctrl+P
|
||||
var pinKeyChord = KeyChordHelpers.FromModifiers(true, false, false, false, 0x50, 0);
|
||||
|
||||
if (isPinned)
|
||||
{
|
||||
newCommands.Add(
|
||||
new CommandContextItem(
|
||||
new UnpinAppCommand(this.AppIdentifier))
|
||||
{
|
||||
RequestedShortcut = pinKeyChord,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
newCommands.Add(
|
||||
new CommandContextItem(
|
||||
new PinAppCommand(this.AppIdentifier))
|
||||
{
|
||||
RequestedShortcut = pinKeyChord,
|
||||
});
|
||||
}
|
||||
|
||||
return newCommands.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -13,14 +14,12 @@ namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class CopyPathCommand : InvokableCommand
|
||||
{
|
||||
private static readonly IconInfo TheIcon = new("\ue8c8");
|
||||
|
||||
private readonly string _target;
|
||||
|
||||
public CopyPathCommand(string target)
|
||||
{
|
||||
Name = Resources.copy_path;
|
||||
Icon = TheIcon;
|
||||
Icon = Icons.CopyIcon;
|
||||
|
||||
_target = target;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -13,14 +14,12 @@ namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class OpenInConsoleCommand : InvokableCommand
|
||||
{
|
||||
private static readonly IconInfo TheIcon = new("\ue838");
|
||||
|
||||
private readonly string _target;
|
||||
|
||||
public OpenInConsoleCommand(string target)
|
||||
{
|
||||
Name = Resources.open_path_in_console;
|
||||
Icon = TheIcon;
|
||||
Icon = Icons.OpenConsoleIcon;
|
||||
|
||||
_target = target;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -11,14 +12,12 @@ namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class OpenPathCommand : InvokableCommand
|
||||
{
|
||||
private static readonly IconInfo TheIcon = new("\ue838");
|
||||
|
||||
private readonly string _target;
|
||||
|
||||
public OpenPathCommand(string target)
|
||||
{
|
||||
Name = Resources.open_location;
|
||||
Icon = TheIcon;
|
||||
Icon = Icons.OpenPathIcon;
|
||||
|
||||
_target = target;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.State;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class PinAppCommand : InvokableCommand
|
||||
{
|
||||
private readonly string _appIdentifier;
|
||||
|
||||
public PinAppCommand(string appIdentifier)
|
||||
{
|
||||
_appIdentifier = appIdentifier;
|
||||
Name = Resources.pin_app;
|
||||
Icon = Icons.PinIcon;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
PinnedAppsManager.Instance.PinApp(_appIdentifier);
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -13,8 +14,6 @@ namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class RunAsAdminCommand : InvokableCommand
|
||||
{
|
||||
private static readonly IconInfo TheIcon = new("\uE7EF");
|
||||
|
||||
private readonly string _target;
|
||||
private readonly string _parentDir;
|
||||
private readonly bool _packaged;
|
||||
@@ -22,7 +21,7 @@ internal sealed partial class RunAsAdminCommand : InvokableCommand
|
||||
public RunAsAdminCommand(string target, string parentDir, bool packaged)
|
||||
{
|
||||
Name = Resources.run_as_administrator;
|
||||
Icon = TheIcon;
|
||||
Icon = Icons.RunAsIcon;
|
||||
|
||||
_target = target;
|
||||
_parentDir = parentDir;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -13,21 +14,19 @@ namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class RunAsUserCommand : InvokableCommand
|
||||
{
|
||||
private static readonly IconInfo TheIcon = new("\uE7EE");
|
||||
|
||||
private readonly string _target;
|
||||
private readonly string _parentDir;
|
||||
|
||||
public RunAsUserCommand(string target, string parentDir)
|
||||
{
|
||||
Name = Resources.run_as_different_user;
|
||||
Icon = TheIcon;
|
||||
Icon = Icons.RunAsUserIcon;
|
||||
|
||||
_target = target;
|
||||
_parentDir = parentDir;
|
||||
}
|
||||
|
||||
internal static async Task RunAsAdmin(string target, string parentDir)
|
||||
internal static async Task RunAsUser(string target, string parentDir)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
@@ -39,7 +38,7 @@ internal sealed partial class RunAsUserCommand : InvokableCommand
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
_ = RunAsAdmin(_target, _parentDir).ConfigureAwait(false);
|
||||
_ = RunAsUser(_target, _parentDir).ConfigureAwait(false);
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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 Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.State;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class UnpinAppCommand : InvokableCommand
|
||||
{
|
||||
private readonly string _appIdentifier;
|
||||
|
||||
public UnpinAppCommand(string appIdentifier)
|
||||
{
|
||||
_appIdentifier = appIdentifier;
|
||||
Name = Resources.unpin_app;
|
||||
Icon = Icons.UnpinIcon;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
PinnedAppsManager.Instance.UnpinApp(_appIdentifier);
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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 Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Helpers;
|
||||
|
||||
public static partial class Icons
|
||||
{
|
||||
public static IconInfo UnpinIcon { get; } = new("\uE77A");
|
||||
|
||||
public static IconInfo PinIcon { get; } = new("\uE840");
|
||||
|
||||
public static IconInfo RunAsIcon { get; } = new("\uE7EF");
|
||||
|
||||
public static IconInfo RunAsUserIcon { get; } = new("\uE7EE");
|
||||
|
||||
public static IconInfo CopyIcon { get; } = new("\ue8c8");
|
||||
|
||||
public static IconInfo OpenConsoleIcon { get; } = new("\ue838");
|
||||
|
||||
public static IconInfo OpenPathIcon { get; } = new("\ue838");
|
||||
|
||||
public static IconInfo AllAppsIcon { get; } = IconHelpers.FromRelativePath("Assets\\AllApps.svg");
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.State;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps;
|
||||
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(PinnedApps))]
|
||||
[JsonSerializable(typeof(List<string>), TypeInfoPropertyName = "StringList")]
|
||||
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true, IncludeFields = true, PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)]
|
||||
internal sealed partial class JsonSerializationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -10,7 +10,9 @@ using System.Xml;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.State;
|
||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
using Windows.Win32;
|
||||
@@ -70,9 +72,15 @@ public class UWPApplication : IProgram
|
||||
return Resources.packaged_application;
|
||||
}
|
||||
|
||||
public List<CommandContextItem> GetCommands()
|
||||
public string GetAppIdentifier()
|
||||
{
|
||||
List<CommandContextItem> commands = [];
|
||||
// Use UserModelId for UWP apps as it's unique
|
||||
return UserModelId;
|
||||
}
|
||||
|
||||
public List<IContextItem> GetCommands()
|
||||
{
|
||||
List<IContextItem> commands = [];
|
||||
|
||||
if (CanRunElevated)
|
||||
{
|
||||
@@ -511,6 +519,25 @@ public class UWPApplication : IProgram
|
||||
}
|
||||
}
|
||||
|
||||
internal AppItem ToAppItem()
|
||||
{
|
||||
var app = this;
|
||||
var iconPath = app.LogoType != LogoType.Error ? app.LogoPath : string.Empty;
|
||||
var item = new AppItem()
|
||||
{
|
||||
Name = app.Name,
|
||||
Subtitle = app.Description,
|
||||
Type = UWPApplication.Type(),
|
||||
IcoPath = iconPath,
|
||||
DirPath = app.Location,
|
||||
UserModelId = app.UserModelId,
|
||||
IsPackaged = true,
|
||||
Commands = app.GetCommands(),
|
||||
AppIdentifier = app.GetAppIdentifier(),
|
||||
};
|
||||
return item;
|
||||
}
|
||||
|
||||
/*
|
||||
public ImageSource Logo()
|
||||
{
|
||||
|
||||
@@ -19,7 +19,9 @@ using System.Windows.Input;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.State;
|
||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.Win32;
|
||||
using Windows.System;
|
||||
@@ -186,9 +188,9 @@ public class Win32Program : IProgram
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<CommandContextItem> GetCommands()
|
||||
public List<IContextItem> GetCommands()
|
||||
{
|
||||
List<CommandContextItem> commands = new List<CommandContextItem>();
|
||||
List<IContextItem> commands = new List<IContextItem>();
|
||||
|
||||
if (AppType != ApplicationType.InternetShortcutApplication && AppType != ApplicationType.Folder && AppType != ApplicationType.GenericFile)
|
||||
{
|
||||
@@ -231,6 +233,12 @@ public class Win32Program : IProgram
|
||||
return ExecutableName;
|
||||
}
|
||||
|
||||
public string GetAppIdentifier()
|
||||
{
|
||||
// Use a combination of name and path to create a unique identifier
|
||||
return $"{Name}|{FullPath}";
|
||||
}
|
||||
|
||||
private static Win32Program CreateWin32Program(string path)
|
||||
{
|
||||
try
|
||||
@@ -933,4 +941,29 @@ public class Win32Program : IProgram
|
||||
return Array.Empty<Win32Program>();
|
||||
}
|
||||
}
|
||||
|
||||
internal AppItem ToAppItem()
|
||||
{
|
||||
var app = this;
|
||||
var icoPath = string.IsNullOrEmpty(app.IcoPath) ?
|
||||
(app.AppType == Win32Program.ApplicationType.InternetShortcutApplication ?
|
||||
app.IcoPath :
|
||||
app.FullPath) :
|
||||
app.IcoPath;
|
||||
|
||||
icoPath = icoPath.EndsWith(".lnk", System.StringComparison.InvariantCultureIgnoreCase) ?
|
||||
app.FullPath :
|
||||
icoPath;
|
||||
return new AppItem()
|
||||
{
|
||||
Name = app.Name,
|
||||
Subtitle = app.Description,
|
||||
Type = app.Type(),
|
||||
IcoPath = icoPath,
|
||||
ExePath = !string.IsNullOrEmpty(app.LnkFilePath) ? app.LnkFilePath : app.FullPath,
|
||||
DirPath = app.Location,
|
||||
Commands = app.GetCommands(),
|
||||
AppIdentifier = app.GetAppIdentifier(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +213,15 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pin.
|
||||
/// </summary>
|
||||
internal static string pin_app {
|
||||
get {
|
||||
return ResourceManager.GetString("pin_app", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Run as administrator.
|
||||
/// </summary>
|
||||
@@ -267,6 +276,15 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unpin.
|
||||
/// </summary>
|
||||
internal static string unpin_app {
|
||||
get {
|
||||
return ResourceManager.GetString("unpin_app", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Experimental: When enabled, Command Palette will load thumbnails from the Windows Shell. Using thumbnails may cause the app to crash on launch.
|
||||
/// </summary>
|
||||
|
||||
@@ -199,4 +199,10 @@
|
||||
<value>Experimental: When enabled, Command Palette will load thumbnails from the Windows Shell. Using thumbnails may cause the app to crash on launch</value>
|
||||
<comment>A description for "use_thumbnails_setting_label"</comment>
|
||||
</data>
|
||||
<data name="pin_app" xml:space="preserve">
|
||||
<value>Pin</value>
|
||||
</data>
|
||||
<data name="unpin_app" xml:space="preserve">
|
||||
<value>Unpin</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.State;
|
||||
|
||||
public class PinStateChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the identifier of the application whose pin state has changed.
|
||||
/// </summary>
|
||||
public string AppIdentifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified app identifier was pinned or not.
|
||||
/// </summary>
|
||||
public bool IsPinned { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PinStateChangedEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="appIdentifier">The identifier of the application whose pin state has changed.</param>
|
||||
public PinStateChangedEventArgs(string appIdentifier, bool isPinned)
|
||||
{
|
||||
AppIdentifier = appIdentifier ?? throw new ArgumentNullException(nameof(appIdentifier));
|
||||
IsPinned = isPinned;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.State;
|
||||
|
||||
public sealed class PinnedApps
|
||||
{
|
||||
public List<string> PinnedAppIdentifiers { get; set; } = [];
|
||||
|
||||
public static PinnedApps ReadFromFile(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return new PinnedApps();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var jsonString = File.ReadAllText(path);
|
||||
var result = JsonSerializer.Deserialize<PinnedApps>(jsonString, JsonSerializationContext.Default.PinnedApps);
|
||||
return result ?? new PinnedApps();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new PinnedApps();
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteToFile(string path, PinnedApps data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jsonString = JsonSerializer.Serialize(data, JsonSerializationContext.Default.PinnedApps);
|
||||
File.WriteAllText(path, jsonString);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently fail - we don't want pinning issues to crash the extension
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.State;
|
||||
|
||||
public sealed class PinnedAppsManager
|
||||
{
|
||||
private static readonly Lazy<PinnedAppsManager> _instance = new(() => new PinnedAppsManager());
|
||||
private readonly string _pinnedAppsFilePath;
|
||||
|
||||
public static PinnedAppsManager Instance => _instance.Value;
|
||||
|
||||
private PinnedApps _pinnedApps = new();
|
||||
|
||||
// Add event for when pinning state changes
|
||||
public event EventHandler<PinStateChangedEventArgs>? PinStateChanged;
|
||||
|
||||
private PinnedAppsManager()
|
||||
{
|
||||
_pinnedAppsFilePath = GetPinnedAppsFilePath();
|
||||
LoadPinnedApps();
|
||||
}
|
||||
|
||||
public bool IsAppPinned(string appIdentifier)
|
||||
{
|
||||
return _pinnedApps.PinnedAppIdentifiers.Contains(appIdentifier, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public void PinApp(string appIdentifier)
|
||||
{
|
||||
if (!IsAppPinned(appIdentifier))
|
||||
{
|
||||
_pinnedApps.PinnedAppIdentifiers.Add(appIdentifier);
|
||||
SavePinnedApps();
|
||||
Logger.LogTrace($"Pinned app: {appIdentifier}");
|
||||
PinStateChanged?.Invoke(this, new PinStateChangedEventArgs(appIdentifier, true));
|
||||
}
|
||||
}
|
||||
|
||||
public string[] GetPinnedAppIdentifiers()
|
||||
{
|
||||
return _pinnedApps.PinnedAppIdentifiers.ToArray();
|
||||
}
|
||||
|
||||
public void UnpinApp(string appIdentifier)
|
||||
{
|
||||
var removed = _pinnedApps.PinnedAppIdentifiers.RemoveAll(id =>
|
||||
string.Equals(id, appIdentifier, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (removed > 0)
|
||||
{
|
||||
SavePinnedApps();
|
||||
Logger.LogTrace($"Unpinned app: {appIdentifier}");
|
||||
PinStateChanged?.Invoke(this, new PinStateChangedEventArgs(appIdentifier, false));
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPinnedApps()
|
||||
{
|
||||
_pinnedApps = PinnedApps.ReadFromFile(_pinnedAppsFilePath);
|
||||
}
|
||||
|
||||
private void SavePinnedApps()
|
||||
{
|
||||
PinnedApps.WriteToFile(_pinnedAppsFilePath, _pinnedApps);
|
||||
}
|
||||
|
||||
private static string GetPinnedAppsFilePath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
return Path.Combine(directory, "apps.pinned.json");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user