Add initial support for Performance Overlay

This commit is contained in:
Kamil Trzciński 2022-11-13 13:36:50 +01:00
parent c3a3cb4640
commit 059d64827f
19 changed files with 1252 additions and 1 deletions

2
.gitignore vendored
View file

@ -2,3 +2,5 @@ bin/
obj/
/.vs/
*.user
build-release/
.vscode/

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="PerformanceOverlay.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<userSettings>
<PerformanceOverlay.Settings>
<setting name="ShowOSD" serializeAs="String">
<value>True</value>
</setting>
<setting name="OSDMode" serializeAs="String">
<value>FPS</value>
</setting>
</PerformanceOverlay.Settings>
</userSettings>
</configuration>

View file

@ -0,0 +1,140 @@
using PerformanceOverlay.External;
using RTSSSharedMemoryNET;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PerformanceOverlay
{
internal class Controller : IDisposable
{
Container components = new Container();
RTSSSharedMemoryNET.OSD? osd;
ToolStripMenuItem showItem;
Sensors sensors = new Sensors();
LibreHardwareMonitor.Hardware.Computer libreHardwareComputer = new LibreHardwareMonitor.Hardware.Computer
{
IsCpuEnabled = true,
IsGpuEnabled = true,
IsStorageEnabled = true,
IsBatteryEnabled = true
};
public Controller()
{
var contextMenu = new System.Windows.Forms.ContextMenuStrip(components);
showItem = new ToolStripMenuItem("&Show OSD");
showItem.Click += ShowItem_Click;
showItem.Checked = Settings.Default.ShowOSD;
contextMenu.Items.Add(showItem);
contextMenu.Items.Add(new ToolStripSeparator());
foreach (var mode in Enum.GetValues<Overlays.Mode>())
{
var modeItem = new ToolStripMenuItem(mode.ToString());
modeItem.Tag = mode;
modeItem.Click += delegate
{
Settings.Default.OSDModeParsed = mode;
updateContextItems(contextMenu);
};
contextMenu.Items.Add(modeItem);
}
updateContextItems(contextMenu);
contextMenu.Items.Add(new ToolStripSeparator());
var exitItem = contextMenu.Items.Add("&Exit");
exitItem.Click += ExitItem_Click;
var notifyIcon = new System.Windows.Forms.NotifyIcon(components);
notifyIcon.Icon = Resources.traffic_light_outline1;
notifyIcon.Text = "Steam Deck Fan Control";
notifyIcon.Visible = true;
notifyIcon.ContextMenuStrip = contextMenu;
notifyIcon.Click += NotifyIcon_Click;
var osdTimer = new System.Windows.Forms.Timer(components);
osdTimer.Tick += OsdTimer_Tick;
osdTimer.Interval = 250;
osdTimer.Enabled = true;
GlobalHotKey.RegisterHotKey("F11", () =>
{
showItem.Checked = !showItem.Checked;
});
// Select next overlay
GlobalHotKey.RegisterHotKey("Shift+F11", () =>
{
var values = Enum.GetValues<Overlays.Mode>().ToList();
int index = values.IndexOf(Settings.Default.OSDModeParsed);
Settings.Default.OSDModeParsed = values[(index + 1) % values.Count];
showItem.Checked = true;
updateContextItems(contextMenu);
});
}
private void updateContextItems(ContextMenuStrip contextMenu)
{
foreach (ToolStripItem item in contextMenu.Items)
{
if (item.Tag is Overlays.Mode)
((ToolStripMenuItem)item).Checked = ((Overlays.Mode)item.Tag == Settings.Default.OSDModeParsed);
}
}
private void NotifyIcon_Click(object? sender, EventArgs e)
{
((NotifyIcon)sender).ContextMenuStrip.Show(Control.MousePosition);
}
private void ShowItem_Click(object? sender, EventArgs e)
{
showItem.Checked = !showItem.Checked;
}
private void OsdTimer_Tick(object? sender, EventArgs e)
{
if (!showItem.Checked)
{
using (osd) { }
osd = null;
return;
}
sensors.Update();
var osdOverlay = Overlays.GetOSD(Settings.Default.OSDModeParsed, sensors);
try
{
if (osd == null)
osd = new OSD("PerformanceOverlay");
osd.Update(osdOverlay);
}
catch(SystemException)
{
osd = null;
}
}
private void ExitItem_Click(object? sender, EventArgs e)
{
Application.Exit();
}
public void Dispose()
{
components.Dispose();
using (osd) { }
using (sensors) { }
}
}
}

View file

@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Input;
namespace PerformanceOverlay.External
{
public class GlobalHotKey : IDisposable
{
/// <summary>
/// Registers a global hotkey
/// </summary>
/// <param name="aKeyGesture">e.g. Alt + Shift + Control + Win + S</param>
/// <param name="aAction">Action to be called when hotkey is pressed</param>
/// <returns>true, if registration succeeded, otherwise false</returns>
public static bool RegisterHotKey(string aKeyGestureString, Action aAction)
{
var c = new KeyGestureConverter();
KeyGesture aKeyGesture = (KeyGesture)c.ConvertFrom(aKeyGestureString);
return RegisterHotKey(aKeyGesture.Modifiers, aKeyGesture.Key, aAction);
}
public static bool RegisterHotKey(ModifierKeys aModifier, Key aKey, Action aAction)
{
if (aModifier == ModifierKeys.None && false)
{
throw new ArgumentException("Modifier must not be ModifierKeys.None");
}
if (aAction is null)
{
throw new ArgumentNullException(nameof(aAction));
}
System.Windows.Forms.Keys aVirtualKeyCode = (System.Windows.Forms.Keys)KeyInterop.VirtualKeyFromKey(aKey);
currentID = currentID + 1;
bool aRegistered = RegisterHotKey(window.Handle, currentID,
(uint)aModifier | MOD_NOREPEAT,
(uint)aVirtualKeyCode);
if (aRegistered)
{
registeredHotKeys.Add(new HotKeyWithAction(aModifier, aKey, aAction));
}
return aRegistered;
}
public void Dispose()
{
// unregister all the registered hot keys.
for (int i = currentID; i > 0; i--)
{
UnregisterHotKey(window.Handle, i);
}
// dispose the inner native window.
window.Dispose();
}
static GlobalHotKey()
{
window.KeyPressed += (s, e) =>
{
registeredHotKeys.ForEach(x =>
{
if (e.Modifier == x.Modifier && e.Key == x.Key)
{
x.Action();
}
});
};
}
private static readonly InvisibleWindowForMessages window = new InvisibleWindowForMessages();
private static int currentID;
private static uint MOD_NOREPEAT = 0x4000;
private static List<HotKeyWithAction> registeredHotKeys = new List<HotKeyWithAction>();
private class HotKeyWithAction
{
public HotKeyWithAction(ModifierKeys modifier, Key key, Action action)
{
Modifier = modifier;
Key = key;
Action = action;
}
public ModifierKeys Modifier { get; }
public Key Key { get; }
public Action Action { get; }
}
// Registers a hot key with Windows.
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
// Unregisters the hot key with Windows.
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private class InvisibleWindowForMessages : System.Windows.Forms.NativeWindow, IDisposable
{
public InvisibleWindowForMessages()
{
CreateHandle(new System.Windows.Forms.CreateParams());
}
private static int WM_HOTKEY = 0x0312;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_HOTKEY)
{
var aWPFKey = KeyInterop.KeyFromVirtualKey(((int)m.LParam >> 16) & 0xFFFF);
ModifierKeys modifier = (ModifierKeys)((int)m.LParam & 0xFFFF);
if (KeyPressed != null)
{
KeyPressed(this, new HotKeyPressedEventArgs(modifier, aWPFKey));
}
}
}
public class HotKeyPressedEventArgs : EventArgs
{
private ModifierKeys _modifier;
private Key _key;
internal HotKeyPressedEventArgs(ModifierKeys modifier, Key key)
{
_modifier = modifier;
_key = key;
}
public ModifierKeys Modifier
{
get { return _modifier; }
}
public Key Key
{
get { return _key; }
}
}
public event EventHandler<HotKeyPressedEventArgs> KeyPressed;
#region IDisposable Members
public void Dispose()
{
this.DestroyHandle();
}
#endregion
}
}
}

View file

@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;
namespace PerformanceOverlay
{
internal class Overlays
{
public enum Mode
{
FPS,
Minimal,
Detail,
All
}
public class Entry
{
public String? Text { get; set; }
public IList<Mode> Include { get; set; } = new List<Mode>();
public IList<Mode> Exclude { get; set; } = new List<Mode>();
public IList<Entry> Nested { get; set; } = new List<Entry>();
public String Separator { get; set; } = "";
public bool IgnoreMissing { get; set; }
public static readonly Regex attributeRegex = new Regex("{([^}]+)}", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
public Entry()
{ }
public Entry(String text)
{
this.Text = text;
}
public IEnumerable<Match> AllAttributes
{
get
{
return attributeRegex.Matches(Text ?? "");
}
}
private String EvaluateText(Sensors sensors)
{
String output = Text ?? "";
foreach (var attribute in AllAttributes)
{
String attributeName = attribute.Groups[1].Value;
String? value = sensors.GetValue(attributeName);
if (value is null && IgnoreMissing)
return "";
output = output.Replace(attribute.Value, value ?? "-");
}
return output;
}
public String? GetValue(Mode mode, Sensors sensors)
{
if (Exclude.Count > 0 && Exclude.Contains(mode))
return null;
if (Include.Count > 0 && !Include.Contains(mode))
return null;
String output = EvaluateText(sensors);
if (Nested.Count > 0)
{
var outputs = Nested.Select(entry => entry.GetValue(mode, sensors)).Where(output => output != null);
if (outputs.Count() == 0)
return null;
output += String.Join(Separator, outputs);
}
if (output == String.Empty)
return null;
return output;
}
}
public static readonly String[] Helpers =
{
"<C0=008040><C1=0080C0><C2=C08080><C3=FF0000><C4=FFFFFF><C250=FF8000>",
"<A0=-4><A1=5><A2=-2><S0=-50><S1=50>",
};
public static readonly Entry OSD = new Entry
{
Nested = {
new Entry { Text = "<C4><FR><C><A><A1><S1><C4> FPS", Include = { Mode.FPS } },
new Entry {
Nested =
{
new Entry
{
Text = "<C1>BATT<C>",
Nested =
{
new Entry("<C4><A0>{BATT_%}<A><A1><S1> %<S><A>"),
new Entry("<C4><A0>{BATT_W}<A><A1><S1> W<S><A>")
}
},
new Entry
{
Text = "<C1>GPU<C>",
Nested =
{
new Entry("<C4><A0>{GPU_%}<A><A1><S1> %<S><A>"),
new Entry("<C4><A0>{GPU_W}<A><A1><S1> W<S><A>"),
new Entry { Text = "<C4><A0>{GPU_T}<A><A1><S1> C<S><A>", IgnoreMissing = true }
}
},
new Entry
{
Text = "<C1>CPU<C>",
Nested =
{
new Entry("<C4><A0>{CPU_%}<A><A1><S1> %<S><A>"),
new Entry("<C4><A0>{CPU_W}<A><A1><S1> W<S><A>"),
new Entry { Text = "<C4><A0>{CPU_T}<A><A1><S1> C<S><A>", IgnoreMissing = true }
}
},
new Entry
{
Text = "<C1>RAM<C>",
Nested =
{
new Entry("<C4><A0>{MEM_GB}<A><A1><S1> GiB<S><A>")
}
},
new Entry
{
Text = "<C2><APP><C>",
Nested =
{
new Entry("<A0><C4><FR><C><A><A1><S1><C4> FPS<C><S><A>")
}
},
new Entry
{
Text = "<C2><OBJ><C>"
}
},
Separator = "<C250>|<C> ",
Include = { Mode.Minimal }
},
new Entry { Text = "<C0>MEM<C> <A0>{MEM_MB}<A><A1><S1> MB<S>", Exclude = { Mode.FPS, Mode.Minimal } },
new Entry { Text = "<C1>CPU<C> <A0>{CPU_%}<A><A1><S1> %<S><A>", Exclude = { Mode.FPS, Mode.Minimal } },
new Entry { Text = "<C1>RAM<C> <A0>{GPU_MB}<A><A1><S1> MB<S><A>", Exclude = { Mode.FPS, Mode.Minimal } },
new Entry { Text = "<C2><APP><C> <A0><C4><FR><C><A><A1><S1><C4> FPS<C><S><A> <A0><C4><FT><C><A><A1><S1><C4> ms<C><S><A>", Exclude = { Mode.FPS, Mode.Minimal } },
new Entry {
Text = "<C1>BAT<C> ",
Nested = {
new Entry("<A0>{BATT_%}<A><A1><S1> %<S><A>"),
new Entry("<A0>{BATT_W}<A><A1><S1> W<S><A>")
},
Exclude = { Mode.FPS, Mode.Minimal }
},
new Entry { Text = "<C2><S1>Frametime<S>", Exclude = { Mode.FPS, Mode.Minimal } },
new Entry { Text = "<OBJ>", Exclude = { Mode.FPS, Mode.Minimal } },
new Entry { Text = "<S1> <A0><FT><A><A1> ms<A><S><C>", Exclude = { Mode.FPS, Mode.Minimal } }
},
Separator = "\r\n"
};
public static String GetOSD(Mode mode, Sensors sensors)
{
var sb = new StringBuilder();
sb.AppendJoin("", Helpers);
sb.Append(OSD.GetValue(mode, sensors) ?? "");
return sb.ToString();
}
}
}

View file

@ -0,0 +1,66 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Resources\traffic-light-outline.ico</ApplicationIcon>
<UseWPF>True</UseWPF>
</PropertyGroup>
<ItemGroup>
<None Remove="app.manifest" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="app.manifest" />
</ItemGroup>
<ItemGroup>
<Content Include="Resources\traffic-light-outline.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="RTSSSharedMemoryNET">
<HintPath>RTSSSharedMemoryNET.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="RTSSSharedMemoryNET.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,29 @@
using RTSSSharedMemoryNET;
namespace PerformanceOverlay
{
internal static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
try
{
foreach (var entry in RTSSSharedMemoryNET.OSD.GetOSDEntries())
{
Console.WriteLine("Entry: {0}", entry.Owner);
Console.WriteLine("\t", entry.Text);
}
}
catch(SystemException)
{ }
using (var controller = new Controller())
{
Application.Run();
}
}
}
}

Binary file not shown.

83
PerformanceOverlay/Resources.Designer.cs generated Normal file
View file

@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PerformanceOverlay {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PerformanceOverlay.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap traffic_light_outline {
get {
object obj = ResourceManager.GetObject("traffic_light_outline", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon traffic_light_outline1 {
get {
object obj = ResourceManager.GetObject("traffic_light_outline1", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
}
}

View file

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<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>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="traffic_light_outline" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\traffic-light-outline.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="traffic_light_outline1" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\traffic-light-outline.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

View file

@ -0,0 +1,259 @@
using LibreHardwareMonitor.Hardware;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using static PerformanceOverlay.Sensors;
namespace PerformanceOverlay
{
internal class Sensors : IDisposable
{
public abstract class Sensor
{
public abstract string? GetValue(Sensors sensors);
}
public abstract class ValueSensor : Sensor
{
public String? Format { get; set; }
public float Multiplier { get; set; } = 1.0f;
public bool IgnoreZero { get; set; }
protected string? ConvertToString(float value)
{
if (value == 0 && IgnoreZero)
return null;
value *= Multiplier;
return value.ToString(Format, CultureInfo.GetCultureInfo("en-US"));
}
}
public class UserValueSensor : ValueSensor
{
public delegate float ValueDelegate();
public ValueDelegate Value { get; set; }
public override string? GetValue(Sensors sensors)
{
return ConvertToString(Value());
}
}
public class HardwareSensor : ValueSensor
{
public string HardwareName { get; set; } = "";
public HardwareType HardwareType { get; set; }
public string SensorName { get; set; } = "";
public SensorType SensorType { get; set; }
public bool Matches(ISensor sensor)
{
return sensor != null &&
sensor.Hardware.HardwareType == HardwareType &&
sensor.Hardware.Name.StartsWith(HardwareName) &&
sensor.SensorType == SensorType &&
sensor.Name == SensorName;
}
public string? GetValue(ISensor sensor)
{
if (!sensor.Value.HasValue)
return null;
return ConvertToString(sensor.Value.Value);
}
public override string? GetValue(Sensors sensors)
{
foreach (var hwSensor in sensors.AllHardwareSensors)
{
if (Matches(hwSensor))
{
return GetValue(hwSensor);
}
}
return null;
}
}
public readonly Dictionary<String, Sensor> AllSensors = new Dictionary<string, Sensor>
{
{
"CPU_%", new HardwareSensor()
{
HardwareType = HardwareType.Cpu,
HardwareName = "AMD Custom APU 0405",
SensorType = SensorType.Load,
SensorName = "CPU Total",
Format = "F0"
}
},
{
"CPU_W", new HardwareSensor()
{
HardwareType = HardwareType.Cpu,
HardwareName = "AMD Custom APU 0405",
SensorType = SensorType.Power,
SensorName = "Package",
Format = "F1"
}
},
{
"CPU_T", new HardwareSensor()
{
HardwareType = HardwareType.Cpu,
HardwareName = "AMD Custom APU 0405",
SensorType = SensorType.Temperature,
SensorName = "Core (Tctl/Tdie)",
Format = "F1",
IgnoreZero = true
}
},
{
"MEM_GB", new HardwareSensor()
{
HardwareType = HardwareType.Memory,
HardwareName = "Generic Memory",
SensorType = SensorType.Data,
SensorName = "Memory Used",
Format = "F1"
}
},
{
"MEM_MB", new HardwareSensor()
{
HardwareType = HardwareType.Memory,
HardwareName = "Generic Memory",
SensorType = SensorType.Data,
SensorName = "Memory Used",
Format = "F0",
Multiplier = 1024
}
},
{
"GPU_%", new HardwareSensor()
{
HardwareType = HardwareType.GpuAmd,
HardwareName = "AMD Custom GPU 0405",
SensorType = SensorType.Load,
SensorName = "D3D 3D",
Format = "F0"
}
},
{
"GPU_MB", new HardwareSensor()
{
HardwareType = HardwareType.GpuAmd,
HardwareName = "AMD Custom GPU 0405",
SensorType = SensorType.SmallData,
SensorName = "D3D Dedicated Memory Used",
Format = "F0"
}
},
{
"GPU_GB", new HardwareSensor()
{
HardwareType = HardwareType.GpuAmd,
HardwareName = "AMD Custom GPU 0405",
SensorType = SensorType.SmallData,
SensorName = "D3D Dedicated Memory Used",
Format = "F0",
Multiplier = 1.0f/1024.0f
}
},
{
"GPU_W", new HardwareSensor()
{
HardwareType = HardwareType.GpuAmd,
HardwareName = "AMD Custom GPU 0405",
SensorType = SensorType.Power,
SensorName = "GPU SoC",
Format = "F1"
}
},
{
"GPU_T", new HardwareSensor()
{
HardwareType = HardwareType.GpuAmd,
HardwareName = "AMD Custom GPU 0405",
SensorType = SensorType.Temperature,
SensorName = "GPU Temperature",
Format = "F1",
IgnoreZero = true
}
},
{
"BATT_%", new HardwareSensor()
{
HardwareType = HardwareType.Battery,
HardwareName = "GETAC",
SensorType = SensorType.Level,
SensorName = "Charge Level",
Format = "F0"
}
},
{
"BATT_W", new HardwareSensor()
{
HardwareType = HardwareType.Battery,
HardwareName = "GETAC",
SensorType = SensorType.Power,
SensorName = "Charge/Discharge Rate",
Format = "F1"
}
}
};
private LibreHardwareMonitor.Hardware.Computer libreHardwareComputer = new LibreHardwareMonitor.Hardware.Computer
{
IsCpuEnabled = true,
IsMemoryEnabled = true,
IsGpuEnabled = true,
IsStorageEnabled = true,
IsBatteryEnabled = true
};
public IList<ISensor> AllHardwareSensors { get; private set; } = new List<ISensor>();
public Sensors()
{
libreHardwareComputer.Open();
}
public void Dispose()
{
libreHardwareComputer.Close();
}
public void Update()
{
var allSensors = new List<ISensor>();
foreach (IHardware hardware in libreHardwareComputer.Hardware)
{
try
{
hardware.Update();
}
catch (SystemException) { }
hardware.Accept(new SensorVisitor(sensor => allSensors.Add(sensor)));
}
this.AllHardwareSensors = allSensors;
}
public string? GetValue(String name)
{
if (!AllSensors.ContainsKey(name))
return null;
return AllSensors[name].GetValue(this);
}
}
}

50
PerformanceOverlay/Settings.Designer.cs generated Normal file
View file

@ -0,0 +1,50 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PerformanceOverlay {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.3.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool ShowOSD {
get {
return ((bool)(this["ShowOSD"]));
}
set {
this["ShowOSD"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("FPS")]
public string OSDMode {
get {
return ((string)(this["OSDMode"]));
}
set {
this["OSDMode"] = value;
}
}
}
}

View file

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static PerformanceOverlay.Overlays;
namespace PerformanceOverlay
{
internal partial class Settings
{
public Overlays.Mode OSDModeParsed
{
get
{
try
{
return (Mode)Enum.Parse<Mode>(OSDMode);
}
catch (ArgumentException)
{
return Mode.FPS;
}
}
set
{
OSDMode = value.ToString();
Save();
}
}
}
}

View file

@ -0,0 +1,12 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="PerformanceOverlay" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="ShowOSD" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="OSDMode" Type="System.String" Scope="User">
<Value Profile="(Default)">FPS</Value>
</Setting>
</Settings>
</SettingsFile>

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="0.1.0.0" name="PerformanceOverlay.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config.
Makes the application long-path aware. See https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View file

@ -3,18 +3,44 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32901.215
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FanControl", "FanControl\FanControl.csproj", "{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FanControl", "FanControl\FanControl.csproj", "{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceOverlay", "PerformanceOverlay\PerformanceOverlay.csproj", "{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Debug|x64.ActiveCfg = Debug|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Debug|x64.Build.0 = Debug|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Debug|x86.ActiveCfg = Debug|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Debug|x86.Build.0 = Debug|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Release|Any CPU.Build.0 = Release|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Release|x64.ActiveCfg = Release|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Release|x64.Build.0 = Release|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Release|x86.ActiveCfg = Release|Any CPU
{A0D9B170-89BA-4F7A-9331-BE6A721E8D5B}.Release|x86.Build.0 = Release|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Debug|x64.ActiveCfg = Debug|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Debug|x64.Build.0 = Debug|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Debug|x86.ActiveCfg = Debug|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Debug|x86.Build.0 = Debug|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Release|Any CPU.Build.0 = Release|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Release|x64.ActiveCfg = Release|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Release|x64.Build.0 = Release|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Release|x86.ActiveCfg = Release|Any CPU
{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE