net 8 upgrade and xbox gamebar widget

This commit is contained in:
your_username 2026-02-22 23:08:07 -05:00
parent 4058798ad5
commit 4c79e4a23c
33 changed files with 1119 additions and 19 deletions

View file

@ -1,3 +1,5 @@
name: build-performance-overlay
on:
workflow_dispatch:
push:
@ -6,6 +8,8 @@ on:
- "PerformanceOverlay/**"
- "CommonHelpers/**"
- "ExternalHelpers/**"
- "XboxGameBarWidget/**"
- "scripts/install_gamebar_widget.ps1"
- "VERSION"
pull_request:
paths:
@ -13,14 +17,17 @@ on:
- "PerformanceOverlay/**"
- "CommonHelpers/**"
- "ExternalHelpers/**"
- "XboxGameBarWidget/**"
- "scripts/install_gamebar_widget.ps1"
- "VERSION"
env:
DOTNET_VERSION: "6.0.x"
DOTNET_VERSION: "8.0.x"
APP_NAME: PerformanceOverlay
WIDGET_NAME: SteamDeckToolsGameBarWidget
jobs:
build-performance-overlay:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
@ -30,27 +37,80 @@ jobs:
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Set RELEASE_VERSION
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Set versions
shell: pwsh
run: |
$majorVersion = (Get-Content VERSION -Raw).Trim()
$releaseVersion = "$majorVersion.${{ github.run_number }}"
"RELEASE_VERSION=$releaseVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
$segments = @($majorVersion.Split('.') | Where-Object { $_ -ne '' })
switch ($segments.Count) {
0 { $widgetVersion = "1.0.${{ github.run_number }}.0" }
1 { $widgetVersion = "$($segments[0]).0.${{ github.run_number }}.0" }
2 { $widgetVersion = "$($segments[0]).$($segments[1]).${{ github.run_number }}.0" }
default { $widgetVersion = "$($segments[0]).$($segments[1]).$($segments[2]).${{ github.run_number }}" }
}
"WIDGET_PACKAGE_VERSION=$widgetVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Restore PerformanceOverlay dependencies
run: dotnet restore PerformanceOverlay/PerformanceOverlay.csproj
- name: Build PerformanceOverlay
run: dotnet build PerformanceOverlay/PerformanceOverlay.csproj --configuration Release --output "${{ env.APP_NAME }}-${{ env.RELEASE_VERSION }}/" "/property:Version=${{ env.RELEASE_VERSION }}" "/property:ExtraDefineConstants=PRODUCTION_BUILD"
- name: Create zip artifact
- name: Create PerformanceOverlay zip artifact
shell: pwsh
run: |
Compress-Archive -Path "${{ env.APP_NAME }}-${{ env.RELEASE_VERSION }}\*" -DestinationPath "${{ env.APP_NAME }}-${{ env.RELEASE_VERSION }}.zip"
- name: Build Game Bar widget package
shell: pwsh
run: |
$manifestPath = "XboxGameBarWidget/Package.appxmanifest"
$manifestContent = Get-Content $manifestPath -Raw
$manifestContent = [regex]::Replace(
$manifestContent,
'(<Identity\s+Name="SteamDeckToolsGameBarWidget"\s+Publisher="CN=SteamDeckTools"\s+Version=")[^"]+(")',
{
param($match)
$match.Groups[1].Value + "${{ env.WIDGET_PACKAGE_VERSION }}" + $match.Groups[2].Value
}
)
Set-Content -Path $manifestPath -Value $manifestContent -Encoding UTF8
$widgetPackageDir = Join-Path $PWD "${{ env.WIDGET_NAME }}-${{ env.RELEASE_VERSION }}"
New-Item -ItemType Directory -Path $widgetPackageDir -Force | Out-Null
msbuild XboxGameBarWidget\SteamDeckToolsGameBarWidget.csproj `
/restore `
/p:Configuration=Release `
/p:Platform=x64 `
/p:AppxBundle=Never `
/p:UapAppxPackageBuildMode=SideloadOnly `
/p:AppxPackageDir="$widgetPackageDir\\" `
/p:AppxPackageSigningEnabled=false
Copy-Item scripts\install_gamebar_widget.ps1 (Join-Path $widgetPackageDir "install_gamebar_widget.ps1") -Force
- name: Create Game Bar widget zip artifact
shell: pwsh
run: |
Compress-Archive -Path "${{ env.WIDGET_NAME }}-${{ env.RELEASE_VERSION }}\*" -DestinationPath "${{ env.WIDGET_NAME }}-${{ env.RELEASE_VERSION }}.zip"
- name: Upload PerformanceOverlay zip
uses: actions/upload-artifact@v4
with:
name: ${{ env.APP_NAME }}-${{ env.RELEASE_VERSION }}.zip
path: ${{ env.APP_NAME }}-${{ env.RELEASE_VERSION }}.zip
retention-days: 14
- name: Upload Game Bar widget zip
uses: actions/upload-artifact@v4
with:
name: ${{ env.WIDGET_NAME }}-${{ env.RELEASE_VERSION }}.zip
path: ${{ env.WIDGET_NAME }}-${{ env.RELEASE_VERSION }}.zip
retention-days: 14

3
.gitignore vendored
View file

@ -5,7 +5,10 @@ obj/
build-Release/
build-Debug/
.vscode/
out/
scripts/Redist/
SteamDeckTools_Setup*.exe
GameProfiles/
ControllerProfiles/
AppPackages/
BundleArtifacts/

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
@ -9,7 +9,7 @@
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
</PropertyGroup>
<Target Name="SetSourceRevisionId" BeforeTargets="InitializeSourceControlInformation">
<Exec Command="git describe --long --always --dirty --exclude=* --abbrev=8" ConsoleToMSBuild="True" IgnoreExitCode="True">
<Exec Command="git describe --long --always --dirty --abbrev=8" ConsoleToMSBuild="True" IgnoreExitCode="True" IgnoreStandardErrorWarningFormat="true">
<Output PropertyName="SourceRevisionId" TaskParameter="ConsoleOutput" />
</Exec>
</Target>

View file

@ -208,7 +208,7 @@
}
private void SetRampRate(byte rampRate)
{
byte[] data = BitConverter.GetBytes(rampRate);
byte[] data = new byte[] { rampRate };
inpOut?.WriteMemory(FRPR, data);
}
}

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Management" Version="7.0.0" />
<PackageReference Include="System.Management" Version="8.0.0" />
<PackageReference Include="TaskScheduler" Version="2.10.1" />
</ItemGroup>

View file

@ -0,0 +1,332 @@
using CommonHelpers;
namespace PerformanceOverlay
{
internal enum OverlayControlResult
{
NotACommand,
HandledAndExit,
ContinueStartup
}
internal enum OverlayControlCommandType
{
SetMode,
CycleMode,
ToggleVisibility,
Show,
Hide
}
internal readonly struct OverlayControlCommand
{
public OverlayControlCommandType CommandType { get; }
public OverlayMode Mode { get; }
private OverlayControlCommand(OverlayControlCommandType commandType, OverlayMode mode = OverlayMode.FPS)
{
CommandType = commandType;
Mode = mode;
}
public static OverlayControlCommand SetMode(OverlayMode mode)
{
return new OverlayControlCommand(OverlayControlCommandType.SetMode, mode);
}
public static OverlayControlCommand CycleMode()
{
return new OverlayControlCommand(OverlayControlCommandType.CycleMode);
}
public static OverlayControlCommand ToggleVisibility()
{
return new OverlayControlCommand(OverlayControlCommandType.ToggleVisibility);
}
public static OverlayControlCommand Show()
{
return new OverlayControlCommand(OverlayControlCommandType.Show);
}
public static OverlayControlCommand Hide()
{
return new OverlayControlCommand(OverlayControlCommandType.Hide);
}
}
internal static class OverlayCommandLine
{
internal const string ProtocolScheme = "steamdecktools-performanceoverlay";
private const string RunOnceMutexName = "Global\\PerformanceOverlay";
public static OverlayControlResult HandleArgs(string[] args)
{
if (!TryParseCommand(args, out var command))
return OverlayControlResult.NotACommand;
if (TryDispatchToRunningInstance(command))
return OverlayControlResult.HandledAndExit;
// Avoid run-once fatal dialog if an existing instance is present but IPC failed.
if (IsPerformanceOverlayInstanceRunning())
return OverlayControlResult.HandledAndExit;
ApplyLocally(command);
return OverlayControlResult.ContinueStartup;
}
private static bool TryParseCommand(string[] args, out OverlayControlCommand command)
{
command = default;
foreach (var arg in args)
{
if (TryParseUriCommand(arg, out command))
return true;
}
return TryParseSwitchCommand(args, out command);
}
private static bool TryParseUriCommand(string arg, out OverlayControlCommand command)
{
command = default;
if (!Uri.TryCreate(arg, UriKind.Absolute, out var uri))
return false;
if (!uri.Scheme.Equals(ProtocolScheme, StringComparison.OrdinalIgnoreCase))
return false;
var segments = new List<string>();
if (!String.IsNullOrWhiteSpace(uri.Host))
segments.Add(uri.Host);
segments.AddRange(
uri.AbsolutePath
.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
);
if (segments.Count == 0)
return false;
var head = segments[0];
if (head.Equals("cycle", StringComparison.OrdinalIgnoreCase))
{
command = OverlayControlCommand.CycleMode();
return true;
}
if (head.Equals("toggle", StringComparison.OrdinalIgnoreCase))
{
command = OverlayControlCommand.ToggleVisibility();
return true;
}
if (head.Equals("show", StringComparison.OrdinalIgnoreCase))
{
command = OverlayControlCommand.Show();
return true;
}
if (head.Equals("hide", StringComparison.OrdinalIgnoreCase))
{
command = OverlayControlCommand.Hide();
return true;
}
if ((head.Equals("mode", StringComparison.OrdinalIgnoreCase) || head.Equals("set", StringComparison.OrdinalIgnoreCase))
&& segments.Count > 1
&& TryParseMode(segments[1], out var uriMode))
{
command = OverlayControlCommand.SetMode(uriMode);
return true;
}
return false;
}
private static bool TryParseSwitchCommand(string[] args, out OverlayControlCommand command)
{
command = default;
for (int i = 0; i < args.Length; i++)
{
var arg = args[i];
if (arg.Equals("--cycle", StringComparison.OrdinalIgnoreCase))
{
command = OverlayControlCommand.CycleMode();
return true;
}
if (arg.Equals("--toggle", StringComparison.OrdinalIgnoreCase))
{
command = OverlayControlCommand.ToggleVisibility();
return true;
}
if (arg.Equals("--show", StringComparison.OrdinalIgnoreCase))
{
command = OverlayControlCommand.Show();
return true;
}
if (arg.Equals("--hide", StringComparison.OrdinalIgnoreCase))
{
command = OverlayControlCommand.Hide();
return true;
}
if (arg.StartsWith("--mode=", StringComparison.OrdinalIgnoreCase))
{
var modeArg = arg["--mode=".Length..];
if (TryParseMode(modeArg, out var inlineMode))
{
command = OverlayControlCommand.SetMode(inlineMode);
return true;
}
}
if (arg.Equals("--mode", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length && TryParseMode(args[i + 1], out var nextMode))
{
command = OverlayControlCommand.SetMode(nextMode);
return true;
}
}
return false;
}
private static bool TryParseMode(string value, out OverlayMode mode)
{
if (Enum.TryParse<OverlayMode>(value, true, out mode))
return true;
var normalized = value.Trim().Replace("-", "").Replace("_", "").ToLowerInvariant();
switch (normalized)
{
case "fpsbattery":
case "fpswithbattery":
mode = OverlayMode.FPSWithBattery;
return true;
default:
return false;
}
}
private static bool TryDispatchToRunningInstance(OverlayControlCommand command)
{
try
{
using var sharedData = SharedData<OverlayModeSetting>.OpenExisting();
if (!sharedData.GetValue(out var value))
value = sharedData.NewValue();
ApplyToSharedState(ref value, command);
return sharedData.SetValue(value);
}
catch (Exception ex)
{
Log.TraceLine("OverlayCommandLine: IPC dispatch failed: {0}", ex.Message);
return false;
}
}
private static void ApplyToSharedState(ref OverlayModeSetting value, OverlayControlCommand command)
{
switch (command.CommandType)
{
case OverlayControlCommandType.SetMode:
value.Desired = command.Mode;
value.DesiredEnabled = OverlayEnabled.Yes;
return;
case OverlayControlCommandType.CycleMode:
value.Desired = NextMode(CurrentMode(value));
value.DesiredEnabled = OverlayEnabled.Yes;
return;
case OverlayControlCommandType.ToggleVisibility:
value.DesiredEnabled = CurrentEnabled(value) == OverlayEnabled.Yes ? OverlayEnabled.No : OverlayEnabled.Yes;
return;
case OverlayControlCommandType.Show:
value.DesiredEnabled = OverlayEnabled.Yes;
return;
case OverlayControlCommandType.Hide:
value.DesiredEnabled = OverlayEnabled.No;
return;
default:
return;
}
}
private static OverlayMode CurrentMode(OverlayModeSetting value)
{
if (Enum.IsDefined<OverlayMode>(value.Current))
return value.Current;
if (Enum.IsDefined<OverlayMode>(value.Desired))
return value.Desired;
return Settings.Default.OSDMode;
}
private static OverlayEnabled CurrentEnabled(OverlayModeSetting value)
{
if (Enum.IsDefined<OverlayEnabled>(value.CurrentEnabled))
return value.CurrentEnabled;
if (Enum.IsDefined<OverlayEnabled>(value.DesiredEnabled))
return value.DesiredEnabled;
return Settings.Default.ShowOSD ? OverlayEnabled.Yes : OverlayEnabled.No;
}
private static void ApplyLocally(OverlayControlCommand command)
{
switch (command.CommandType)
{
case OverlayControlCommandType.SetMode:
Settings.Default.OSDMode = command.Mode;
Settings.Default.ShowOSD = true;
return;
case OverlayControlCommandType.CycleMode:
Settings.Default.OSDMode = NextMode(Settings.Default.OSDMode);
Settings.Default.ShowOSD = true;
return;
case OverlayControlCommandType.ToggleVisibility:
Settings.Default.ShowOSD = !Settings.Default.ShowOSD;
return;
case OverlayControlCommandType.Show:
Settings.Default.ShowOSD = true;
return;
case OverlayControlCommandType.Hide:
Settings.Default.ShowOSD = false;
return;
default:
return;
}
}
private static OverlayMode NextMode(OverlayMode current)
{
var values = Enum.GetValues<OverlayMode>();
var currentIndex = Array.IndexOf(values, current);
if (currentIndex < 0)
currentIndex = 0;
return values[(currentIndex + 1) % values.Length];
}
private static bool IsPerformanceOverlayInstanceRunning()
{
try
{
using var mutex = Mutex.OpenExisting(RunOnceMutexName);
return true;
}
catch (WaitHandleCannotBeOpenedException)
{
return false;
}
catch (UnauthorizedAccessException)
{
return true;
}
}
}
}

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>

View file

@ -1,6 +1,5 @@
using CommonHelpers;
using RTSSSharedMemoryNET;
using System.Diagnostics;
using System.Linq;
namespace PerformanceOverlay
{
@ -11,6 +10,12 @@ namespace PerformanceOverlay
{
Instance.WithSentry(() =>
{
UriProtocolRegistration.EnsureRegistered();
var commandResult = OverlayCommandLine.HandleArgs(Environment.GetCommandLineArgs().Skip(1).ToArray());
if (commandResult == OverlayControlResult.HandledAndExit)
return;
ApplicationConfiguration.Initialize();
using (var controller = new Controller())
@ -20,4 +25,4 @@ namespace PerformanceOverlay
});
}
}
}
}

View file

@ -0,0 +1,37 @@
using CommonHelpers;
using Microsoft.Win32;
namespace PerformanceOverlay
{
internal static class UriProtocolRegistration
{
private const string ProtocolKey = @"Software\Classes\steamdecktools-performanceoverlay";
public static void EnsureRegistered()
{
try
{
var exePath = Environment.ProcessPath;
if (String.IsNullOrWhiteSpace(exePath))
return;
using var protocol = Registry.CurrentUser.CreateSubKey(ProtocolKey);
if (protocol is null)
return;
protocol.SetValue("", "URL:Steam Deck Tools Performance Overlay");
protocol.SetValue("URL Protocol", "");
using var icon = protocol.CreateSubKey("DefaultIcon");
icon?.SetValue("", "\"" + exePath + "\",0");
using var command = protocol.CreateSubKey(@"shell\open\command");
command?.SetValue("", "\"" + exePath + "\" \"%1\"");
}
catch (Exception ex)
{
Log.TraceLine("UriProtocolRegistration: {0}", ex.Message);
}
}
}
}

View file

@ -4,13 +4,14 @@ This repository is a personal fork of Steam Deck Tools, customized for my ROG Al
## Scope
This fork is focused on `PerformanceOverlay` only.
This fork is focused on `PerformanceOverlay` and companion tooling around it (including a Game Bar widget).
Included projects:
- `PerformanceOverlay`
- `CommonHelpers`
- `ExternalHelpers`
- `XboxGameBarWidget`
The other original applications are intentionally not part of active development in this fork.
@ -21,9 +22,44 @@ dotnet restore PerformanceOverlay/PerformanceOverlay.csproj
dotnet build PerformanceOverlay/PerformanceOverlay.csproj --configuration Release
```
This fork now targets `.NET 8` for desktop projects.
## CI
GitHub Actions is configured to build only `PerformanceOverlay` and publish a ZIP artifact.
GitHub Actions builds and publishes:
- `PerformanceOverlay-<version>.zip`
- `SteamDeckToolsGameBarWidget-<version>.zip` (includes MSIX package + install script)
## Xbox Game Bar widget (Windows 11)
The widget appears in Xbox Game Bar (`Win+G`) as **Performance Overlay Control** and lets you:
- Switch directly to `FPS`, `FPSWithBattery`, `Battery`, `Minimal`, `Detail`, `Full`
- `Cycle`, `Show`, `Hide`, or `Toggle` the overlay
The widget sends URI commands to `PerformanceOverlay` via:
- `steamdecktools-performanceoverlay://mode/<name>`
- `steamdecktools-performanceoverlay://cycle`
- `steamdecktools-performanceoverlay://show`
- `steamdecktools-performanceoverlay://hide`
- `steamdecktools-performanceoverlay://toggle`
Run `PerformanceOverlay` once after extracting it so it can register the URI protocol in `HKCU`.
## Install widget package (PowerShell)
After downloading the `SteamDeckToolsGameBarWidget-<version>.zip` artifact:
```powershell
powershell -ExecutionPolicy Bypass -File scripts/install_gamebar_widget.ps1 -PackagePath "C:\path\to\SteamDeckToolsGameBarWidget-<version>.zip"
```
Use `-ForceReinstall` to remove an existing widget package before reinstalling.
If Windows blocks unsigned package installation, enable Developer Mode:
`Settings > Privacy & security > For developers > Developer Mode`.
## Install latest GitHub build artifact (PowerShell)
@ -114,4 +150,4 @@ Steam Deck Tools is not affiliated with Valve, Steam, or any of their partners.
[Creative Commons Attribution-NonCommercial-ShareAlike (CC-BY-NC-SA)](http://creativecommons.org/licenses/by-nc-sa/4.0/).
Free for personal use. Contact me in other cases (`ayufan@ayufan.eu`).
Free for personal use. Contact me in other cases (`ayufan@ayufan.eu`).

View file

@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "CommonHelp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExternalHelpers", "ExternalHelpers\ExternalHelpers.csproj", "{A3FD29A4-844F-42B1-80C5-0301BD053AC0}"
EndProject
Project("{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A}") = "SteamDeckToolsGameBarWidget", "XboxGameBarWidget\SteamDeckToolsGameBarWidget.csproj", "{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -55,6 +57,18 @@ Global
{A3FD29A4-844F-42B1-80C5-0301BD053AC0}.Release|x64.Build.0 = Release|Any CPU
{A3FD29A4-844F-42B1-80C5-0301BD053AC0}.Release|x86.ActiveCfg = Release|Any CPU
{A3FD29A4-844F-42B1-80C5-0301BD053AC0}.Release|x86.Build.0 = Release|Any CPU
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Debug|Any CPU.ActiveCfg = Debug|x64
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Debug|Any CPU.Build.0 = Debug|x64
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Debug|x64.ActiveCfg = Debug|x64
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Debug|x64.Build.0 = Debug|x64
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Debug|x86.ActiveCfg = Debug|x86
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Debug|x86.Build.0 = Debug|x86
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Release|Any CPU.ActiveCfg = Release|x64
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Release|Any CPU.Build.0 = Release|x64
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Release|x64.ActiveCfg = Release|x64
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Release|x64.Build.0 = Release|x64
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Release|x86.ActiveCfg = Release|x86
{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -0,0 +1,5 @@
<Application
x:Class="SteamDeckToolsGameBarWidget.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SteamDeckToolsGameBarWidget" />

View file

@ -0,0 +1,93 @@
using Microsoft.Gaming.XboxGameBar;
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace SteamDeckToolsGameBarWidget
{
sealed partial class App : Application
{
private XboxGameBarWidget widget = null;
public App()
{
InitializeComponent();
Suspending += OnSuspending;
}
protected override void OnActivated(IActivatedEventArgs args)
{
XboxGameBarWidgetActivatedEventArgs widgetArgs = null;
if (args.Kind == ActivationKind.Protocol)
{
var protocolArgs = args as IProtocolActivatedEventArgs;
if (protocolArgs != null && protocolArgs.Uri.Scheme.Equals("ms-gamebarwidget", StringComparison.OrdinalIgnoreCase))
{
widgetArgs = args as XboxGameBarWidgetActivatedEventArgs;
}
}
if (widgetArgs == null)
return;
if (widgetArgs.IsLaunchActivation)
{
var rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
Window.Current.Content = rootFrame;
widget = new XboxGameBarWidget(
widgetArgs,
Window.Current.CoreWindow,
rootFrame
);
rootFrame.Navigate(typeof(WidgetPage));
Window.Current.Closed += WidgetWindow_Closed;
Window.Current.Activate();
}
}
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
var rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
Window.Current.Content = rootFrame;
}
if (!e.PrelaunchActivated)
{
if (rootFrame.Content == null)
rootFrame.Navigate(typeof(MainPage), e.Arguments);
Window.Current.Activate();
}
}
private void WidgetWindow_Closed(object sender, Windows.UI.Core.CoreWindowEventArgs e)
{
widget = null;
Window.Current.Closed -= WidgetWindow_Closed;
}
private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load page " + e.SourcePageType.FullName);
}
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
widget = null;
deferral.Complete();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -0,0 +1,16 @@
<Page
x:Class="SteamDeckToolsGameBarWidget.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:SteamDeckToolsGameBarWidget"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid Padding="16">
<TextBlock
TextWrapping="Wrap"
Text="Open Xbox Game Bar (Win+G), then add 'Performance Overlay Control' from the widget list." />
</Grid>
</Page>

View file

@ -0,0 +1,12 @@
using Windows.UI.Xaml.Controls;
namespace SteamDeckToolsGameBarWidget
{
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
IgnorableNamespaces="uap uap3 mp">
<Identity
Name="SteamDeckToolsGameBarWidget"
Publisher="CN=SteamDeckTools"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="5b2bd271-f8d5-43d8-8fe8-56c4f57c44ee" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>Steam Deck Tools Game Bar Widget</DisplayName>
<PublisherDisplayName>SteamDeckTools</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application
Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="SteamDeckToolsGameBarWidget.App">
<uap:VisualElements
DisplayName="Steam Deck Tools Game Bar Widget"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="Control Steam Deck Tools PerformanceOverlay modes from Xbox Game Bar."
BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square71x71Logo="Assets\SmallTile.png" Square310x310Logo="Assets\LargeTile.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension
Name="microsoft.gameBarUIExtension"
Id="PerformanceOverlayControl"
DisplayName="Performance Overlay Control"
Description="Quickly switch PerformanceOverlay views."
PublicFolder="GameBar">
<uap3:Properties>
<GameBarWidget Type="Standard">
<HomeMenuVisible>true</HomeMenuVisible>
<PinningSupported>true</PinningSupported>
<Window>
<Size>
<Height>330</Height>
<Width>420</Width>
<MinHeight>240</MinHeight>
<MinWidth>320</MinWidth>
<MaxHeight>500</MaxHeight>
<MaxWidth>700</MaxWidth>
</Size>
<ResizeSupported>
<Horizontal>true</Horizontal>
<Vertical>true</Vertical>
</ResizeSupported>
</Window>
</GameBarWidget>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
</Application>
</Applications>
<Extensions>
<!-- Required for Game Bar metadata-based marshaling interfaces. -->
<Extension Category="windows.activatableClass.proxyStub">
<ProxyStub ClassId="00000355-0000-0000-C000-000000000046">
<Path>Microsoft.Gaming.XboxGameBar.winmd</Path>
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarAppTargetHost" InterfaceId="38CDC43C-0A0E-4B3B-BBD3-A581AE220D53" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarAppTargetInfo" InterfaceId="D7689E93-5587-47D1-A42E-78D16B2FA807" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarActivityHost" InterfaceId="2B113C9B-E370-49B2-A20B-83E0F5737577" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarHotkeyManagerHost" InterfaceId="F6225A53-B34C-4833-9511-AA377B43316F" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetAuthHost" InterfaceId="DC263529-B12F-469E-BB35-B94069F5B15A" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetControlHost" InterfaceId="C309CAC7-8435-4082-8F37-784523747047" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetForegroundWorkerHost" InterfaceId="DDB52B57-FA83-420C-AFDE-6FA556E18B83" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetForegroundWorkerPrivate" InterfaceId="42BACDFC-BB28-4E71-99B4-24C034C7B7E0" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarNavigationKeyCombo" InterfaceId="5EEA3DBF-09BB-42A5-B491-CF561E33C172" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetActivatedEventArgsPrivate" InterfaceId="782535A7-9407-4572-BFCB-316B4086F102" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetHost" InterfaceId="5D12BC93-212B-4B9F-9091-76B73BF56525" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetHost2" InterfaceId="28717C8B-D8E8-47A8-AF47-A1D5263BAE9B" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetHost3" InterfaceId="3F5A3F12-C1E4-4942-B80D-3117BC948E29" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetHost4" InterfaceId="FA696D9E-2501-4B01-B26F-4BB85344740F" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetHost5" InterfaceId="A6C878CC-2B08-4B94-B1C3-222C6A913F3C" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetHost6" InterfaceId="CE6F0D73-C44F-4BBD-9652-A0FC52C37A34" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetNotificationHost" InterfaceId="6F68D392-E4A9-46F7-A024-5275BC2FE7BA" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetNotificationPrivate" InterfaceId="C94C8DC8-C8B5-4560-AF6E-A588B558213A" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetPrivate" InterfaceId="22ABA97F-FB0F-4439-9BDD-2C67B2D5AA8F" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetPrivate2" InterfaceId="B2F7DB8C-7540-48DA-9B46-4E60CE0D9DEB" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetPrivate3" InterfaceId="4FB89FB6-7CB8-489D-8408-2269E6C733A1" />
<Interface Name="Microsoft.Gaming.XboxGameBar.Private.IXboxGameBarWidgetPrivate4" InterfaceId="5638D65A-3733-48CC-90E5-984688D62786" />
</ProxyStub>
</Extension>
</Extensions>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>

View file

@ -0,0 +1,14 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("SteamDeckToolsGameBarWidget")]
[assembly: AssemblyDescription("Xbox Game Bar widget controls for Steam Deck Tools PerformanceOverlay.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SteamDeckToolsGameBarWidget")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(false)]

View file

@ -0,0 +1,5 @@
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<Assembly Name="*Application*" Dynamic="Required All" />
</Application>
</Directives>

View file

@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{4E1DE235-5D8B-4ED9-B4AE-2D6B3E5C4E6A}</ProjectGuid>
<OutputType>AppContainerExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SteamDeckToolsGameBarWidget</RootNamespace>
<AssemblyName>SteamDeckToolsGameBarWidget</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.22621.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WindowsXamlEnableOverview>true</WindowsXamlEnableOverview>
<AppxPackageSigningEnabled>False</AppxPackageSigningEnabled>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<AppxSymbolPackageEnabled>False</AppxSymbolPackageEnabled>
<GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundle>Never</AppxBundle>
<AppxBundlePlatforms>x64|arm64</AppxBundlePlatforms>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
<OutputPath>bin\ARM64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="WidgetPage.xaml.cs">
<DependentUpon>WidgetPage.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<Content Include="Assets\LargeTile.scale-100.png" />
<Content Include="Assets\SmallTile.scale-100.png" />
<Content Include="Assets\SplashScreen.scale-100.png" />
<Content Include="Assets\Square150x150Logo.scale-100.png" />
<Content Include="Assets\Square44x44Logo.scale-100.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.scale-100.png" />
<Content Include="Assets\Wide310x150Logo.scale-100.png" />
<Content Include="Properties\Default.rd.xml" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="WidgetPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Gaming.XboxGameBar">
<Version>7.2.240903001</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.9</Version>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<XboxGameBar-Platform Condition="'$(Platform)' == 'x86'">win32</XboxGameBar-Platform>
<XboxGameBar-Platform Condition="'$(Platform)' != 'x86'">$(Platform)</XboxGameBar-Platform>
</PropertyGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
</Project>

View file

@ -0,0 +1,68 @@
<Page
x:Class="SteamDeckToolsGameBarWidget.WidgetPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:SteamDeckToolsGameBarWidget"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="#15181F">
<StackPanel Margin="12">
<TextBlock
Margin="0,0,0,8"
FontSize="16"
FontWeight="SemiBold"
Foreground="White"
Text="Performance Overlay" />
<Grid Margin="0,0,0,8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://mode/fps">FPS</Button>
<Button Grid.Row="0" Grid.Column="1" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://mode/fpswithbattery">FPS + Battery</Button>
<Button Grid.Row="0" Grid.Column="2" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://mode/battery">Battery</Button>
<Button Grid.Row="1" Grid.Column="0" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://mode/minimal">Minimal</Button>
<Button Grid.Row="1" Grid.Column="1" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://mode/detail">Detail</Button>
<Button Grid.Row="1" Grid.Column="2" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://mode/full">Full</Button>
</Grid>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://show">Show</Button>
<Button Grid.Column="1" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://hide">Hide</Button>
<Button Grid.Column="2" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://toggle">Toggle</Button>
</Grid>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://cycle">Cycle</Button>
<Button Grid.Column="1" Margin="2" Click="CommandButton_Click" Tag="steamdecktools-performanceoverlay://mode/fps">Quick Reset</Button>
</Grid>
<TextBlock
x:Name="StatusText"
Foreground="#B7C7D6"
Text="Requires PerformanceOverlay running at least once to register the URI protocol."
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Page>

View file

@ -0,0 +1,42 @@
using System;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace SteamDeckToolsGameBarWidget
{
public sealed partial class WidgetPage : Page
{
public WidgetPage()
{
InitializeComponent();
}
private async void CommandButton_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button == null || !(button.Tag is string))
return;
try
{
var targetUri = (string)button.Tag;
Uri uri;
if (!Uri.TryCreate(targetUri, UriKind.Absolute, out uri))
{
StatusText.Text = "Invalid command URI.";
return;
}
bool launched = await Launcher.LaunchUriAsync(uri);
StatusText.Text = launched
? "Sent command: " + button.Content
: "Unable to launch command URI. Start PerformanceOverlay first.";
}
catch (Exception ex)
{
StatusText.Text = "Failed to send command: " + ex.Message;
}
}
}
}

View file

@ -10,6 +10,27 @@ It currently registers two global hotkeys:
- **Shift+F11** - enable performance overlay
- **Alt+Shift+F11** - cycle to next performance overlay (and enable it)
It also supports command URIs (used by the Xbox Game Bar widget in this fork):
- `steamdecktools-performanceoverlay://mode/fps`
- `steamdecktools-performanceoverlay://mode/fpswithbattery`
- `steamdecktools-performanceoverlay://mode/battery`
- `steamdecktools-performanceoverlay://mode/minimal`
- `steamdecktools-performanceoverlay://mode/detail`
- `steamdecktools-performanceoverlay://mode/full`
- `steamdecktools-performanceoverlay://cycle`
- `steamdecktools-performanceoverlay://show`
- `steamdecktools-performanceoverlay://hide`
- `steamdecktools-performanceoverlay://toggle`
Equivalent command-line switches are available:
- `--mode <fps|fpswithbattery|battery|minimal|detail|full>`
- `--cycle`
- `--show`
- `--hide`
- `--toggle`
There are 5 modes of presentation:
## 1. FPS

View file

@ -30,5 +30,5 @@ if ($lastVer) {
}
echo "nextVer: $nextVer"
dotnet --list-sdks -or winget install Microsoft.DotNet.SDK.6
dotnet --list-sdks -or winget install Microsoft.DotNet.SDK.8
dotnet build PerformanceOverlay/PerformanceOverlay.csproj --configuration "$configuration" "/property:Version=$nextVer" --output "$path"

View file

@ -0,0 +1,66 @@
param(
[Parameter(Mandatory = $true)]
[string]$PackagePath,
[switch]$ForceReinstall
)
$ErrorActionPreference = "Stop"
if (-not (Test-Path -LiteralPath $PackagePath)) {
throw "PackagePath not found: $PackagePath"
}
$tempExtractDir = $null
try {
$packageItem = Get-Item -LiteralPath $PackagePath
if (-not $packageItem.PSIsContainer -and $packageItem.Extension -eq ".zip") {
$tempExtractDir = Join-Path ([System.IO.Path]::GetTempPath()) ("sdt-widget-install-" + [System.Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $tempExtractDir -Force | Out-Null
Expand-Archive -Path $packageItem.FullName -DestinationPath $tempExtractDir -Force
$packageItem = Get-Item -LiteralPath $tempExtractDir
}
if ($packageItem.PSIsContainer) {
$packageItem = Get-ChildItem -LiteralPath $packageItem.FullName -Recurse -File |
Where-Object { $_.Extension -in ".msix", ".appx", ".msixbundle", ".appxbundle" } |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
}
if (-not $packageItem) {
throw "No .msix/.appx package was found under '$PackagePath'."
}
$identityName = "SteamDeckToolsGameBarWidget"
$installedPackage = Get-AppxPackage -Name $identityName -ErrorAction SilentlyContinue
if ($ForceReinstall -and $installedPackage) {
Write-Host "Removing existing package: $($installedPackage.PackageFullName)"
Remove-AppxPackage -Package $installedPackage.PackageFullName
}
Write-Host "Installing package: $($packageItem.FullName)"
try {
Add-AppxPackage `
-Path $packageItem.FullName `
-ForceUpdateFromAnyVersion `
-ForceApplicationShutdown `
-AllowUnsigned
}
catch {
Write-Host ""
Write-Host "Install failed. If this is an unsigned package, enable Developer Mode in Windows:"
Write-Host "Settings > Privacy & security > For developers > Developer Mode"
throw
}
Write-Host "Installed. Open Xbox Game Bar (Win+G), click Widget Menu, then add 'Performance Overlay Control'."
}
finally {
if ($tempExtractDir -and (Test-Path -LiteralPath $tempExtractDir)) {
Remove-Item -LiteralPath $tempExtractDir -Recurse -Force
}
}

View file

@ -6,10 +6,10 @@ cd "%~dp0\.."
:retry
taskkill /F /IM "%2.exe"
del %2\bin\Debug\net6.0-windows\%2.exe
del %2\bin\Debug\net8.0-windows\%2.exe
dotnet build %2\%2.csproj
%2\bin\Debug\net6.0-windows\%2.exe
%2\bin\Debug\net8.0-windows\%2.exe
timeout /t 3
goto retry