mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-14 23:29:58 +01:00
Compare commits
18 Commits
dev/vanzue
...
niels9001/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fdbab1d2f | ||
|
|
3036a944c5 | ||
|
|
9e7060ab68 | ||
|
|
45ce27c115 | ||
|
|
2ec439fb27 | ||
|
|
a46a67c3aa | ||
|
|
6ef3670321 | ||
|
|
b93a496bcd | ||
|
|
a15a7587cc | ||
|
|
603ac55f8a | ||
|
|
3f5418132d | ||
|
|
e935faf08c | ||
|
|
eb5f4c6bd0 | ||
|
|
658f90d6f8 | ||
|
|
3f3e04086e | ||
|
|
3b874a9567 | ||
|
|
7a86543c8d | ||
|
|
67a013f729 |
@@ -1,5 +1,5 @@
|
||||
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started
|
||||
properties:
|
||||
resources:
|
||||
- resource: Microsoft.Windows.Settings/WindowsSettings
|
||||
@@ -13,11 +13,11 @@ properties:
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
id: vsPackage
|
||||
directives:
|
||||
description: Install Visual Studio 2022 Enterprise (Any edition will work)
|
||||
description: Install Visual Studio 2026 Enterprise (Any edition will work)
|
||||
# Requires elevation for the set operation
|
||||
securityContext: elevated
|
||||
settings:
|
||||
id: Microsoft.VisualStudio.2022.Enterprise
|
||||
id: Microsoft.VisualStudio.Enterprise
|
||||
source: winget
|
||||
- resource: Microsoft.VisualStudio.DSC/VSComponents
|
||||
dependsOn:
|
||||
@@ -29,7 +29,7 @@ properties:
|
||||
securityContext: elevated
|
||||
settings:
|
||||
productId: Microsoft.VisualStudio.Product.Enterprise
|
||||
channelId: VisualStudio.17.Release
|
||||
channelId: VisualStudio.18.Release
|
||||
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
|
||||
configurationVersion: 0.2.0
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started
|
||||
properties:
|
||||
resources:
|
||||
- resource: Microsoft.Windows.Settings/WindowsSettings
|
||||
@@ -13,11 +13,11 @@ properties:
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
id: vsPackage
|
||||
directives:
|
||||
description: Install Visual Studio 2022 Professional (Any edition will work)
|
||||
description: Install Visual Studio 2026 Professional (Any edition will work)
|
||||
# Requires elevation for the set operation
|
||||
securityContext: elevated
|
||||
settings:
|
||||
id: Microsoft.VisualStudio.2022.Professional
|
||||
id: Microsoft.VisualStudio.Professional
|
||||
source: winget
|
||||
- resource: Microsoft.VisualStudio.DSC/VSComponents
|
||||
dependsOn:
|
||||
@@ -29,7 +29,7 @@ properties:
|
||||
securityContext: elevated
|
||||
settings:
|
||||
productId: Microsoft.VisualStudio.Product.Professional
|
||||
channelId: VisualStudio.17.Release
|
||||
channelId: VisualStudio.18.Release
|
||||
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
|
||||
configurationVersion: 0.2.0
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started
|
||||
properties:
|
||||
resources:
|
||||
- resource: Microsoft.Windows.Settings/WindowsSettings
|
||||
@@ -13,11 +13,11 @@ properties:
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
id: vsPackage
|
||||
directives:
|
||||
description: Install Visual Studio 2022 Community (Any edition will work)
|
||||
description: Install Visual Studio 2026 Community (Any edition will work)
|
||||
# Requires elevation for the set operation
|
||||
securityContext: elevated
|
||||
settings:
|
||||
id: Microsoft.VisualStudio.2022.Community
|
||||
id: Microsoft.VisualStudio.Community
|
||||
source: winget
|
||||
- resource: Microsoft.VisualStudio.DSC/VSComponents
|
||||
dependsOn:
|
||||
@@ -29,7 +29,7 @@ properties:
|
||||
securityContext: elevated
|
||||
settings:
|
||||
productId: Microsoft.VisualStudio.Product.Community
|
||||
channelId: VisualStudio.17.Release
|
||||
channelId: VisualStudio.18.Release
|
||||
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
|
||||
configurationVersion: 0.2.0
|
||||
|
||||
|
||||
7
.github/actions/spell-check/allow/code.txt
vendored
7
.github/actions/spell-check/allow/code.txt
vendored
@@ -38,6 +38,7 @@ Gbps
|
||||
gcode
|
||||
Heatshrink
|
||||
Mbits
|
||||
Kbits
|
||||
MBs
|
||||
mkv
|
||||
msix
|
||||
@@ -97,6 +98,7 @@ Yubico
|
||||
Perplexity
|
||||
Groq
|
||||
svgl
|
||||
devhome
|
||||
|
||||
# KEYS
|
||||
|
||||
@@ -322,6 +324,7 @@ REGSTR
|
||||
|
||||
# Misc Win32 APIs and PInvokes
|
||||
INVOKEIDLIST
|
||||
MEMORYSTATUSEX
|
||||
|
||||
# PowerRename metadata pattern abbreviations (used in tests and regex patterns)
|
||||
DDDD
|
||||
@@ -342,3 +345,7 @@ reportbug
|
||||
#ffmpeg
|
||||
crf
|
||||
nostdin
|
||||
|
||||
# Performance counter keys
|
||||
engtype
|
||||
Nonpaged
|
||||
|
||||
3
.github/actions/spell-check/allow/names.txt
vendored
3
.github/actions/spell-check/allow/names.txt
vendored
@@ -192,6 +192,7 @@ ycv
|
||||
yeelam
|
||||
Yuniardi
|
||||
yuyoyuppe
|
||||
zadjii
|
||||
Zeol
|
||||
Zhao
|
||||
Zhaopeng
|
||||
@@ -228,6 +229,7 @@ regedit
|
||||
roslyn
|
||||
Skia
|
||||
Spotify
|
||||
taskmgr
|
||||
tldr
|
||||
Vanara
|
||||
wangyi
|
||||
@@ -243,4 +245,3 @@ xamlstyler
|
||||
Xavalon
|
||||
Xbox
|
||||
Youdao
|
||||
zadjii
|
||||
|
||||
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@@ -197,6 +197,7 @@ Canvascustomlayout
|
||||
CAPTUREBLT
|
||||
CAPTURECHANGED
|
||||
CARETBLINKING
|
||||
carlos
|
||||
Carlseibert
|
||||
CAtl
|
||||
caub
|
||||
@@ -217,6 +218,7 @@ certmgr
|
||||
cfp
|
||||
CHANGECBCHAIN
|
||||
changecursor
|
||||
chatasweetie
|
||||
checkmarks
|
||||
CHILDACTIVATE
|
||||
CHILDWINDOW
|
||||
@@ -2242,6 +2244,7 @@ YSpeed
|
||||
YStr
|
||||
YTimer
|
||||
YVIRTUALSCREEN
|
||||
zamora
|
||||
ZEROINIT
|
||||
zonability
|
||||
zonable
|
||||
|
||||
@@ -219,6 +219,10 @@
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
<Deploy />
|
||||
</Project>
|
||||
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Microsoft.CmdPal.Ext.PerformanceMonitor.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
|
||||
30
README.md
30
README.md
@@ -103,10 +103,38 @@ There are <a href="https://learn.microsoft.com/windows/powertoys/install#communi
|
||||
</details>
|
||||
|
||||
## ✨ What's new
|
||||
**Version 0.97.1 (January 2026)**
|
||||
|
||||
**Version 0.97.2 (Feb 2026)**
|
||||
|
||||
This patch release fixes several important stability issues identified in v0.97.0 based on incoming reports. Check out the [v0.97.0](https://github.com/microsoft/PowerToys/releases/tag/v0.97.0) notes for the full list of changes.
|
||||
|
||||
## Advanced Paste
|
||||
- #45207 Fixed a crash in the Advanced Paste settings page caused by null values during JSON deserialization.
|
||||
|
||||
## Color Picker
|
||||
- #45367 Fixed contrast issue in Color picker UI.
|
||||
|
||||
## Command Palette
|
||||
- #45194 Fixed an issue where some Command Palette PowerToys Extension strings were not localised.
|
||||
|
||||
## Cursor Wrap
|
||||
- #45210 Fixed "Automatically activate on utility startup" setting not persisting when disabled. Thanks [@ThanhNguyxn](https://github.com/ThanhNguyxn)!
|
||||
- #45303 Added option to disable Cursor Wrapping when only a single monitor is connected. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
|
||||
|
||||
## Image Resizer
|
||||
- #45184 Fixed Image Resizer not working after upgrading PowerToys on Windows 10 by properly cleaning up legacy sparse app packages.
|
||||
|
||||
## LightSwitch
|
||||
- #45304 Fixed Light Switch startup logic to correctly apply the appropriate theme on launch.
|
||||
|
||||
## Workspaces
|
||||
- #45183 Fixed overlay positioning issue in workspace snapshot draw caused by DPI-aware coordinate mismatch.
|
||||
|
||||
## Quick Access and Measure Tool
|
||||
- #45443 Fixed crash related to `IsShownInSwitchers` property when Explorer is not running.
|
||||
|
||||
**Version 0.97.1 (January 2026)**
|
||||
|
||||
**Highlights**
|
||||
|
||||
### Advanced Paste
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
### Building PowerToys Locally
|
||||
|
||||
#### One stop script for building installer
|
||||
1. Open `Developer Powershell for VS 2022` or `Developer PowerShell for VS` for VS 2026.
|
||||
1. Open `Developer PowerShell for VS`.
|
||||
2. Run tools\build\build-installer.ps1
|
||||
> For the first-time setup, please run the installer as an administrator. This ensures that the Wix tool can move wix.target to the desired location and trust the certificate used to sign the MSIX packages.
|
||||
|
||||
@@ -109,7 +109,7 @@ dotnet tool install --global wix --version 5.0.2
|
||||
|
||||
##### From the command line
|
||||
|
||||
1. From the start menu, open a `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS`
|
||||
1. From the start menu, open a `Developer Command Prompt for VS`
|
||||
1. Ensure `nuget.exe` is in your `%path%`
|
||||
1. In the repo root, run these commands:
|
||||
|
||||
@@ -140,7 +140,7 @@ If you prefer, you can alternatively build prerequisite projects for the install
|
||||
|
||||
The resulting installer will be available in the `installer\PowerToysSetupVNext\x64\Release\` folder.
|
||||
|
||||
To build the installer from the command line, run `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS` in admin mode and execute the following commands. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`.
|
||||
To build the installer from the command line, run `Developer Command Prompt for VS` in admin mode and execute the following commands. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`.
|
||||
|
||||
```
|
||||
git clean -xfd -e *exe -- .\installer\
|
||||
|
||||
@@ -15,7 +15,7 @@ Before you can start debugging PowerToys, you need to set up your development en
|
||||
|
||||
You can build the entire solution from the command line, which is sometimes faster than building within Visual Studio:
|
||||
|
||||
1. Open `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS`
|
||||
1. Open `Developer Command Prompt for VS`
|
||||
2. Navigate to the repository root directory
|
||||
3. Run the following command(don't forget to set the correct platform):
|
||||
```pwsh
|
||||
@@ -105,7 +105,7 @@ If you encounter build errors about missing image files (e.g., `.png`, `.ico`, o
|
||||
|
||||
1. **Clean the solution in Visual Studio**: Build > Clean Solution
|
||||
|
||||
Or from the command line (Developer Command Prompt for VS 2022 or Developer Command Prompt for VS):
|
||||
Or from the command line (`Developer Command Prompt for VS`):
|
||||
```pwsh
|
||||
msbuild PowerToys.slnx /t:Clean /p:Platform=x64 /p:Configuration=Debug
|
||||
```
|
||||
|
||||
@@ -15,9 +15,27 @@ VS Code extensions Needed:
|
||||
---
|
||||
|
||||
## Building in VS Code
|
||||
### Configure Developer Powershell for VS 2022 or Developer Powershell for VS for more convenient dev in vscode.
|
||||
1. Configure profile in in settings, entry: "terminal.integrated.profiles.windows"
|
||||
2. Add below config as entry (choose VS 2022 or VS 2026 based on your installation):
|
||||
### Configure Developer PowerShell for VS for more convenient development experience in VS Code
|
||||
1. Configure profile in settings, entry: `terminal.integrated.profiles.windows`
|
||||
2. Add below config as entry (choose VS 2026 or VS 2022 based on your installation):
|
||||
|
||||
**For Visual Studio 2026 (recommended):**
|
||||
```json
|
||||
"Developer PowerShell for VS": {
|
||||
// Configure based on your preference
|
||||
"path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe",
|
||||
"args": [
|
||||
"-NoExit",
|
||||
"-Command",
|
||||
"& {",
|
||||
"$orig = Get-Location;",
|
||||
// Adjust path based on your edition (Community/Professional/Enterprise)
|
||||
"& 'C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';",
|
||||
"Set-Location $orig",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
**For Visual Studio 2022:**
|
||||
```json
|
||||
@@ -37,24 +55,6 @@ VS Code extensions Needed:
|
||||
},
|
||||
```
|
||||
|
||||
**For Visual Studio 2026:**
|
||||
```json
|
||||
"Developer PowerShell for VS": {
|
||||
// Configure based on your preference
|
||||
"path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe",
|
||||
"args": [
|
||||
"-NoExit",
|
||||
"-Command",
|
||||
"& {",
|
||||
"$orig = Get-Location;",
|
||||
// Adjust path based on your edition (Community/Professional/Enterprise)
|
||||
"& 'C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';",
|
||||
"Set-Location $orig",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
3. [Optional] Set your Developer PowerShell profile as the default, so that you can get a deep integration with vscode coding agent.
|
||||
|
||||
4. Now you can build with plain `msbuild` or configure tasks.json in below section.
|
||||
|
||||
@@ -12,22 +12,11 @@ A PowerToy module is a self-contained utility integrated into the PowerToys ecos
|
||||
|
||||
### Requirements
|
||||
|
||||
- [Visual Studio 2026](https://visualstudio.microsoft.com/downloads/) and the following workloads/individual components:
|
||||
- Desktop Development with C++
|
||||
- WinUI application development
|
||||
- .NET desktop development
|
||||
- Windows 10 SDK (10.0.22621.0)
|
||||
- Windows 11 SDK (10.0.26100.3916)
|
||||
- .NET 8 SDK
|
||||
- Fork the [PowerToys repository](https://github.com/microsoft/PowerToys/tree/main) locally
|
||||
- [Validate that you are able to build and run](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/development/debugging.md) `PowerToys.slnx`.
|
||||
Follow the [Getting Started](../readme.md#getting-started) guide to set up your development environment, then [validate that you are able to build and run](debugging.md) `PowerToys.slnx`.
|
||||
|
||||
Optional:
|
||||
- [WiX v5 toolset](https://github.com/microsoft/PowerToys/tree/main) for the installer
|
||||
|
||||
> [!NOTE]
|
||||
> To ensure all the correct VS Workloads are installed, use [the WinGet configuration files](https://github.com/microsoft/PowerToys/tree/e13d6a78aafbcf32a4bb5f8581d041e1d057c3f1/.config) in the project repository. (Use the one that matches your VS distribution. ie: VS Community would use `configuration.winget`)
|
||||
|
||||
### Folder structure
|
||||
|
||||
```
|
||||
|
||||
197
doc/devdocs/events.md
Normal file
197
doc/devdocs/events.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# Telemetry Events
|
||||
|
||||
PowerToys collects limited telemetry to understand feature usage, reliability, and product quality. When adding a new telemetry event, follow the steps below to ensure the event is properly declared, documented, and available after release.
|
||||
|
||||
**⚠️ Important**: Telemetry must never include personal information, file paths, or user‑generated content.
|
||||
|
||||
## Developer Effort Overview (What to Expect)
|
||||
|
||||
Adding a telemetry event is a **multi-step process** that typically spans several areas of the codebase and documentation.
|
||||
|
||||
At a high level, developers should expect to:
|
||||
|
||||
1. Within one PR:
|
||||
1. Add a new telemetry event(s) to module
|
||||
1. Add the new event(s) DATA_AND_PRIVACY.md
|
||||
1. Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s)
|
||||
|
||||
### Privacy Guidelines
|
||||
|
||||
**NEVER** log:
|
||||
|
||||
- User data (text, files, emails, etc.)
|
||||
- File paths or filenames
|
||||
- Personal information
|
||||
- Sensitive system information
|
||||
- Anything that could identify a specific user
|
||||
|
||||
DO log:
|
||||
|
||||
- Feature usage (which features, how often)
|
||||
- Success/failure status
|
||||
- Timing/performance metrics
|
||||
- Error types (not error messages with user data)
|
||||
- Aggregate counts
|
||||
|
||||
### Event Naming Convention
|
||||
|
||||
Follow this pattern: `UtilityName_EventDescription`
|
||||
|
||||
Examples:
|
||||
|
||||
- `ColorPicker_Session`
|
||||
- `FancyZones_LayoutApplied`
|
||||
- `PowerRename_Rename`
|
||||
- `AdvancedPaste_FormatClicked`
|
||||
- `CmdPal_ExtensionInvoked`
|
||||
|
||||
## Adding Telemetry Events to PowerToys
|
||||
|
||||
PowerToys uses ETW (Event Tracing for Windows) for telemetry in both C++ and C# modules. The telemetry system is:
|
||||
|
||||
- Opt-in by default (disabled since v0.86)
|
||||
- Privacy-focused - never logs personal info, file paths, or user-generated content
|
||||
- Controlled by registry - HKEY_CURRENT_USER\Software\Classes\PowerToys\AllowDataDiagnostics
|
||||
|
||||
### C++ Telemetry Implementation
|
||||
|
||||
**Core Components**
|
||||
|
||||
| File | Purpose |
|
||||
| ------------- |:-------------:|
|
||||
| [ProjectTelemetry.h](../../src/common/Telemetry/ProjectTelemetry.h) | Declares the global ETW provider g_hProvider |
|
||||
| [TraceBase.h](../../src/common/Telemetry/TraceBase.h) | Base class with RegisterProvider(), UnregisterProvider(), and IsDataDiagnosticsEnabled() check |
|
||||
| [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Privacy tags and telemetry option group macros
|
||||
|
||||
|
||||
#### Pattern for C++ Modules
|
||||
|
||||
1. Create a `Trace` class inheriting from `telemetry::TraceBase` (src/common/Telemetry/TraceBase.h):
|
||||
|
||||
```c
|
||||
// trace.h
|
||||
#pragma once
|
||||
#include <common/Telemetry/TraceBase.h>
|
||||
|
||||
class Trace : public telemetry::TraceBase
|
||||
{
|
||||
public:
|
||||
static void MyEvent(/* parameters */);
|
||||
};
|
||||
```
|
||||
|
||||
2. Implement events using `TraceLoggingWriteWrapper`:
|
||||
|
||||
```cpp
|
||||
// trace.cpp
|
||||
#include "trace.h"
|
||||
#include <common/Telemetry/TraceBase.h>
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::MyEvent(bool enabled)
|
||||
{
|
||||
TraceLoggingWriteWrapper(
|
||||
g_hProvider,
|
||||
"ModuleName_EventName", // Event name
|
||||
TraceLoggingBoolean(enabled, "Enabled"), // Event data
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
```
|
||||
|
||||
**Key C++ Telemetry Macros**
|
||||
|
||||
| Macro | Purpose |
|
||||
| ------------- |:-------------:|
|
||||
| `TraceLoggingWriteWrapper` [CustomAction.cpp](../../installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp) | Wraps `TraceLoggingWrite` with `IsDataDiagnosticsEnabled()` check |
|
||||
| `ProjectTelemetryPrivacyDataTag(tag)` [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Sets privacy classification |
|
||||
|
||||
### C# Telemetry Implementation
|
||||
|
||||
**Core Components**
|
||||
|
||||
| File | Purpose |
|
||||
| ------------- |:-------------:|
|
||||
| [PowerToysTelemetry.cs](../../src/common/ManagedTelemetry/Telemetry/PowerToysTelemetry.cs) | Singleton `Log` instance with `WriteEvent<T>()` method |
|
||||
| [EventBase.cs](../../src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) | Base class for all events (provides `EventName`, `Version`) |
|
||||
| [IEvent.cs](../../src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) | Interface requiring `PartA_PrivTags` property |
|
||||
| [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Inherits from `EventSource`, defines ETW constants |
|
||||
| [DataDiagnosticsSettings.cs](../../src/common/ManagedTelemetry/Telemetry/DataDiagnosticsSettings.cs) | Registry-based enable/disable check
|
||||
|
||||
#### Pattern for C# Modules
|
||||
|
||||
1. Create an event class inheriting from `EventBase` and implementing `IEvent`:
|
||||
|
||||
```csharp
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace MyModule.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class MyModuleEvent : EventBase, IEvent
|
||||
{
|
||||
// Event properties (logged as telemetry data)
|
||||
public string SomeProperty { get; set; }
|
||||
public int SomeValue { get; set; }
|
||||
|
||||
// Required: Privacy tag
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
// Optional: Set EventName in constructor (defaults to class name)
|
||||
public MyModuleEvent(string prop, int val)
|
||||
{
|
||||
EventName = "MyModule_EventName";
|
||||
SomeProperty = prop;
|
||||
SomeValue = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Log the event:
|
||||
|
||||
```csharp
|
||||
PowerToysTelemetry.Log.WriteEvent(new MyModuleEvent("value", 42));
|
||||
```
|
||||
|
||||
**Privacy Tags (C#)**
|
||||
|
||||
| Tag | Use Case |
|
||||
| ------------- |:-------------:|
|
||||
| `PartA_PrivTags.ProductAndServiceUsage` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Feature usage events
|
||||
| `PartA_PrivTags.ProductAndServicePerformance` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Performance/timing events
|
||||
|
||||
### Update DATA_AND_PRIVACY.md file
|
||||
|
||||
Add your new event(s) to [DATA_AND_PRIVACY.md](../../DATA_AND_PRIVACY.md).
|
||||
|
||||
## Launch Product Version Containing the new events
|
||||
|
||||
Events do not become active until they ship in a released PowerToys version. After your PRs are merged:
|
||||
|
||||
- The event will begin firing once users install the version that includes it
|
||||
- In order for PowerToys to process these events, you must complete the next section
|
||||
|
||||
## Next Steps
|
||||
|
||||
Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s).
|
||||
|
||||
## Summary
|
||||
|
||||
Required steps:
|
||||
|
||||
1. In one PR:
|
||||
- Add the event(s) in code
|
||||
- Document event(s) in DATA_AND_PRIVACY.md
|
||||
1. Ship the change in a PowerToys release
|
||||
1. Reach out for next steps
|
||||
@@ -152,7 +152,7 @@ FancyZones is divided into several projects:
|
||||
## Development Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
- Visual Studio 2022 or 2026: Required for building and debugging
|
||||
- Visual Studio 2026 (or 2022 17.4+): Required for building and debugging
|
||||
- Windows 10 SDK: Ensure the latest version is installed
|
||||
- PowerToys Repository: Clone from GitHub
|
||||
|
||||
@@ -183,7 +183,7 @@ FancyZones is divided into several projects:
|
||||
## Debugging
|
||||
|
||||
### Setup for Debugging
|
||||
1. In Visual Studio 2022 or 2026, set FancyZonesEditor as the startup project
|
||||
1. In Visual Studio, set FancyZonesEditor as the startup project
|
||||
2. Set breakpoints in the code where needed
|
||||
3. Click Run to start debugging
|
||||
|
||||
|
||||
@@ -2,91 +2,31 @@
|
||||
|
||||
Welcome to the PowerToys developer documentation. This documentation provides information for developers who want to contribute to PowerToys or understand how it works.
|
||||
|
||||
## Core Architecture
|
||||
## Getting Started
|
||||
|
||||
- [Architecture Overview](core/architecture.md) - Overview of the PowerToys architecture and module interface
|
||||
- [Runner and System tray](core/runner.md) - Details about the PowerToys Runner process
|
||||
- [Settings](core/settings/readme.md) - Documentation on the settings system
|
||||
- [Installer](core/installer.md) - Information about the PowerToys installer
|
||||
- [Modules](modules/readme.md) - Documentation for individual PowerToys modules
|
||||
### Prerequisites
|
||||
|
||||
## Common Components
|
||||
1. Windows 10 April 2018 Update (version 1803) or newer
|
||||
1. [Visual Studio 2026](https://visualstudio.microsoft.com/downloads/) (recommended) or Visual Studio 2022 17.4+ with the following workloads/components:
|
||||
- Desktop Development with C++
|
||||
- WinUI application development
|
||||
- .NET desktop development
|
||||
- Windows 10 SDK (10.0.22621.0)
|
||||
- Windows 11 SDK (10.0.26100.3916)
|
||||
1. .NET 8 SDK
|
||||
1. Enable long paths in Windows (see [Enable Long Paths](https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation#enabling-long-paths-in-windows-10-version-1607-and-later) for details)
|
||||
|
||||
- [Context Menu Handlers](common/context-menus.md) - How PowerToys implements and registers Explorer context menu handlers
|
||||
- [Monaco Editor](common/monaco-editor.md) - How PowerToys uses the Monaco code editor component across modules
|
||||
- [Logging and Telemetry](development/logging.md) - How to use logging and telemetry
|
||||
- [Localization](development/localization.md) - How to support multiple languages
|
||||
> **Tip:** You can install Visual Studio with all required workloads automatically using the [WinGet configuration files](https://github.com/microsoft/PowerToys/tree/main/.config) in the repository:
|
||||
> ```powershell
|
||||
> winget configure .config\configuration.winget
|
||||
> ```
|
||||
> Pick the file that matches your VS edition (e.g., `configuration.vsProfessional.winget` or `configuration.vsEnterprise.winget`).
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
- [Coding Guidelines](development/guidelines.md) - Development guidelines and best practices
|
||||
- [Coding Style](development/style.md) - Code formatting and style conventions
|
||||
- [UI Testing](development/ui-tests.md) - How to write UI tests for PowerToys
|
||||
- [Debugging](development/debugging.md) - Techniques for debugging PowerToys
|
||||
|
||||
## Tools
|
||||
|
||||
- [Tools Overview](tools/readme.md) - Overview of tools in PowerToys
|
||||
- [Build Tools](tools/build-tools.md) - Tools that help building PowerToys
|
||||
- [Bug Report Tool](tools/bug-report-tool.md) - Tool for collecting logs and system information
|
||||
- [Debugging Tools](tools/debugging-tools.md) - Specialized tools for debugging
|
||||
- [Fuzzing Testing](tools/fuzzingtesting.md) - How to implement and run fuzz testing for PowerToys modules
|
||||
|
||||
## Processes
|
||||
|
||||
- [Release Process](processes/release-process.md) - How PowerToys releases are prepared and published
|
||||
- [Update Process](processes/update-process.md) - How PowerToys updates work
|
||||
- [GPO Implementation](processes/gpo.md) - Group Policy Objects implementation details
|
||||
|
||||
## Other Resources
|
||||
|
||||
- [aka.ms links](akaLinks.md) - List of short links
|
||||
- [Issue/PR commands](commands.md) - Special commands for managing issues and pull requests
|
||||
|
||||
## Fork, Clone, Branch and Create your PR
|
||||
|
||||
Once you've discussed your proposed feature/fix/etc. with a team member, and an approach or a spec has been written and approved, it's time to start development:
|
||||
### Fork, Clone, and Set Up
|
||||
|
||||
1. Fork the repo on GitHub if you haven't already
|
||||
1. Clone your fork locally
|
||||
1. Create a feature branch
|
||||
1. Work on your changes
|
||||
1. Create a [Draft Pull Request (PR)](https://github.blog/2019-02-14-introducing-draft-pull-requests/)
|
||||
1. When ready, mark your PR as "ready for review".
|
||||
|
||||
## Rules
|
||||
|
||||
- **Follow the pattern of what you already see in the code.**
|
||||
- [Coding style](development/style.md).
|
||||
- Try to package new functionality/components into libraries that have nicely defined interfaces.
|
||||
- Package new functionality into classes or refactor existing functionality into a class as you extend the code.
|
||||
- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests.
|
||||
|
||||
## GitHub Workflow
|
||||
|
||||
- Before starting to work on a fix/feature, make sure there is an open issue to track the work.
|
||||
- Add the `In progress` label to the issue, if not already present. Also add a `Cost-Small/Medium/Large` estimate and make sure all appropriate labels are set.
|
||||
- If you are a community contributor, you will not be able to add labels to the issue; in that case just add a comment saying that you have started work on the issue and try to give an estimate for the delivery date.
|
||||
- If the work item has a medium/large cost, using the markdown task list, list each sub item and update the list with a check mark after completing each sub item.
|
||||
- **Before opening a PR, ensure your changes build successfully locally and functionality tests pass.** This is especially important for AI-assisted (vibe coding) contributions—always verify AI-generated code works as intended. Exploratory PRs or draft PRs for discussion are exceptions.
|
||||
- When opening a PR, follow the PR template.
|
||||
- When you'd like the team to take a look (even if the work is not yet fully complete) mark the PR as 'Ready For Review' so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge.
|
||||
- When the PR is approved, let the owner of the PR merge it. For community contributions, the reviewer who approved the PR can also merge it.
|
||||
- Use the `Squash and merge` option to merge a PR. If you don't want to squash it because there are logically different commits, use `Rebase and merge`.
|
||||
- Close issues automatically when referenced in a PR. You can use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) in the body of the PR to have GitHub automatically link your PR to the issue.
|
||||
|
||||
## Compiling PowerToys
|
||||
|
||||
### Prerequisites for Compiling PowerToys
|
||||
|
||||
1. Windows 10 April 2018 Update (version 1803) or newer
|
||||
1. Visual Studio Community/Professional/Enterprise 2022 17.4 or newer, or Visual Studio 2026
|
||||
1. A local clone of the PowerToys repository
|
||||
1. Enable long paths in Windows (see [Enable Long Paths](https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation#enabling-long-paths-in-windows-10-version-1607-and-later) for details)
|
||||
|
||||
### Automated Setup (Recommended)
|
||||
|
||||
Run the setup script to automatically configure your development environment:
|
||||
1. Run the automated setup script (**recommended**):
|
||||
|
||||
```powershell
|
||||
.\tools\build\setup-dev-environment.ps1
|
||||
@@ -98,15 +38,10 @@ This script will:
|
||||
- Guide you through installing required Visual Studio components from `.vsconfig`
|
||||
- Initialize git submodules
|
||||
|
||||
Run with `-Help` to see all available options:
|
||||
Run with `-Help` to see all available options.
|
||||
|
||||
```powershell
|
||||
.\tools\build\setup-dev-environment.ps1 -Help
|
||||
```
|
||||
|
||||
### Manual Setup
|
||||
|
||||
If you prefer to set up manually, follow these steps:
|
||||
<details>
|
||||
<summary><strong>Manual setup (if you prefer not to use the script)</strong></summary>
|
||||
|
||||
#### Install Visual Studio dependencies
|
||||
|
||||
@@ -115,15 +50,17 @@ If you prefer to set up manually, follow these steps:
|
||||
|
||||
Alternatively, import the `.vsconfig` file from the repository root using Visual Studio Installer to install all required workloads.
|
||||
|
||||
#### Get Submodules to compile
|
||||
#### Initialize submodules
|
||||
|
||||
We have submodules that need to be initialized before you can compile most parts of PowerToys. This should be a one-time step.
|
||||
This is a one-time step required before you can compile most parts of PowerToys.
|
||||
|
||||
1. Open a terminal
|
||||
1. Navigate to the folder you cloned PowerToys to.
|
||||
1. Run `git submodule update --init --recursive`
|
||||
|
||||
### Compiling Source Code
|
||||
</details>
|
||||
|
||||
### Building
|
||||
|
||||
#### Using Visual Studio
|
||||
|
||||
@@ -151,7 +88,77 @@ You can also build from the command line using the provided scripts in `tools\bu
|
||||
.\tools\build\build-installer.ps1
|
||||
```
|
||||
|
||||
## Compile the installer
|
||||
### Debugging
|
||||
|
||||
See [Debugging](development/debugging.md) for detailed debugging techniques, including Visual Studio setup, attaching to child processes, and troubleshooting build errors.
|
||||
|
||||
### Creating a New PowerToy
|
||||
|
||||
See [Creating a New PowerToy](development/new-powertoy.md) for an end-to-end guide covering module architecture, settings integration, installer packaging, and testing.
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
- [Coding Guidelines](development/guidelines.md) - Development guidelines and best practices
|
||||
- [Coding Style](development/style.md) - Code formatting and style conventions
|
||||
- [Logging and Telemetry](development/logging.md) - How to use logging and telemetry
|
||||
- [Localization](development/localization.md) - How to support multiple languages
|
||||
- [UI Testing](development/ui-tests.md) - How to write UI tests for PowerToys
|
||||
- [Developing with VS Code](development/dev-with-vscode.md) - Build, debug, and contribute using VS Code
|
||||
|
||||
## Rules
|
||||
|
||||
- **Follow the pattern of what you already see in the code.**
|
||||
- [Coding style](development/style.md).
|
||||
- Try to package new functionality/components into libraries that have nicely defined interfaces.
|
||||
- Package new functionality into classes or refactor existing functionality into a class as you extend the code.
|
||||
- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests.
|
||||
|
||||
## GitHub Workflow
|
||||
|
||||
- Before starting to work on a fix/feature, make sure there is an open issue to track the work.
|
||||
- Add the `In progress` label to the issue, if not already present. Also add a `Cost-Small/Medium/Large` estimate and make sure all appropriate labels are set.
|
||||
- If you are a community contributor, you will not be able to add labels to the issue; in that case just add a comment saying that you have started work on the issue and try to give an estimate for the delivery date.
|
||||
- If the work item has a medium/large cost, using the markdown task list, list each sub item and update the list with a check mark after completing each sub item.
|
||||
- **Before opening a PR, ensure your changes build successfully locally and functionality tests pass.** This is especially important for AI-assisted (vibe coding) contributions—always verify AI-generated code works as intended. Exploratory PRs or draft PRs for discussion are exceptions.
|
||||
- When opening a PR, follow the PR template.
|
||||
- When you'd like the team to take a look (even if the work is not yet fully complete) mark the PR as 'Ready For Review' so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge.
|
||||
- When the PR is approved, let the owner of the PR merge it. For community contributions, the reviewer who approved the PR can also merge it.
|
||||
- Use the `Squash and merge` option to merge a PR. If you don't want to squash it because there are logically different commits, use `Rebase and merge`.
|
||||
- Close issues automatically when referenced in a PR. You can use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) in the body of the PR to have GitHub automatically link your PR to the issue.
|
||||
|
||||
## Core Architecture
|
||||
|
||||
- [Architecture Overview](core/architecture.md) - Overview of the PowerToys architecture and module interface
|
||||
- [Runner and System tray](core/runner.md) - Details about the PowerToys Runner process
|
||||
- [Settings](core/settings/readme.md) - Documentation on the settings system
|
||||
- [Installer](core/installer.md) - Information about the PowerToys installer
|
||||
- [Modules](modules/readme.md) - Documentation for individual PowerToys modules
|
||||
|
||||
## Common Components
|
||||
|
||||
- [Context Menu Handlers](common/context-menus.md) - How PowerToys implements and registers Explorer context menu handlers
|
||||
- [Monaco Editor](common/monaco-editor.md) - How PowerToys uses the Monaco code editor component across modules
|
||||
|
||||
## Tools
|
||||
|
||||
- [Tools Overview](tools/readme.md) - Overview of tools in PowerToys
|
||||
- [Build Tools](tools/build-tools.md) - Tools that help building PowerToys
|
||||
- [Bug Report Tool](tools/bug-report-tool.md) - Tool for collecting logs and system information
|
||||
- [Debugging Tools](tools/debugging-tools.md) - Specialized tools for debugging
|
||||
- [Fuzzing Testing](tools/fuzzingtesting.md) - How to implement and run fuzz testing for PowerToys modules
|
||||
|
||||
## Processes
|
||||
|
||||
- [Release Process](processes/release-process.md) - How PowerToys releases are prepared and published
|
||||
- [Update Process](processes/update-process.md) - How PowerToys updates work
|
||||
- [GPO Implementation](processes/gpo.md) - Group Policy Objects implementation details
|
||||
|
||||
## Other Resources
|
||||
|
||||
- [aka.ms links](akaLinks.md) - List of short links
|
||||
- [Issue/PR commands](commands.md) - Special commands for managing issues and pull requests
|
||||
|
||||
## Building the Installer
|
||||
|
||||
Our installer is two parts, an EXE and an MSI. The EXE (Bootstrapper) contains the MSI and handles more complex installation logic.
|
||||
- The EXE installs all prerequisites and installs PowerToys via the MSI. It has additional features such as the installation flags (see below).
|
||||
@@ -165,8 +172,3 @@ The installer can only be compiled in `Release` mode; steps 1 and 2 must be perf
|
||||
1. Compile `PowerToysSetup.slnx` Path from root: `installer\PowerToysSetup.slnx` (details listed below)
|
||||
|
||||
See [Installer](core/installer.md) for more details on building and debugging the installer.
|
||||
|
||||
## How to create new PowerToys
|
||||
|
||||
See the instructions on [how to install the PowerToys Module project template](/tools/project_template). <br />
|
||||
Specifications for the [PowerToys settings API](core/settings/readme.md).
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
@@ -34,8 +34,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
}
|
||||
|
||||
// Check if model is in catalog
|
||||
var isInCatalog = _catalogModels?.Any(m => m.Name == modelId) ?? false;
|
||||
if (!isInCatalog)
|
||||
if (!EnsureModelInCatalog(modelId))
|
||||
{
|
||||
var errorMessage = $"{modelId} is not supported in Foundry Local. Please configure supported models in Settings.";
|
||||
Logger.LogError($"[FoundryLocal] {errorMessage}");
|
||||
@@ -43,15 +42,28 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
}
|
||||
|
||||
// Ensure the model is loaded before returning chat client
|
||||
var isLoaded = _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
|
||||
var isLoaded = EnsureModelLoadedWithRefresh(modelId);
|
||||
if (!isLoaded)
|
||||
{
|
||||
Logger.LogError($"[FoundryLocal] Failed to load model: {modelId}");
|
||||
throw new InvalidOperationException($"Failed to load the model '{modelId}'.");
|
||||
}
|
||||
|
||||
var client = _foundryClient;
|
||||
if (client == null)
|
||||
{
|
||||
const string message = "Foundry Local client could not be created. Please make sure Foundry Local is installed and running.";
|
||||
Logger.LogError($"[FoundryLocal] {message}");
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
// Use ServiceUri instead of Endpoint since Endpoint already includes /v1
|
||||
var baseUri = _foundryClient.GetServiceUri();
|
||||
var baseUri = client.GetServiceUri();
|
||||
if (baseUri == null && TryRefreshClient("Service URI was not available"))
|
||||
{
|
||||
baseUri = _foundryClient?.GetServiceUri();
|
||||
}
|
||||
|
||||
if (baseUri == null)
|
||||
{
|
||||
const string message = "Foundry Local service URL is not available. Please make sure Foundry Local is installed and running.";
|
||||
@@ -124,6 +136,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
if (_foundryClient != null && _catalogModels != null && _catalogModels.Any())
|
||||
{
|
||||
await _foundryClient.EnsureRunning().ConfigureAwait(false);
|
||||
_serviceUrl = await _foundryClient.GetServiceUrl().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,4 +166,75 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
Logger.LogInfo($"[FoundryLocal] Available: {available}");
|
||||
return available;
|
||||
}
|
||||
|
||||
private bool EnsureModelInCatalog(string modelId)
|
||||
{
|
||||
var isInCatalog = _catalogModels?.Any(m => m.Name == modelId) ?? false;
|
||||
if (isInCatalog)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger.LogWarning($"[FoundryLocal] Model not found in catalog. Refreshing client for model: {modelId}");
|
||||
if (!TryRefreshClient("Model not in catalog"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _catalogModels?.Any(m => m.Name == modelId) ?? false;
|
||||
}
|
||||
|
||||
private bool EnsureModelLoadedWithRefresh(string modelId)
|
||||
{
|
||||
var isLoaded = false;
|
||||
|
||||
try
|
||||
{
|
||||
isLoaded = _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"[FoundryLocal] EnsureModelLoaded failed: {ex.Message}");
|
||||
}
|
||||
|
||||
if (isLoaded)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TryRefreshClient("EnsureModelLoaded failed"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"[FoundryLocal] EnsureModelLoaded failed after refresh: {ex.Message}", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryRefreshClient(string reason)
|
||||
{
|
||||
Logger.LogInfo($"[FoundryLocal] Refreshing Foundry Local client: {reason}");
|
||||
|
||||
try
|
||||
{
|
||||
_foundryClient = null;
|
||||
_catalogModels = null;
|
||||
_serviceUrl = null;
|
||||
|
||||
InitializeAsync().GetAwaiter().GetResult();
|
||||
return _foundryClient != null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"[FoundryLocal] Failed to refresh Foundry Local client: {ex.Message}", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
{
|
||||
public ExtensionObject<ICommand> Model { get; private set; } = new(null);
|
||||
|
||||
public bool IsSet => Model.Unsafe is not null;
|
||||
|
||||
protected bool IsInitialized { get; private set; }
|
||||
|
||||
protected bool IsFastInitialized { get; private set; }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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 enum ListItemType
|
||||
{
|
||||
Item,
|
||||
SectionHeader,
|
||||
Separator,
|
||||
}
|
||||
@@ -24,7 +24,9 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
|
||||
public string Section { get; private set; } = string.Empty;
|
||||
|
||||
public bool IsSectionOrSeparator { get; private set; }
|
||||
public ListItemType Type { get; private set; }
|
||||
|
||||
public bool IsInteractive => Type == ListItemType.Item;
|
||||
|
||||
public DetailsViewModel? Details { get; private set; }
|
||||
|
||||
@@ -85,15 +87,17 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
|
||||
UpdateTags(li.Tags);
|
||||
Section = li.Section ?? string.Empty;
|
||||
IsSectionOrSeparator = IsSeparator(li);
|
||||
UpdateProperty(nameof(Section), nameof(IsSectionOrSeparator));
|
||||
Type = EvaluateType();
|
||||
UpdateProperty(nameof(Section), nameof(Type), nameof(IsInteractive));
|
||||
|
||||
UpdateAccessibleName();
|
||||
}
|
||||
|
||||
private bool IsSeparator(IListItem item)
|
||||
private ListItemType EvaluateType()
|
||||
{
|
||||
return item.Command is null;
|
||||
return Command.IsSet
|
||||
? ListItemType.Item
|
||||
: string.IsNullOrEmpty(Section) ? ListItemType.Separator : ListItemType.SectionHeader;
|
||||
}
|
||||
|
||||
public override void SlowInitializeProperties()
|
||||
@@ -140,12 +144,12 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
break;
|
||||
case nameof(model.Section):
|
||||
Section = model.Section ?? string.Empty;
|
||||
IsSectionOrSeparator = IsSeparator(model);
|
||||
UpdateProperty(nameof(Section), nameof(IsSectionOrSeparator));
|
||||
Type = EvaluateType();
|
||||
UpdateProperty(nameof(Section), nameof(Type), nameof(IsInteractive));
|
||||
break;
|
||||
case nameof(model.Command):
|
||||
IsSectionOrSeparator = IsSeparator(model);
|
||||
UpdateProperty(nameof(IsSectionOrSeparator));
|
||||
Type = EvaluateType();
|
||||
UpdateProperty(nameof(Type), nameof(IsInteractive));
|
||||
break;
|
||||
case nameof(Details):
|
||||
var extensionDetails = model.Details;
|
||||
|
||||
@@ -13,6 +13,7 @@ using Microsoft.CmdPal.Ext.Bookmarks;
|
||||
using Microsoft.CmdPal.Ext.Calc;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory;
|
||||
using Microsoft.CmdPal.Ext.Indexer;
|
||||
using Microsoft.CmdPal.Ext.PerformanceMonitor;
|
||||
using Microsoft.CmdPal.Ext.Registry;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop;
|
||||
using Microsoft.CmdPal.Ext.Shell;
|
||||
@@ -177,6 +178,7 @@ public partial class App : Application, IDisposable
|
||||
services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>();
|
||||
services.AddSingleton<ICommandProvider, RemoteDesktopCommandProvider>();
|
||||
services.AddSingleton<ICommandProvider, PerformanceMonitorCommandsProvider>();
|
||||
}
|
||||
|
||||
private static void AddUIServices(ServiceCollection services, DispatcherQueue dispatcherQueue)
|
||||
|
||||
@@ -24,11 +24,19 @@ internal sealed partial class GridItemContainerStyleSelector : StyleSelector
|
||||
|
||||
protected override Style? SelectStyleCore(object item, DependencyObject container)
|
||||
{
|
||||
if (item is ListItemViewModel { IsSectionOrSeparator: true } listItem)
|
||||
if (item is not ListItemViewModel element)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(listItem.Title)
|
||||
? Separator!
|
||||
: Section;
|
||||
return Medium;
|
||||
}
|
||||
|
||||
switch (element.Type)
|
||||
{
|
||||
case ListItemType.Separator:
|
||||
return Separator;
|
||||
case ListItemType.SectionHeader:
|
||||
return Section;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return GridProperties switch
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
@@ -24,15 +25,19 @@ internal sealed partial class GridItemTemplateSelector : DataTemplateSelector
|
||||
|
||||
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
|
||||
{
|
||||
if (item is ListItemViewModel element && element.IsSectionOrSeparator)
|
||||
if (item is not ListItemViewModel element)
|
||||
{
|
||||
if (dependencyObject is UIElement li)
|
||||
{
|
||||
li.IsTabStop = false;
|
||||
li.IsHitTestVisible = false;
|
||||
}
|
||||
return Medium;
|
||||
}
|
||||
|
||||
return string.IsNullOrWhiteSpace(element.Section) ? Separator : Section;
|
||||
switch (element.Type)
|
||||
{
|
||||
case ListItemType.Separator:
|
||||
return Separator;
|
||||
case ListItemType.SectionHeader:
|
||||
return Section;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return GridProperties switch
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
@@ -18,11 +19,20 @@ internal sealed partial class ListItemContainerStyleSelector : StyleSelector
|
||||
|
||||
protected override Style? SelectStyleCore(object item, DependencyObject container)
|
||||
{
|
||||
return item switch
|
||||
if (item is not ListItemViewModel element)
|
||||
{
|
||||
ListItemViewModel { IsSectionOrSeparator: true } listItemViewModel when string.IsNullOrWhiteSpace(listItemViewModel.Title) => Separator!,
|
||||
ListItemViewModel { IsSectionOrSeparator: true } => Section,
|
||||
_ => Default,
|
||||
};
|
||||
return Default;
|
||||
}
|
||||
|
||||
switch (element.Type)
|
||||
{
|
||||
case ListItemType.Separator:
|
||||
return Separator;
|
||||
case ListItemType.SectionHeader:
|
||||
return Section;
|
||||
case ListItemType.Item:
|
||||
default:
|
||||
return Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,30 +18,20 @@ public sealed partial class ListItemTemplateSelector : DataTemplateSelector
|
||||
|
||||
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
DataTemplate? dataTemplate = ListItem;
|
||||
|
||||
if (container is ListViewItem listItem)
|
||||
if (item is not ListItemViewModel element)
|
||||
{
|
||||
if (item is ListItemViewModel element)
|
||||
{
|
||||
if (container is ListViewItem li && element.IsSectionOrSeparator)
|
||||
{
|
||||
li.IsEnabled = false;
|
||||
li.AllowFocusWhenDisabled = false;
|
||||
li.AllowFocusOnInteraction = false;
|
||||
li.IsHitTestVisible = false;
|
||||
dataTemplate = string.IsNullOrWhiteSpace(element.Section) ? Separator : Section;
|
||||
}
|
||||
else
|
||||
{
|
||||
listItem.IsEnabled = true;
|
||||
listItem.AllowFocusWhenDisabled = true;
|
||||
listItem.AllowFocusOnInteraction = true;
|
||||
listItem.IsHitTestVisible = true;
|
||||
}
|
||||
}
|
||||
return ListItem;
|
||||
}
|
||||
|
||||
return dataTemplate;
|
||||
switch (element.Type)
|
||||
{
|
||||
case ListItemType.Separator:
|
||||
return Separator;
|
||||
case ListItemType.SectionHeader:
|
||||
return Section;
|
||||
case ListItemType.Item:
|
||||
default:
|
||||
return ListItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,23 +206,30 @@
|
||||
x:Key="ListSectionContainerStyle"
|
||||
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
||||
TargetType="ListViewItem">
|
||||
<Setter Property="IsEnabled" Value="False" />
|
||||
<Setter Property="AllowFocusWhenDisabled" Value="False" />
|
||||
<Setter Property="AllowFocusOnInteraction" Value="False" />
|
||||
<Setter Property="IsHitTestVisible" Value="False" />
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="IsHoldingEnabled" Value="False" />
|
||||
<Setter Property="AllowDrop" Value="False" />
|
||||
<Setter Property="Padding" Value="16,8,12,0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Bottom" />
|
||||
<Setter Property="MinHeight" Value="{StaticResource ListViewSectionMinHeight}" />
|
||||
<Setter Property="AllowDrop" Value="False" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="ListSeparatorContainerStyle"
|
||||
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
||||
TargetType="ListViewItem">
|
||||
<Setter Property="IsEnabled" Value="False" />
|
||||
<Setter Property="AllowFocusWhenDisabled" Value="False" />
|
||||
<Setter Property="AllowFocusOnInteraction" Value="False" />
|
||||
<Setter Property="IsHitTestVisible" Value="False" />
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="IsHoldingEnabled" Value="False" />
|
||||
<Setter Property="AllowDrop" Value="False" />
|
||||
<Setter Property="Padding" Value="16,4,12,4" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
|
||||
@@ -1026,10 +1026,7 @@ public sealed partial class ListPage : Page,
|
||||
ItemView.SelectedIndex = newIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Code stealed from <see cref="Controls.ContextMenu.IsSeparator(object)"/>
|
||||
/// </summary>
|
||||
private bool IsSeparator(object? item) => item is ListItemViewModel li && li.IsSectionOrSeparator;
|
||||
private bool IsSeparator(object? item) => item is ListItemViewModel li && !li.IsInteractive;
|
||||
|
||||
private enum InputSource
|
||||
{
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Apps\Microsoft.CmdPal.Ext.Apps.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Bookmark\Microsoft.CmdPal.Ext.Bookmarks.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Calc\Microsoft.CmdPal.Ext.Calc.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.PerformanceMonitor\Microsoft.CmdPal.Ext.PerformanceMonitor.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Registry\Microsoft.CmdPal.Ext.Registry.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Shell\Microsoft.CmdPal.Ext.Shell.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.TimeDate\Microsoft.CmdPal.Ext.TimeDate.csproj" />
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -774,4 +774,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="Settings_AppearancePage_ResetAppearanceButton_Text.Text" xml:space="preserve">
|
||||
<value>Reset to defaults</value>
|
||||
</data>
|
||||
<data name="Settings_ExtensionsPage_More_Button.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>More options</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 CoreWidgetProvider.Widgets.Enums;
|
||||
|
||||
public enum WidgetDataState
|
||||
{
|
||||
Unknown,
|
||||
Requested, // Request is out, waiting on a response. Current data is stale.
|
||||
Okay, // Received and updated data, stable state.
|
||||
Failed, // Failed retrieving data.
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 CoreWidgetProvider.Widgets.Enums;
|
||||
|
||||
public enum WidgetPageState
|
||||
{
|
||||
Unknown,
|
||||
Configure,
|
||||
Loading,
|
||||
Content,
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace CoreWidgetProvider.Helpers;
|
||||
|
||||
internal sealed partial class CPUStats : IDisposable
|
||||
{
|
||||
// CPU counters
|
||||
private readonly PerformanceCounter _procPerf = new("Processor Information", "% Processor Utility", "_Total");
|
||||
private readonly PerformanceCounter _procPerformance = new("Processor Information", "% Processor Performance", "_Total");
|
||||
private readonly PerformanceCounter _procFrequency = new("Processor Information", "Processor Frequency", "_Total");
|
||||
private readonly Dictionary<Process, PerformanceCounter> _cpuCounters = new();
|
||||
|
||||
internal sealed class ProcessStats
|
||||
{
|
||||
public Process? Process { get; set; }
|
||||
|
||||
public float CpuUsage { get; set; }
|
||||
}
|
||||
|
||||
public float CpuUsage { get; set; }
|
||||
|
||||
public float CpuSpeed { get; set; }
|
||||
|
||||
public ProcessStats[] ProcessCPUStats { get; set; }
|
||||
|
||||
public List<float> CpuChartValues { get; set; } = new();
|
||||
|
||||
public CPUStats()
|
||||
{
|
||||
CpuUsage = 0;
|
||||
ProcessCPUStats =
|
||||
[
|
||||
new ProcessStats(),
|
||||
new ProcessStats(),
|
||||
new ProcessStats()
|
||||
];
|
||||
|
||||
InitCPUPerfCounters();
|
||||
}
|
||||
|
||||
private void InitCPUPerfCounters()
|
||||
{
|
||||
var allProcesses = Process.GetProcesses().Where(p => (long)p.MainWindowHandle != 0);
|
||||
|
||||
foreach (var process in allProcesses)
|
||||
{
|
||||
_cpuCounters.Add(process, new PerformanceCounter("Process", "% Processor Time", process.ProcessName, true));
|
||||
}
|
||||
}
|
||||
|
||||
public void GetData(bool includeTopProcesses)
|
||||
{
|
||||
var timer = Stopwatch.StartNew();
|
||||
CpuUsage = _procPerf.NextValue() / 100;
|
||||
var usageMs = timer.ElapsedMilliseconds;
|
||||
CpuSpeed = _procFrequency.NextValue() * (_procPerformance.NextValue() / 100);
|
||||
var speedMs = timer.ElapsedMilliseconds - usageMs;
|
||||
lock (CpuChartValues)
|
||||
{
|
||||
ChartHelper.AddNextChartValue(CpuUsage * 100, CpuChartValues);
|
||||
}
|
||||
|
||||
var chartMs = timer.ElapsedMilliseconds - speedMs;
|
||||
|
||||
var processCPUUsages = new Dictionary<Process, float>();
|
||||
|
||||
if (includeTopProcesses)
|
||||
{
|
||||
foreach (var processCounter in _cpuCounters)
|
||||
{
|
||||
try
|
||||
{
|
||||
// process might be terminated
|
||||
processCPUUsages.Add(processCounter.Key, processCounter.Value.NextValue() / Environment.ProcessorCount);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// _log.Information($"ProcessCounter Key {processCounter.Key} no longer exists, removing from _cpuCounters.");
|
||||
_cpuCounters.Remove(processCounter.Key);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// _log.Error(ex, "Error going through process counters.");
|
||||
}
|
||||
}
|
||||
|
||||
var cpuIndex = 0;
|
||||
foreach (var processCPUValue in processCPUUsages.OrderByDescending(x => x.Value).Take(3))
|
||||
{
|
||||
ProcessCPUStats[cpuIndex].Process = processCPUValue.Key;
|
||||
ProcessCPUStats[cpuIndex].CpuUsage = processCPUValue.Value;
|
||||
cpuIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
timer.Stop();
|
||||
var total = timer.ElapsedMilliseconds;
|
||||
var processesMs = total - chartMs;
|
||||
|
||||
// CoreLogger.LogDebug($"[{usageMs}]+[{speedMs}]+[{chartMs}]+[{processesMs}]=[{total}]");
|
||||
}
|
||||
|
||||
internal string CreateCPUImageUrl()
|
||||
{
|
||||
return ChartHelper.CreateImageUrl(CpuChartValues, ChartHelper.ChartType.CPU);
|
||||
}
|
||||
|
||||
internal string GetCpuProcessText(int cpuProcessIndex)
|
||||
{
|
||||
if (cpuProcessIndex >= ProcessCPUStats.Length)
|
||||
{
|
||||
return "no data";
|
||||
}
|
||||
|
||||
return $"{ProcessCPUStats[cpuProcessIndex].Process?.ProcessName} ({ProcessCPUStats[cpuProcessIndex].CpuUsage / 100:p})";
|
||||
}
|
||||
|
||||
internal void KillTopProcess(int cpuProcessIndex)
|
||||
{
|
||||
if (cpuProcessIndex >= ProcessCPUStats.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessCPUStats[cpuProcessIndex].Process?.Kill();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_procPerf.Dispose();
|
||||
_procPerformance.Dispose();
|
||||
_procFrequency.Dispose();
|
||||
|
||||
foreach (var counter in _cpuCounters.Values)
|
||||
{
|
||||
counter.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace CoreWidgetProvider.Helpers;
|
||||
|
||||
internal sealed class ChartHelper
|
||||
{
|
||||
public enum ChartType
|
||||
{
|
||||
CPU,
|
||||
GPU,
|
||||
Mem,
|
||||
Net,
|
||||
}
|
||||
|
||||
public const int ChartHeight = 86;
|
||||
public const int ChartWidth = 268;
|
||||
|
||||
private const string LightGrayBoxStyle = "fill:none;stroke:lightgrey;stroke-width:1";
|
||||
|
||||
private const string CPULineStyle = "fill:none;stroke:rgb(57,184,227);stroke-width:1";
|
||||
private const string GPULineStyle = "fill:none;stroke:rgb(222,104,242);stroke-width:1";
|
||||
private const string MemLineStyle = "fill:none;stroke:rgb(92,158,250);stroke-width:1";
|
||||
private const string NetLineStyle = "fill:none;stroke:rgb(245,98,142);stroke-width:1";
|
||||
|
||||
private const string FillStyle = "fill:url(#gradientId);stroke:transparent";
|
||||
|
||||
private const string CPUBrushStop1Style = "stop-color:rgb(57,184,227);stop-opacity:0.4";
|
||||
private const string CPUBrushStop2Style = "stop-color:rgb(0,86,110);stop-opacity:0.25";
|
||||
|
||||
private const string GPUBrushStop1Style = "stop-color:rgb(222,104,242);stop-opacity:0.4";
|
||||
private const string GPUBrushStop2Style = "stop-color:rgb(125,0,138);stop-opacity:0.25";
|
||||
|
||||
private const string MemBrushStop1Style = "stop-color:rgb(92,158,250);stop-opacity:0.4";
|
||||
private const string MemBrushStop2Style = "stop-color:rgb(0,34,92);stop-opacity:0.25";
|
||||
|
||||
private const string NetBrushStop1Style = "stop-color:rgb(245,98,142);stop-opacity:0.4";
|
||||
private const string NetBrushStop2Style = "stop-color:rgb(130,0,47);stop-opacity:0.25";
|
||||
|
||||
private const string SvgElement = "svg";
|
||||
private const string RectElement = "rect";
|
||||
private const string PolylineElement = "polyline";
|
||||
private const string DefsElement = "defs";
|
||||
private const string LinearGradientElement = "linearGradient";
|
||||
private const string StopElement = "stop";
|
||||
|
||||
private const string HeightAttr = "height";
|
||||
private const string WidthAttr = "width";
|
||||
private const string StyleAttr = "style";
|
||||
private const string PointsAttr = "points";
|
||||
private const string OffsetAttr = "offset";
|
||||
private const string X1Attr = "x1";
|
||||
private const string X2Attr = "x2";
|
||||
private const string Y1Attr = "y1";
|
||||
private const string Y2Attr = "y2";
|
||||
private const string IdAttr = "id";
|
||||
|
||||
private const int MaxChartValues = 34;
|
||||
|
||||
public static string CreateImageUrl(List<float> chartValues, ChartType type)
|
||||
{
|
||||
var chartStr = CreateChart(chartValues, type);
|
||||
return "data:image/svg+xml;utf8," + chartStr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an SVG image for the chart.
|
||||
/// </summary>
|
||||
/// <param name="chartValues">The values to plot on the chart</param>
|
||||
/// <param name="type">The type of chart. Each chart type uses different colors.</param>
|
||||
/// <remarks>
|
||||
/// The SVG is made of three shapes: <br/>
|
||||
/// 1. A colored line, plotting the points on the graph <br/>
|
||||
/// 2. A transparent line, outlining the gradient under the graph <br/>
|
||||
/// 3. A grey box, outlining the entire image <br/>
|
||||
/// The SVG also contains a definition for the fill gradient.
|
||||
/// </remarks>
|
||||
/// <returns>A string representing the chart as an SVG image.</returns>
|
||||
public static string CreateChart(List<float> chartValues, ChartType type)
|
||||
{
|
||||
// The SVG created by this method will look similar to this:
|
||||
/*
|
||||
<svg height="102" width="264">
|
||||
<defs>
|
||||
<linearGradient x1="0%" x2="0%" y1="0%" y2="100%" id="gradientId">
|
||||
<stop offset="0%" style="stop-color:rgb(222,104,242);stop-opacity:0.4" />
|
||||
<stop offset="95%" style="stop-color:rgb(125,0,138);stop-opacity:0.25" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<polyline points="1,91 10,71 253,51 262,31 262,101 1,101" style="fill:url(#gradientId);stroke:transparent" />
|
||||
<polyline points="1,91 10,71 253,51 262,31" style="fill:none;stroke:rgb(222,104,242);stroke-width:1" />
|
||||
<rect height="102" width="264" style="fill:none;stroke:lightgrey;stroke-width:1" />
|
||||
</svg>
|
||||
*/
|
||||
|
||||
// The following code can be uncommented for testing when a static image is desired.
|
||||
/* chartValues.Clear();
|
||||
chartValues = new List<float>
|
||||
{
|
||||
10, 30, 20, 40, 30, 50, 40, 60, 50, 100,
|
||||
10, 30, 20, 40, 30, 50, 40, 60, 50, 70,
|
||||
0, 30, 20, 40, 30, 50, 40, 60, 50, 70,
|
||||
};*/
|
||||
|
||||
var chartDoc = new XDocument();
|
||||
|
||||
lock (chartValues)
|
||||
{
|
||||
var svgElement = CreateBlankSvg(ChartHeight, ChartWidth);
|
||||
|
||||
// Create the line that will show the points on the graph.
|
||||
var lineElement = new XElement(PolylineElement);
|
||||
var points = TransformPointsToLine(chartValues, out var startX, out var finalX);
|
||||
lineElement.SetAttributeValue(PointsAttr, points.ToString());
|
||||
lineElement.SetAttributeValue(StyleAttr, GetLineStyle(type));
|
||||
|
||||
// Create the line that will contain the gradient fill.
|
||||
TransformPointsToLoop(points, startX, finalX);
|
||||
var fillElement = new XElement(PolylineElement);
|
||||
fillElement.SetAttributeValue(PointsAttr, points.ToString());
|
||||
fillElement.SetAttributeValue(StyleAttr, FillStyle);
|
||||
|
||||
// Add the gradient definition and the three shapes to the svg.
|
||||
svgElement.Add(CreateGradientDefinition(type));
|
||||
svgElement.Add(fillElement);
|
||||
svgElement.Add(lineElement);
|
||||
svgElement.Add(CreateBorderBox(ChartHeight, ChartWidth));
|
||||
|
||||
chartDoc.Add(svgElement);
|
||||
}
|
||||
|
||||
return chartDoc.ToString();
|
||||
}
|
||||
|
||||
private static XElement CreateBlankSvg(int height, int width)
|
||||
{
|
||||
var svgElement = new XElement(SvgElement);
|
||||
svgElement.SetAttributeValue(HeightAttr, height);
|
||||
svgElement.SetAttributeValue(WidthAttr, width);
|
||||
return svgElement;
|
||||
}
|
||||
|
||||
private static XElement CreateGradientDefinition(ChartType type)
|
||||
{
|
||||
var defsElement = new XElement(DefsElement);
|
||||
var gradientElement = new XElement(LinearGradientElement);
|
||||
|
||||
// Vertical gradients are created when x1 and x2 are equal and y1 and y2 differ.
|
||||
gradientElement.SetAttributeValue(X1Attr, "0%");
|
||||
gradientElement.SetAttributeValue(X2Attr, "0%");
|
||||
gradientElement.SetAttributeValue(Y1Attr, "0%");
|
||||
gradientElement.SetAttributeValue(Y2Attr, "100%");
|
||||
gradientElement.SetAttributeValue(IdAttr, "gradientId");
|
||||
|
||||
string stop1Style;
|
||||
string stop2Style;
|
||||
switch (type)
|
||||
{
|
||||
case ChartType.GPU:
|
||||
stop1Style = GPUBrushStop1Style;
|
||||
stop2Style = GPUBrushStop2Style;
|
||||
break;
|
||||
case ChartType.Mem:
|
||||
stop1Style = MemBrushStop1Style;
|
||||
stop2Style = MemBrushStop2Style;
|
||||
break;
|
||||
case ChartType.Net:
|
||||
stop1Style = NetBrushStop1Style;
|
||||
stop2Style = NetBrushStop2Style;
|
||||
break;
|
||||
case ChartType.CPU:
|
||||
default:
|
||||
stop1Style = CPUBrushStop1Style;
|
||||
stop2Style = CPUBrushStop2Style;
|
||||
break;
|
||||
}
|
||||
|
||||
var stop1 = new XElement(StopElement);
|
||||
stop1.SetAttributeValue(OffsetAttr, "0%");
|
||||
stop1.SetAttributeValue(StyleAttr, stop1Style);
|
||||
|
||||
var stop2 = new XElement(StopElement);
|
||||
stop2.SetAttributeValue(OffsetAttr, "95%");
|
||||
stop2.SetAttributeValue(StyleAttr, stop2Style);
|
||||
|
||||
gradientElement.Add(stop1);
|
||||
gradientElement.Add(stop2);
|
||||
defsElement.Add(gradientElement);
|
||||
|
||||
return defsElement;
|
||||
}
|
||||
|
||||
private static XElement CreateBorderBox(int height, int width)
|
||||
{
|
||||
var boxElement = new XElement(RectElement);
|
||||
boxElement.SetAttributeValue(HeightAttr, height);
|
||||
boxElement.SetAttributeValue(WidthAttr, width);
|
||||
boxElement.SetAttributeValue(StyleAttr, LightGrayBoxStyle);
|
||||
return boxElement;
|
||||
}
|
||||
|
||||
private static string GetLineStyle(ChartType type)
|
||||
{
|
||||
var lineStyle = type switch
|
||||
{
|
||||
ChartType.CPU => CPULineStyle,
|
||||
ChartType.GPU => GPULineStyle,
|
||||
ChartType.Mem => MemLineStyle,
|
||||
ChartType.Net => NetLineStyle,
|
||||
_ => CPULineStyle,
|
||||
};
|
||||
|
||||
return lineStyle;
|
||||
}
|
||||
|
||||
private static StringBuilder TransformPointsToLine(List<float> chartValues, out int startX, out int finalX)
|
||||
{
|
||||
var points = new StringBuilder();
|
||||
|
||||
// The X value where the graph starts must be adjusted so that the graph is right-aligned.
|
||||
// The max available width of the widget is 268. Since there is a 1 px border around the chart, the width of the chart's line must be <=266.
|
||||
// To create a chart of exactly the right size, we'll have 34 points with 8 pixels in between:
|
||||
// 1 px left border + 1 px for first point + 33 segments * 8 px per segment + 1 px right border = 267 pixels total in width.
|
||||
const int pxBetweenPoints = 8;
|
||||
|
||||
// When the chart doesn't have all points yet, move the chart over to the right by increasing the starting X coordinate.
|
||||
// For a chart with only 1 point, the svg will not render a polyline.
|
||||
// For a chart with 2 points, starting X coordinate == 2 + (34 - 2) * 8 == 1 + 32 * 8 == 1 + 256 == 257
|
||||
// For a chart with 30 points, starting X coordinate == 2 + (34 - 34) * 8 == 1 + 0 * 8 == 1 + 0 == 2
|
||||
startX = 2 + ((MaxChartValues - chartValues.Count) * pxBetweenPoints);
|
||||
finalX = startX;
|
||||
|
||||
// Extend graph by one pixel to cover gap on the left when the chart is otherwise full.
|
||||
if (startX == 2)
|
||||
{
|
||||
var invertedHeight = 100 - chartValues[0];
|
||||
var finalY = (invertedHeight * (ChartHeight / 100.0)) - 1;
|
||||
points.Append(CultureInfo.InvariantCulture, $"1,{finalY} ");
|
||||
}
|
||||
|
||||
foreach (var origY in chartValues)
|
||||
{
|
||||
// We receive the height as a number up from the X axis (bottom of the chart), but we have to invert it
|
||||
// since the Y coordinate is relative to the top of the chart.
|
||||
var invertedHeight = 100 - origY;
|
||||
|
||||
// Scale the final Y to whatever the chart height is.
|
||||
var finalY = (invertedHeight * (ChartHeight / 100.0)) - 1;
|
||||
|
||||
points.Append(CultureInfo.InvariantCulture, $"{finalX},{finalY} ");
|
||||
finalX += pxBetweenPoints;
|
||||
}
|
||||
|
||||
// Remove the trailing space.
|
||||
if (points.Length > 0)
|
||||
{
|
||||
points.Remove(points.Length - 1, 1);
|
||||
finalX -= pxBetweenPoints;
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
private static void TransformPointsToLoop(StringBuilder points, int startX, int finalX)
|
||||
{
|
||||
// Close the loop.
|
||||
// Add a point at the most recent X value that corresponds with y = 0
|
||||
points.Append(CultureInfo.InvariantCulture, $" {finalX},{ChartHeight - 1}");
|
||||
|
||||
// Add a point at the start of the chart that corresponds with y = 0
|
||||
points.Append(CultureInfo.InvariantCulture, $" {startX},{ChartHeight - 1}");
|
||||
}
|
||||
|
||||
public static void AddNextChartValue(float value, List<float> chartValues)
|
||||
{
|
||||
if (chartValues.Count >= MaxChartValues)
|
||||
{
|
||||
chartValues.RemoveAt(0);
|
||||
}
|
||||
|
||||
chartValues.Add(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace CoreWidgetProvider.Helpers;
|
||||
|
||||
internal sealed partial class DataManager : IDisposable
|
||||
{
|
||||
private readonly SystemData _systemData;
|
||||
private readonly DataType _dataType;
|
||||
private readonly Timer _updateTimer;
|
||||
private readonly Action _updateAction;
|
||||
|
||||
private const int OneSecondInMilliseconds = 1000;
|
||||
|
||||
public DataManager(DataType type, Action updateWidget)
|
||||
{
|
||||
_systemData = new SystemData();
|
||||
_updateAction = updateWidget;
|
||||
_dataType = type;
|
||||
|
||||
_updateTimer = new Timer(OneSecondInMilliseconds);
|
||||
_updateTimer.Elapsed += UpdateTimer_Elapsed;
|
||||
_updateTimer.AutoReset = true;
|
||||
_updateTimer.Enabled = false;
|
||||
}
|
||||
|
||||
private void GetMemoryData()
|
||||
{
|
||||
lock (SystemData.MemStats)
|
||||
{
|
||||
SystemData.MemStats.GetData();
|
||||
}
|
||||
}
|
||||
|
||||
private void GetNetworkData()
|
||||
{
|
||||
lock (SystemData.NetStats)
|
||||
{
|
||||
SystemData.NetStats.GetData();
|
||||
}
|
||||
}
|
||||
|
||||
private void GetGPUData()
|
||||
{
|
||||
lock (SystemData.GPUStats)
|
||||
{
|
||||
SystemData.GPUStats.GetData();
|
||||
}
|
||||
}
|
||||
|
||||
private void GetCPUData(bool includeTopProcesses)
|
||||
{
|
||||
lock (SystemData.CpuStats)
|
||||
{
|
||||
SystemData.CpuStats.GetData(includeTopProcesses);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
switch (_dataType)
|
||||
{
|
||||
case DataType.CPU:
|
||||
case DataType.CpuWithTopProcesses:
|
||||
{
|
||||
// CPU
|
||||
GetCPUData(_dataType == DataType.CpuWithTopProcesses);
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.GPU:
|
||||
{
|
||||
// gpu
|
||||
GetGPUData();
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Memory:
|
||||
{
|
||||
// memory
|
||||
GetMemoryData();
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Network:
|
||||
{
|
||||
// network
|
||||
GetNetworkData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_updateAction?.Invoke();
|
||||
}
|
||||
|
||||
internal MemoryStats GetMemoryStats()
|
||||
{
|
||||
lock (SystemData.MemStats)
|
||||
{
|
||||
return SystemData.MemStats;
|
||||
}
|
||||
}
|
||||
|
||||
internal NetworkStats GetNetworkStats()
|
||||
{
|
||||
lock (SystemData.NetStats)
|
||||
{
|
||||
return SystemData.NetStats;
|
||||
}
|
||||
}
|
||||
|
||||
internal GPUStats GetGPUStats()
|
||||
{
|
||||
lock (SystemData.GPUStats)
|
||||
{
|
||||
return SystemData.GPUStats;
|
||||
}
|
||||
}
|
||||
|
||||
internal CPUStats GetCPUStats()
|
||||
{
|
||||
lock (SystemData.CpuStats)
|
||||
{
|
||||
return SystemData.CpuStats;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_updateTimer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_updateTimer.Stop();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_systemData.Dispose();
|
||||
_updateTimer.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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 CoreWidgetProvider.Helpers;
|
||||
|
||||
public enum DataType
|
||||
{
|
||||
/// <summary>
|
||||
/// CPU related data.
|
||||
/// </summary>
|
||||
CPU,
|
||||
|
||||
/// <summary>
|
||||
/// CPU related data, including the top processes.
|
||||
/// Calculating the top processes takes a lot longer,
|
||||
/// so by default we don't.
|
||||
/// </summary>
|
||||
CpuWithTopProcesses,
|
||||
|
||||
/// <summary>
|
||||
/// Memory related data.
|
||||
/// </summary>
|
||||
Memory,
|
||||
|
||||
/// <summary>
|
||||
/// GPU related data.
|
||||
/// </summary>
|
||||
GPU,
|
||||
|
||||
/// <summary>
|
||||
/// Network related data.
|
||||
/// </summary>
|
||||
Network,
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace CoreWidgetProvider.Helpers;
|
||||
|
||||
internal sealed partial class GPUStats : IDisposable
|
||||
{
|
||||
// GPU counters
|
||||
private readonly Dictionary<int, List<PerformanceCounter>> _gpuCounters = new();
|
||||
|
||||
private readonly List<Data> _stats = new();
|
||||
|
||||
public sealed class Data
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
|
||||
public int PhysId { get; set; }
|
||||
|
||||
public float Usage { get; set; }
|
||||
|
||||
public float Temperature { get; set; }
|
||||
|
||||
public List<float> GpuChartValues { get; set; } = new();
|
||||
}
|
||||
|
||||
public GPUStats()
|
||||
{
|
||||
GetGPUPerfCounters();
|
||||
LoadGPUsFromCounters();
|
||||
}
|
||||
|
||||
public void GetGPUPerfCounters()
|
||||
{
|
||||
// There are really 4 different things we should be tracking the usage
|
||||
// of. Similar to how the instance name ends with `3D`, the following
|
||||
// suffixes are important.
|
||||
//
|
||||
// * `3D`
|
||||
// * `VideoEncode`
|
||||
// * `VideoDecode`
|
||||
// * `VideoProcessing`
|
||||
//
|
||||
// We could totally put each of those sets of counters into their own
|
||||
// set. That's what we should do, so that we can report the sum of those
|
||||
// numbers as the total utilization, and then have them broken out in
|
||||
// the card template and in the details metadata.
|
||||
_gpuCounters.Clear();
|
||||
|
||||
var perfCounterCategory = new PerformanceCounterCategory("GPU Engine");
|
||||
var instanceNames = perfCounterCategory.GetInstanceNames();
|
||||
|
||||
foreach (var instanceName in instanceNames)
|
||||
{
|
||||
if (!instanceName.EndsWith("3D", StringComparison.InvariantCulture))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var utilizationCounters = perfCounterCategory.GetCounters(instanceName)
|
||||
.Where(x => x.CounterName.StartsWith("Utilization Percentage", StringComparison.InvariantCulture));
|
||||
|
||||
foreach (var counter in utilizationCounters)
|
||||
{
|
||||
var counterKey = counter.InstanceName;
|
||||
|
||||
// skip these values
|
||||
GetKeyValueFromCounterKey("pid", ref counterKey);
|
||||
GetKeyValueFromCounterKey("luid", ref counterKey);
|
||||
|
||||
int phys;
|
||||
var success = int.TryParse(GetKeyValueFromCounterKey("phys", ref counterKey), out phys);
|
||||
if (success)
|
||||
{
|
||||
GetKeyValueFromCounterKey("eng", ref counterKey);
|
||||
var engtype = GetKeyValueFromCounterKey("engtype", ref counterKey);
|
||||
if (engtype != "3D")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_gpuCounters.TryGetValue(phys, out var value))
|
||||
{
|
||||
value = new();
|
||||
_gpuCounters.Add(phys, value);
|
||||
}
|
||||
|
||||
value.Add(counter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadGPUsFromCounters()
|
||||
{
|
||||
// The old dev home code tracked GPU stats by querying WMI for the list
|
||||
// of GPUs, and then matching them up with the performance counter IDs.
|
||||
//
|
||||
// We can't use WMI here, because it drags in a dependency on
|
||||
// Microsoft.Management.Infrastructure, which is not compatible with
|
||||
// AOT.
|
||||
//
|
||||
// For now, we'll just use the indices as the GPU names.
|
||||
_stats.Clear();
|
||||
foreach (var (k, v) in _gpuCounters)
|
||||
{
|
||||
var id = k;
|
||||
var counters = v;
|
||||
_stats.Add(new Data() { PhysId = id, Name = "GPU " + id });
|
||||
}
|
||||
}
|
||||
|
||||
public void GetData()
|
||||
{
|
||||
foreach (var gpu in _stats)
|
||||
{
|
||||
List<PerformanceCounter>? counters;
|
||||
var success = _gpuCounters.TryGetValue(gpu.PhysId, out counters);
|
||||
|
||||
if (success && counters != null)
|
||||
{
|
||||
// TODO: This outer try/catch should be replaced with more secure locking around shared resources.
|
||||
try
|
||||
{
|
||||
var sum = 0.0f;
|
||||
var countersToRemove = new List<PerformanceCounter>();
|
||||
foreach (var counter in counters)
|
||||
{
|
||||
try
|
||||
{
|
||||
// NextValue() can throw an InvalidOperationException if the counter is no longer there.
|
||||
sum += counter.NextValue();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// We can't modify the list during the loop, so save it to remove at the end.
|
||||
// _log.Information(ex, "Failed to get next value, remove");
|
||||
countersToRemove.Add(counter);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// _log.Error(ex, "Error going through process counters.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var counter in countersToRemove)
|
||||
{
|
||||
counters.Remove(counter);
|
||||
counter.Dispose();
|
||||
}
|
||||
|
||||
gpu.Usage = sum / 100;
|
||||
lock (gpu.GpuChartValues)
|
||||
{
|
||||
ChartHelper.AddNextChartValue(sum, gpu.GpuChartValues);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// _log.Error(ex, "Error summing process counters.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal string CreateGPUImageUrl(int gpuChartIndex)
|
||||
{
|
||||
return ChartHelper.CreateImageUrl(_stats.ElementAt(gpuChartIndex).GpuChartValues, ChartHelper.ChartType.GPU);
|
||||
}
|
||||
|
||||
internal string GetGPUName(int gpuActiveIndex)
|
||||
{
|
||||
if (_stats.Count <= gpuActiveIndex)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return _stats[gpuActiveIndex].Name ?? string.Empty;
|
||||
}
|
||||
|
||||
internal int GetPrevGPUIndex(int gpuActiveIndex)
|
||||
{
|
||||
if (_stats.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (gpuActiveIndex == 0)
|
||||
{
|
||||
return _stats.Count - 1;
|
||||
}
|
||||
|
||||
return gpuActiveIndex - 1;
|
||||
}
|
||||
|
||||
internal int GetNextGPUIndex(int gpuActiveIndex)
|
||||
{
|
||||
if (_stats.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (gpuActiveIndex == _stats.Count - 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return gpuActiveIndex + 1;
|
||||
}
|
||||
|
||||
internal float GetGPUUsage(int gpuActiveIndex, string gpuActiveEngType)
|
||||
{
|
||||
if (_stats.Count <= gpuActiveIndex)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _stats[gpuActiveIndex].Usage;
|
||||
}
|
||||
|
||||
internal string GetGPUTemperature(int gpuActiveIndex)
|
||||
{
|
||||
// MG Jan 2026: This code was lifted from the old Dev Home codebase.
|
||||
// However, the performance counters for GPU temperature are not being
|
||||
// collected. So this function always returns "--" for now.
|
||||
//
|
||||
// I have not done the code archeology to figure out why they were
|
||||
// removed.
|
||||
if (_stats.Count <= gpuActiveIndex)
|
||||
{
|
||||
return "--";
|
||||
}
|
||||
|
||||
var temperature = _stats[gpuActiveIndex].Temperature;
|
||||
if (temperature == 0)
|
||||
{
|
||||
return "--";
|
||||
}
|
||||
|
||||
return temperature.ToString("0.", CultureInfo.InvariantCulture) + " \x00B0C";
|
||||
}
|
||||
|
||||
private string GetKeyValueFromCounterKey(string key, ref string counterKey)
|
||||
{
|
||||
if (!counterKey.StartsWith(key, StringComparison.InvariantCulture))
|
||||
{
|
||||
return "error";
|
||||
}
|
||||
|
||||
counterKey = counterKey.Substring(key.Length + 1);
|
||||
if (key.Equals("engtype", StringComparison.Ordinal))
|
||||
{
|
||||
return counterKey;
|
||||
}
|
||||
|
||||
var pos = counterKey.IndexOf('_');
|
||||
if (key.Equals("luid", StringComparison.Ordinal))
|
||||
{
|
||||
pos = counterKey.IndexOf('_', pos + 1);
|
||||
}
|
||||
|
||||
var retValue = counterKey.Substring(0, pos);
|
||||
counterKey = counterKey.Substring(pos + 1);
|
||||
return retValue;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var counterPair in _gpuCounters)
|
||||
{
|
||||
foreach (var counter in counterPair.Value)
|
||||
{
|
||||
counter.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32;
|
||||
|
||||
namespace CoreWidgetProvider.Helpers;
|
||||
|
||||
internal sealed partial class MemoryStats : IDisposable
|
||||
{
|
||||
private readonly PerformanceCounter _memCommitted = new("Memory", "Committed Bytes", string.Empty);
|
||||
private readonly PerformanceCounter _memCached = new("Memory", "Cache Bytes", string.Empty);
|
||||
private readonly PerformanceCounter _memCommittedLimit = new("Memory", "Commit Limit", string.Empty);
|
||||
private readonly PerformanceCounter _memPoolPaged = new("Memory", "Pool Paged Bytes", string.Empty);
|
||||
private readonly PerformanceCounter _memPoolNonPaged = new("Memory", "Pool Nonpaged Bytes", string.Empty);
|
||||
|
||||
public float MemUsage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong AllMem
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong UsedMem
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong MemCommitted
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong MemCommitLimit
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong MemCached
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong MemPagedPool
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong MemNonPagedPool
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<float> MemChartValues { get; set; } = new();
|
||||
|
||||
public void GetData()
|
||||
{
|
||||
Windows.Win32.System.SystemInformation.MEMORYSTATUSEX memStatus = default;
|
||||
memStatus.dwLength = (uint)Marshal.SizeOf<Windows.Win32.System.SystemInformation.MEMORYSTATUSEX>();
|
||||
if (PInvoke.GlobalMemoryStatusEx(ref memStatus))
|
||||
{
|
||||
AllMem = memStatus.ullTotalPhys;
|
||||
var availableMem = memStatus.ullAvailPhys;
|
||||
UsedMem = AllMem - availableMem;
|
||||
|
||||
MemUsage = (float)UsedMem / AllMem;
|
||||
lock (MemChartValues)
|
||||
{
|
||||
ChartHelper.AddNextChartValue(MemUsage * 100, MemChartValues);
|
||||
}
|
||||
}
|
||||
|
||||
MemCached = (ulong)_memCached.NextValue();
|
||||
MemCommitted = (ulong)_memCommitted.NextValue();
|
||||
MemCommitLimit = (ulong)_memCommittedLimit.NextValue();
|
||||
MemPagedPool = (ulong)_memPoolPaged.NextValue();
|
||||
MemNonPagedPool = (ulong)_memPoolNonPaged.NextValue();
|
||||
}
|
||||
|
||||
public string CreateMemImageUrl()
|
||||
{
|
||||
return ChartHelper.CreateImageUrl(MemChartValues, ChartHelper.ChartType.Mem);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_memCommitted.Dispose();
|
||||
_memCached.Dispose();
|
||||
_memCommittedLimit.Dispose();
|
||||
_memPoolPaged.Dispose();
|
||||
_memPoolNonPaged.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace CoreWidgetProvider.Helpers;
|
||||
|
||||
internal sealed partial class NetworkStats : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, List<PerformanceCounter>> _networkCounters = new();
|
||||
|
||||
private Dictionary<string, Data> NetworkUsages { get; set; } = new();
|
||||
|
||||
private Dictionary<string, List<float>> NetChartValues { get; set; } = new();
|
||||
|
||||
public sealed class Data
|
||||
{
|
||||
public float Usage
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public float Sent
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public float Received
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkStats()
|
||||
{
|
||||
InitNetworkPerfCounters();
|
||||
}
|
||||
|
||||
private void InitNetworkPerfCounters()
|
||||
{
|
||||
var perfCounterCategory = new PerformanceCounterCategory("Network Interface");
|
||||
var instanceNames = perfCounterCategory.GetInstanceNames();
|
||||
foreach (var instanceName in instanceNames)
|
||||
{
|
||||
var instanceCounters = new List<PerformanceCounter>();
|
||||
instanceCounters.Add(new PerformanceCounter("Network Interface", "Bytes Sent/sec", instanceName));
|
||||
instanceCounters.Add(new PerformanceCounter("Network Interface", "Bytes Received/sec", instanceName));
|
||||
instanceCounters.Add(new PerformanceCounter("Network Interface", "Current Bandwidth", instanceName));
|
||||
_networkCounters.Add(instanceName, instanceCounters);
|
||||
NetChartValues.Add(instanceName, new List<float>());
|
||||
NetworkUsages.Add(instanceName, new Data());
|
||||
}
|
||||
}
|
||||
|
||||
public void GetData()
|
||||
{
|
||||
float maxUsage = 0;
|
||||
foreach (var networkCounterWithName in _networkCounters)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sent = networkCounterWithName.Value[0].NextValue();
|
||||
var received = networkCounterWithName.Value[1].NextValue();
|
||||
var bandWidth = networkCounterWithName.Value[2].NextValue();
|
||||
if (bandWidth == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var usage = 8 * (sent + received) / bandWidth;
|
||||
var name = networkCounterWithName.Key;
|
||||
NetworkUsages[name].Sent = sent;
|
||||
NetworkUsages[name].Received = received;
|
||||
NetworkUsages[name].Usage = usage;
|
||||
|
||||
var chartValues = NetChartValues[name];
|
||||
lock (chartValues)
|
||||
{
|
||||
ChartHelper.AddNextChartValue(usage * 100, chartValues);
|
||||
}
|
||||
|
||||
if (usage > maxUsage)
|
||||
{
|
||||
maxUsage = usage;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Log.Error(ex, "Error getting network data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string CreateNetImageUrl(int netChartIndex)
|
||||
{
|
||||
return ChartHelper.CreateImageUrl(NetChartValues.ElementAt(netChartIndex).Value, ChartHelper.ChartType.Net);
|
||||
}
|
||||
|
||||
public string GetNetworkName(int networkIndex)
|
||||
{
|
||||
if (NetChartValues.Count <= networkIndex)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return NetChartValues.ElementAt(networkIndex).Key;
|
||||
}
|
||||
|
||||
public Data GetNetworkUsage(int networkIndex)
|
||||
{
|
||||
if (NetChartValues.Count <= networkIndex)
|
||||
{
|
||||
return new Data();
|
||||
}
|
||||
|
||||
var currNetworkName = NetChartValues.ElementAt(networkIndex).Key;
|
||||
if (!NetworkUsages.TryGetValue(currNetworkName, out var value))
|
||||
{
|
||||
return new Data();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public int GetPrevNetworkIndex(int networkIndex)
|
||||
{
|
||||
if (NetChartValues.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (networkIndex == 0)
|
||||
{
|
||||
return NetChartValues.Count - 1;
|
||||
}
|
||||
|
||||
return networkIndex - 1;
|
||||
}
|
||||
|
||||
public int GetNextNetworkIndex(int networkIndex)
|
||||
{
|
||||
if (NetChartValues.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (networkIndex == NetChartValues.Count - 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return networkIndex + 1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var counterPair in _networkCounters)
|
||||
{
|
||||
foreach (var counter in counterPair.Value)
|
||||
{
|
||||
counter.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using Microsoft.CmdPal.Core.Common;
|
||||
|
||||
namespace CoreWidgetProvider.Helpers;
|
||||
|
||||
// This class was pilfered from devhome, but changed much more substantially to
|
||||
// get the resources out of our resources.pri the way we need.
|
||||
public static class Resources
|
||||
{
|
||||
private static readonly Windows.ApplicationModel.Resources.Core.ResourceMap? _map;
|
||||
|
||||
private static readonly string ResourcesPath = "Microsoft.CmdPal.Ext.PerformanceMonitor/Resources";
|
||||
|
||||
static Resources()
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentResourceManager = Windows.ApplicationModel.Resources.Core.ResourceManager.Current;
|
||||
if (currentResourceManager.MainResourceMap is not null)
|
||||
{
|
||||
_map = currentResourceManager.MainResourceMap;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Resource map not available (e.g., during unit tests)
|
||||
_map = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetResource(string identifier, ILogger? log = null)
|
||||
{
|
||||
if (_map is null)
|
||||
{
|
||||
return identifier;
|
||||
}
|
||||
|
||||
var fullKey = $"{ResourcesPath}/{identifier}";
|
||||
|
||||
var val = _map.GetValue(fullKey);
|
||||
#if DEBUG
|
||||
if (val == null)
|
||||
{
|
||||
log?.LogError($"Failed loading resource: {identifier}");
|
||||
|
||||
DebugResources(log);
|
||||
}
|
||||
#endif
|
||||
return val!.ValueAsString;
|
||||
}
|
||||
|
||||
public static string ReplaceIdentifersFast(
|
||||
string original)
|
||||
{
|
||||
// walk the string, looking for a pair of '%' characters
|
||||
StringBuilder sb = new();
|
||||
var length = original.Length;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
if (original[i] == '%')
|
||||
{
|
||||
var end = original.IndexOf('%', i + 1);
|
||||
if (end > i)
|
||||
{
|
||||
var identifier = original.Substring(i + 1, end - i - 1);
|
||||
var resourceString = GetResource(identifier);
|
||||
sb.Append(resourceString);
|
||||
i = end; // move index to the end '%'
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(original[i]);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void DebugResources(ILogger? log)
|
||||
{
|
||||
var currentResourceManager = Windows.ApplicationModel.Resources.Core.ResourceManager.Current;
|
||||
StringBuilder sb = new();
|
||||
|
||||
foreach (var (k, v) in currentResourceManager.AllResourceMaps)
|
||||
{
|
||||
sb.AppendLine(k);
|
||||
foreach (var (k2, v2) in v)
|
||||
{
|
||||
sb.Append('\t');
|
||||
sb.AppendLine(k2);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
log?.LogDebug($"Resource maps:");
|
||||
log?.LogDebug(sb.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace CoreWidgetProvider.Helpers;
|
||||
|
||||
internal sealed partial class SystemData : IDisposable
|
||||
{
|
||||
public static MemoryStats MemStats { get; set; } = new MemoryStats();
|
||||
|
||||
public static NetworkStats NetStats { get; set; } = new NetworkStats();
|
||||
|
||||
public static GPUStats GPUStats { get; set; } = new GPUStats();
|
||||
|
||||
public static CPUStats CpuStats { get; set; } = new CPUStats();
|
||||
|
||||
public SystemData()
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
The code in this directory was largely lifted from the [DevHome repo].
|
||||
|
||||
The specific directory we're using is
|
||||
https://github.com/microsoft/devhome/tree/main/extensions/CoreWidgetProvider
|
||||
This has code for all the DevHome performance widgets.
|
||||
|
||||
Minimal changes have been made to match our style guidelines.
|
||||
Additionally, a much larger change was made to Resources.cs, to match our own
|
||||
resource loading needs.
|
||||
|
||||
The code was lifted as of commit d52734ce0e33a82af3313d24c3c2979c37b68bab
|
||||
|
||||
|
||||
[DevHome repo]: https://github.com/microsoft/devhome/
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"type": "AdaptiveCard",
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"version": "1.5",
|
||||
"body": [
|
||||
{
|
||||
"type": "Container",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "%Widget_Template/Loading%",
|
||||
"wrap": true,
|
||||
"horizontalAlignment": "center"
|
||||
}
|
||||
],
|
||||
"verticalContentAlignment": "center",
|
||||
"height": "stretch"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"type": "AdaptiveCard",
|
||||
"body": [
|
||||
{
|
||||
"type": "Container",
|
||||
"$when": "${errorMessage != null}",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "${errorMessage}",
|
||||
"wrap": true,
|
||||
"size": "small"
|
||||
}
|
||||
],
|
||||
"style": "warning"
|
||||
},
|
||||
{
|
||||
"type": "Container",
|
||||
"$when": "${errorMessage == null}",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"url": "${cpuGraphUrl}",
|
||||
"height": "${chartHeight}",
|
||||
"width": "${chartWidth}",
|
||||
"$when": "${$host.widgetSize != \"small\"}",
|
||||
"horizontalAlignment": "center"
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"text": "%CPUUsage_Widget_Template/CPU_Usage%"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"size": "large",
|
||||
"weight": "bolder",
|
||||
"text": "${cpuUsage}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"horizontalAlignment": "right",
|
||||
"text": "%CPUUsage_Widget_Template/CPU_Speed%"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"size": "large",
|
||||
"horizontalAlignment": "right",
|
||||
"text": "${cpuSpeed}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Container",
|
||||
"$when": false,
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"text": "%CPUUsage_Widget_Template/Processes%",
|
||||
"wrap": true
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"size": "medium",
|
||||
"text": "${cpuProc1}"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"size": "medium",
|
||||
"text": "${cpuProc2}"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"size": "medium",
|
||||
"text": "${cpuProc3}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"version": "1.5"
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"type": "AdaptiveCard",
|
||||
"body": [
|
||||
{
|
||||
"type": "Container",
|
||||
"$when": "${errorMessage != null}",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "${errorMessage}",
|
||||
"wrap": true,
|
||||
"size": "small"
|
||||
}
|
||||
],
|
||||
"style": "warning"
|
||||
},
|
||||
{
|
||||
"type": "Container",
|
||||
"$when": "${errorMessage == null}",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"url": "${gpuGraphUrl}",
|
||||
"height": "${chartHeight}",
|
||||
"width": "${chartWidth}",
|
||||
"$when": "${$host.widgetSize != \"small\"}",
|
||||
"horizontalAlignment": "center"
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%GPUUsage_Widget_Template/GPU_Usage%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true
|
||||
},
|
||||
{
|
||||
"text": "${gpuUsage}",
|
||||
"type": "TextBlock",
|
||||
"size": "large",
|
||||
"weight": "bolder"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%GPUUsage_Widget_Template/GPU_Temperature%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true,
|
||||
"horizontalAlignment": "right"
|
||||
},
|
||||
{
|
||||
"text": "${gpuTemp}",
|
||||
"type": "TextBlock",
|
||||
"size": "large",
|
||||
"weight": "bolder",
|
||||
"horizontalAlignment": "right"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "%GPUUsage_Widget_Template/GPU_Name%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true
|
||||
},
|
||||
{
|
||||
"text": "${gpuName}",
|
||||
"type": "TextBlock",
|
||||
"size": "medium"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"version": "1.5"
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
{
|
||||
"type": "AdaptiveCard",
|
||||
"body": [
|
||||
{
|
||||
"type": "Container",
|
||||
"$when": "${errorMessage != null}",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "${errorMessage}",
|
||||
"wrap": true,
|
||||
"size": "small"
|
||||
}
|
||||
],
|
||||
"style": "warning"
|
||||
},
|
||||
{
|
||||
"type": "Container",
|
||||
"$when": "${errorMessage == null}",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"url": "${memGraphUrl}",
|
||||
"height": "${chartHeight}",
|
||||
"width": "${chartWidth}",
|
||||
"$when": "${$host.widgetSize != \"small\"}",
|
||||
"horizontalAlignment": "center"
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%Memory_Widget_Template/UsedMemory%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true
|
||||
},
|
||||
{
|
||||
"text": "${usedMem}",
|
||||
"type": "TextBlock",
|
||||
"size": "${if($host.widgetSize == \"small\", \"medium\", \"large\")}",
|
||||
"weight": "bolder"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%Memory_Widget_Template/AllMemory%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true,
|
||||
"horizontalAlignment": "right"
|
||||
},
|
||||
{
|
||||
"text": "${allMem}",
|
||||
"type": "TextBlock",
|
||||
"size": "${if($host.widgetSize == \"small\", \"medium\", \"large\")}",
|
||||
"weight": "bolder",
|
||||
"horizontalAlignment": "right"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%Memory_Widget_Template/Committed%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true
|
||||
},
|
||||
{
|
||||
"text": "${committedMem}/${committedLimitMem}",
|
||||
"type": "TextBlock",
|
||||
"size": "medium"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%Memory_Widget_Template/Cached%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true,
|
||||
"horizontalAlignment": "right"
|
||||
},
|
||||
{
|
||||
"text": "${cachedMem}",
|
||||
"type": "TextBlock",
|
||||
"size": "medium",
|
||||
"horizontalAlignment": "right"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"$when": "${$host.widgetSize == \"large\"}",
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%Memory_Widget_Template/PagedPool%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true
|
||||
},
|
||||
{
|
||||
"text": "${pagedPoolMem}",
|
||||
"type": "TextBlock",
|
||||
"size": "medium"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%Memory_Widget_Template/NonPagedPool%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true,
|
||||
"horizontalAlignment": "right"
|
||||
},
|
||||
{
|
||||
"text": "${nonPagedPoolMem}",
|
||||
"type": "TextBlock",
|
||||
"size": "medium",
|
||||
"horizontalAlignment": "right"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"$when": "${$host.widgetSize != \"small\"}",
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%Memory_Widget_Template/MemoryUsage%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true,
|
||||
"horizontalAlignment": "right"
|
||||
},
|
||||
{
|
||||
"text": "${memUsage}",
|
||||
"type": "TextBlock",
|
||||
"size": "medium",
|
||||
"horizontalAlignment": "right"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"version": "1.5"
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"type": "AdaptiveCard",
|
||||
"body": [
|
||||
{
|
||||
"type": "Container",
|
||||
"$when": "${errorMessage != null}",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "${errorMessage}",
|
||||
"wrap": true,
|
||||
"size": "small"
|
||||
}
|
||||
],
|
||||
"style": "warning"
|
||||
},
|
||||
{
|
||||
"type": "Container",
|
||||
"$when": "${errorMessage == null}",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"url": "${netGraphUrl}",
|
||||
"height": "${chartHeight}",
|
||||
"width": "${chartWidth}",
|
||||
"$when": "${$host.widgetSize != \"small\"}",
|
||||
"horizontalAlignment": "center"
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%NetworkUsage_Widget_Template/Sent%",
|
||||
"type": "TextBlock",
|
||||
"spacing": "none",
|
||||
"size": "small",
|
||||
"isSubtle": true
|
||||
},
|
||||
{
|
||||
"text": "${netSent}",
|
||||
"type": "TextBlock",
|
||||
"size": "large",
|
||||
"weight": "bolder"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"items": [
|
||||
{
|
||||
"text": "%NetworkUsage_Widget_Template/Received%",
|
||||
"type": "TextBlock",
|
||||
"spacing": "none",
|
||||
"size": "small",
|
||||
"isSubtle": true,
|
||||
"horizontalAlignment": "right"
|
||||
},
|
||||
{
|
||||
"text": "${netReceived}",
|
||||
"type": "TextBlock",
|
||||
"size": "large",
|
||||
"weight": "bolder",
|
||||
"horizontalAlignment": "right"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "%NetworkUsage_Widget_Template/Network_Name%",
|
||||
"type": "TextBlock",
|
||||
"size": "small",
|
||||
"isSubtle": true
|
||||
},
|
||||
{
|
||||
"text": "${networkName}",
|
||||
"type": "TextBlock",
|
||||
"size": "medium"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"version": "1.5"
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.PerformanceMonitor;
|
||||
|
||||
internal sealed class Icons
|
||||
{
|
||||
internal static IconInfo CpuIcon => new("\uE9D9"); // CPU icon
|
||||
|
||||
internal static IconInfo MemoryIcon => new("\uE964"); // Memory icon
|
||||
|
||||
internal static IconInfo DiskIcon => new("\uE977"); // PC1 icon
|
||||
|
||||
internal static IconInfo HardDriveIcon => new("\uEDA2"); // HardDrive icon
|
||||
|
||||
internal static IconInfo NetworkIcon => new("\uEC05"); // Network icon
|
||||
|
||||
internal static IconInfo StackedAreaIcon => new("\uE9D2"); // StackedArea icon
|
||||
|
||||
internal static IconInfo GpuIcon => new("\uE950"); // Component icon
|
||||
|
||||
internal static IconInfo NavigateBackwardIcon => new("\uE72B"); // Previous icon
|
||||
|
||||
internal static IconInfo NavigateForwardIcon => new("\uE72A"); // Next icon
|
||||
}
|
||||
|
||||
|
||||
#pragma warning restore SA1402 // File may only contain a single type
|
||||
@@ -0,0 +1,58 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
<Import Project="..\Common.ExtDependencies.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.PerformanceMonitor</RootNamespace>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
|
||||
<ProjectPriFileName>Microsoft.CmdPal.Ext.PerformanceMonitor.pri</ProjectPriFileName>
|
||||
<nullable>enable</nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Diagnostics.PerformanceCounter" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\Core\Microsoft.CmdPal.Core.Common\Microsoft.CmdPal.Core.Common.csproj" />
|
||||
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="DevHome\Templates\SystemCPUUsageTemplate.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DevHome\Templates\SystemGPUUsageTemplate.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DevHome\Templates\SystemMemoryTemplate.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DevHome\Templates\SystemNetworkUsageTemplate.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1 @@
|
||||
GlobalMemoryStatusEx
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.PerformanceMonitor;
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for creating ListPage's which can listen for when they're
|
||||
/// loaded and unloaded. This works because CmdPal will attach an event handler
|
||||
/// to the ItemsChanged event when the page is added to the UI, and remove it
|
||||
/// when the page is removed from the UI.
|
||||
///
|
||||
/// Subclasses should override the Loaded and Unloaded methods to start/stop
|
||||
/// any background work needed to populate the page.
|
||||
/// </summary>
|
||||
internal abstract partial class OnLoadStaticListPage : OnLoadBasePage, IListPage
|
||||
{
|
||||
private string _searchText = string.Empty;
|
||||
|
||||
public virtual string PlaceholderText { get; set => SetProperty(ref field, value); } = string.Empty;
|
||||
|
||||
public virtual string SearchText { get => _searchText; set => SetProperty(ref _searchText, value); }
|
||||
|
||||
public virtual bool ShowDetails { get; set => SetProperty(ref field, value); }
|
||||
|
||||
public virtual bool HasMoreItems { get; set => SetProperty(ref field, value); }
|
||||
|
||||
public virtual IFilters? Filters { get; set => SetProperty(ref field, value); }
|
||||
|
||||
public virtual IGridProperties? GridProperties { get; set => SetProperty(ref field, value); }
|
||||
|
||||
public virtual ICommandItem? EmptyContent { get; set => SetProperty(ref field, value); }
|
||||
|
||||
public void LoadMore()
|
||||
{
|
||||
}
|
||||
|
||||
protected void SetSearchNoUpdate(string newSearchText)
|
||||
{
|
||||
_searchText = newSearchText;
|
||||
}
|
||||
|
||||
public abstract IListItem[] GetItems();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for creating ContentPage's which can listen for when they're
|
||||
/// loaded and unloaded. This works because CmdPal will attach an event handler
|
||||
/// to the ItemsChanged event when the page is added to the UI, and remove it
|
||||
/// when the page is removed from the UI.
|
||||
///
|
||||
/// Subclasses should override the Loaded and Unloaded methods to start/stop
|
||||
/// any background work needed to populate the page.
|
||||
/// </summary>
|
||||
internal abstract partial class OnLoadContentPage : OnLoadBasePage, IContentPage
|
||||
{
|
||||
public virtual IDetails? Details { get; set => SetProperty(ref field, value); }
|
||||
|
||||
public virtual IContextItem[] Commands { get; set => SetProperty(ref field, value); } = [];
|
||||
|
||||
public abstract IContent[] GetContent();
|
||||
}
|
||||
|
||||
internal abstract partial class OnLoadBasePage : Page
|
||||
{
|
||||
private int _loadCount;
|
||||
|
||||
#pragma warning disable CS0067 // The event is never used
|
||||
|
||||
private event TypedEventHandler<object, IItemsChangedEventArgs>? InternalItemsChanged;
|
||||
#pragma warning restore CS0067 // The event is never used
|
||||
|
||||
public event TypedEventHandler<object, IItemsChangedEventArgs> ItemsChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
InternalItemsChanged += value;
|
||||
if (_loadCount == 0)
|
||||
{
|
||||
Loaded();
|
||||
}
|
||||
|
||||
_loadCount++;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
InternalItemsChanged -= value;
|
||||
_loadCount--;
|
||||
_loadCount = Math.Max(0, _loadCount);
|
||||
if (_loadCount == 0)
|
||||
{
|
||||
Unloaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void Loaded();
|
||||
|
||||
protected abstract void Unloaded();
|
||||
|
||||
protected void RaiseItemsChanged(int totalItems = -1)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO #181 - This is the same thing that BaseObservable has to deal with.
|
||||
InternalItemsChanged?.Invoke(this, new ItemsChangedEventArgs(totalItems));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma warning restore SA1402 // File may only contain a single type
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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 CoreWidgetProvider.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.PerformanceMonitor;
|
||||
|
||||
public partial class PerformanceMonitorCommandsProvider : CommandProvider
|
||||
{
|
||||
private readonly ICommandItem[] _commands;
|
||||
private readonly ICommandItem _band;
|
||||
|
||||
public PerformanceMonitorCommandsProvider()
|
||||
{
|
||||
DisplayName = Resources.GetResource("Performance_Monitor_Title");
|
||||
Id = "PerformanceMonitor";
|
||||
Icon = Icons.StackedAreaIcon;
|
||||
|
||||
var page = new PerformanceWidgetsPage(false);
|
||||
var band = new PerformanceWidgetsPage(true);
|
||||
_band = new CommandItem(band) { Title = DisplayName };
|
||||
_commands = [
|
||||
new CommandItem(page) { Title = DisplayName },
|
||||
];
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands()
|
||||
{
|
||||
return _commands;
|
||||
}
|
||||
|
||||
// Soon...
|
||||
// public override ICommandItem[]? GetDockBands()
|
||||
// {
|
||||
// return new ICommandItem[] { _band };
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,926 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json.Nodes;
|
||||
using CoreWidgetProvider.Helpers;
|
||||
using CoreWidgetProvider.Widgets.Enums;
|
||||
using Microsoft.CmdPal.Core.Common;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.PerformanceMonitor;
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
/// <summary>
|
||||
/// Page for displaying performance monitor widgets. Can be used as both a list
|
||||
/// in the main window, or as a band in the dock.
|
||||
/// By using OnLoadStaticListPage, we can get onload/onunload events to start/stop
|
||||
/// the data gathering.
|
||||
/// </summary>
|
||||
internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDisposable
|
||||
{
|
||||
public override string Id => "com.microsoft.cmdpal.performanceWidget";
|
||||
|
||||
public override string Title => Resources.GetResource("Performance_Monitor_Title");
|
||||
|
||||
public override IconInfo Icon => Icons.StackedAreaIcon;
|
||||
|
||||
private readonly bool _isBandPage;
|
||||
|
||||
private readonly SystemCPUUsageWidgetPage _cpuPage = new();
|
||||
private readonly ListItem _cpuItem;
|
||||
|
||||
private readonly SystemMemoryUsageWidgetPage _memoryPage = new();
|
||||
private readonly ListItem _memoryItem;
|
||||
|
||||
private readonly SystemNetworkUsageWidgetPage _networkPage = new();
|
||||
private readonly ListItem _networkItem;
|
||||
|
||||
private readonly SystemGPUUsageWidgetPage _gpuPage = new();
|
||||
private readonly ListItem _gpuItem;
|
||||
|
||||
// For bands, we want two bands, one for up and one for down
|
||||
private ListItem? _networkUpItem;
|
||||
private ListItem? _networkDownItem;
|
||||
private string _networkUpSpeed = string.Empty;
|
||||
private string _networkDownSpeed = string.Empty;
|
||||
|
||||
public PerformanceWidgetsPage(bool isBandPage = false)
|
||||
{
|
||||
_isBandPage = isBandPage;
|
||||
_cpuItem = new ListItem(_cpuPage)
|
||||
{
|
||||
Title = _cpuPage.GetItemTitle(isBandPage),
|
||||
MoreCommands = _cpuPage.Commands,
|
||||
};
|
||||
|
||||
_cpuPage.Updated += (s, e) =>
|
||||
{
|
||||
_cpuItem.Title = _cpuPage.GetItemTitle(isBandPage);
|
||||
};
|
||||
|
||||
_memoryItem = new ListItem(_memoryPage)
|
||||
{
|
||||
Title = _memoryPage.GetItemTitle(isBandPage),
|
||||
MoreCommands = _memoryPage.Commands,
|
||||
};
|
||||
|
||||
_memoryPage.Updated += (s, e) =>
|
||||
{
|
||||
_memoryItem.Title = _memoryPage.GetItemTitle(isBandPage);
|
||||
};
|
||||
|
||||
_networkItem = new ListItem(_networkPage)
|
||||
{
|
||||
Title = _networkPage.GetItemTitle(isBandPage),
|
||||
MoreCommands = _networkPage.Commands,
|
||||
};
|
||||
|
||||
_networkPage.Updated += (s, e) =>
|
||||
{
|
||||
_networkItem.Title = _networkPage.GetItemTitle(isBandPage);
|
||||
_networkUpSpeed = _networkPage.GetUpSpeed();
|
||||
_networkDownSpeed = _networkPage.GetDownSpeed();
|
||||
_networkDownItem?.Title = $"{_networkDownSpeed}";
|
||||
_networkUpItem?.Title = $"{_networkUpSpeed}";
|
||||
};
|
||||
|
||||
_gpuItem = new ListItem(_gpuPage)
|
||||
{
|
||||
Title = _gpuPage.GetItemTitle(isBandPage),
|
||||
MoreCommands = _gpuPage.Commands,
|
||||
};
|
||||
|
||||
_gpuPage.Updated += (s, e) =>
|
||||
{
|
||||
_gpuItem.Title = _gpuPage.GetItemTitle(isBandPage);
|
||||
};
|
||||
|
||||
if (_isBandPage)
|
||||
{
|
||||
// add subtitles to them all
|
||||
_cpuItem.Subtitle = Resources.GetResource("CPU_Usage_Subtitle");
|
||||
_memoryItem.Subtitle = Resources.GetResource("Memory_Usage_Subtitle");
|
||||
_networkItem.Subtitle = Resources.GetResource("Network_Usage_Subtitle");
|
||||
_gpuItem.Subtitle = Resources.GetResource("GPU_Usage_Subtitle");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Loaded()
|
||||
{
|
||||
_cpuPage.PushActivate();
|
||||
_memoryPage.PushActivate();
|
||||
_networkPage.PushActivate();
|
||||
_gpuPage.PushActivate();
|
||||
}
|
||||
|
||||
protected override void Unloaded()
|
||||
{
|
||||
_cpuPage.PopActivate();
|
||||
_memoryPage.PopActivate();
|
||||
_networkPage.PopActivate();
|
||||
_gpuPage.PopActivate();
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
if (!_isBandPage)
|
||||
{
|
||||
// TODO add details
|
||||
return new[] { _cpuItem, _memoryItem, _networkItem, _gpuItem };
|
||||
}
|
||||
else
|
||||
{
|
||||
_networkUpItem = new ListItem(_networkPage)
|
||||
{
|
||||
Title = $"{_networkUpSpeed}",
|
||||
Subtitle = Resources.GetResource("Network_Send_Subtitle"),
|
||||
MoreCommands = _networkPage.Commands,
|
||||
};
|
||||
|
||||
_networkDownItem = new ListItem(_networkPage)
|
||||
{
|
||||
Title = $"{_networkDownSpeed}",
|
||||
Subtitle = Resources.GetResource("Network_Receive_Subtitle"),
|
||||
MoreCommands = _networkPage.Commands,
|
||||
};
|
||||
|
||||
return new[] { _cpuItem, _memoryItem, _networkDownItem, _networkUpItem, _gpuItem };
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cpuPage.Dispose();
|
||||
_memoryPage.Dispose();
|
||||
_networkPage.Dispose();
|
||||
_gpuPage.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all the performance monitor widget pages.
|
||||
/// This handles common stuff like loading their widget JSON
|
||||
/// and updating it when needed.
|
||||
/// </summary>
|
||||
internal abstract partial class WidgetPage : OnLoadContentPage
|
||||
{
|
||||
internal event EventHandler? Updated;
|
||||
|
||||
protected Dictionary<string, string> ContentData { get; } = new();
|
||||
|
||||
protected WidgetPageState Page { get; set; } = WidgetPageState.Unknown;
|
||||
|
||||
protected Dictionary<WidgetPageState, string> Template { get; set; } = new();
|
||||
|
||||
protected JsonObject ContentDataJson
|
||||
{
|
||||
get
|
||||
{
|
||||
var json = new JsonObject();
|
||||
lock (ContentData)
|
||||
{
|
||||
foreach (var kvp in ContentData)
|
||||
{
|
||||
if (kvp.Value is not null)
|
||||
{
|
||||
json[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly FormContent _formContent = new();
|
||||
|
||||
public void UpdateWidget()
|
||||
{
|
||||
lock (ContentData)
|
||||
{
|
||||
LoadContentData();
|
||||
}
|
||||
|
||||
_formContent.DataJson = ContentDataJson.ToJsonString();
|
||||
|
||||
Updated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected abstract void LoadContentData();
|
||||
|
||||
protected abstract string GetTemplatePath(WidgetPageState page);
|
||||
|
||||
protected string GetTemplateForPage(WidgetPageState page)
|
||||
{
|
||||
if (Template.TryGetValue(page, out var value))
|
||||
{
|
||||
CoreLogger.LogDebug($"Using cached template for {page}");
|
||||
return value;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(Package.Current.EffectivePath, GetTemplatePath(page));
|
||||
var template = File.ReadAllText(path, Encoding.Default) ?? throw new FileNotFoundException(path);
|
||||
|
||||
template = Resources.ReplaceIdentifersFast(template);
|
||||
CoreLogger.LogDebug($"Caching template for {page}");
|
||||
Template[page] = template;
|
||||
return template;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CoreLogger.LogError("Error getting template.", e);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public override IContent[] GetContent()
|
||||
{
|
||||
_formContent.TemplateJson = GetTemplateForPage(WidgetPageState.Content);
|
||||
|
||||
return [_formContent];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increment our tracker of how many pages have needed us active. This is a
|
||||
/// little wackier than just OnLoad/Unload. Both the ListPage for
|
||||
/// PerformanceWidgetsPage itself, AND the widget itself need the stats to
|
||||
/// be updating. So we use a counter to track how many "clients" need us
|
||||
/// active. When either is activated, we'll start updating. When both are
|
||||
/// removed, we'll stop updating.
|
||||
/// </summary>
|
||||
internal virtual void PushActivate()
|
||||
{
|
||||
_loadCount++;
|
||||
}
|
||||
|
||||
internal virtual void PopActivate()
|
||||
{
|
||||
_loadCount--;
|
||||
}
|
||||
|
||||
private int _loadCount;
|
||||
|
||||
protected bool IsActive => _loadCount > 0;
|
||||
|
||||
protected override void Loaded()
|
||||
{
|
||||
PushActivate();
|
||||
}
|
||||
|
||||
protected override void Unloaded()
|
||||
{
|
||||
PopActivate();
|
||||
}
|
||||
|
||||
internal static string FloatToPercentString(float value)
|
||||
{
|
||||
return ((int)(value * 100)).ToString(CultureInfo.InvariantCulture) + "%";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class SystemCPUUsageWidgetPage : WidgetPage, IDisposable
|
||||
{
|
||||
public override string Title => Resources.GetResource("CPU_Usage_Title");
|
||||
|
||||
public override string Id => "com.microsoft.cmdpal.cpu_widget";
|
||||
|
||||
public override IconInfo Icon => Icons.CpuIcon;
|
||||
|
||||
private readonly DataManager _dataManager;
|
||||
|
||||
public SystemCPUUsageWidgetPage()
|
||||
{
|
||||
_dataManager = new(DataType.CPU, () => UpdateWidget());
|
||||
Commands = [
|
||||
new CommandContextItem(OpenTaskManagerCommand.Instance),
|
||||
];
|
||||
}
|
||||
|
||||
protected override void LoadContentData()
|
||||
{
|
||||
// CoreLogger.LogDebug("Getting CPU stats");
|
||||
try
|
||||
{
|
||||
ContentData.Clear();
|
||||
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
var currentData = _dataManager.GetCPUStats();
|
||||
|
||||
var dataDuration = timer.ElapsedMilliseconds;
|
||||
|
||||
ContentData["cpuUsage"] = FloatToPercentString(currentData.CpuUsage);
|
||||
ContentData["cpuSpeed"] = SpeedToString(currentData.CpuSpeed);
|
||||
ContentData["cpuGraphUrl"] = currentData.CreateCPUImageUrl();
|
||||
ContentData["chartHeight"] = ChartHelper.ChartHeight + "px";
|
||||
ContentData["chartWidth"] = ChartHelper.ChartWidth + "px";
|
||||
|
||||
// ContentData["cpuProc1"] = currentData.GetCpuProcessText(0);
|
||||
// ContentData["cpuProc2"] = currentData.GetCpuProcessText(1);
|
||||
// ContentData["cpuProc3"] = currentData.GetCpuProcessText(2);
|
||||
var contentDuration = timer.ElapsedMilliseconds - dataDuration;
|
||||
|
||||
// CoreLogger.LogDebug($"CPU stats retrieved in {dataDuration} ms, content prepared in {contentDuration} ms. (Total {timer.ElapsedMilliseconds} ms)");
|
||||
// DataState = WidgetDataState.Okay;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Log.Error(e, "Error retrieving stats.");
|
||||
ContentData.Clear();
|
||||
ContentData["errorMessage"] = e.Message;
|
||||
|
||||
// ContentData = content.ToJsonString();
|
||||
// DataState = WidgetDataState.Failed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetTemplatePath(WidgetPageState page)
|
||||
{
|
||||
return page switch
|
||||
{
|
||||
WidgetPageState.Content => @"DevHome\Templates\SystemCPUUsageTemplate.json",
|
||||
WidgetPageState.Loading => @"DevHome\Templates\SystemCPUUsageTemplate.json",
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public string GetItemTitle(bool isBandPage)
|
||||
{
|
||||
if (ContentData.TryGetValue("cpuUsage", out var usage))
|
||||
{
|
||||
return isBandPage ? usage : string.Format(CultureInfo.CurrentCulture, Resources.GetResource("CPU_Usage_Label"), usage);
|
||||
}
|
||||
else
|
||||
{
|
||||
return isBandPage ? Resources.GetResource("CPU_Usage_Unknown") : Resources.GetResource("CPU_Usage_Unknown_Label");
|
||||
}
|
||||
}
|
||||
|
||||
private string SpeedToString(float cpuSpeed)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0:0.00} GHz", cpuSpeed / 1000);
|
||||
}
|
||||
|
||||
internal override void PushActivate()
|
||||
{
|
||||
base.PushActivate();
|
||||
if (IsActive)
|
||||
{
|
||||
_dataManager.Start();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void PopActivate()
|
||||
{
|
||||
base.PopActivate();
|
||||
if (!IsActive)
|
||||
{
|
||||
_dataManager.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dataManager.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class SystemMemoryUsageWidgetPage : WidgetPage, IDisposable
|
||||
{
|
||||
public override string Id => "com.microsoft.cmdpal.memory_widget";
|
||||
|
||||
public override string Title => Resources.GetResource("Memory_Usage_Title");
|
||||
|
||||
public override IconInfo Icon => Icons.MemoryIcon;
|
||||
|
||||
private readonly DataManager _dataManager;
|
||||
|
||||
public SystemMemoryUsageWidgetPage()
|
||||
{
|
||||
_dataManager = new(DataType.Memory, () => UpdateWidget());
|
||||
Commands = [
|
||||
new CommandContextItem(OpenTaskManagerCommand.Instance),
|
||||
];
|
||||
}
|
||||
|
||||
protected override void LoadContentData()
|
||||
{
|
||||
// CoreLogger.LogDebug("Getting Memory stats");
|
||||
try
|
||||
{
|
||||
ContentData.Clear();
|
||||
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
var currentData = _dataManager.GetMemoryStats();
|
||||
|
||||
var dataDuration = timer.ElapsedMilliseconds;
|
||||
|
||||
ContentData["allMem"] = MemUlongToString(currentData.AllMem);
|
||||
ContentData["usedMem"] = MemUlongToString(currentData.UsedMem);
|
||||
ContentData["memUsage"] = FloatToPercentString(currentData.MemUsage);
|
||||
ContentData["committedMem"] = MemUlongToString(currentData.MemCommitted);
|
||||
ContentData["committedLimitMem"] = MemUlongToString(currentData.MemCommitLimit);
|
||||
ContentData["cachedMem"] = MemUlongToString(currentData.MemCached);
|
||||
ContentData["pagedPoolMem"] = MemUlongToString(currentData.MemPagedPool);
|
||||
ContentData["nonPagedPoolMem"] = MemUlongToString(currentData.MemNonPagedPool);
|
||||
ContentData["memGraphUrl"] = currentData.CreateMemImageUrl();
|
||||
ContentData["chartHeight"] = ChartHelper.ChartHeight + "px";
|
||||
ContentData["chartWidth"] = ChartHelper.ChartWidth + "px";
|
||||
|
||||
var contentDuration = timer.ElapsedMilliseconds - dataDuration;
|
||||
|
||||
// CoreLogger.LogDebug($"Memory stats retrieved in {dataDuration} ms, content prepared in {contentDuration} ms. (Total {timer.ElapsedMilliseconds} ms)");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ContentData.Clear();
|
||||
ContentData["errorMessage"] = e.Message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetTemplatePath(WidgetPageState page)
|
||||
{
|
||||
return page switch
|
||||
{
|
||||
WidgetPageState.Content => @"DevHome\Templates\SystemMemoryTemplate.json",
|
||||
WidgetPageState.Loading => @"DevHome\Templates\SystemMemoryTemplate.json",
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public string GetItemTitle(bool isBandPage)
|
||||
{
|
||||
if (ContentData.TryGetValue("memUsage", out var usage))
|
||||
{
|
||||
return isBandPage ? usage : string.Format(CultureInfo.CurrentCulture, Resources.GetResource("Memory_Usage_Label"), usage);
|
||||
}
|
||||
else
|
||||
{
|
||||
return isBandPage ? Resources.GetResource("Memory_Usage_Unknown") : Resources.GetResource("Memory_Usage_Unknown_Label");
|
||||
}
|
||||
}
|
||||
|
||||
private string MemUlongToString(ulong memBytes)
|
||||
{
|
||||
if (memBytes < 1024)
|
||||
{
|
||||
return memBytes.ToString(CultureInfo.InvariantCulture) + " B";
|
||||
}
|
||||
|
||||
var memSize = memBytes / 1024.0;
|
||||
if (memSize < 1024)
|
||||
{
|
||||
return memSize.ToString("0.00", CultureInfo.InvariantCulture) + " kB";
|
||||
}
|
||||
|
||||
memSize /= 1024;
|
||||
if (memSize < 1024)
|
||||
{
|
||||
return memSize.ToString("0.00", CultureInfo.InvariantCulture) + " MB";
|
||||
}
|
||||
|
||||
memSize /= 1024;
|
||||
return memSize.ToString("0.00", CultureInfo.InvariantCulture) + " GB";
|
||||
}
|
||||
|
||||
internal override void PushActivate()
|
||||
{
|
||||
base.PushActivate();
|
||||
if (IsActive)
|
||||
{
|
||||
_dataManager.Start();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void PopActivate()
|
||||
{
|
||||
base.PopActivate();
|
||||
if (!IsActive)
|
||||
{
|
||||
_dataManager.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dataManager.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class SystemNetworkUsageWidgetPage : WidgetPage, IDisposable
|
||||
{
|
||||
public override string Id => "com.microsoft.cmdpal.network_widget";
|
||||
|
||||
public override string Title => Resources.GetResource("Network_Usage_Title");
|
||||
|
||||
public override IconInfo Icon => Icons.NetworkIcon;
|
||||
|
||||
private readonly DataManager _dataManager;
|
||||
private int _networkIndex;
|
||||
|
||||
public SystemNetworkUsageWidgetPage()
|
||||
{
|
||||
_dataManager = new(DataType.Network, () => UpdateWidget());
|
||||
Commands = [
|
||||
new CommandContextItem(new PrevNetworkCommand(this) { Name = Resources.GetResource("Previous_Network_Title") }),
|
||||
new CommandContextItem(new NextNetworkCommand(this) { Name = Resources.GetResource("Next_Network_Title") }),
|
||||
new CommandContextItem(OpenTaskManagerCommand.Instance),
|
||||
];
|
||||
}
|
||||
|
||||
protected override void LoadContentData()
|
||||
{
|
||||
// CoreLogger.LogDebug("Getting Network stats");
|
||||
try
|
||||
{
|
||||
ContentData.Clear();
|
||||
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
var currentData = _dataManager.GetNetworkStats();
|
||||
|
||||
var dataDuration = timer.ElapsedMilliseconds;
|
||||
|
||||
var netName = currentData.GetNetworkName(_networkIndex);
|
||||
var networkStats = currentData.GetNetworkUsage(_networkIndex);
|
||||
|
||||
ContentData["networkUsage"] = FloatToPercentString(networkStats.Usage);
|
||||
ContentData["netSent"] = BytesToBitsPerSecString(networkStats.Sent);
|
||||
ContentData["netReceived"] = BytesToBitsPerSecString(networkStats.Received);
|
||||
ContentData["networkName"] = netName;
|
||||
ContentData["netGraphUrl"] = currentData.CreateNetImageUrl(_networkIndex);
|
||||
ContentData["chartHeight"] = ChartHelper.ChartHeight + "px";
|
||||
ContentData["chartWidth"] = ChartHelper.ChartWidth + "px";
|
||||
|
||||
var contentDuration = timer.ElapsedMilliseconds - dataDuration;
|
||||
|
||||
// CoreLogger.LogDebug($"Network stats retrieved in {dataDuration} ms, content prepared in {contentDuration} ms. (Total {timer.ElapsedMilliseconds} ms)");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ContentData.Clear();
|
||||
ContentData["errorMessage"] = e.Message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetTemplatePath(WidgetPageState page)
|
||||
{
|
||||
return page switch
|
||||
{
|
||||
WidgetPageState.Content => @"DevHome\Templates\SystemNetworkUsageTemplate.json",
|
||||
WidgetPageState.Loading => @"DevHome\Templates\SystemNetworkUsageTemplate.json",
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public string GetItemTitle(bool isBandPage)
|
||||
{
|
||||
if (ContentData.TryGetValue("networkName", out var name) && ContentData.TryGetValue("networkUsage", out var usage))
|
||||
{
|
||||
return isBandPage ? usage : string.Format(CultureInfo.CurrentCulture, Resources.GetResource("Network_Usage_Label"), name, usage);
|
||||
}
|
||||
else
|
||||
{
|
||||
return isBandPage ? Resources.GetResource("Network_Usage_Unknown") : Resources.GetResource("Network_Usage_Unknown_Label");
|
||||
}
|
||||
}
|
||||
|
||||
// up/down speed is always used for bands
|
||||
public string GetUpSpeed()
|
||||
{
|
||||
if (ContentData.TryGetValue("netSent", out var upSpeed))
|
||||
{
|
||||
return upSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
public string GetDownSpeed()
|
||||
{
|
||||
if (ContentData.TryGetValue("netReceived", out var downSpeed))
|
||||
{
|
||||
return downSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
private string BytesToBitsPerSecString(float value)
|
||||
{
|
||||
// Bytes to bits
|
||||
value *= 8;
|
||||
|
||||
// bits to Kbits
|
||||
value /= 1024;
|
||||
if (value < 1024)
|
||||
{
|
||||
if (value < 100)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Kbps", value);
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0:0} Kbps", value);
|
||||
}
|
||||
|
||||
// Kbits to Mbits
|
||||
value /= 1024;
|
||||
if (value < 1024)
|
||||
{
|
||||
if (value < 100)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Mbps", value);
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0:0} Mbps", value);
|
||||
}
|
||||
|
||||
// Mbits to Gbits
|
||||
value /= 1024;
|
||||
if (value < 100)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Gbps", value);
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0:0} Gbps", value);
|
||||
}
|
||||
|
||||
internal override void PushActivate()
|
||||
{
|
||||
base.PushActivate();
|
||||
if (IsActive)
|
||||
{
|
||||
_dataManager.Start();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void PopActivate()
|
||||
{
|
||||
base.PopActivate();
|
||||
if (!IsActive)
|
||||
{
|
||||
_dataManager.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePrevNetwork()
|
||||
{
|
||||
_networkIndex = _dataManager.GetNetworkStats().GetPrevNetworkIndex(_networkIndex);
|
||||
UpdateWidget();
|
||||
}
|
||||
|
||||
private void HandleNextNetwork()
|
||||
{
|
||||
_networkIndex = _dataManager.GetNetworkStats().GetNextNetworkIndex(_networkIndex);
|
||||
UpdateWidget();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dataManager.Dispose();
|
||||
}
|
||||
|
||||
private sealed partial class PrevNetworkCommand : InvokableCommand
|
||||
{
|
||||
private readonly SystemNetworkUsageWidgetPage _page;
|
||||
|
||||
public PrevNetworkCommand(SystemNetworkUsageWidgetPage page)
|
||||
{
|
||||
_page = page;
|
||||
}
|
||||
|
||||
public override string Id => "com.microsoft.cmdpal.network_widget.prev";
|
||||
|
||||
public override IconInfo Icon => Icons.NavigateBackwardIcon;
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
_page.HandlePrevNetwork();
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed partial class NextNetworkCommand : InvokableCommand
|
||||
{
|
||||
private readonly SystemNetworkUsageWidgetPage _page;
|
||||
|
||||
public NextNetworkCommand(SystemNetworkUsageWidgetPage page)
|
||||
{
|
||||
_page = page;
|
||||
}
|
||||
|
||||
public override string Id => "com.microsoft.cmdpal.network_widget.next";
|
||||
|
||||
public override IconInfo Icon => Icons.NavigateForwardIcon;
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
_page.HandleNextNetwork();
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class SystemGPUUsageWidgetPage : WidgetPage, IDisposable
|
||||
{
|
||||
public override string Id => "com.microsoft.cmdpal.gpu_widget";
|
||||
|
||||
public override string Title => Resources.GetResource("GPU_Usage_Title");
|
||||
|
||||
public override IconInfo Icon => Icons.GpuIcon;
|
||||
|
||||
private readonly DataManager _dataManager;
|
||||
private readonly string _gpuActiveEngType = "3D";
|
||||
private int _gpuActiveIndex;
|
||||
|
||||
public SystemGPUUsageWidgetPage()
|
||||
{
|
||||
_dataManager = new(DataType.GPU, () => UpdateWidget());
|
||||
|
||||
Commands = [
|
||||
new CommandContextItem(new PrevGPUCommand(this) { Name = Resources.GetResource("Previous_GPU_Title") }),
|
||||
new CommandContextItem(new NextGPUCommand(this) { Name = Resources.GetResource("Next_GPU_Title") }),
|
||||
new CommandContextItem(OpenTaskManagerCommand.Instance),
|
||||
];
|
||||
}
|
||||
|
||||
protected override void LoadContentData()
|
||||
{
|
||||
// CoreLogger.LogDebug("Getting GPU stats");
|
||||
try
|
||||
{
|
||||
ContentData.Clear();
|
||||
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
var stats = _dataManager.GetGPUStats();
|
||||
|
||||
var dataDuration = timer.ElapsedMilliseconds;
|
||||
|
||||
var gpuName = stats.GetGPUName(_gpuActiveIndex);
|
||||
|
||||
ContentData["gpuUsage"] = FloatToPercentString(stats.GetGPUUsage(_gpuActiveIndex, _gpuActiveEngType));
|
||||
ContentData["gpuName"] = gpuName;
|
||||
ContentData["gpuTemp"] = stats.GetGPUTemperature(_gpuActiveIndex);
|
||||
ContentData["gpuGraphUrl"] = stats.CreateGPUImageUrl(_gpuActiveIndex);
|
||||
ContentData["chartHeight"] = ChartHelper.ChartHeight + "px";
|
||||
ContentData["chartWidth"] = ChartHelper.ChartWidth + "px";
|
||||
|
||||
var contentDuration = timer.ElapsedMilliseconds - dataDuration;
|
||||
|
||||
// CoreLogger.LogDebug($"GPU stats retrieved in {dataDuration} ms, content prepared in {contentDuration} ms. (Total {timer.ElapsedMilliseconds} ms)");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ContentData.Clear();
|
||||
ContentData["errorMessage"] = e.Message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetTemplatePath(WidgetPageState page)
|
||||
{
|
||||
return page switch
|
||||
{
|
||||
WidgetPageState.Content => @"DevHome\Templates\SystemGPUUsageTemplate.json",
|
||||
WidgetPageState.Loading => @"DevHome\Templates\SystemGPUUsageTemplate.json",
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public string GetItemTitle(bool isBandPage)
|
||||
{
|
||||
if (ContentData.TryGetValue("gpuName", out var name) && ContentData.TryGetValue("gpuUsage", out var usage))
|
||||
{
|
||||
return isBandPage ? usage : string.Format(CultureInfo.CurrentCulture, Resources.GetResource("GPU_Usage_Label"), name, usage);
|
||||
}
|
||||
else
|
||||
{
|
||||
return isBandPage ? Resources.GetResource("GPU_Usage_Unknown") : Resources.GetResource("GPU_Usage_Unknown_Label");
|
||||
}
|
||||
}
|
||||
|
||||
internal override void PushActivate()
|
||||
{
|
||||
base.PushActivate();
|
||||
if (IsActive)
|
||||
{
|
||||
_dataManager.Start();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void PopActivate()
|
||||
{
|
||||
base.PopActivate();
|
||||
if (!IsActive)
|
||||
{
|
||||
_dataManager.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePrevGPU()
|
||||
{
|
||||
_gpuActiveIndex = _dataManager.GetGPUStats().GetPrevGPUIndex(_gpuActiveIndex);
|
||||
UpdateWidget();
|
||||
}
|
||||
|
||||
private void HandleNextGPU()
|
||||
{
|
||||
_gpuActiveIndex = _dataManager.GetGPUStats().GetNextGPUIndex(_gpuActiveIndex);
|
||||
UpdateWidget();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dataManager.Dispose();
|
||||
}
|
||||
|
||||
private sealed partial class PrevGPUCommand : InvokableCommand
|
||||
{
|
||||
private readonly SystemGPUUsageWidgetPage _page;
|
||||
|
||||
public PrevGPUCommand(SystemGPUUsageWidgetPage page)
|
||||
{
|
||||
_page = page;
|
||||
}
|
||||
|
||||
public override string Id => "com.microsoft.cmdpal.gpu_widget.prev";
|
||||
|
||||
public override IconInfo Icon => Icons.NavigateBackwardIcon;
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
_page.HandlePrevGPU();
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed partial class NextGPUCommand : InvokableCommand
|
||||
{
|
||||
private readonly SystemGPUUsageWidgetPage _page;
|
||||
|
||||
public NextGPUCommand(SystemGPUUsageWidgetPage page)
|
||||
{
|
||||
_page = page;
|
||||
}
|
||||
|
||||
public override string Id => "com.microsoft.cmdpal.gpu_widget.next";
|
||||
|
||||
public override IconInfo Icon => Icons.NavigateForwardIcon;
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
_page.HandleNextGPU();
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class OpenTaskManagerCommand : InvokableCommand
|
||||
{
|
||||
internal static readonly OpenTaskManagerCommand Instance = new();
|
||||
|
||||
public override string Id => "com.microsoft.cmdpal.open_task_manager";
|
||||
|
||||
public override IconInfo Icon => Icons.StackedAreaIcon; // StackedAreaIcon looks like task manager's icon
|
||||
|
||||
public override string Name => Resources.GetResource("Open_Task_Manager_Title");
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "taskmgr.exe",
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CoreLogger.LogError("Error launching Task Manager.", e);
|
||||
}
|
||||
|
||||
return CommandResult.Hide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Widget_Template.Loading" xml:space="preserve">
|
||||
<value>Loading...</value>
|
||||
<comment>Shown in Widget, when loading config file content</comment>
|
||||
</data>
|
||||
<data name="Widget_Template_Tooltip.Submit" xml:space="preserve">
|
||||
<value>Submit</value>
|
||||
<comment>Shown in Widget, Tooltip text</comment>
|
||||
</data>
|
||||
<data name="SSH_Widget_Template.Name" xml:space="preserve">
|
||||
<value>SSH keychain</value>
|
||||
</data>
|
||||
<data name="SSH_Widget_Template.Target" xml:space="preserve">
|
||||
<value>Local</value>
|
||||
</data>
|
||||
<data name="SSH_Widget_Template.ConfigFilePath" xml:space="preserve">
|
||||
<value>Config file path</value>
|
||||
</data>
|
||||
<data name="SSH_Widget_Template.ConfigFileNotFound" xml:space="preserve">
|
||||
<value>File not found</value>
|
||||
</data>
|
||||
<data name="SSH_Widget_Template.EmptyHosts" xml:space="preserve">
|
||||
<value>There are no hosts in this config file.</value>
|
||||
</data>
|
||||
<data name="SSH_Widget_Template.NumOfHosts" xml:space="preserve">
|
||||
<value>Number of hosts found</value>
|
||||
</data>
|
||||
<data name="SSH_Widget_Template.Connect" xml:space="preserve">
|
||||
<value>Connect</value>
|
||||
</data>
|
||||
<data name="SSH_Widget_Template.ErrorProcessingConfigFile" xml:space="preserve">
|
||||
<value>Processing config file failed</value>
|
||||
</data>
|
||||
<data name="Memory_Widget_Template.SystemMemory" xml:space="preserve">
|
||||
<value>System memory</value>
|
||||
</data>
|
||||
<data name="Memory_Widget_Template.MemoryUsage" xml:space="preserve">
|
||||
<value>Utilization</value>
|
||||
</data>
|
||||
<data name="Memory_Widget_Template.AllMemory" xml:space="preserve">
|
||||
<value>All memory</value>
|
||||
</data>
|
||||
<data name="Memory_Widget_Template.UsedMemory" xml:space="preserve">
|
||||
<value>In use (compressed)</value>
|
||||
</data>
|
||||
<data name="Memory_Widget_Template.Committed" xml:space="preserve">
|
||||
<value>Committed</value>
|
||||
</data>
|
||||
<data name="Memory_Widget_Template.Cached" xml:space="preserve">
|
||||
<value>Cached</value>
|
||||
</data>
|
||||
<data name="Memory_Widget_Template.NonPagedPool" xml:space="preserve">
|
||||
<value>Non-paged pool</value>
|
||||
</data>
|
||||
<data name="Memory_Widget_Template.PagedPool" xml:space="preserve">
|
||||
<value>Paged pool</value>
|
||||
</data>
|
||||
<data name="NetworkUsage_Widget_Template.Network_Usage" xml:space="preserve">
|
||||
<value>Utilization</value>
|
||||
</data>
|
||||
<data name="NetworkUsage_Widget_Template.Sent" xml:space="preserve">
|
||||
<value>Send</value>
|
||||
</data>
|
||||
<data name="NetworkUsage_Widget_Template.Received" xml:space="preserve">
|
||||
<value>Receive</value>
|
||||
</data>
|
||||
<data name="NetworkUsage_Widget_Template.Network_Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="Previous_Network_Title" xml:space="preserve">
|
||||
<value>Previous network</value>
|
||||
</data>
|
||||
<data name="Next_Network_Title" xml:space="preserve">
|
||||
<value>Next network</value>
|
||||
</data>
|
||||
<data name="NetworkUsage_Widget_Template.Ethernet_Heading" xml:space="preserve">
|
||||
<value>Ethernet</value>
|
||||
</data>
|
||||
<data name="GPUUsage_Widget_Template.GPU_Usage" xml:space="preserve">
|
||||
<value>Utilization</value>
|
||||
</data>
|
||||
<data name="GPUUsage_Widget_Template.GPU_Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="GPUUsage_Widget_Template.GPU_Temperature" xml:space="preserve">
|
||||
<value>Temperature</value>
|
||||
</data>
|
||||
<data name="Previous_GPU_Title" xml:space="preserve">
|
||||
<value>Previous GPU</value>
|
||||
</data>
|
||||
<data name="Next_GPU_Title" xml:space="preserve">
|
||||
<value>Next GPU</value>
|
||||
</data>
|
||||
<data name="CPUUsage_Widget_Template.CPU_Usage" xml:space="preserve">
|
||||
<value>Utilization</value>
|
||||
</data>
|
||||
<data name="CPUUsage_Widget_Template.CPU_Speed" xml:space="preserve">
|
||||
<value>Speed</value>
|
||||
</data>
|
||||
<data name="CPUUsage_Widget_Template.Processes" xml:space="preserve">
|
||||
<value>Processes</value>
|
||||
</data>
|
||||
<data name="CPUUsage_Widget_Template.End_Process" xml:space="preserve">
|
||||
<value>End process</value>
|
||||
</data>
|
||||
<data name="Widget_Template_Button.Preview" xml:space="preserve">
|
||||
<value>Preview</value>
|
||||
<comment>Shown in Widget, Button text</comment>
|
||||
</data>
|
||||
<data name="Widget_Template_Button.Save" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
<comment>Shown in Widget, Button text</comment>
|
||||
</data>
|
||||
<data name="Widget_Template_Button.Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
<comment>Shown in Widget, Button text</comment>
|
||||
</data>
|
||||
<data name="CPU_Usage_Subtitle" xml:space="preserve">
|
||||
<value>CPU</value>
|
||||
</data>
|
||||
<data name="Memory_Usage_Subtitle" xml:space="preserve">
|
||||
<value>Memory</value>
|
||||
</data>
|
||||
<data name="Network_Usage_Subtitle" xml:space="preserve">
|
||||
<value>Network</value>
|
||||
</data>
|
||||
<data name="GPU_Usage_Subtitle" xml:space="preserve">
|
||||
<value>GPU</value>
|
||||
</data>
|
||||
<data name="Performance_Monitor_Title" xml:space="preserve">
|
||||
<value>Performance monitor</value>
|
||||
</data>
|
||||
<data name="CPU_Usage_Title" xml:space="preserve">
|
||||
<value>CPU Usage</value>
|
||||
</data>
|
||||
<data name="CPU_Usage_Label" xml:space="preserve">
|
||||
<value>CPU Usage: {0}</value>
|
||||
<comment>{0} is the CPU usage percentage</comment>
|
||||
</data>
|
||||
<data name="CPU_Usage_Unknown" xml:space="preserve">
|
||||
<value>???</value>
|
||||
</data>
|
||||
<data name="CPU_Usage_Unknown_Label" xml:space="preserve">
|
||||
<value>CPU Usage: ???</value>
|
||||
</data>
|
||||
<data name="Memory_Usage_Title" xml:space="preserve">
|
||||
<value>Memory Usage</value>
|
||||
</data>
|
||||
<data name="Memory_Usage_Label" xml:space="preserve">
|
||||
<value>Memory Usage: {0}</value>
|
||||
<comment>{0} is the memory usage percentage</comment>
|
||||
</data>
|
||||
<data name="Memory_Usage_Unknown" xml:space="preserve">
|
||||
<value>???</value>
|
||||
</data>
|
||||
<data name="Memory_Usage_Unknown_Label" xml:space="preserve">
|
||||
<value>Memory Usage: ???</value>
|
||||
</data>
|
||||
<data name="Network_Usage_Title" xml:space="preserve">
|
||||
<value>Network Usage</value>
|
||||
</data>
|
||||
<data name="Network_Usage_Label" xml:space="preserve">
|
||||
<value>Network ({0}): {1}</value>
|
||||
<comment>{0} is the network adapter name, {1} is the usage percentage</comment>
|
||||
</data>
|
||||
<data name="Network_Usage_Unknown" xml:space="preserve">
|
||||
<value>???</value>
|
||||
</data>
|
||||
<data name="Network_Usage_Unknown_Label" xml:space="preserve">
|
||||
<value>Network Usage: ???</value>
|
||||
</data>
|
||||
<data name="GPU_Usage_Title" xml:space="preserve">
|
||||
<value>GPU Usage</value>
|
||||
</data>
|
||||
<data name="GPU_Usage_Label" xml:space="preserve">
|
||||
<value>GPU ({0}): {1}</value>
|
||||
<comment>{0} is the GPU name, {1} is the usage percentage</comment>
|
||||
</data>
|
||||
<data name="GPU_Usage_Unknown" xml:space="preserve">
|
||||
<value>???</value>
|
||||
</data>
|
||||
<data name="GPU_Usage_Unknown_Label" xml:space="preserve">
|
||||
<value>GPU Usage: ???</value>
|
||||
</data>
|
||||
<data name="Open_Task_Manager_Title" xml:space="preserve">
|
||||
<value>Open Task Manager</value>
|
||||
</data>
|
||||
<data name="Network_Send_Subtitle" xml:space="preserve">
|
||||
<value>Send ↑</value>
|
||||
</data>
|
||||
<data name="Network_Receive_Subtitle" xml:space="preserve">
|
||||
<value>Receive ↓</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,10 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Shmuelie.WinRTServer;
|
||||
using Shmuelie.WinRTServer.CsWinRT;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
@@ -15,18 +17,22 @@ public class Program
|
||||
{
|
||||
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
|
||||
{
|
||||
using ExtensionServer server = new();
|
||||
var extensionDisposedEvent = new ManualResetEvent(false);
|
||||
var extensionInstance = new SampleExtension(extensionDisposedEvent);
|
||||
global::Shmuelie.WinRTServer.ComServer server = new();
|
||||
|
||||
ManualResetEvent extensionDisposedEvent = new(false);
|
||||
|
||||
// We are instantiating an extension instance once above, and returning it every time the callback in RegisterExtension below is called.
|
||||
// This makes sure that only one instance of SampleExtension is alive, which is returned every time the host asks for the IExtension object.
|
||||
// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
|
||||
server.RegisterExtension(() => extensionInstance);
|
||||
SampleExtension extensionInstance = new(extensionDisposedEvent);
|
||||
server.RegisterClass<SampleExtension, IExtension>(() => extensionInstance);
|
||||
server.Start();
|
||||
|
||||
// This will make the main thread wait until the event is signalled by the extension class.
|
||||
// Since we have a single instance of the extension object, we exit as soon as it is disposed.
|
||||
// Since we have single instance of the extension object, we exit as soon as it is disposed.
|
||||
extensionDisposedEvent.WaitOne();
|
||||
server.Stop();
|
||||
server.UnsafeDispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Shmuelie.WinRTServer" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -8,8 +8,14 @@ using WinRT;
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class CommandItem : BaseObservable, ICommandItem, IExtendedAttributesProvider
|
||||
public partial class CommandItem : BaseObservable, ICommandItem
|
||||
{
|
||||
// NOTE TO MAINTAINERS: Do NOT implement `IExtendedAttributesProvider` here
|
||||
// directly. Instead, implement it in derived classes like `ListItem` where
|
||||
// appropriate.
|
||||
//
|
||||
// Putting it directly here will cause out-of-proc extensions to fail to
|
||||
// load the context menu commands, for unknown CsWinRT reasons.
|
||||
private readonly PropertySet _extendedAttributes = new();
|
||||
|
||||
private ICommand? _command;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class FallbackCommandItem : CommandItem, IFallbackCommandItem, IFallbackHandler, IFallbackCommandItem2
|
||||
public partial class FallbackCommandItem : CommandItem, IFallbackCommandItem, IFallbackHandler, IFallbackCommandItem2, IExtendedAttributesProvider
|
||||
{
|
||||
private readonly IFallbackHandler? _fallbackHandler;
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class ListItem : CommandItem, IListItem
|
||||
public partial class ListItem : CommandItem, IListItem, IExtendedAttributesProvider
|
||||
{
|
||||
public virtual ITag[] Tags { get; set => SetProperty(ref field, value); } = [];
|
||||
|
||||
|
||||
@@ -155,18 +155,6 @@
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.AdvancedPasteUIShortcut, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
</tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard Name="AdvancedPasteEnableClipboardPreview" ContentAlignment="Left">
|
||||
<controls:CheckBoxWithDescriptionControl x:Uid="AdvancedPaste_EnableClipboardPreview" IsChecked="{x:Bind ViewModel.EnableClipboardPreview, Mode=TwoWay}" />
|
||||
@@ -180,6 +168,16 @@
|
||||
IsEnabled="{x:Bind ViewModel.ClipboardHistoryEnabled, Mode=OneWay}">
|
||||
<controls:CheckBoxWithDescriptionControl x:Uid="AdvancedPaste_Clipboard_History_Enabled_SettingsCard" IsChecked="{x:Bind ViewModel.ClipboardHistoryEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<tkcontrols:SettingsCard Name="AdvancedPasteCloseAfterLosingFocus" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="AdvancedPaste_CloseAfterLosingFocus" IsChecked="{x:Bind ViewModel.CloseAfterLosingFocus, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
@@ -506,22 +506,30 @@
|
||||
</InfoBar>
|
||||
</tkcontrols:SettingsExpander.ItemsFooter>
|
||||
</tkcontrols:SettingsExpander>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard x:Uid="GeneralPage_ReportBugPackage" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button
|
||||
x:Uid="GeneralPageReportBugPackage"
|
||||
Click="BugReportToolClicked"
|
||||
Visibility="{x:Bind ViewModel.IsBugReportRunning, Converter={StaticResource ReverseBoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
<ProgressRing
|
||||
Width="24"
|
||||
Height="24"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{x:Bind ViewModel.IsBugReportRunning, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<tkcontrols:SettingsCard x:Uid="GeneralPage_ReportBugPackage" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button
|
||||
x:Uid="GeneralPageReportBugPackage"
|
||||
Click="BugReportToolClicked"
|
||||
Visibility="{x:Bind ViewModel.IsBugReportRunning, Converter={StaticResource ReverseBoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
<ProgressRing
|
||||
Width="24"
|
||||
Height="24"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{x:Bind ViewModel.IsBugReportRunning, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ Runs the full setup process.
|
||||
Runs setup but skips Visual Studio component installation.
|
||||
|
||||
.EXAMPLE
|
||||
.\tools\build\setup-dev-environment.ps1 -VSInstallPath "C:\Program Files\Microsoft Visual Studio\2022\Enterprise"
|
||||
.\tools\build\setup-dev-environment.ps1 -VSInstallPath "C:\Program Files\Microsoft Visual Studio\18\Enterprise"
|
||||
Runs setup with a custom Visual Studio installation path.
|
||||
|
||||
.NOTES
|
||||
@@ -170,6 +170,9 @@ if (-not $SkipVSComponents) {
|
||||
|
||||
if (-not $VSInstallPath) {
|
||||
$commonPaths = @(
|
||||
"${env:ProgramFiles}\Microsoft Visual Studio\18\Enterprise",
|
||||
"${env:ProgramFiles}\Microsoft Visual Studio\18\Professional",
|
||||
"${env:ProgramFiles}\Microsoft Visual Studio\18\Community",
|
||||
"${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise",
|
||||
"${env:ProgramFiles}\Microsoft Visual Studio\2022\Professional",
|
||||
"${env:ProgramFiles}\Microsoft Visual Studio\2022\Community"
|
||||
@@ -183,8 +186,8 @@ if (-not $SkipVSComponents) {
|
||||
}
|
||||
|
||||
if (-not $VSInstallPath -or -not (Test-Path $VSInstallPath)) {
|
||||
Write-Warning " Could not find Visual Studio 2022 installation"
|
||||
Write-Warning " Please install Visual Studio 2022 and try again, or import .vsconfig manually"
|
||||
Write-Warning " Could not find Visual Studio installation"
|
||||
Write-Warning " Please install Visual Studio 2026 (or 2022 17.4+) and try again, or import .vsconfig manually"
|
||||
} else {
|
||||
Write-Host " Found: $VSInstallPath" -ForegroundColor DarkGray
|
||||
|
||||
@@ -282,7 +285,7 @@ Write-Host ""
|
||||
Write-Host "Setup complete" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:"
|
||||
Write-Host " 1. Open PowerToys.slnx in Visual Studio 2022"
|
||||
Write-Host " 1. Open PowerToys.slnx in Visual Studio"
|
||||
Write-Host " 2. If prompted to install additional components, click Install"
|
||||
Write-Host " 3. Build the solution (Ctrl+Shift+B)"
|
||||
Write-Host ""
|
||||
|
||||
Reference in New Issue
Block a user