Compare commits

..

3 Commits

Author SHA1 Message Date
Jaylyn Barbee
f40bd3e49c Fix typo in LightSwitchStateManager filename 2025-11-20 09:41:40 -08:00
Niels Laute
d052e33607 Update doc/devdocs/modules/lightswitch.md 2025-11-20 09:26:11 -08:00
Jaylyn Barbee
bdfa7c0f5f Clarify LightSwitchService and LightSwitchStateManager roles
Updated LightSwitch module documentation to clarify the role of LightSwitchService and LightSwitchStateManager.
2025-11-20 09:24:25 -08:00
126 changed files with 1151 additions and 1462 deletions

View File

@@ -33,9 +33,12 @@ The **Light Switch** module lets users automatically transition between light an
> **Note:** Using the shortcut overrides the current schedule until the next transition event.
* **LightSwitchService**
Reads settings and applies theming. Runs a check every minute to ensure the state is correct.
* **LightSwitchService.cpp**
is the heart beat of the module. Controls ticking every minute and depending on user actions (manual override, settings changing, etc) triggers the state manager to perform the corresponding operation.
* **LightSwitchStateManager.cpp**
handles updating the state based on the signals sent by LightSwitchService.
* **SettingsXAML/LightSwitch**
Provides the settings UI for configuring schedules, syncing location, and customizing shortcuts.

View File

@@ -0,0 +1,62 @@
// 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;
namespace Microsoft.CmdPal.Core.Common;
public static class CoreLogger
{
public static void InitializeLogger(ILogger implementation)
{
_logger = implementation;
}
private static ILogger? _logger;
public static void LogError(string message, Exception ex, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogError(message, ex, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogError(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogError(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogWarning(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogWarning(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogInfo(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogInfo(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogDebug(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogTrace(memberName, sourceFilePath, sourceLineNumber);
}
}
public interface ILogger
{
void LogError(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogError(string message, Exception ex, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogWarning(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogInfo(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
}

View File

@@ -9,8 +9,4 @@
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
</ItemGroup>
</Project>

View File

@@ -2,6 +2,8 @@
// 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.Threading.Tasks;
using Windows.Foundation;
namespace Microsoft.CmdPal.Core.Common.Services;
@@ -24,4 +26,12 @@ public interface IExtensionService
void EnableExtension(string extensionUniqueId);
void DisableExtension(string extensionUniqueId);
///// <summary>
///// Gets a boolean indicating whether the extension was disabled due to the corresponding Windows optional feature
///// being absent from the machine or in an unknown state.
///// </summary>
///// <param name="extension">The out of proc extension object</param>
///// <returns>True only if the extension was disabled. False otherwise.</returns>
// public Task<bool> DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension);
}

View File

@@ -23,3 +23,12 @@ public interface IRunHistoryService
/// <param name="item">The run history item to add.</param>
void AddRunHistoryItem(string item);
}
public interface ITelemetryService
{
void LogRunQuery(string query, int resultCount, ulong durationMs);
void LogRunCommand(string command, bool asAdmin, bool success);
void LogOpenUri(string uri, bool isWeb, bool success);
}

View File

@@ -1,10 +0,0 @@
// 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.
namespace Microsoft.CmdPal.Core.Common.Services.Telemetry;
public interface ITelemetryService
{
void WriteEvent(TelemetryEventBase telemetryEvent);
}

View File

@@ -1,14 +0,0 @@
// 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.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.Core.Common.Services.Telemetry;
public abstract class TelemetryEventBase : EventBase, IEvent
{
// Overridden in derived classes
public abstract PartA_PrivTags PartA_PrivTags { get; }
}

View File

@@ -4,9 +4,9 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.Foundation;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -14,7 +14,6 @@ namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class AppExtensionHost : IExtensionHost
{
private static readonly GlobalLogPageContext _globalLogPageContext = new();
private readonly ILogger _logger;
private static ulong _hostingHwnd;
@@ -28,11 +27,6 @@ public abstract partial class AppExtensionHost : IExtensionHost
public static void SetHostHwnd(ulong hostHwnd) => _hostingHwnd = hostHwnd;
public AppExtensionHost(ILogger logger)
{
_logger = logger;
}
public void DebugLog(string message)
{
#if DEBUG
@@ -66,7 +60,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
return Task.CompletedTask.AsAsyncAction();
}
Log_DebugMessage(message.Message);
CoreLogger.LogDebug(message.Message);
_ = Task.Run(() =>
{
@@ -102,7 +96,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
public void ProcessLogMessage(ILogMessage message)
{
var vm = new LogMessageViewModel(message, _globalLogPageContext, _logger);
var vm = new LogMessageViewModel(message, _globalLogPageContext);
vm.SafeInitializePropertiesSynchronous();
Task.Factory.StartNew(
@@ -133,7 +127,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
return;
}
var vm = new StatusMessageViewModel(message, new(_globalLogPageContext), _logger);
var vm = new StatusMessageViewModel(message, new(_globalLogPageContext));
vm.SafeInitializePropertiesSynchronous();
Task.Factory.StartNew(
@@ -164,11 +158,6 @@ public abstract partial class AppExtensionHost : IExtensionHost
}
public abstract string? GetExtensionDisplayName();
public abstract AppExtensionHost GetHostForCommand(object? context, AppExtensionHost? currentHost);
[LoggerMessage(Level = LogLevel.Debug, Message = "{Message}")]
partial void Log_DebugMessage(string message);
}
public interface IAppHostService

View File

@@ -6,7 +6,6 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.System;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -14,8 +13,6 @@ namespace Microsoft.CmdPal.Core.ViewModels;
public partial class CommandBarViewModel : ObservableObject,
IRecipient<UpdateCommandBarMessage>
{
private readonly ILogger _logger;
public ICommandBarContext? SelectedItem
{
get => field;
@@ -51,9 +48,8 @@ public partial class CommandBarViewModel : ObservableObject,
[ObservableProperty]
public partial PageViewModel? CurrentPage { get; set; }
public CommandBarViewModel(ILogger logger)
public CommandBarViewModel()
{
_logger = logger;
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
}

View File

@@ -5,12 +5,12 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context, ILogger logger) : CommandItemViewModel(new(contextItem), context, logger), IContextItemViewModel
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context), IContextItemViewModel
{
private readonly KeyChord nullKeyChord = new(0, 0, 0);

View File

@@ -3,11 +3,11 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -86,11 +86,11 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
_errorIcon.InitializeProperties();
}
public CommandItemViewModel(ExtensionObject<ICommandItem> item, WeakReference<IPageContext> errorContext, ILogger logger)
: base(errorContext, logger)
public CommandItemViewModel(ExtensionObject<ICommandItem> item, WeakReference<IPageContext> errorContext)
: base(errorContext)
{
_commandItemModel = item;
Command = new(null, errorContext, Logger);
Command = new(null, errorContext);
}
public void FastInitializeProperties()
@@ -106,7 +106,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
return;
}
Command = new(model.Command, PageContext, Logger);
Command = new(model.Command, PageContext);
Command.FastInitializeProperties();
_itemTitle = model.Title;
@@ -184,7 +184,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
MoreCommands = more
.Select<IContextItem, IContextItemViewModel>(item =>
{
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext, Logger) : new SeparatorViewModel();
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel();
})
.ToList();
}
@@ -201,7 +201,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
if (!string.IsNullOrEmpty(model.Command?.Name))
{
_defaultCommandContextItemViewModel = new CommandContextItemViewModel(new CommandContextItem(model.Command!), PageContext, Logger)
_defaultCommandContextItemViewModel = new CommandContextItemViewModel(new CommandContextItem(model.Command!), PageContext)
{
_itemTitle = Name,
Subtitle = Subtitle,
@@ -231,8 +231,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
catch (Exception ex)
{
Log_ErrorFastInitializingCommandItemViewModel(Logger, ex);
Command = new(null, PageContext, Logger);
CoreLogger.LogError("error fast initializing CommandItemViewModel", ex);
Command = new(null, PageContext);
_itemTitle = "Error";
Subtitle = "Item failed to load";
MoreCommands = [];
@@ -253,7 +253,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
catch (Exception ex)
{
Initialized |= InitializedState.Error;
Log_ErrorSlowInitializingCommandItemViewModel(Logger, ex);
CoreLogger.LogError("error slow initializing CommandItemViewModel", ex);
}
return false;
@@ -268,8 +268,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
catch (Exception ex)
{
Log_ErrorSlowInitializingCommandItemViewModel(Logger, ex);
Command = new(null, PageContext, Logger);
CoreLogger.LogError("error initializing CommandItemViewModel", ex);
Command = new(null, PageContext);
_itemTitle = "Error";
Subtitle = "Item failed to load";
MoreCommands = [];
@@ -304,7 +304,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
{
case nameof(Command):
Command.PropertyChanged -= Command_PropertyChanged;
Command = new(model.Command, PageContext, Logger);
Command = new(model.Command, PageContext);
Command.InitializeProperties();
Command.PropertyChanged += Command_PropertyChanged;
@@ -351,7 +351,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
var newContextMenu = more
.Select<IContextItem, IContextItemViewModel>(item =>
{
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext, Logger) : new SeparatorViewModel();
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel();
})
.ToList();
lock (MoreCommands)
@@ -464,15 +464,6 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
base.SafeCleanup();
Initialized |= InitializedState.CleanedUp;
}
[LoggerMessage(level: LogLevel.Error, message: "error fast initializing CommandItemViewModel")]
static partial void Log_ErrorFastInitializingCommandItemViewModel(ILogger logger, Exception ex);
[LoggerMessage(level: LogLevel.Error, message: "error slow initializing CommandItemViewModel")]
static partial void Log_ErrorSlowInitializingCommandItemViewModel(ILogger logger, Exception ex);
[LoggerMessage(level: LogLevel.Error, message: "error initializing CommandItemViewModel")]
static partial void Log_ErrorInitializingCommandItemViewModel(ILogger logger, Exception ex);
}
[Flags]

View File

@@ -4,7 +4,6 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -39,8 +38,8 @@ public partial class CommandViewModel : ExtensionObjectViewModel
public IReadOnlyDictionary<string, ExtensionObject<object>>? Properties => _properties?.AsReadOnly();
public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext, ILogger logger)
: base(pageContext, logger)
public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext)
: base(pageContext)
{
Model = new(command);
Icon = new(null);

View File

@@ -4,14 +4,13 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ConfirmResultViewModel(IConfirmationArgs? args, WeakReference<IPageContext> context, ILogger logger)
: ExtensionObjectViewModel(context, logger)
public partial class ConfirmResultViewModel(IConfirmationArgs _args, WeakReference<IPageContext> context) :
ExtensionObjectViewModel(context)
{
public ExtensionObject<IConfirmationArgs> Model { get; private set; } = new(args);
public ExtensionObject<IConfirmationArgs> Model { get; } = new(_args);
// Remember - "observable" properties from the model (via PropChanged)
// cannot be marked [ObservableProperty]
@@ -21,7 +20,7 @@ public partial class ConfirmResultViewModel(IConfirmationArgs? args, WeakReferen
public bool IsPrimaryCommandCritical { get; private set; }
public CommandViewModel PrimaryCommand { get; private set; } = new(null, context, logger);
public CommandViewModel PrimaryCommand { get; private set; } = new(null, context);
public override void InitializeProperties()
{
@@ -34,7 +33,7 @@ public partial class ConfirmResultViewModel(IConfirmationArgs? args, WeakReferen
Title = model.Title;
Description = model.Description;
IsPrimaryCommandCritical = model.IsPrimaryCommandCritical;
PrimaryCommand = new(model.PrimaryCommand, PageContext, Logger);
PrimaryCommand = new(model.PrimaryCommand, PageContext);
PrimaryCommand.InitializeProperties();
UpdateProperty(nameof(Title));

View File

@@ -11,14 +11,12 @@ using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
private readonly ExtensionObject<IContentPage> _model;
private readonly ILogger _logger;
[ObservableProperty]
public partial ObservableCollection<ContentViewModel> Content { get; set; } = [];
@@ -49,11 +47,10 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
// Remember - "observable" properties from the model (via PropChanged)
// cannot be marked [ObservableProperty]
public ContentPageViewModel(IContentPage model, AppExtensionHost host, ILogger logger)
: base(model, host, logger)
public ContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
{
_model = new(model);
_logger = logger;
}
// TODO: Does this need to hop to a _different_ thread, so that we don't block the extension while we're fetching?
@@ -118,7 +115,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext, _logger);
return new CommandContextItemViewModel(contextItem, PageContext);
}
else
{
@@ -138,7 +135,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
var extensionDetails = model.Details;
if (extensionDetails is not null)
{
Details = new(extensionDetails, PageContext, _logger);
Details = new(extensionDetails, PageContext);
Details.InitializeProperties();
}
@@ -177,7 +174,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
{
if (item is ICommandContextItem contextItem)
{
return new CommandContextItemViewModel(contextItem, PageContext, _logger) as IContextItemViewModel;
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
}
else
{
@@ -219,7 +216,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
break;
case nameof(Details):
var extensionDetails = model.Details;
Details = extensionDetails is not null ? new(extensionDetails, PageContext, _logger) : null;
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
UpdateDetails();
break;
}

View File

@@ -2,12 +2,10 @@
// 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.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class ContentViewModel(WeakReference<IPageContext> context, ILogger logger) :
ExtensionObjectViewModel(context, logger)
public abstract partial class ContentViewModel(WeakReference<IPageContext> context) :
ExtensionObjectViewModel(context)
{
public bool OnlyControlOnPage { get; internal set; }
}

View File

@@ -5,6 +5,7 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -137,7 +138,11 @@ public partial class ContextMenuViewModel : ObservableObject,
if (item is CommandContextItemViewModel cmd && cmd.HasRequestedShortcut)
{
var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
_ = result.TryAdd(key, cmd);
var added = result.TryAdd(key, cmd);
if (!added)
{
CoreLogger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
}
}
}

View File

@@ -4,26 +4,19 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsCommandsViewModel : DetailsElementViewModel
public partial class DetailsCommandsViewModel(
IDetailsElement _detailsElement,
WeakReference<IPageContext> context) : DetailsElementViewModel(_detailsElement, context)
{
private readonly ILogger _logger;
public List<CommandViewModel> Commands { get; private set; } = [];
public bool HasCommands => Commands.Count > 0;
private readonly ExtensionObject<IDetailsCommands> _dataModel;
public DetailsCommandsViewModel(IDetailsElement _detailsElement, WeakReference<IPageContext> context, ILogger logger)
: base(_detailsElement, context, logger)
{
_logger = logger;
_dataModel = new(_detailsElement.Data as IDetailsCommands);
}
private readonly ExtensionObject<IDetailsCommands> _dataModel =
new(_detailsElement.Data as IDetailsCommands);
public override void InitializeProperties()
{
@@ -38,7 +31,7 @@ public partial class DetailsCommandsViewModel : DetailsElementViewModel
.Commands?
.Select(c =>
{
var vm = new CommandViewModel(c, PageContext, _logger);
var vm = new CommandViewModel(c, PageContext);
vm.InitializeProperties();
return vm;
})

View File

@@ -2,10 +2,11 @@
// 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.Extensions.Logging;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class DetailsDataViewModel(IPageContext context, ILogger logger) : ExtensionObjectViewModel(context, logger)
public abstract partial class DetailsDataViewModel(IPageContext context) : ExtensionObjectViewModel(context)
{
}

View File

@@ -4,11 +4,10 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class DetailsElementViewModel(IDetailsElement _detailsElement, WeakReference<IPageContext> context, ILogger logger) : ExtensionObjectViewModel(context, logger)
public abstract partial class DetailsElementViewModel(IDetailsElement _detailsElement, WeakReference<IPageContext> context) : ExtensionObjectViewModel(context)
{
private readonly ExtensionObject<IDetailsElement> _model = new(_detailsElement);

View File

@@ -6,12 +6,12 @@ using CommunityToolkit.Mvvm.Input;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsLinkViewModel(IDetailsElement _detailsElement, WeakReference<IPageContext> context, ILogger logger)
: DetailsElementViewModel(_detailsElement, context, logger)
public partial class DetailsLinkViewModel(
IDetailsElement _detailsElement,
WeakReference<IPageContext> context) : DetailsElementViewModel(_detailsElement, context)
{
private static readonly string[] _initProperties = [
nameof(Text),

View File

@@ -4,13 +4,12 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsSeparatorViewModel(
IDetailsElement _detailsElement,
WeakReference<IPageContext> context, ILogger logger) : DetailsElementViewModel(_detailsElement, context, logger)
WeakReference<IPageContext> context) : DetailsElementViewModel(_detailsElement, context)
{
private readonly ExtensionObject<IDetailsSeparator> _dataModel =
new(_detailsElement.Data as IDetailsSeparator);

View File

@@ -4,25 +4,19 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsTagsViewModel : DetailsElementViewModel
public partial class DetailsTagsViewModel(
IDetailsElement _detailsElement,
WeakReference<IPageContext> context) : DetailsElementViewModel(_detailsElement, context)
{
public List<TagViewModel> Tags { get; private set; } = [];
public bool HasTags => Tags.Count > 0;
private readonly ExtensionObject<IDetailsTags> _dataModel;
private readonly ILogger _logger;
public DetailsTagsViewModel(IDetailsElement _detailsElement, WeakReference<IPageContext> context, ILogger logger)
: base(_detailsElement, context, logger)
{
_logger = logger;
_dataModel = new(_detailsElement.Data as IDetailsTags);
}
private readonly ExtensionObject<IDetailsTags> _dataModel =
new(_detailsElement.Data as IDetailsTags);
public override void InitializeProperties()
{
@@ -37,7 +31,7 @@ public partial class DetailsTagsViewModel : DetailsElementViewModel
.Tags?
.Select(t =>
{
var vm = new TagViewModel(t, PageContext, _logger);
var vm = new TagViewModel(t, PageContext);
vm.InitializeProperties();
return vm;
})

View File

@@ -4,11 +4,10 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class DetailsViewModel(IDetails _details, WeakReference<IPageContext> context, ILogger logger) : ExtensionObjectViewModel(context, logger)
public partial class DetailsViewModel(IDetails _details, WeakReference<IPageContext> context) : ExtensionObjectViewModel(context)
{
private readonly ExtensionObject<IDetails> _detailsModel = new(_details);
@@ -48,10 +47,10 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
{
DetailsElementViewModel? vm = element.Data switch
{
IDetailsSeparator => new DetailsSeparatorViewModel(element, this.PageContext, Logger),
IDetailsLink => new DetailsLinkViewModel(element, this.PageContext, Logger),
IDetailsCommands => new DetailsCommandsViewModel(element, this.PageContext, Logger),
IDetailsTags => new DetailsTagsViewModel(element, this.PageContext, Logger),
IDetailsSeparator => new DetailsSeparatorViewModel(element, this.PageContext),
IDetailsLink => new DetailsLinkViewModel(element, this.PageContext),
IDetailsCommands => new DetailsCommandsViewModel(element, this.PageContext),
IDetailsTags => new DetailsTagsViewModel(element, this.PageContext),
_ => null,
};
if (vm is not null)

View File

@@ -3,29 +3,23 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.Logging;
using Microsoft.CmdPal.Core.Common;
namespace Microsoft.CmdPal.Core.ViewModels;
public abstract partial class ExtensionObjectViewModel : ObservableObject
{
private readonly ILogger _logger;
public ILogger Logger => _logger;
public WeakReference<IPageContext> PageContext { get; set; }
internal ExtensionObjectViewModel(IPageContext? context, ILogger logger)
internal ExtensionObjectViewModel(IPageContext? context)
{
var realContext = context ?? (this is IPageContext c ? c : throw new ArgumentException("You need to pass in an IErrorContext"));
_logger = logger;
PageContext = new(realContext);
}
internal ExtensionObjectViewModel(WeakReference<IPageContext> context, ILogger logger)
internal ExtensionObjectViewModel(WeakReference<IPageContext> context)
{
PageContext = context;
_logger = logger;
}
public async virtual Task InitializePropertiesAsync()
@@ -120,10 +114,7 @@ public abstract partial class ExtensionObjectViewModel : ObservableObject
}
catch (Exception ex)
{
Log_CleanupException(ex);
CoreLogger.LogDebug(ex.ToString());
}
}
[LoggerMessage(Level = LogLevel.Debug)]
partial void Log_CleanupException(Exception exception);
}

View File

@@ -4,7 +4,6 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -24,8 +23,8 @@ public partial class FilterItemViewModel : ExtensionObjectViewModel, IFilterItem
public bool IsInErrorState => Initialized.HasFlag(InitializedState.Error);
public FilterItemViewModel(IFilter filter, WeakReference<IPageContext> context, ILogger logger)
: base(context, logger)
public FilterItemViewModel(IFilter filter, WeakReference<IPageContext> context)
: base(context)
{
_model = new(filter);
}

View File

@@ -2,9 +2,10 @@
// 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 Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -20,8 +21,8 @@ public partial class FiltersViewModel : ExtensionObjectViewModel
public bool ShouldShowFilters => Filters.Length > 0;
public FiltersViewModel(ExtensionObject<IFilters> filters, WeakReference<IPageContext> context, ILogger logger)
: base(context, logger)
public FiltersViewModel(ExtensionObject<IFilters> filters, WeakReference<IPageContext> context)
: base(context)
{
_filtersModel = filters;
}
@@ -70,7 +71,7 @@ public partial class FiltersViewModel : ExtensionObjectViewModel
{
if (filter is IFilter filterItem)
{
var filterItemViewModel = new FilterItemViewModel(filterItem, PageContext, Logger);
var filterItemViewModel = new FilterItemViewModel(filterItem, PageContext);
filterItemViewModel.InitializeProperties();
if (firstFilterItem is null)

View File

@@ -1,21 +0,0 @@
// 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.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.Core.ViewModels;
// Represents everything the command bar needs to know about to show command
// buttons at the bottom.
//
// This is implemented by both ListItemViewModel and ContentPageViewModel,
// the two things with sub-commands.
public interface ICommandBarContext : IContextMenuContext
{
public string SecondaryCommandName { get; }
public CommandItemViewModel? PrimaryCommand { get; }
public CommandItemViewModel? SecondaryCommand { get; }
}

View File

@@ -1,12 +0,0 @@
// 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.
namespace Microsoft.CmdPal.Core.ViewModels;
public interface IPageContext
{
void ShowException(Exception ex, string? extensionHint = null);
TaskScheduler Scheduler { get; }
}

View File

@@ -1,19 +0,0 @@
// 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;
namespace Microsoft.CmdPal.Core.ViewModels;
public interface IPageViewModelFactoryService
{
/// <summary>
/// Creates a new instance of the page view model for the given page type.
/// </summary>
/// <param name="page">The page for which to create the view model.</param>
/// <param name="nested">Indicates whether the page is not the top-level page.</param>
/// <param name="host">The command palette host that will host the page (for status messages)</param>
/// <returns>A new instance of the page view model.</returns>
PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host);
}

View File

@@ -7,14 +7,13 @@ using Microsoft.CmdPal.Core.ViewModels.Commands;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
// Fix for CS9107: Do not capture 'logger' in the primary constructor; use a regular constructor instead.
public partial class ListItemViewModel : CommandItemViewModel
public partial class ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
: CommandItemViewModel(new(model), context)
{
public new ExtensionObject<IListItem> Model { get; }
public new ExtensionObject<IListItem> Model { get; } = new(model);
public List<TagViewModel>? Tags { get; set; }
@@ -33,15 +32,6 @@ public partial class ListItemViewModel : CommandItemViewModel
public string AccessibleName { get; private set; } = string.Empty;
private readonly ILogger _logger;
public ListItemViewModel(IListItem model, WeakReference<IPageContext> context, ILogger logger)
: base(new(model), context, logger)
{
Model = new(model);
_logger = logger;
}
public override void InitializeProperties()
{
if (IsInitialized)
@@ -79,7 +69,7 @@ public partial class ListItemViewModel : CommandItemViewModel
var extensionDetails = model.Details;
if (extensionDetails is not null)
{
Details = new(extensionDetails, PageContext, Logger);
Details = new(extensionDetails, PageContext);
Details.InitializeProperties();
UpdateProperty(nameof(Details));
UpdateProperty(nameof(HasDetails));
@@ -114,7 +104,7 @@ public partial class ListItemViewModel : CommandItemViewModel
break;
case nameof(Details):
var extensionDetails = model.Details;
Details = extensionDetails is not null ? new(extensionDetails, PageContext, Logger) : null;
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
Details?.InitializeProperties();
UpdateProperty(nameof(Details));
UpdateProperty(nameof(HasDetails));
@@ -156,7 +146,7 @@ public partial class ListItemViewModel : CommandItemViewModel
// Create the view model for the show details command
var showDetailsCommand = new ShowDetailsCommand(Details);
var showDetailsContextItem = new CommandContextItem(showDetailsCommand);
var showDetailsContextItemViewModel = new CommandContextItemViewModel(showDetailsContextItem, PageContext, _logger);
var showDetailsContextItemViewModel = new CommandContextItemViewModel(showDetailsContextItem, PageContext);
showDetailsContextItemViewModel.SlowInitializeProperties();
MoreCommands.Add(showDetailsContextItemViewModel);
}
@@ -190,7 +180,7 @@ public partial class ListItemViewModel : CommandItemViewModel
// Create the view model for the show details command
var showDetailsCommand = new ShowDetailsCommand(Details);
var showDetailsContextItem = new CommandContextItem(showDetailsCommand);
var showDetailsContextItemViewModel = new CommandContextItemViewModel(showDetailsContextItem, PageContext, _logger);
var showDetailsContextItemViewModel = new CommandContextItemViewModel(showDetailsContextItem, PageContext);
showDetailsContextItemViewModel.SlowInitializeProperties();
MoreCommands.Add(showDetailsContextItemViewModel);
@@ -203,7 +193,7 @@ public partial class ListItemViewModel : CommandItemViewModel
{
var newTags = newTagsFromModel?.Select(t =>
{
var vm = new TagViewModel(t, PageContext, Logger);
var vm = new TagViewModel(t, PageContext);
vm.InitializeProperties();
return vm;
})

View File

@@ -11,7 +11,6 @@ using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.Foundation;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -20,7 +19,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
{
// private readonly HashSet<ListItemViewModel> _itemCache = [];
private readonly TaskFactory filterTaskFactory = new(new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler);
private readonly ILogger _logger;
// TODO: Do we want a base "ItemsPageViewModel" for anything that's going to have items?
@@ -92,12 +90,11 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
}
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host, ILogger logger)
: base(model, host, logger)
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
{
_model = new(model);
EmptyContent = new(new(null), PageContext, logger);
_logger = logger;
EmptyContent = new(new(null), PageContext);
}
private void FiltersPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@@ -235,7 +232,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
return;
}
ListItemViewModel viewModel = new(item, new(this), _logger);
ListItemViewModel viewModel = new(item, new(this));
// If an item fails to load, silently ignore it.
if (viewModel.SafeFastInit())
@@ -597,11 +594,11 @@ public partial class ListViewModel : PageViewModel, IDisposable
UpdateProperty(nameof(SearchText));
UpdateProperty(nameof(InitialSearchText));
EmptyContent = new(new(model.EmptyContent), PageContext, _logger);
EmptyContent = new(new(model.EmptyContent), PageContext);
EmptyContent.SlowInitializeProperties();
Filters?.PropertyChanged -= FiltersPropertyChanged;
Filters = new(new(model.Filters), PageContext, _logger);
Filters = new(new(model.Filters), PageContext);
Filters?.PropertyChanged += FiltersPropertyChanged;
Filters?.InitializeProperties();
@@ -699,12 +696,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
SearchText = model.SearchText;
break;
case nameof(EmptyContent):
EmptyContent = new(new(model.EmptyContent), PageContext, _logger);
EmptyContent = new(new(model.EmptyContent), PageContext);
EmptyContent.SlowInitializeProperties();
break;
case nameof(Filters):
Filters?.PropertyChanged -= FiltersPropertyChanged;
Filters = new(new(model.Filters), PageContext, _logger);
Filters = new(new(model.Filters), PageContext);
Filters?.PropertyChanged += FiltersPropertyChanged;
Filters?.InitializeProperties();
break;
@@ -754,7 +751,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
base.UnsafeCleanup();
EmptyContent?.SafeCleanup();
EmptyContent = new(new(null), PageContext, _logger); // necessary?
EmptyContent = new(new(null), PageContext); // necessary?
_cancellationTokenSource?.Cancel();
filterCancellationTokenSource?.Cancel();

View File

@@ -3,14 +3,13 @@
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class LoadingPageViewModel : PageViewModel
{
public LoadingPageViewModel(IPage? model, AppExtensionHost host, ILogger logger)
: base(model, host, logger)
public LoadingPageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
{
ModelIsLoading = true;
IsInitialized = false;

View File

@@ -4,7 +4,6 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -14,8 +13,8 @@ public partial class LogMessageViewModel : ExtensionObjectViewModel
public string Message { get; private set; } = string.Empty;
public LogMessageViewModel(ILogMessage message, IPageContext context, ILogger logger)
: base(context, logger)
public LogMessageViewModel(ILogMessage message, IPageContext context)
: base(context)
{
_model = new(message);
}

View File

@@ -7,4 +7,6 @@ namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to perform a list item's secondary command when the user presses ctrl+enter in the search box
/// </summary>
public record ActivateSecondaryCommandMessage;
public record ActivateSecondaryCommandMessage
{
}

View File

@@ -7,4 +7,6 @@ namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to perform a list item's command when the user presses enter in the search box
/// </summary>
public record ActivateSelectedListItemMessage;
public record ActivateSelectedListItemMessage
{
}

View File

@@ -4,4 +4,6 @@
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ClearSearchMessage();
public record ClearSearchMessage()
{
}

View File

@@ -7,4 +7,6 @@ namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to announce that a context menu should close
/// </summary>
public record CloseContextMenuMessage;
public record CloseContextMenuMessage
{
}

View File

@@ -4,4 +4,6 @@
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record DismissMessage();
public record DismissMessage()
{
}

View File

@@ -4,4 +4,6 @@
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record FocusSearchBoxMessage();
public record FocusSearchBoxMessage()
{
}

View File

@@ -4,5 +4,7 @@
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
// TODO! sticking these properties here feels like leaking the UI into the models
public record GoBackMessage(bool WithAnimation = true, bool FocusSearch = true);
public record GoBackMessage(bool WithAnimation = true, bool FocusSearch = true)
{
// TODO! sticking these properties here feels like leaking the UI into the models
}

View File

@@ -5,4 +5,6 @@
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
// TODO! sticking these properties here feels like leaking the UI into the models
public record GoHomeMessage(bool WithAnimation = true, bool FocusSearch = true);
public record GoHomeMessage(bool WithAnimation = true, bool FocusSearch = true)
{
}

View File

@@ -7,4 +7,6 @@ using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record HandleCommandResultMessage(ExtensionObject<ICommandResult> Result);
public record HandleCommandResultMessage(ExtensionObject<ICommandResult> Result)
{
}

View File

@@ -2,6 +2,11 @@
// 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.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record HideDetailsMessage();
public record HideDetailsMessage()
{
}

View File

@@ -1,47 +0,0 @@
// 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.ComponentModel;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public interface IContextMenuContext : INotifyPropertyChanged
{
public IEnumerable<IContextItemViewModel> MoreCommands { get; }
public bool HasMoreCommands { get; }
public List<IContextItemViewModel> AllCommands { get; }
/// <summary>
/// Generates a mapping of key -> command item for this particular item's
/// MoreCommands. (This won't include the primary Command, but it will
/// include the secondary one). This map can be used to quickly check if a
/// shortcut key was pressed
/// </summary>
/// <returns>a dictionary of KeyChord -> Context commands, for all commands
/// that have a shortcut key set.</returns>
public Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
{
var result = new Dictionary<KeyChord, CommandContextItemViewModel>();
var menu = MoreCommands;
if (menu is null)
{
return result;
}
foreach (var item in menu)
{
if (item is CommandContextItemViewModel cmd && cmd.HasRequestedShortcut)
{
var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
var added = result.TryAdd(key, cmd);
}
}
return result;
}
}

View File

@@ -2,6 +2,11 @@
// 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.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record LaunchUriMessage(Uri Uri);
public record LaunchUriMessage(Uri Uri)
{
}

View File

@@ -4,4 +4,6 @@
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record NavigateBackMessage(bool FromBackspace = false);
public record NavigateBackMessage(bool FromBackspace = false)
{
}

View File

@@ -7,4 +7,6 @@ namespace Microsoft.CmdPal.Core.ViewModels.Commands;
/// <summary>
/// Used to navigate to the next command in the page when pressing the Down key in the SearchBox.
/// </summary>
public record NavigateNextCommand;
public record NavigateNextCommand
{
}

View File

@@ -7,4 +7,6 @@ namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to navigate down one page in the page when pressing the PageDown key in the SearchBox.
/// </summary>
public record NavigatePageDownCommand;
public record NavigatePageDownCommand
{
}

View File

@@ -7,4 +7,6 @@ namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to navigate up one page in the page when pressing the PageUp key in the SearchBox.
/// </summary>
public record NavigatePageUpCommand;
public record NavigatePageUpCommand
{
}

View File

@@ -7,4 +7,6 @@ namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to navigate to the previous command in the page when pressing the Down key in the SearchBox.
/// </summary>
public record NavigatePreviousCommand;
public record NavigatePreviousCommand
{
}

View File

@@ -4,4 +4,6 @@
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ShowConfirmationMessage(Microsoft.CommandPalette.Extensions.IConfirmationArgs? Args);
public record ShowConfirmationMessage(Microsoft.CommandPalette.Extensions.IConfirmationArgs? Args)
{
}

View File

@@ -2,6 +2,11 @@
// 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.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ShowDetailsMessage(DetailsViewModel Details);
public record ShowDetailsMessage(DetailsViewModel Details)
{
}

View File

@@ -4,4 +4,6 @@
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ShowToastMessage(string Message);
public record ShowToastMessage(string Message)
{
}

View File

@@ -4,4 +4,6 @@
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record ShowWindowMessage(IntPtr Hwnd);
public record ShowWindowMessage(IntPtr Hwnd)
{
}

View File

@@ -2,9 +2,73 @@
// 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.ComponentModel;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
/// <summary>
/// Used to update the command bar at the bottom to reflect the commands for a list item
/// </summary>
public record UpdateCommandBarMessage(ICommandBarContext? ViewModel);
public record UpdateCommandBarMessage(ICommandBarContext? ViewModel)
{
}
public interface IContextMenuContext : INotifyPropertyChanged
{
public IEnumerable<IContextItemViewModel> MoreCommands { get; }
public bool HasMoreCommands { get; }
public List<IContextItemViewModel> AllCommands { get; }
/// <summary>
/// Generates a mapping of key -> command item for this particular item's
/// MoreCommands. (This won't include the primary Command, but it will
/// include the secondary one). This map can be used to quickly check if a
/// shortcut key was pressed
/// </summary>
/// <returns>a dictionary of KeyChord -> Context commands, for all commands
/// that have a shortcut key set.</returns>
public Dictionary<KeyChord, CommandContextItemViewModel> Keybindings()
{
var result = new Dictionary<KeyChord, CommandContextItemViewModel>();
var menu = MoreCommands;
if (menu is null)
{
return result;
}
foreach (var item in menu)
{
if (item is CommandContextItemViewModel cmd && cmd.HasRequestedShortcut)
{
var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
var added = result.TryAdd(key, cmd);
if (!added)
{
CoreLogger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
}
}
}
return result;
}
}
// Represents everything the command bar needs to know about to show command
// buttons at the bottom.
//
// This is implemented by both ListItemViewModel and ContentPageViewModel,
// the two things with sub-commands.
public interface ICommandBarContext : IContextMenuContext
{
public string SecondaryCommandName { get; }
public CommandItemViewModel? PrimaryCommand { get; }
public CommandItemViewModel? SecondaryCommand { get; }
}

View File

@@ -4,4 +4,6 @@
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
public record UpdateSuggestionMessage(string TextToSuggest);
public record UpdateSuggestionMessage(string TextToSuggest)
{
}

View File

@@ -2,9 +2,7 @@
// 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.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
internal sealed partial class NullPageViewModel(AppExtensionHost extensionHost, ILogger logger)
: PageViewModel(null, extensionHost, logger);
internal sealed partial class NullPageViewModel(TaskScheduler scheduler, AppExtensionHost extensionHost)
: PageViewModel(null, scheduler, extensionHost);

View File

@@ -8,15 +8,14 @@ using CommunityToolkit.Mvvm.Input;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
{
private readonly ExtensionObject<IPage> _pageModel;
public TaskScheduler Scheduler { get; private set; }
public TaskScheduler Scheduler { get; private set; } = TaskScheduler.FromCurrentSynchronizationContext();
private readonly ExtensionObject<IPage> _pageModel;
public bool IsLoading => ModelIsLoading || (!IsInitialized);
@@ -77,10 +76,11 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
public IconInfoViewModel Icon { get; protected set; }
public PageViewModel(IPage? model, AppExtensionHost extensionHost, ILogger logger)
: base((IPageContext?)null, logger)
public PageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost extensionHost)
: base((IPageContext?)null)
{
_pageModel = new(model);
Scheduler = scheduler;
PageContext = new(this);
ExtensionHost = extensionHost;
Icon = new(null);
@@ -258,3 +258,22 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
}
}
}
public interface IPageContext
{
void ShowException(Exception ex, string? extensionHint = null);
TaskScheduler Scheduler { get; }
}
public interface IPageViewModelFactoryService
{
/// <summary>
/// Creates a new instance of the page view model for the given page type.
/// </summary>
/// <param name="page">The page for which to create the view model.</param>
/// <param name="nested">Indicates whether the page is not the top-level page.</param>
/// <param name="host">The command palette host that will host the page (for status messages)</param>
/// <returns>A new instance of the page view model.</returns>
PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host);
}

View File

@@ -4,7 +4,6 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -16,8 +15,8 @@ public partial class ProgressViewModel : ExtensionObjectViewModel
public uint ProgressPercent { get; private set; }
public ProgressViewModel(IProgressState progress, WeakReference<IPageContext> context, ILogger logger)
: base(context, logger)
public ProgressViewModel(IProgressState progress, WeakReference<IPageContext> context)
: base(context)
{
Model = new(progress);
}

View File

@@ -6,10 +6,10 @@ using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -18,10 +18,9 @@ public partial class ShellViewModel : ObservableObject,
IRecipient<HandleCommandResultMessage>
{
private readonly IRootPageService _rootPageService;
private readonly AppExtensionHost _appHost;
private readonly TaskScheduler _scheduler = TaskScheduler.FromCurrentSynchronizationContext();
private readonly IAppHostService _appHostService;
private readonly TaskScheduler _scheduler;
private readonly IPageViewModelFactoryService _pageViewModelFactory;
private readonly ILogger _logger;
private readonly Lock _invokeLock = new();
private Task? _handleInvokeTask;
@@ -61,7 +60,7 @@ public partial class ShellViewModel : ObservableObject,
}
catch (Exception ex)
{
Log_Exception(ex);
CoreLogger.LogError(ex.ToString());
}
}
}
@@ -85,18 +84,18 @@ public partial class ShellViewModel : ObservableObject,
public PageViewModel NullPage { get; private set; }
public ShellViewModel(
TaskScheduler scheduler,
IRootPageService rootPageService,
IPageViewModelFactoryService pageViewModelFactory,
AppExtensionHost appHost,
ILogger<ShellViewModel> logger)
IAppHostService appHostService)
{
_pageViewModelFactory = pageViewModelFactory;
_scheduler = scheduler;
_rootPageService = rootPageService;
_appHost = appHost;
_logger = logger;
_appHostService = appHostService;
NullPage = new NullPageViewModel(_appHost, _logger);
_currentPage = new LoadingPageViewModel(null, _appHost, _logger);
NullPage = new NullPageViewModel(_scheduler, appHostService.GetDefaultHost());
_currentPage = new LoadingPageViewModel(null, _scheduler, appHostService.GetDefaultHost());
// Register to receive messages
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
@@ -163,7 +162,7 @@ public partial class ShellViewModel : ObservableObject,
{
if (viewModel.InitializeCommand.ExecutionTask.Exception is AggregateException ex)
{
Log_Exception(ex);
CoreLogger.LogError(ex.ToString());
}
}
else
@@ -181,7 +180,7 @@ public partial class ShellViewModel : ObservableObject,
}
catch (Exception ex)
{
Log_Exception(ex);
CoreLogger.LogError(ex.ToString());
}
}
@@ -211,7 +210,7 @@ public partial class ShellViewModel : ObservableObject,
}
catch (Exception ex)
{
Log_Exception(ex);
CoreLogger.LogError(ex.ToString());
}
}
@@ -241,7 +240,7 @@ public partial class ShellViewModel : ObservableObject,
}
catch (Exception ex)
{
Log_Exception(ex);
CoreLogger.LogError(ex.ToString());
}
finally
{
@@ -257,7 +256,7 @@ public partial class ShellViewModel : ObservableObject,
return;
}
var host = _appHost.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
var host = _appHostService.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
_rootPageService.OnPerformCommand(message.Context, !CurrentPage.IsNested, host);
@@ -265,7 +264,7 @@ public partial class ShellViewModel : ObservableObject,
{
if (command is IPage page)
{
Log_NavigateToPage();
CoreLogger.LogDebug($"Navigating to page");
var isMainPage = command == _rootPage;
_isNested = !isMainPage;
@@ -274,7 +273,7 @@ public partial class ShellViewModel : ObservableObject,
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
if (pageViewModel is null)
{
Log_FailedToCreateViewModel(page.GetType().Name);
CoreLogger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
throw new NotSupportedException();
}
@@ -304,7 +303,7 @@ public partial class ShellViewModel : ObservableObject,
}
else if (command is IInvokableCommand invokable)
{
Log_InvokingCommand();
CoreLogger.LogDebug($"Invoking command");
WeakReferenceMessenger.Default.Send<BeginInvokeMessage>();
StartInvoke(message, invokable, host);
@@ -370,7 +369,7 @@ public partial class ShellViewModel : ObservableObject,
}
var kind = result.Kind;
Log_HandlingCommandResult(kind.ToString());
CoreLogger.LogDebug($"handling {kind.ToString()}");
WeakReferenceMessenger.Default.Send<CmdPalInvokeResultMessage>(new(kind));
switch (kind)
@@ -461,19 +460,4 @@ public partial class ShellViewModel : ObservableObject,
{
_navigationCts?.Cancel();
}
[LoggerMessage(Level = LogLevel.Error)]
partial void Log_Exception(Exception exception);
[LoggerMessage(Level = LogLevel.Debug, Message = "Navigating to page")]
partial void Log_NavigateToPage();
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to create ViewModel for page {PageTypeName}")]
partial void Log_FailedToCreateViewModel(string pageTypeName);
[LoggerMessage(Level = LogLevel.Debug, Message = "Invoking command")]
partial void Log_InvokingCommand();
[LoggerMessage(Level = LogLevel.Debug, Message = "Handling {CommandResultKind}")]
partial void Log_HandlingCommandResult(string commandResultKind);
}

View File

@@ -4,7 +4,6 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -20,8 +19,8 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
public bool HasProgress => Progress is not null;
public StatusMessageViewModel(IStatusMessage message, WeakReference<IPageContext> context, ILogger logger)
: base(context, logger)
public StatusMessageViewModel(IStatusMessage message, WeakReference<IPageContext> context)
: base(context)
{
Model = new(message);
}
@@ -39,7 +38,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
var modelProgress = model.Progress;
if (modelProgress is not null)
{
Progress = new(modelProgress, this.PageContext, Logger);
Progress = new(modelProgress, this.PageContext);
Progress.InitializeProperties();
UpdateProperty(nameof(HasProgress));
}
@@ -79,7 +78,7 @@ public partial class StatusMessageViewModel : ExtensionObjectViewModel
var modelProgress = model.Progress;
if (modelProgress is not null)
{
Progress = new(modelProgress, this.PageContext, Logger);
Progress = new(modelProgress, this.PageContext);
Progress.InitializeProperties();
}
else

View File

@@ -4,11 +4,10 @@
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class TagViewModel(ITag _tag, WeakReference<IPageContext> context, ILogger logger) : ExtensionObjectViewModel(context, logger)
public partial class TagViewModel(ITag _tag, WeakReference<IPageContext> context) : ExtensionObjectViewModel(context)
{
private readonly ExtensionObject<ITag> _tagModel = new(_tag);

View File

@@ -4,20 +4,23 @@
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandPaletteContentPageViewModel(IContentPage model, AppExtensionHost host, ILogger logger)
: ContentPageViewModel(model, host, logger)
public partial class CommandPaletteContentPageViewModel : ContentPageViewModel
{
public CommandPaletteContentPageViewModel(IContentPage model, TaskScheduler scheduler, AppExtensionHost host)
: base(model, scheduler, host)
{
}
public override ContentViewModel? ViewModelFromContent(IContent content, WeakReference<IPageContext> context)
{
ContentViewModel? viewModel = content switch
{
IFormContent form => new ContentFormViewModel(form, context, Logger),
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context, Logger),
ITreeContent tree => new ContentTreeViewModel(tree, context, Logger),
IFormContent form => new ContentFormViewModel(form, context),
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context),
ITreeContent tree => new ContentTreeViewModel(tree, context),
_ => null,
};
return viewModel;

View File

@@ -5,29 +5,29 @@
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public sealed partial class CommandPaletteHost : AppExtensionHost, IExtensionHost
{
// Static singleton, so that we can access this from anywhere
// Post MVVM - this should probably be like, a dependency injection thing.
public static CommandPaletteHost Instance { get; } = new();
public IExtensionWrapper? Extension { get; }
private readonly ICommandProvider? _builtInProvider;
public CommandPaletteHost(ILogger logger)
: base(logger)
private CommandPaletteHost()
{
}
public CommandPaletteHost(IExtensionWrapper source, ILogger logger)
: base(logger)
public CommandPaletteHost(IExtensionWrapper source)
{
Extension = source;
}
public CommandPaletteHost(ICommandProvider builtInProvider, ILogger logger)
: base(logger)
public CommandPaletteHost(ICommandProvider builtInProvider)
{
_builtInProvider = builtInProvider;
}
@@ -36,15 +36,4 @@ public sealed partial class CommandPaletteHost : AppExtensionHost, IExtensionHos
{
return Extension?.ExtensionDisplayName;
}
public override AppExtensionHost GetHostForCommand(object? context, AppExtensionHost? currentHost)
{
AppExtensionHost? topLevelHost = null;
if (context is TopLevelViewModel topLevelViewModel)
{
topLevelHost = topLevelViewModel.ExtensionHost;
}
return topLevelHost ?? currentHost ?? this;
}
}

View File

@@ -4,27 +4,25 @@
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public class CommandPalettePageViewModelFactory
: IPageViewModelFactoryService
{
private readonly TaskScheduler _scheduler = TaskScheduler.FromCurrentSynchronizationContext();
private readonly ILogger _logger;
private readonly TaskScheduler _scheduler;
public CommandPalettePageViewModelFactory(ILogger logger)
public CommandPalettePageViewModelFactory(TaskScheduler scheduler)
{
_logger = logger;
_scheduler = scheduler;
}
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host, _logger) { IsNested = nested },
IContentPage contentPage => new CommandPaletteContentPageViewModel(contentPage, host, _logger),
IListPage listPage => new ListViewModel(listPage, _scheduler, host) { IsNested = nested },
IContentPage contentPage => new CommandPaletteContentPageViewModel(contentPage, _scheduler, host),
_ => null,
};
}

View File

@@ -2,23 +2,22 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
public sealed partial class CommandProviderWrapper
public sealed class CommandProviderWrapper
{
public bool IsExtension => Extension is not null;
private readonly bool isValid;
private readonly ILogger _logger;
private readonly AliasManager _aliasManager;
private readonly HotkeyManager _hotKeyManager;
private readonly ExtensionObject<ICommandProvider> _commandProvider;
@@ -52,18 +51,15 @@ public sealed partial class CommandProviderWrapper
}
}
public CommandProviderWrapper(ICommandProvider provider, TaskScheduler mainThread, AliasManager aliasManager, HotkeyManager hotkeyManager, ILogger logger)
public CommandProviderWrapper(ICommandProvider provider, TaskScheduler mainThread)
{
// This ctor is only used for in-proc builtin commands. So the Unsafe!
// calls are pretty dang safe actually.
_commandProvider = new(provider);
_taskScheduler = mainThread;
_logger = logger;
_aliasManager = aliasManager;
_hotKeyManager = hotkeyManager;
// Hook the extension back into us
ExtensionHost = new CommandPaletteHost(provider, logger);
ExtensionHost = new CommandPaletteHost(provider);
_commandProvider.Unsafe!.InitializeWithHost(ExtensionHost);
_commandProvider.Unsafe!.ItemsChanged += CommandProvider_ItemsChanged;
@@ -76,20 +72,16 @@ public sealed partial class CommandProviderWrapper
// Note: explicitly not InitializeProperties()ing the settings here. If
// we do that, then we'd regress GH #38321
Settings = new(provider.Settings, this, _taskScheduler, _logger);
Settings = new(provider.Settings, this, _taskScheduler);
Log_CommandProviderInitialized(ProviderId);
Logger.LogDebug($"Initialized command provider {ProviderId}");
}
public CommandProviderWrapper(IExtensionWrapper extension, TaskScheduler mainThread, AliasManager aliasManager, HotkeyManager hotkeyManager, ILogger logger)
public CommandProviderWrapper(IExtensionWrapper extension, TaskScheduler mainThread)
{
_taskScheduler = mainThread;
_logger = logger;
_aliasManager = aliasManager;
_hotKeyManager = hotkeyManager;
Extension = extension;
ExtensionHost = new CommandPaletteHost(extension, logger);
ExtensionHost = new CommandPaletteHost(extension);
if (!Extension.IsRunning())
{
throw new ArgumentException("You forgot to start the extension. This is a CmdPal error - we need to make sure to call StartExtensionAsync");
@@ -114,11 +106,13 @@ public sealed partial class CommandProviderWrapper
isValid = true;
Log_ExtensionInitialized(Extension.PackageFamilyName, Extension.ExtensionUniqueId);
Logger.LogDebug($"Initialized extension command provider {Extension.PackageFamilyName}:{Extension.ExtensionUniqueId}");
}
catch (Exception e)
{
Log_FailedToInitializeCommandProviderForExtension(Extension!.PackageFamilyName, e);
Logger.LogError("Failed to initialize CommandProvider for extension.");
Logger.LogError($"Extension was {Extension!.PackageFamilyName}");
Logger.LogError(e.ToString());
}
isValid = true;
@@ -129,7 +123,7 @@ public sealed partial class CommandProviderWrapper
return settings.GetProviderSettings(this);
}
public async Task LoadTopLevelCommands(SettingsModel settingsModel, WeakReference<IPageContext> pageContext)
public async Task LoadTopLevelCommands(IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
{
if (!isValid)
{
@@ -137,7 +131,9 @@ public sealed partial class CommandProviderWrapper
return;
}
IsActive = GetProviderSettings(settingsModel).IsEnabled;
var settings = serviceProvider.GetService<SettingsModel>()!;
IsActive = GetProviderSettings(settings).IsEnabled;
if (!IsActive)
{
return;
@@ -169,27 +165,30 @@ public sealed partial class CommandProviderWrapper
// Note: explicitly not InitializeProperties()ing the settings here. If
// we do that, then we'd regress GH #38321
Settings = new(model.Settings, this, _taskScheduler, _logger);
Settings = new(model.Settings, this, _taskScheduler);
// We do need to explicitly initialize commands though
InitializeCommands(commands, fallbacks, settingsModel, pageContext);
InitializeCommands(commands, fallbacks, serviceProvider, pageContext);
Log_LoadedCommandsFromExtension(DisplayName, ProviderId);
Logger.LogDebug($"Loaded commands from {DisplayName} ({ProviderId})");
}
catch (Exception e)
{
Log_FailedToLoadCommandsFromProvider(Extension!.PackageFamilyName, e);
Logger.LogError("Failed to load commands from extension");
Logger.LogError($"Extension was {Extension!.PackageFamilyName}");
Logger.LogError(e.ToString());
}
}
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, SettingsModel settingsModel, WeakReference<IPageContext> pageContext)
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
{
var providerSettings = GetProviderSettings(settingsModel);
var settings = serviceProvider.GetService<SettingsModel>()!;
var providerSettings = GetProviderSettings(settings);
Func<ICommandItem?, bool, TopLevelViewModel> makeAndAdd = (ICommandItem? i, bool fallback) =>
{
CommandItemViewModel commandItemViewModel = new(new(i), pageContext, _logger);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settingsModel, providerSettings, _aliasManager, _hotKeyManager);
CommandItemViewModel commandItemViewModel = new(new(i), pageContext);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, providerSettings, serviceProvider);
topLevelViewModel.InitializeProperties();
return topLevelViewModel;
@@ -212,13 +211,12 @@ public sealed partial class CommandProviderWrapper
private void UnsafePreCacheApiAdditions(ICommandProvider2 provider)
{
var apiExtensions = provider.GetApiExtensionStubs();
Log_ProviderCount(apiExtensions.Length);
Logger.LogDebug($"Provider supports {apiExtensions.Length} extensions");
foreach (var a in apiExtensions)
{
if (a is IExtendedAttributesProvider command2)
{
Log_IExtendedAttributesProviderFound(ProviderId);
Logger.LogDebug($"{ProviderId}: Found an IExtendedAttributesProvider");
}
}
}
@@ -236,25 +234,4 @@ public sealed partial class CommandProviderWrapper
// In handling this, a call will be made to `LoadTopLevelCommands` to
// retrieve the new items.
this.CommandsChanged?.Invoke(this, args);
[LoggerMessage(Level = LogLevel.Debug, Message = "Initialized CommandProvider '{ProviderId}'")]
partial void Log_CommandProviderInitialized(string providerId);
[LoggerMessage(Level = LogLevel.Debug, Message = "{ProviderId}: Found an IExtendedAttributesProvider")]
partial void Log_IExtendedAttributesProviderFound(string providerId);
[LoggerMessage(Level = LogLevel.Debug, Message = "Provider exposed {Count} API extensions")]
partial void Log_ProviderCount(int count);
[LoggerMessage(Level = LogLevel.Debug, Message = "Initialized CommandProvider from extension '{PackageFamilyName}' ({ExtensionId})")]
partial void Log_ExtensionInitialized(string packageFamilyName, string extensionId);
[LoggerMessage(Level = LogLevel.Debug, Message = "Loaded commands from {DisplayName} ({ProviderId})")]
partial void Log_LoadedCommandsFromExtension(string displayName, string providerId);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to load commands from extension '{PackageFamilyName}'")]
partial void Log_FailedToLoadCommandsFromProvider(string packageFamilyName, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to initialize CommandProvider for extension '{PackageFamilyName}'")]
partial void Log_FailedToInitializeCommandProviderForExtension(string packageFamilyName, Exception exception);
}

View File

@@ -2,14 +2,14 @@
// 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.Core.Common;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread, ILogger logger)
public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread)
{
private readonly ExtensionObject<ICommandSettings> _model = new(_unsafeSettings);
@@ -31,7 +31,7 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
if (model.SettingsPage is not null)
{
SettingsPage = new CommandPaletteContentPageViewModel(model.SettingsPage, provider.ExtensionHost, logger);
SettingsPage = new CommandPaletteContentPageViewModel(model.SettingsPage, mainThread, provider.ExtensionHost);
SettingsPage.InitializeProperties();
}
}
@@ -44,7 +44,7 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
}
catch (Exception ex)
{
Log_FailedToLoadSettingsPage(ex);
CoreLogger.LogError($"Failed to load settings page", ex: ex);
}
Initialized = true;
@@ -58,7 +58,4 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
TaskCreationOptions.None,
mainThread);
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to load settings page")]
partial void Log_FailedToLoadSettingsPage(Exception ex);
}

View File

@@ -38,6 +38,7 @@ public partial class MainListPage : DynamicListPage,
"com.microsoft.cmdpal.builtin.datetime",
];
private readonly IServiceProvider _serviceProvider;
private readonly TopLevelCommandManager _tlcManager;
private List<Scored<IListItem>>? _filteredItems;
private List<Scored<IListItem>>? _filteredApps;
@@ -52,13 +53,14 @@ public partial class MainListPage : DynamicListPage,
private CancellationTokenSource? _cancellationTokenSource;
public MainListPage(TopLevelCommandManager topLevelCommandManager, SettingsModel settingsModel)
public MainListPage(IServiceProvider serviceProvider)
{
Title = Resources.builtin_home_name;
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder;
_serviceProvider = serviceProvider;
_tlcManager = topLevelCommandManager;
_tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
_tlcManager.PropertyChanged += TlcManager_PropertyChanged;
_tlcManager.TopLevelCommands.CollectionChanged += Commands_CollectionChanged;
@@ -76,7 +78,7 @@ public partial class MainListPage : DynamicListPage,
WeakReferenceMessenger.Default.Register<ClearSearchMessage>(this);
WeakReferenceMessenger.Default.Register<UpdateFallbackItemsMessage>(this);
var settings = settingsModel;
var settings = _serviceProvider.GetService<SettingsModel>()!;
settings.SettingsChanged += SettingsChangedHandler;
HotReloadSettings(settings);
_includeApps = _tlcManager.IsProviderActive(AllAppsCommandProvider.WellKnownId);

View File

@@ -7,26 +7,19 @@ using System.Text.Json;
using AdaptiveCards.ObjectModel.WinUI3;
using AdaptiveCards.Templating;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
using Windows.Data.Json;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ContentFormViewModel : ContentViewModel
public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPageContext> context) :
ContentViewModel(context)
{
private readonly ExtensionObject<IFormContent> _formModel;
private readonly ILogger _logger;
public ContentFormViewModel(IFormContent _form, WeakReference<IPageContext> context, ILogger logger)
: base(context, logger)
{
_formModel = new(_form);
_logger = logger;
}
private readonly ExtensionObject<IFormContent> _formModel = new(_form);
// Remember - "observable" properties from the model (via PropChanged)
// cannot be marked [ObservableProperty]
@@ -45,8 +38,7 @@ public partial class ContentFormViewModel : ContentViewModel
string templateJson,
string dataJson,
out AdaptiveCardParseResult? card,
out Exception? error,
ILogger logger)
out Exception? error)
{
card = null;
error = null;
@@ -60,7 +52,7 @@ public partial class ContentFormViewModel : ContentViewModel
}
catch (Exception ex)
{
Log_ErrorBuildindCard(logger, ex);
Logger.LogError("Error building card from template", ex);
error = ex;
return false;
}
@@ -78,7 +70,7 @@ public partial class ContentFormViewModel : ContentViewModel
StateJson = model.StateJson;
DataJson = model.DataJson;
if (TryBuildCard(TemplateJson, DataJson, out var builtCard, out var renderingError, _logger))
if (TryBuildCard(TemplateJson, DataJson, out var builtCard, out var renderingError))
{
Card = builtCard;
UpdateProperty(nameof(Card));
@@ -95,7 +87,7 @@ public partial class ContentFormViewModel : ContentViewModel
}
""";
if (TryBuildCard(ErrorCardJson, errorPayload, out var errorCard, out var _, _logger))
if (TryBuildCard(ErrorCardJson, errorPayload, out var errorCard, out var _))
{
Card = errorCard;
UpdateProperty(nameof(Card));
@@ -181,7 +173,4 @@ public partial class ContentFormViewModel : ContentViewModel
]
}
""";
[LoggerMessage(Level = LogLevel.Error, Message = "Error building adaptive card for form.")]
static partial void Log_ErrorBuildindCard(ILogger logger, Exception ex);
}

View File

@@ -5,12 +5,11 @@
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakReference<IPageContext> context, ILogger logger) :
ContentViewModel(context, logger)
public partial class ContentMarkdownViewModel(IMarkdownContent _markdown, WeakReference<IPageContext> context) :
ContentViewModel(context)
{
public ExtensionObject<IMarkdownContent> Model { get; } = new(_markdown);

View File

@@ -7,12 +7,11 @@ using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPageContext> context, ILogger logger) :
ContentViewModel(context, logger)
public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPageContext> context) :
ContentViewModel(context)
{
public ExtensionObject<ITreeContent> Model { get; } = new(_tree);
@@ -56,9 +55,9 @@ public partial class ContentTreeViewModel(ITreeContent _tree, WeakReference<IPag
{
ContentViewModel? viewModel = content switch
{
IFormContent form => new ContentFormViewModel(form, context, Logger),
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context, Logger),
ITreeContent tree => new ContentTreeViewModel(tree, context, Logger),
IFormContent form => new ContentFormViewModel(form, context),
IMarkdownContent markdown => new ContentMarkdownViewModel(markdown, context),
ITreeContent tree => new ContentTreeViewModel(tree, context),
_ => null,
};
return viewModel;

View File

@@ -2,9 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.Logging;
using Windows.ApplicationModel;
using Windows.ApplicationModel.AppExtensions;
using Windows.Foundation;
@@ -22,7 +22,6 @@ public partial class ExtensionService : IExtensionService, IDisposable
private static readonly Lock _lock = new();
private readonly SemaphoreSlim _getInstalledExtensionsLock = new(1, 1);
private readonly SemaphoreSlim _getInstalledWidgetsLock = new(1, 1);
private readonly ILogger _logger;
// private readonly ILocalSettingsService _localSettingsService;
private bool _disposedValue;
@@ -33,9 +32,8 @@ public partial class ExtensionService : IExtensionService, IDisposable
private static readonly List<IExtensionWrapper> _installedExtensions = [];
private static readonly List<IExtensionWrapper> _enabledExtensions = [];
public ExtensionService(ILogger logger)
public ExtensionService()
{
_logger = logger;
_catalog.PackageInstalling += Catalog_PackageInstalling;
_catalog.PackageUninstalling += Catalog_PackageUninstalling;
_catalog.PackageUpdating += Catalog_PackageUpdating;
@@ -94,14 +92,14 @@ public partial class ExtensionService : IExtensionService, IDisposable
var extension = isCmdPalExtensionResult.Extension;
if (isExtension && extension is not null)
{
Log_ExtensionInstalled(extension.DisplayName);
CommandPaletteHost.Instance.DebugLog($"Installed new extension app {extension.DisplayName}");
Task.Run(async () =>
{
await _getInstalledExtensionsLock.WaitAsync();
try
{
var wrappers = await CreateWrappersForExtension(extension, _logger);
var wrappers = await CreateWrappersForExtension(extension);
UpdateExtensionsListsFromWrappers(wrappers);
@@ -122,7 +120,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
{
if (extension.PackageFullName == package.Id.FullName)
{
Log_ExtensionUninstalled(extension.PackageDisplayName);
CommandPaletteHost.Instance.DebugLog($"Uninstalled extension app {extension.PackageDisplayName}");
removedExtensions.Add(extension);
}
@@ -201,7 +199,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
var extensions = await GetInstalledAppExtensionsAsync();
foreach (var extension in extensions)
{
var wrappers = await CreateWrappersForExtension(extension, _logger);
var wrappers = await CreateWrappersForExtension(extension);
UpdateExtensionsListsFromWrappers(wrappers);
}
}
@@ -235,7 +233,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
}
}
private static async Task<List<ExtensionWrapper>> CreateWrappersForExtension(AppExtension extension, ILogger logger)
private static async Task<List<ExtensionWrapper>> CreateWrappersForExtension(AppExtension extension)
{
var (cmdPalProvider, classIds) = await GetCmdPalExtensionPropertiesAsync(extension);
@@ -247,14 +245,14 @@ public partial class ExtensionService : IExtensionService, IDisposable
List<ExtensionWrapper> wrappers = [];
foreach (var classId in classIds)
{
var extensionWrapper = CreateExtensionWrapper(extension, cmdPalProvider, classId, logger);
var extensionWrapper = CreateExtensionWrapper(extension, cmdPalProvider, classId);
wrappers.Add(extensionWrapper);
}
return wrappers;
}
private static ExtensionWrapper CreateExtensionWrapper(AppExtension extension, IPropertySet cmdPalProvider, string classId, ILogger logger)
private static ExtensionWrapper CreateExtensionWrapper(AppExtension extension, IPropertySet cmdPalProvider, string classId)
{
var extensionWrapper = new ExtensionWrapper(extension, classId);
@@ -271,7 +269,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
else
{
// log warning that extension declared unsupported extension interface
Log_InvalidExtensionInterface(logger, extension.DisplayName, supportedInterface.Key);
CommandPaletteHost.Instance.DebugLog($"Extension {extension.DisplayName} declared an unsupported interface: {supportedInterface.Key}");
}
}
}
@@ -290,8 +288,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
var installedExtensions = await GetInstalledExtensionsAsync();
foreach (var installedExtension in installedExtensions)
{
Log_SignalingDispose(installedExtension.ExtensionUniqueId);
Logger.LogDebug($"Signaling dispose to {installedExtension.ExtensionUniqueId}");
try
{
if (installedExtension.IsRunning())
@@ -301,7 +298,7 @@ public partial class ExtensionService : IExtensionService, IDisposable
}
catch (Exception ex)
{
Log_ErrorSignalingDispose(installedExtension.ExtensionUniqueId, ex);
Logger.LogError($"Failed to send dispose signal to extension {installedExtension.ExtensionUniqueId}", ex);
}
}
}
@@ -403,21 +400,6 @@ public partial class ExtensionService : IExtensionService, IDisposable
_enabledExtensions.Remove(extension.First());
}
[LoggerMessage(Level = LogLevel.Information, Message = "Installed new extension app {ExtensionName}")]
partial void Log_ExtensionInstalled(string extensionName);
[LoggerMessage(Level = LogLevel.Information, Message = "Uninstalled extension app {ExtensionName}")]
partial void Log_ExtensionUninstalled(string extensionName);
[LoggerMessage(Level = LogLevel.Debug, Message = "Signaling dispose to {ExtensionUniqueId}")]
partial void Log_SignalingDispose(string extensionUniqueId);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to send dispose signal to extension {ExtensionUniqueId}")]
partial void Log_ErrorSignalingDispose(string extensionUniqueId, Exception exception);
[LoggerMessage(Level = LogLevel.Warning, Message = "Extension {ExtensionName} declared unsupported extension interface: {InterfaceName}")]
static partial void Log_InvalidExtensionInterface(ILogger logger, string extensionName, string interfaceName);
/*
///// <inheritdoc cref="IExtensionService.DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper)"/>
//public async Task<bool> DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension)

View File

@@ -8,13 +8,14 @@ using System.Diagnostics;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -23,14 +24,8 @@ public partial class TopLevelCommandManager : ObservableObject,
IPageContext,
IDisposable
{
private readonly TaskScheduler _taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
private readonly ILogger _logger;
private readonly AppExtensionHost _commandPaletteHost;
private readonly IExtensionService _extensionService;
private readonly IEnumerable<ICommandProvider> _builtInProviders;
private readonly SettingsModel _settingsModel;
private readonly AliasManager _aliasManager;
private readonly HotkeyManager _hotkeyManager;
private readonly IServiceProvider _serviceProvider;
private readonly TaskScheduler _taskScheduler;
private readonly List<CommandProviderWrapper> _builtInCommands = [];
private readonly List<CommandProviderWrapper> _extensionCommandProviders = [];
@@ -39,22 +34,10 @@ public partial class TopLevelCommandManager : ObservableObject,
TaskScheduler IPageContext.Scheduler => _taskScheduler;
public TopLevelCommandManager(
AppExtensionHost commandPaletteHost,
IExtensionService extensionService,
IEnumerable<ICommandProvider> builtInProviders,
SettingsModel settingsModel,
AliasManager aliasManager,
HotkeyManager hotkeyManager,
ILogger logger)
public TopLevelCommandManager(IServiceProvider serviceProvider)
{
_logger = logger;
_commandPaletteHost = commandPaletteHost;
_extensionService = extensionService;
_builtInProviders = builtInProviders;
_settingsModel = settingsModel;
_aliasManager = aliasManager;
_hotkeyManager = hotkeyManager;
_serviceProvider = serviceProvider;
_taskScheduler = _serviceProvider.GetService<TaskScheduler>()!;
WeakReferenceMessenger.Default.Register<ReloadCommandsMessage>(this);
_reloadCommandsGate = new(ReloadAllCommandsAsyncCore);
}
@@ -87,9 +70,10 @@ public partial class TopLevelCommandManager : ObservableObject,
// Load built-In commands first. These are all in-proc, and
// owned by our ServiceProvider.
foreach (var provider in _builtInProviders)
var builtInCommands = _serviceProvider.GetServices<ICommandProvider>();
foreach (var provider in builtInCommands)
{
CommandProviderWrapper wrapper = new(provider, _taskScheduler, _aliasManager, _hotkeyManager, _logger);
CommandProviderWrapper wrapper = new(provider, _taskScheduler);
lock (_commandProvidersLock)
{
_builtInCommands.Add(wrapper);
@@ -107,7 +91,7 @@ public partial class TopLevelCommandManager : ObservableObject,
s.Stop();
Log_BuiltInsLoaded(s.ElapsedMilliseconds);
Logger.LogDebug($"Loading built-ins took {s.ElapsedMilliseconds}ms");
return true;
}
@@ -117,7 +101,7 @@ public partial class TopLevelCommandManager : ObservableObject,
{
WeakReference<IPageContext> weakSelf = new(this);
await commandProvider.LoadTopLevelCommands(_settingsModel, weakSelf);
await commandProvider.LoadTopLevelCommands(_serviceProvider, weakSelf);
var commands = await Task.Factory.StartNew(
() =>
@@ -165,7 +149,7 @@ public partial class TopLevelCommandManager : ObservableObject,
private async Task UpdateCommandsForProvider(CommandProviderWrapper sender, IItemsChangedEventArgs args)
{
WeakReference<IPageContext> weakSelf = new(this);
await sender.LoadTopLevelCommands(_settingsModel, weakSelf);
await sender.LoadTopLevelCommands(_serviceProvider, weakSelf);
List<TopLevelViewModel> newItems = [.. sender.TopLevelItems];
foreach (var i in sender.FallbackItems)
@@ -232,7 +216,8 @@ public partial class TopLevelCommandManager : ObservableObject,
private async Task ReloadAllCommandsAsyncCore(CancellationToken cancellationToken)
{
IsLoading = true;
await _extensionService.SignalStopExtensionsAsync();
var extensionService = _serviceProvider.GetService<IExtensionService>()!;
await extensionService.SignalStopExtensionsAsync();
lock (TopLevelCommands)
{
@@ -253,10 +238,12 @@ public partial class TopLevelCommandManager : ObservableObject,
[RelayCommand]
public async Task<bool> LoadExtensionsAsync()
{
_extensionService.OnExtensionAdded -= ExtensionService_OnExtensionAdded;
_extensionService.OnExtensionRemoved -= ExtensionService_OnExtensionRemoved;
var extensionService = _serviceProvider.GetService<IExtensionService>()!;
var extensions = (await _extensionService.GetInstalledExtensionsAsync()).ToImmutableList();
extensionService.OnExtensionAdded -= ExtensionService_OnExtensionAdded;
extensionService.OnExtensionRemoved -= ExtensionService_OnExtensionRemoved;
var extensions = (await extensionService.GetInstalledExtensionsAsync()).ToImmutableList();
lock (_commandProvidersLock)
{
_extensionCommandProviders.Clear();
@@ -267,8 +254,8 @@ public partial class TopLevelCommandManager : ObservableObject,
await StartExtensionsAndGetCommands(extensions);
}
_extensionService.OnExtensionAdded += ExtensionService_OnExtensionAdded;
_extensionService.OnExtensionRemoved += ExtensionService_OnExtensionRemoved;
extensionService.OnExtensionAdded += ExtensionService_OnExtensionAdded;
extensionService.OnExtensionRemoved += ExtensionService_OnExtensionRemoved;
IsLoading = false;
@@ -323,20 +310,20 @@ public partial class TopLevelCommandManager : ObservableObject,
}
timer.Stop();
Log_ExtensionsLoaded(timer.ElapsedMilliseconds);
Logger.LogDebug($"Loading extensions took {timer.ElapsedMilliseconds} ms");
}
private async Task<CommandProviderWrapper?> StartExtensionWithTimeoutAsync(IExtensionWrapper extension)
{
Log_StartingExtension(extension.PackageFullName);
Logger.LogDebug($"Starting {extension.PackageFullName}");
try
{
await extension.StartExtensionAsync().WaitAsync(TimeSpan.FromSeconds(10));
return new CommandProviderWrapper(extension, _taskScheduler, _aliasManager, _hotkeyManager, _logger);
return new CommandProviderWrapper(extension, _taskScheduler);
}
catch (Exception ex)
{
Log_FailedToStartExtension(extension.PackageFullName, ex);
Logger.LogError($"Failed to start extension {extension.PackageFullName}: {ex}");
return null; // Return null for failed extensions
}
}
@@ -349,11 +336,11 @@ public partial class TopLevelCommandManager : ObservableObject,
}
catch (TimeoutException)
{
Log_LoadingCommandsTimedOut(wrapper!.ExtensionHost?.Extension?.PackageFullName);
Logger.LogError($"Loading commands from {wrapper!.ExtensionHost?.Extension?.PackageFullName} timed out");
}
catch (Exception ex)
{
Log_FailedToLoadCommandsForExtension(wrapper!.ExtensionHost?.Extension?.PackageFullName, ex);
Logger.LogError($"Failed to load commands for extension {wrapper!.ExtensionHost?.Extension?.PackageFullName}: {ex}");
}
return null;
@@ -427,7 +414,7 @@ public partial class TopLevelCommandManager : ObservableObject,
void IPageContext.ShowException(Exception ex, string? extensionHint)
{
var message = DiagnosticsHelper.BuildExceptionMessage(ex, extensionHint ?? "TopLevelCommandManager");
_commandPaletteHost.Log(message);
CommandPaletteHost.Instance.Log(message);
}
internal bool IsProviderActive(string id)
@@ -444,22 +431,4 @@ public partial class TopLevelCommandManager : ObservableObject,
_reloadCommandsGate.Dispose();
GC.SuppressFinalize(this);
}
[LoggerMessage(Level = LogLevel.Debug, Message = "Loading built-ins took {ElapsedMilliseconds}ms")]
partial void Log_BuiltInsLoaded(long elapsedMilliseconds);
[LoggerMessage(Level = LogLevel.Debug, Message = "Loading extensions took {ElapsedMilliseconds}ms")]
partial void Log_ExtensionsLoaded(long elapsedMilliseconds);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to load commands for extension {ExtensionName}")]
partial void Log_FailedToLoadCommandsForExtension(string? extensionName, Exception exception);
[LoggerMessage(Level = LogLevel.Warning, Message = "Loading commands for extension {ExtensionName} timed out")]
partial void Log_LoadingCommandsTimedOut(string? extensionName);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to start extension {ExtensionName}")]
partial void Log_FailedToStartExtension(string extensionName, Exception exception);
[LoggerMessage(Level = LogLevel.Debug, Message = "Starting extension {ExtensionName}")]
partial void Log_StartingExtension(string extensionName);
}

View File

@@ -12,6 +12,7 @@ using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.DependencyInjection;
using Windows.Foundation;
using WyHash;
@@ -21,8 +22,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
private readonly SettingsModel _settings;
private readonly ProviderSettings _providerSettings;
private readonly HotkeyManager _hotKeyManager;
private readonly AliasManager _aliasManager;
private readonly IServiceProvider _serviceProvider;
private readonly CommandItemViewModel _commandItemViewModel;
private readonly string _commandProviderId;
@@ -99,7 +99,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
get => _hotkey;
set
{
_hotKeyManager.UpdateHotkey(Id, value);
_serviceProvider.GetService<HotkeyManager>()!.UpdateHotkey(Id, value);
UpdateHotkey();
UpdateTags();
Save();
@@ -177,11 +177,9 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
string commandProviderId,
SettingsModel settings,
ProviderSettings providerSettings,
AliasManager aliasManager,
HotkeyManager hotkeyManager)
IServiceProvider serviceProvider)
{
_hotKeyManager = hotkeyManager;
_aliasManager = aliasManager;
_serviceProvider = serviceProvider;
_settings = settings;
_providerSettings = providerSettings;
_commandProviderId = commandProviderId;
@@ -270,15 +268,16 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
? null
: new CommandAlias(Alias.Alias, Alias.CommandId, Alias.IsDirect);
_aliasManager.UpdateAlias(Id, commandAlias);
_serviceProvider.GetService<AliasManager>()!.UpdateAlias(Id, commandAlias);
UpdateTags();
}
private void FetchAliasFromAliasManager()
{
if (_aliasManager is not null)
var am = _serviceProvider.GetService<AliasManager>();
if (am is not null)
{
var commandAlias = _aliasManager.AliasFromId(Id);
var commandAlias = am.AliasFromId(Id);
if (commandAlias is not null)
{
// Decouple from the alias manager alias object

View File

@@ -2,6 +2,33 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Ext.Apps;
using Microsoft.CmdPal.Ext.Bookmarks;
using Microsoft.CmdPal.Ext.Calc;
using Microsoft.CmdPal.Ext.ClipboardHistory;
using Microsoft.CmdPal.Ext.Indexer;
using Microsoft.CmdPal.Ext.Registry;
using Microsoft.CmdPal.Ext.Shell;
using Microsoft.CmdPal.Ext.System;
using Microsoft.CmdPal.Ext.TimeDate;
using Microsoft.CmdPal.Ext.WebSearch;
using Microsoft.CmdPal.Ext.WindowsServices;
using Microsoft.CmdPal.Ext.WindowsSettings;
using Microsoft.CmdPal.Ext.WindowsTerminal;
using Microsoft.CmdPal.Ext.WindowWalker;
using Microsoft.CmdPal.Ext.WinGet;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
@@ -13,6 +40,8 @@ namespace Microsoft.CmdPal.UI;
/// </summary>
public partial class App : Application
{
private readonly GlobalErrorHandler _globalErrorHandler = new();
/// <summary>
/// Gets the current <see cref="App"/> instance in use.
/// </summary>
@@ -20,16 +49,43 @@ public partial class App : Application
public Window? AppWindow { get; private set; }
public ETWTrace EtwTrace { get; private set; } = new ETWTrace();
/// <summary>
/// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
/// </summary>
public IServiceProvider Services { get; }
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App(MainWindow mainWindow)
public App()
{
AppWindow = mainWindow;
#if !CMDPAL_DISABLE_GLOBAL_ERROR_HANDLER
_globalErrorHandler.Register(this);
#endif
Services = ConfigureServices();
this.InitializeComponent();
// Ensure types used in XAML are preserved for AOT compilation
TypePreservation.PreserveTypes();
NativeEventWaiter.WaitForEventLoop(
"Local\\PowerToysCmdPal-ExitEvent-eb73f6be-3f22-4b36-aee3-62924ba40bfd", () =>
{
EtwTrace?.Dispose();
AppWindow?.Close();
Environment.Exit(0);
});
// Connect the PT logging to the core project's logging.
// This way, log statements from the core project will be captured by the PT logs
var logWrapper = new LogWrapper();
CoreLogger.InitializeLogger(logWrapper);
}
/// <summary>
@@ -38,7 +94,84 @@ public partial class App : Application
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
AppWindow = new MainWindow();
var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
((MainWindow)AppWindow!).HandleLaunchNonUI(activatedEventArgs);
((MainWindow)AppWindow).HandleLaunchNonUI(activatedEventArgs);
}
/// <summary>
/// Configures the services for the application
/// </summary>
private static ServiceProvider ConfigureServices()
{
// TODO: It's in the Labs feed, but we can use Sergio's AOT-friendly source generator for this: https://github.com/CommunityToolkit/Labs-Windows/discussions/463
ServiceCollection services = new();
// Root services
services.AddSingleton(TaskScheduler.FromCurrentSynchronizationContext());
// Built-in Commands. Order matters - this is the order they'll be presented by default.
var allApps = new AllAppsCommandProvider();
var files = new IndexerCommandsProvider();
files.SuppressFallbackWhen(ShellCommandsProvider.SuppressFileFallbackIf);
services.AddSingleton<ICommandProvider>(allApps);
services.AddSingleton<ICommandProvider, ShellCommandsProvider>();
services.AddSingleton<ICommandProvider, CalculatorCommandProvider>();
services.AddSingleton<ICommandProvider>(files);
services.AddSingleton<ICommandProvider, BookmarksCommandProvider>(_ => BookmarksCommandProvider.CreateWithDefaultStore());
services.AddSingleton<ICommandProvider, WindowWalkerCommandsProvider>();
services.AddSingleton<ICommandProvider, WebSearchCommandsProvider>();
services.AddSingleton<ICommandProvider, ClipboardHistoryCommandsProvider>();
// GH #38440: Users might not have WinGet installed! Or they might have
// a ridiculously old version. Or might be running as admin.
// We shouldn't explode in the App ctor if we fail to instantiate an
// instance of PackageManager, which will happen in the static ctor
// for WinGetStatics
try
{
var winget = new WinGetExtensionCommandsProvider();
var callback = allApps.LookupApp;
winget.SetAllLookup(callback);
services.AddSingleton<ICommandProvider>(winget);
}
catch (Exception ex)
{
Logger.LogError("Couldn't load winget");
Logger.LogError(ex.ToString());
}
services.AddSingleton<ICommandProvider, WindowsTerminalCommandsProvider>();
services.AddSingleton<ICommandProvider, WindowsSettingsCommandsProvider>();
services.AddSingleton<ICommandProvider, RegistryCommandsProvider>();
services.AddSingleton<ICommandProvider, WindowsServicesCommandsProvider>();
services.AddSingleton<ICommandProvider, BuiltInsCommandProvider>();
services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>();
services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>();
// Models
services.AddSingleton<TopLevelCommandManager>();
services.AddSingleton<AliasManager>();
services.AddSingleton<HotkeyManager>();
var sm = SettingsModel.LoadSettings();
services.AddSingleton(sm);
var state = AppStateModel.LoadState();
services.AddSingleton(state);
services.AddSingleton<IExtensionService, ExtensionService>();
services.AddSingleton<TrayIconService>();
services.AddSingleton<IRunHistoryService, RunHistoryService>();
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
services.AddSingleton<ITelemetryService, TelemetryForwarder>();
// ViewModels
services.AddSingleton<ShellViewModel>();
services.AddSingleton<IPageViewModelFactoryService, CommandPalettePageViewModelFactory>();
return services.BuildServiceProvider();
}
}

View File

@@ -20,7 +20,7 @@ public sealed partial class CommandBar : UserControl,
IRecipient<TryCommandKeybindingMessage>,
ICurrentPageAware
{
public CommandBarViewModel ViewModel { get; set; }
public CommandBarViewModel ViewModel { get; } = new();
public PageViewModel? CurrentPageViewModel
{
@@ -32,10 +32,9 @@ public sealed partial class CommandBar : UserControl,
public static readonly DependencyProperty CurrentPageViewModelProperty =
DependencyProperty.Register(nameof(CurrentPageViewModel), typeof(PageViewModel), typeof(CommandBar), new PropertyMetadata(null));
public CommandBar(CommandBarViewModel commandBarViewModel)
public CommandBar()
{
this.InitializeComponent();
ViewModel = commandBarViewModel;
// RegisterAll isn't AOT compatible
WeakReferenceMessenger.Default.Register<OpenContextMenuMessage>(this);

View File

@@ -21,7 +21,6 @@ public sealed partial class ContextMenu : UserControl,
IRecipient<UpdateCommandBarMessage>,
IRecipient<TryCommandKeybindingMessage>
{
// Fix for CS8618: Mark ViewModel property as required to ensure it is initialized by the constructor.
public ContextMenuViewModel ViewModel { get; } = new();
public ContextMenu()

View File

@@ -4,18 +4,18 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class BeginInvokeEvent : TelemetryEventBase
public class BeginInvoke : EventBase, IEvent
{
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public BeginInvokeEvent()
public BeginInvoke()
{
EventName = "CmdPal_BeginInvoke";
}

View File

@@ -4,19 +4,15 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class ProcessStartedEvent : TelemetryEventBase
public class CmdPalDismissedOnEsc : EventBase, IEvent
{
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public ProcessStartedEvent()
{
EventName = "CmdPal_ProcessStarted";
}
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}

View File

@@ -4,19 +4,15 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.CommandPalette.Extensions;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class DismissedOnEscEvent : TelemetryEventBase
public class CmdPalDismissedOnLostFocus : EventBase, IEvent
{
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public DismissedOnEscEvent()
{
EventName = "CmdPal_DismissedOnEsc";
}
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}

View File

@@ -4,22 +4,22 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.CommandPalette.Extensions;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class HotkeySummonedEvent : TelemetryEventBase
public class CmdPalHotkeySummoned : EventBase, IEvent
{
public bool Global { get; set; }
public HotkeySummonedEvent(bool global)
public CmdPalHotkeySummoned(bool global)
{
Global = global;
EventName = "CmdPal_HotkeySummoned";
}
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}

View File

@@ -4,23 +4,22 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.CommandPalette.Extensions;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class InvokeResultEvent : TelemetryEventBase
public class CmdPalInvokeResult : EventBase, IEvent
{
public string ResultKind { get; set; }
public InvokeResultEvent(CommandResultKind resultKind)
public CmdPalInvokeResult(CommandResultKind resultKind)
{
EventName = "CmdPal_InvokeResult";
ResultKind = resultKind.ToString();
}
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}

View File

@@ -0,0 +1,18 @@
// 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 System.Diagnostics.Tracing;
using Microsoft.CommandPalette.Extensions;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class CmdPalProcessStarted : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}

View File

@@ -4,18 +4,18 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class ColdLaunchEvent : TelemetryEventBase
public class ColdLaunch : EventBase, IEvent
{
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public ColdLaunchEvent()
public ColdLaunch()
{
EventName = "CmdPal_ColdLaunch";
}

View File

@@ -1,22 +0,0 @@
// 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 System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.PowerToys.Telemetry;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class DismissedOnLostFocusEvent : TelemetryEventBase
{
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public DismissedOnLostFocusEvent()
{
EventName = "CmdPal_DismissedOnLostFocus";
}
}

View File

@@ -4,20 +4,20 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class OpenPageEvent : TelemetryEventBase
public class OpenPage : EventBase, IEvent
{
public int PageDepth { get; set; }
public string Id { get; set; }
public OpenPageEvent(int pageDepth, string id)
public OpenPage(int pageDepth, string id)
{
PageDepth = pageDepth;
Id = id;
@@ -25,5 +25,5 @@ public class OpenPageEvent : TelemetryEventBase
EventName = "CmdPal_OpenPage";
}
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}

View File

@@ -1,31 +0,0 @@
// 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 System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.PowerToys.Telemetry;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class OpenUriEvent : TelemetryEventBase
{
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public string Uri { get; set; }
public bool IsWeb { get; set; }
public bool Success { get; set; }
public OpenUriEvent(string uri, bool isWeb, bool success)
{
EventName = "CmdPal_OpenUri";
Uri = uri;
IsWeb = isWeb;
Success = success;
}
}

View File

@@ -4,18 +4,18 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class ReactivateInstanceEvent : TelemetryEventBase
public class ReactivateInstance : EventBase, IEvent
{
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public ReactivateInstanceEvent()
public ReactivateInstance()
{
EventName = "CmdPal_ReactivateInstance";
}

View File

@@ -1,31 +0,0 @@
// 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 System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.PowerToys.Telemetry;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class RunCommandEvent : TelemetryEventBase
{
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public string Command { get; set; }
public bool AsAdmin { get; set; }
public bool Success { get; set; }
public RunCommandEvent(string command, bool asAdmin, bool success)
{
EventName = "CmdPal_RunCommand";
Command = command;
AsAdmin = asAdmin;
Success = success;
}
}

View File

@@ -0,0 +1,80 @@
// 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 System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.CmdPal.UI.Events;
// Just put all the run events in one file for simplicity.
#pragma warning disable SA1402 // File may only contain a single type
#pragma warning disable SA1649 // File name should match first type name
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class CmdPalRunQuery : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public string Query { get; set; }
public int ResultCount { get; set; }
public ulong DurationMs { get; set; }
public CmdPalRunQuery(string query, int resultCount, ulong durationMs)
{
EventName = "CmdPal_RunQuery";
Query = query;
ResultCount = resultCount;
DurationMs = durationMs;
}
}
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class CmdPalRunCommand : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public string Command { get; set; }
public bool AsAdmin { get; set; }
public bool Success { get; set; }
public CmdPalRunCommand(string command, bool asAdmin, bool success)
{
EventName = "CmdPal_RunCommand";
Command = command;
AsAdmin = asAdmin;
Success = success;
}
}
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class CmdPalOpenUri : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public string Uri { get; set; }
public bool IsWeb { get; set; }
public bool Success { get; set; }
public CmdPalOpenUri(string uri, bool isWeb, bool success)
{
EventName = "CmdPal_OpenUri";
Uri = uri;
IsWeb = isWeb;
Success = success;
}
}
#pragma warning restore SA1649 // File name should match first type name
#pragma warning restore SA1402 // File may only contain a single type

View File

@@ -1,31 +0,0 @@
// 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 System.Diagnostics.Tracing;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.PowerToys.Telemetry;
namespace Microsoft.CmdPal.UI.Events;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class RunQueryEvent : TelemetryEventBase
{
public override PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public string Query { get; set; }
public int ResultCount { get; set; }
public ulong DurationMs { get; set; }
public RunQueryEvent(string query, int resultCount, ulong durationMs)
{
EventName = "CmdPal_RunQuery";
Query = query;
ResultCount = resultCount;
DurationMs = durationMs;
}
}

View File

@@ -12,6 +12,7 @@
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.CmdPal.UI"
xmlns:markdownImageProviders="using:Microsoft.CmdPal.UI.Helpers.MarkdownImageProviders"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
@@ -24,7 +25,7 @@
x:Key="DefaultMarkdownThemeConfig"
H3FontSize="12"
H3FontWeight="Normal" />
<Frame x:Name="ImageProviderFrame"/>
<markdownImageProviders:ImageProvider x:Key="ImageProvider" />
<tkcontrols:MarkdownConfig
x:Key="DefaultMarkdownConfig"
ImageProvider="{StaticResource ImageProvider}"

View File

@@ -5,7 +5,7 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Helpers.MarkdownImageProviders;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
@@ -19,6 +19,8 @@ public sealed partial class ContentPage : Page,
IRecipient<ActivateSelectedListItemMessage>,
IRecipient<ActivateSecondaryCommandMessage>
{
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
public ContentPageViewModel? ViewModel
{
get => (ContentPageViewModel?)GetValue(ViewModelProperty);
@@ -29,15 +31,10 @@ public sealed partial class ContentPage : Page,
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register(nameof(ViewModel), typeof(ContentPageViewModel), typeof(ContentPage), new PropertyMetadata(null));
private readonly ImageProvider _imageProvider;
public ContentPage(ImageProvider imageProvider)
public ContentPage()
{
this.InitializeComponent();
this.Unloaded += OnUnloaded;
_imageProvider = imageProvider;
ImageProviderFrame.Content = _imageProvider;
}
private void OnUnloaded(object sender, RoutedEventArgs e)

View File

@@ -4,13 +4,14 @@
using System.Diagnostics;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Commands;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
@@ -30,11 +31,8 @@ public sealed partial class ListPage : Page,
IRecipient<ActivateSelectedListItemMessage>,
IRecipient<ActivateSecondaryCommandMessage>
{
private readonly SettingsModel settings;
private InputSource _lastInputSource;
public ILogger Logger { get; private set; }
internal ListViewModel? ViewModel
{
get => (ListViewModel?)GetValue(ViewModelProperty);
@@ -53,11 +51,9 @@ public sealed partial class ListPage : Page,
}
}
public ListPage(SettingsModel settingsModel, ILogger logger)
public ListPage()
{
this.InitializeComponent();
settings = settingsModel;
Logger = logger;
this.NavigationCacheMode = NavigationCacheMode.Disabled;
this.ItemView.Loaded += Items_Loaded;
this.ItemView.PreviewKeyDown += Items_PreviewKeyDown;
@@ -137,6 +133,7 @@ public sealed partial class ListPage : Page,
return;
}
var settings = App.Current.Services.GetService<SettingsModel>()!;
if (settings.SingleClickActivates)
{
ViewModel?.InvokeItemCommand.Execute(item);
@@ -153,6 +150,7 @@ public sealed partial class ListPage : Page,
{
if (ItemView.SelectedItem is ListItemViewModel vm)
{
var settings = App.Current.Services.GetService<SettingsModel>()!;
if (!settings.SingleClickActivates)
{
ViewModel?.InvokeItemCommand.Execute(vm);
@@ -458,7 +456,7 @@ public sealed partial class ListPage : Page,
}
else if (e.NewValue is null)
{
Log_ClearedViewModel(@this.Logger);
Logger.LogDebug("cleared view model");
}
}
}
@@ -578,7 +576,4 @@ public sealed partial class ListPage : Page,
Keyboard,
Pointer,
}
[LoggerMessage(Level = LogLevel.Debug, Message = "Cleared view model")]
static partial void Log_ClearedViewModel(ILogger logger);
}

View File

@@ -2,8 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.Extensions.Logging;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
@@ -17,13 +17,6 @@ namespace Microsoft.CmdPal.UI.Helpers;
/// </summary>
internal sealed partial class GlobalErrorHandler
{
private readonly ILogger _logger;
public GlobalErrorHandler(ILogger logger)
{
_logger = logger;
}
// GlobalErrorHandler is designed to be self-contained; it can be registered and invoked before a service provider is available.
internal void Register(App app)
{
@@ -61,9 +54,9 @@ internal sealed partial class GlobalErrorHandler
HandleException(e.Exception, Context.UnobservedTaskException);
}
private void HandleException(Exception ex, Context context)
private static void HandleException(Exception ex, Context context)
{
Log_UnhandledException(ex, context);
Logger.LogError($"Unhandled exception detected ({context})", ex);
if (context == Context.MainThreadException)
{
@@ -100,7 +93,7 @@ internal sealed partial class GlobalErrorHandler
}
}
private string? StoreReport(string report, bool storeOnDesktop)
private static string? StoreReport(string report, bool storeOnDesktop)
{
// Generate a unique name for the report file; include timestamp and a random zero-padded number to avoid collisions
// in case of crash storm.
@@ -108,16 +101,15 @@ internal sealed partial class GlobalErrorHandler
// Always store a copy in log directory, this way it is available for Bug Report Tool
string? reportPath = null;
// if (!string.IsNullOrEmpty(logWrapper.CurrentVersionLogDirectoryPath))
// {
// reportPath = Save(report, name, logWrapper.CurrentVersionLogDirectoryPath);
// s}
if (Logger.CurrentVersionLogDirectoryPath != null)
{
reportPath = Save(report, name, static () => Logger.CurrentVersionLogDirectoryPath);
}
// Optionally store a copy on the desktop for user (in)convenience
if (storeOnDesktop)
{
var path = Save(report, name, Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory));
var path = Save(report, name, static () => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory));
// show the desktop copy if both succeeded
if (path != null)
@@ -128,10 +120,11 @@ internal sealed partial class GlobalErrorHandler
return reportPath;
string? Save(string reportContent, string reportFileName, string logDirectory)
static string? Save(string reportContent, string reportFileName, Func<string> directory)
{
try
{
var logDirectory = directory();
Directory.CreateDirectory(logDirectory);
var reportFilePath = Path.Combine(logDirectory, reportFileName);
File.WriteAllText(reportFilePath, reportContent);
@@ -139,7 +132,7 @@ internal sealed partial class GlobalErrorHandler
}
catch (Exception ex)
{
Log_FailureToStoreExceptionReport(ex);
Logger.LogError("Failed to store exception report", ex);
return null;
}
}
@@ -153,10 +146,4 @@ internal sealed partial class GlobalErrorHandler
UnobservedTaskException,
AppDomainUnhandledException,
}
[LoggerMessage(level: LogLevel.Error, Message = "Failed to store exception report")]
partial void Log_FailureToStoreExceptionReport(Exception ex);
[LoggerMessage(level: LogLevel.Error, message: "Unhandled exception detected ({Context})")]
partial void Log_UnhandledException(Exception ex, Context context);
}

View File

@@ -2,7 +2,8 @@
// 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.Extensions.Logging;
using ManagedCommon;
using Windows.System;
using Windows.Win32;
using Windows.Win32.Foundation;
@@ -15,13 +16,6 @@ namespace Microsoft.CmdPal.UI.Helpers;
/// </summary>
internal sealed partial class LocalKeyboardListener : IDisposable
{
private readonly ILogger _logger;
public LocalKeyboardListener(ILogger logger)
{
_logger = logger;
}
/// <summary>
/// Event that is raised when a key is pressed down.
/// </summary>
@@ -74,7 +68,7 @@ internal sealed partial class LocalKeyboardListener : IDisposable
}
catch (Exception ex)
{
Log_FailureToRegisterHook(ex);
Logger.LogError("Failed to register hook", ex);
return false;
}
}
@@ -127,7 +121,7 @@ internal sealed partial class LocalKeyboardListener : IDisposable
}
catch (Exception ex)
{
Log_FailureToInvokeKeyDownHook(ex);
Logger.LogError("Failed when invoking key down keyboard hook event", ex);
}
// Call next hook in chain - pass null as first parameter for current hook
@@ -160,10 +154,4 @@ internal sealed partial class LocalKeyboardListener : IDisposable
_disposed = true;
}
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to register hook")]
partial void Log_FailureToRegisterHook(Exception ex);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed when invoking key down keyboard hook event")]
partial void Log_FailureToInvokeKeyDownHook(Exception ex);
}

View File

@@ -3,20 +3,14 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.WinUI.Controls;
using Microsoft.Extensions.Logging;
using ManagedCommon;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI.Helpers.MarkdownImageProviders;
public sealed partial class ImageProvider : IImageProvider
internal sealed partial class ImageProvider : IImageProvider
{
private readonly CompositeImageSourceProvider _compositeProvider = new();
private readonly ILogger _logger;
public ImageProvider(ILogger logger)
{
_logger = logger;
}
public async Task<Image> GetImage(string url)
{
@@ -37,7 +31,7 @@ public sealed partial class ImageProvider : IImageProvider
}
catch (Exception ex)
{
Log_FailedToProvideImageFromUrl(url, ex);
Logger.LogError($"Failed to provide an image from URI '{url}'", ex);
return null!;
}
}
@@ -46,7 +40,4 @@ public sealed partial class ImageProvider : IImageProvider
{
return _compositeProvider.ShouldUseThisProvider(url);
}
[LoggerMessage(level: LogLevel.Error, message: "Failed to provide an image from URI '{url}'")]
partial void Log_FailedToProvideImageFromUrl(string url, Exception ex);
}

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.Common.Services.Telemetry;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Events;
using Microsoft.PowerToys.Telemetry;
@@ -30,30 +30,28 @@ internal sealed class TelemetryForwarder :
WeakReferenceMessenger.Default.Register<CmdPalInvokeResultMessage>(this);
}
public void WriteEvent(TelemetryEventBase telemetryEvent) => PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
public void Receive(CmdPalInvokeResultMessage message)
{
PowerToysTelemetry.Log.WriteEvent(new InvokeResultEvent(message.Kind));
PowerToysTelemetry.Log.WriteEvent(new CmdPalInvokeResult(message.Kind));
}
public void Receive(BeginInvokeMessage message)
{
PowerToysTelemetry.Log.WriteEvent(new BeginInvokeEvent());
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
}
public void LogRunQuery(string query, int resultCount, ulong durationMs)
{
PowerToysTelemetry.Log.WriteEvent(new RunQueryEvent(query, resultCount, durationMs));
PowerToysTelemetry.Log.WriteEvent(new CmdPalRunQuery(query, resultCount, durationMs));
}
public void LogRunCommand(string command, bool asAdmin, bool success)
{
PowerToysTelemetry.Log.WriteEvent(new RunCommandEvent(command, asAdmin, success));
PowerToysTelemetry.Log.WriteEvent(new CmdPalRunCommand(command, asAdmin, success));
}
public void LogOpenUri(string uri, bool isWeb, bool success)
{
PowerToysTelemetry.Log.WriteEvent(new OpenUriEvent(uri, isWeb, success));
PowerToysTelemetry.Log.WriteEvent(new CmdPalOpenUri(uri, isWeb, success));
}
}

View File

@@ -20,7 +20,7 @@ namespace Microsoft.CmdPal.UI.Helpers;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_*")]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_*")]
public sealed partial class TrayIconService
internal sealed partial class TrayIconService
{
private const uint MY_NOTIFY_ID = 1000;
private const uint WM_TRAY_ICON = PInvoke.WM_USER + 1;

View File

@@ -2,6 +2,11 @@
// 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;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
@@ -9,6 +14,10 @@ namespace Microsoft.CmdPal.UI.Helpers;
public static partial class UIHelper
{
static UIHelper()
{
}
public static void AnnounceActionForAccessibility(UIElement ue, string announcement, string activityID)
{
if (FrameworkElementAutomationPeer.FromElement(ue) is AutomationPeer peer)

Some files were not shown because too many files have changed in this diff Show More