mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-02 09:19:48 +01:00
Compare commits
3 Commits
main
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcb9a2a205 | ||
|
|
14c10b192a | ||
|
|
5da332fc9b |
6
.github/actions/spell-check/allow/code.txt
vendored
6
.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
|
||||
|
||||
@@ -342,3 +344,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
|
||||
|
||||
@@ -218,6 +218,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" />
|
||||
|
||||
@@ -12,6 +12,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;
|
||||
@@ -167,6 +168,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)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user