mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-03 02:46:37 +01:00
Compare commits
7 Commits
dev/vanzue
...
cmdpal-add
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9129bb5df9 | ||
|
|
182a5f02e4 | ||
|
|
5720aee40b | ||
|
|
5523fb13a6 | ||
|
|
5cf5618efa | ||
|
|
26103fa0c7 | ||
|
|
8bb900eee2 |
@@ -262,6 +262,7 @@ _If you want to find diagnostic data events in the source code, these two links
|
||||
</table>
|
||||
|
||||
### Command Palette
|
||||
|
||||
<table style="width:100%">
|
||||
<tr>
|
||||
<th>Event Name</th>
|
||||
@@ -315,6 +316,14 @@ _If you want to find diagnostic data events in the source code, these two links
|
||||
<td>Microsoft.PowerToys.CmdPalProcessStarted</td>
|
||||
<td>Triggered when the Command Palette process is started.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPal_ExtensionInvoked</td>
|
||||
<td>Tracks extension usage including extension ID, command details, success status, and execution time.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.CmdPal_SessionDuration</td>
|
||||
<td>Logs session metrics from launch to dismissal including duration, commands executed, pages visited, search queries, navigation depth, and errors.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Crop And Lock
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Message sent when an error occurs during command execution.
|
||||
/// Used to track session error count for telemetry.
|
||||
/// </summary>
|
||||
public record ErrorOccurredMessage();
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Message sent when an extension command or page is invoked.
|
||||
/// Captures extension usage metrics for telemetry tracking.
|
||||
/// </summary>
|
||||
public record ExtensionInvokedMessage(string ExtensionId, string CommandId, string CommandName, bool Success, ulong ExecutionTimeMs);
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Message containing the current navigation depth (BackStack count) when navigating to a page.
|
||||
/// Used to track maximum navigation depth reached during a session for telemetry.
|
||||
/// </summary>
|
||||
public record NavigationDepthMessage(int Depth);
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Message sent when a search query is executed in the Command Palette.
|
||||
/// Used to track session search activity for telemetry.
|
||||
/// </summary>
|
||||
public record SearchQueryMessage();
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Message containing session telemetry data from Command Palette launch to dismissal.
|
||||
/// Used to aggregate metrics like duration, commands executed, pages visited, and search activity.
|
||||
/// </summary>
|
||||
public record SessionDurationMessage(ulong DurationMs, int CommandsExecuted, int PagesVisited, string DismissalReason, int SearchQueriesCount, int MaxNavigationDepth, int ErrorCount);
|
||||
@@ -0,0 +1,10 @@
|
||||
// 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.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Telemetry message sent when command invocation begins.
|
||||
/// </summary>
|
||||
public record TelemetryBeginInvokeMessage;
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Telemetry message sent when an extension command or page is invoked.
|
||||
/// Captures extension usage metrics for telemetry tracking.
|
||||
/// </summary>
|
||||
public record TelemetryExtensionInvokedMessage(string ExtensionId, string CommandId, string CommandName, bool Success, ulong ExecutionTimeMs);
|
||||
@@ -0,0 +1,10 @@
|
||||
// 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.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Telemetry message sent when command invocation completes with a result.
|
||||
/// </summary>
|
||||
public record TelemetryInvokeResultMessage(Microsoft.CommandPalette.Extensions.CommandResultKind Kind);
|
||||
@@ -270,8 +270,18 @@ public partial class ShellViewModel : ObservableObject,
|
||||
var isMainPage = command == _rootPage;
|
||||
_isNested = !isMainPage;
|
||||
|
||||
// Telemetry: Track extension page navigation for session metrics
|
||||
if (host is not null)
|
||||
{
|
||||
string extensionId = host.GetExtensionDisplayName() ?? "builtin";
|
||||
string commandId = command?.Id ?? "unknown";
|
||||
string commandName = command?.Name ?? "unknown";
|
||||
WeakReferenceMessenger.Default.Send<TelemetryExtensionInvokedMessage>(
|
||||
new(extensionId, commandId, commandName, true, 0));
|
||||
}
|
||||
|
||||
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
|
||||
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host);
|
||||
var pageViewModel = _pageViewModelFactory.TryCreatePageViewModel(page, _isNested, host!);
|
||||
if (pageViewModel is null)
|
||||
{
|
||||
CoreLogger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
|
||||
@@ -306,7 +316,7 @@ public partial class ShellViewModel : ObservableObject,
|
||||
{
|
||||
CoreLogger.LogDebug($"Invoking command");
|
||||
|
||||
WeakReferenceMessenger.Default.Send<BeginInvokeMessage>();
|
||||
WeakReferenceMessenger.Default.Send<TelemetryBeginInvokeMessage>();
|
||||
StartInvoke(message, invokable, host);
|
||||
}
|
||||
}
|
||||
@@ -339,6 +349,14 @@ public partial class ShellViewModel : ObservableObject,
|
||||
|
||||
private void SafeHandleInvokeCommandSynchronous(PerformCommandMessage message, IInvokableCommand invokable, AppExtensionHost? host)
|
||||
{
|
||||
// Telemetry: Track command execution time and success
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
var command = message.Command.Unsafe;
|
||||
string extensionId = host?.GetExtensionDisplayName() ?? "builtin";
|
||||
string commandId = command?.Id ?? "unknown";
|
||||
string commandName = command?.Name ?? "unknown";
|
||||
bool success = false;
|
||||
|
||||
try
|
||||
{
|
||||
// Call out to extension process.
|
||||
@@ -349,16 +367,28 @@ public partial class ShellViewModel : ObservableObject,
|
||||
// But if it did succeed, we need to handle the result.
|
||||
UnsafeHandleCommandResult(result);
|
||||
|
||||
success = true;
|
||||
_handleInvokeTask = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
success = false;
|
||||
_handleInvokeTask = null;
|
||||
|
||||
// Telemetry: Track errors for session metrics
|
||||
WeakReferenceMessenger.Default.Send<ErrorOccurredMessage>(new());
|
||||
|
||||
// TODO: It would be better to do this as a page exception, rather
|
||||
// than a silent log message.
|
||||
host?.Log(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Telemetry: Send extension invocation metrics (always sent, even on failure)
|
||||
stopwatch.Stop();
|
||||
WeakReferenceMessenger.Default.Send<TelemetryExtensionInvokedMessage>(
|
||||
new(extensionId, commandId, commandName, success, (ulong)stopwatch.ElapsedMilliseconds));
|
||||
}
|
||||
}
|
||||
|
||||
private void UnsafeHandleCommandResult(ICommandResult? result)
|
||||
@@ -372,7 +402,7 @@ public partial class ShellViewModel : ObservableObject,
|
||||
var kind = result.Kind;
|
||||
CoreLogger.LogDebug($"handling {kind.ToString()}");
|
||||
|
||||
WeakReferenceMessenger.Default.Send<CmdPalInvokeResultMessage>(new(kind));
|
||||
WeakReferenceMessenger.Default.Send<TelemetryInvokeResultMessage>(new(kind));
|
||||
switch (kind)
|
||||
{
|
||||
case CommandResultKind.Dismiss:
|
||||
|
||||
@@ -34,6 +34,6 @@ public sealed partial class CommandPaletteHost : AppExtensionHost, IExtensionHos
|
||||
|
||||
public override string? GetExtensionDisplayName()
|
||||
{
|
||||
return Extension?.ExtensionDisplayName;
|
||||
return Extension?.ExtensionDisplayName ?? _builtInProvider?.DisplayName ?? _builtInProvider?.Id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,6 +379,12 @@ public sealed partial class SearchBar : UserControl,
|
||||
if (CurrentPageViewModel is not null)
|
||||
{
|
||||
CurrentPageViewModel.SearchTextBox = FilterBox.Text;
|
||||
|
||||
// Telemetry: Track search query count for session metrics (only non-empty queries)
|
||||
if (!string.IsNullOrWhiteSpace(FilterBox.Text))
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<SearchQueryMessage>(new());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks extension usage with extension name and invocation details.
|
||||
/// Purpose: Identify popular vs. unused plugins and track extension performance.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class CmdPalExtensionInvoked : EventBase, IEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the extension provider.
|
||||
/// </summary>
|
||||
public string ExtensionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the non-localized identifier of the command being invoked.
|
||||
/// </summary>
|
||||
public string CommandId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the localized display name of the command being invoked.
|
||||
/// </summary>
|
||||
public string CommandName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the command executed successfully.
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the execution time in milliseconds.
|
||||
/// </summary>
|
||||
public ulong ExecutionTimeMs { get; set; }
|
||||
|
||||
public CmdPalExtensionInvoked(string extensionId, string commandId, string commandName, bool success, ulong executionTimeMs)
|
||||
{
|
||||
EventName = "CmdPal_ExtensionInvoked";
|
||||
ExtensionId = extensionId;
|
||||
CommandId = commandId;
|
||||
CommandName = commandName;
|
||||
Success = success;
|
||||
ExecutionTimeMs = executionTimeMs;
|
||||
}
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public class CmdPalInvokeResult : EventBase, IEvent
|
||||
|
||||
public CmdPalInvokeResult(CommandResultKind resultKind)
|
||||
{
|
||||
EventName = "CmdPal_InvokeResult";
|
||||
ResultKind = resultKind.ToString();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks Command Palette session duration from launch to close.
|
||||
/// Purpose: Understand user engagement patterns - quick actions vs. browsing behavior.
|
||||
/// </summary>
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class CmdPalSessionDuration : EventBase, IEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the session duration in milliseconds.
|
||||
/// </summary>
|
||||
public ulong DurationMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of commands executed during the session.
|
||||
/// </summary>
|
||||
public int CommandsExecuted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of pages visited during the session.
|
||||
/// </summary>
|
||||
public int PagesVisited { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason for dismissal (Escape, LostFocus, Command, etc.).
|
||||
/// </summary>
|
||||
public string DismissalReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of search queries executed during the session.
|
||||
/// </summary>
|
||||
public int SearchQueriesCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum navigation depth reached during the session.
|
||||
/// </summary>
|
||||
public int MaxNavigationDepth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of errors encountered during the session.
|
||||
/// </summary>
|
||||
public int ErrorCount { get; set; }
|
||||
|
||||
public CmdPalSessionDuration(ulong durationMs, int commandsExecuted, int pagesVisited, string dismissalReason, int searchQueriesCount, int maxNavigationDepth, int errorCount)
|
||||
{
|
||||
EventName = "CmdPal_SessionDuration";
|
||||
DurationMs = durationMs;
|
||||
CommandsExecuted = commandsExecuted;
|
||||
PagesVisited = pagesVisited;
|
||||
DismissalReason = dismissalReason;
|
||||
SearchQueriesCount = searchQueriesCount;
|
||||
MaxNavigationDepth = maxNavigationDepth;
|
||||
ErrorCount = errorCount;
|
||||
}
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -6,40 +6,78 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
/// <summary>
|
||||
/// TelemetryForwarder is responsible for forwarding telemetry events from the
|
||||
/// command palette core to PowerToys Telemetry.
|
||||
/// This allows us to emit telemetry events as messages from the core,
|
||||
/// and then handle them by logging to our PT telemetry provider.
|
||||
///
|
||||
/// We may in the future want to replace this with a more generic "ITelemetryService"
|
||||
/// or something similar, but this works for now.
|
||||
/// command palette to PowerToys Telemetry.
|
||||
/// Listens to telemetry-specific messages from the core layer and logs them to PowerToys telemetry.
|
||||
/// Also implements ITelemetryService for dependency injection in extensions.
|
||||
/// </summary>
|
||||
internal sealed class TelemetryForwarder :
|
||||
ITelemetryService,
|
||||
IRecipient<BeginInvokeMessage>,
|
||||
IRecipient<CmdPalInvokeResultMessage>
|
||||
IRecipient<TelemetryBeginInvokeMessage>,
|
||||
IRecipient<TelemetryInvokeResultMessage>,
|
||||
IRecipient<TelemetryExtensionInvokedMessage>
|
||||
{
|
||||
public TelemetryForwarder()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<BeginInvokeMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<CmdPalInvokeResultMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<TelemetryBeginInvokeMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<TelemetryInvokeResultMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<TelemetryExtensionInvokedMessage>(this);
|
||||
}
|
||||
|
||||
public void Receive(CmdPalInvokeResultMessage message)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalInvokeResult(message.Kind));
|
||||
}
|
||||
|
||||
public void Receive(BeginInvokeMessage message)
|
||||
// Message handlers for telemetry events from core layer
|
||||
public void Receive(TelemetryBeginInvokeMessage message)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
|
||||
}
|
||||
|
||||
public void Receive(TelemetryInvokeResultMessage message)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalInvokeResult(message.Kind));
|
||||
}
|
||||
|
||||
public void Receive(TelemetryExtensionInvokedMessage message)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalExtensionInvoked(
|
||||
message.ExtensionId,
|
||||
message.CommandId,
|
||||
message.CommandName,
|
||||
message.Success,
|
||||
message.ExecutionTimeMs));
|
||||
|
||||
// Increment session counter for commands executed
|
||||
if (App.Current.AppWindow is MainWindow mainWindow)
|
||||
{
|
||||
mainWindow.IncrementCommandsExecuted();
|
||||
}
|
||||
}
|
||||
|
||||
// Static method for logging session duration from UI layer
|
||||
public static void LogSessionDuration(
|
||||
ulong durationMs,
|
||||
int commandsExecuted,
|
||||
int pagesVisited,
|
||||
string dismissalReason,
|
||||
int searchQueriesCount,
|
||||
int maxNavigationDepth,
|
||||
int errorCount)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalSessionDuration(
|
||||
durationMs,
|
||||
commandsExecuted,
|
||||
pagesVisited,
|
||||
dismissalReason,
|
||||
searchQueriesCount,
|
||||
maxNavigationDepth,
|
||||
errorCount));
|
||||
}
|
||||
|
||||
// ITelemetryService implementation for dependency injection in extensions
|
||||
public void LogRunQuery(string query, int resultCount, ulong durationMs)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalRunQuery(query, resultCount, durationMs));
|
||||
|
||||
@@ -52,6 +52,10 @@ public sealed partial class MainWindow : WindowEx,
|
||||
IRecipient<ShowWindowMessage>,
|
||||
IRecipient<HideWindowMessage>,
|
||||
IRecipient<QuitMessage>,
|
||||
IRecipient<NavigateToPageMessage>,
|
||||
IRecipient<NavigationDepthMessage>,
|
||||
IRecipient<SearchQueryMessage>,
|
||||
IRecipient<ErrorOccurredMessage>,
|
||||
IRecipient<DragStartedMessage>,
|
||||
IRecipient<DragCompletedMessage>,
|
||||
IDisposable
|
||||
@@ -75,6 +79,14 @@ public sealed partial class MainWindow : WindowEx,
|
||||
private bool _ignoreHotKeyWhenFullScreen = true;
|
||||
private bool _themeServiceInitialized;
|
||||
|
||||
// Session tracking for telemetry
|
||||
private Stopwatch? _sessionStopwatch;
|
||||
private int _sessionCommandsExecuted;
|
||||
private int _sessionPagesVisited;
|
||||
private int _sessionSearchQueriesCount;
|
||||
private int _sessionMaxNavigationDepth;
|
||||
private int _sessionErrorCount;
|
||||
|
||||
private DesktopAcrylicController? _acrylicController;
|
||||
private SystemBackdropConfiguration? _configurationSource;
|
||||
private TimeSpan _autoGoHomeInterval = Timeout.InfiniteTimeSpan;
|
||||
@@ -123,6 +135,10 @@ public sealed partial class MainWindow : WindowEx,
|
||||
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<NavigationDepthMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<SearchQueryMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ErrorOccurredMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<DragStartedMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<DragCompletedMessage>(this);
|
||||
|
||||
@@ -524,6 +540,11 @@ public sealed partial class MainWindow : WindowEx,
|
||||
{
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
|
||||
// Start session tracking
|
||||
_sessionStopwatch = Stopwatch.StartNew();
|
||||
_sessionCommandsExecuted = 0;
|
||||
_sessionPagesVisited = 0;
|
||||
|
||||
ShowHwnd(message.Hwnd, settings.SummonOn);
|
||||
}
|
||||
|
||||
@@ -532,6 +553,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
// This might come in off the UI thread. Make sure to hop back.
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
EndSession("Hide");
|
||||
HideWindow();
|
||||
});
|
||||
}
|
||||
@@ -551,10 +573,67 @@ public sealed partial class MainWindow : WindowEx,
|
||||
// This might come in off the UI thread. Make sure to hop back.
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
EndSession("Dismiss");
|
||||
HideWindow();
|
||||
});
|
||||
}
|
||||
|
||||
// Session telemetry: Track metrics during the Command Palette session
|
||||
// These receivers increment counters that are sent when EndSession is called
|
||||
public void Receive(NavigateToPageMessage message)
|
||||
{
|
||||
_sessionPagesVisited++;
|
||||
}
|
||||
|
||||
public void Receive(NavigationDepthMessage message)
|
||||
{
|
||||
if (message.Depth > _sessionMaxNavigationDepth)
|
||||
{
|
||||
_sessionMaxNavigationDepth = message.Depth;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(SearchQueryMessage message)
|
||||
{
|
||||
_sessionSearchQueriesCount++;
|
||||
}
|
||||
|
||||
public void Receive(ErrorOccurredMessage message)
|
||||
{
|
||||
_sessionErrorCount++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current telemetry session and emits the CmdPal_SessionDuration event.
|
||||
/// Aggregates all session metrics collected since ShowWindow and sends them to telemetry.
|
||||
/// </summary>
|
||||
/// <param name="dismissalReason">The reason the session ended (e.g., Dismiss, Hide, LostFocus).</param>
|
||||
private void EndSession(string dismissalReason)
|
||||
{
|
||||
if (_sessionStopwatch is not null)
|
||||
{
|
||||
_sessionStopwatch.Stop();
|
||||
TelemetryForwarder.LogSessionDuration(
|
||||
(ulong)_sessionStopwatch.ElapsedMilliseconds,
|
||||
_sessionCommandsExecuted,
|
||||
_sessionPagesVisited,
|
||||
dismissalReason,
|
||||
_sessionSearchQueriesCount,
|
||||
_sessionMaxNavigationDepth,
|
||||
_sessionErrorCount);
|
||||
_sessionStopwatch = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the session commands executed counter for telemetry.
|
||||
/// Called by TelemetryForwarder when an extension command is invoked.
|
||||
/// </summary>
|
||||
internal void IncrementCommandsExecuted()
|
||||
{
|
||||
_sessionCommandsExecuted++;
|
||||
}
|
||||
|
||||
private void HideWindow()
|
||||
{
|
||||
// Cloak our HWND to avoid all animations.
|
||||
@@ -764,6 +843,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
}
|
||||
|
||||
// This will DWM cloak our window:
|
||||
EndSession("LostFocus");
|
||||
HideWindow();
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new CmdPalDismissedOnLostFocus());
|
||||
|
||||
@@ -161,6 +161,9 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth, message.Page.Id));
|
||||
|
||||
// Telemetry: Send navigation depth for session max depth tracking
|
||||
WeakReferenceMessenger.Default.Send(new NavigationDepthMessage(RootFrame.BackStackDepth));
|
||||
|
||||
if (!ViewModel.IsNested)
|
||||
{
|
||||
// todo BODGY
|
||||
|
||||
Reference in New Issue
Block a user