Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
416 changes: 416 additions & 0 deletions docs/SETTINGS-REFERENCE.md

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/GamerGuardian/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,17 @@ protected override void OnStartup(StartupEventArgs e)
new TailoredExperiencesMonitor(),
new CdpMonitor(),
new ActivityHistoryMonitor(),
new OnlineSpeechMonitor(),
new InkingTypingMonitor(),
// Debloat tab -- ads, nags, suggested content & background bloat:
new SuggestedContentMonitor(),
new LockScreenSpotlightMonitor(),
new FinishSetupNagMonitor(),
new StartRecommendationsMonitor(),
new ExplorerAdsMonitor(),
new FeedbackNagMonitor(),
new WidgetsMonitor(),
new EdgeBackgroundMonitor(),
// System toggles:
new PowerThrottlingMonitor(), // CPU/Power tab
new FastStartupMonitor(), // Global gaming tab
Expand Down
17 changes: 17 additions & 0 deletions src/GamerGuardian/Models/AppConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,23 @@ public sealed class GlobalPreferences
public ToggleSettingPref Cdp { get; set; } = new() { DesiredOn = true };
public ToggleSettingPref ActivityHistory { get; set; } = new() { DesiredOn = true };

// ---- Privacy data-collection toggles (Privacy tab). Intuitive Enabled/Disabled;
// DesiredOn=feature enabled, so the privacy-optimized default is DesiredOn=false. ----
public ToggleSettingPref OnlineSpeech { get; set; } = new() { DesiredOn = false };
public ToggleSettingPref InkingTyping { get; set; } = new() { DesiredOn = false };

// ---- Debloat toggles (Debloat tab). Intuitive Enabled/Disabled; DesiredOn maps
// to the bloat feature being enabled, so the debloated default is DesiredOn=false.
// Monitor=false by default: zero behavior change until the user opts in. ----
public ToggleSettingPref SuggestedContent { get; set; } = new() { DesiredOn = false };
public ToggleSettingPref LockScreenSpotlight { get; set; } = new() { DesiredOn = false };
public ToggleSettingPref FinishSetupNag { get; set; } = new() { DesiredOn = false };
public ToggleSettingPref StartRecommendations { get; set; } = new() { DesiredOn = false };
public ToggleSettingPref ExplorerAds { get; set; } = new() { DesiredOn = false };
public ToggleSettingPref FeedbackNag { get; set; } = new() { DesiredOn = false };
public ToggleSettingPref Widgets { get; set; } = new() { DesiredOn = false };
public ToggleSettingPref EdgeBackground { get; set; } = new() { DesiredOn = false };

// ---- System toggles (inverted Gaming/Default; DesiredOn=true = gaming) ----
public ToggleSettingPref PowerThrottling { get; set; } = new() { DesiredOn = true };
public ToggleSettingPref FastStartup { get; set; } = new() { DesiredOn = true };
Expand Down
73 changes: 73 additions & 0 deletions src/GamerGuardian/Monitors/EdgeBackgroundMonitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using GamerGuardian.Models;
using GamerGuardian.Services;
using Microsoft.Win32;

namespace GamerGuardian.Monitors;

/// <summary>
/// Microsoft Edge "startup boost" + "background mode" -- the pair that keeps
/// Edge processes resident at boot and running after every window is closed,
/// costing idle RAM/CPU on a machine where Edge isn't the daily browser. Set via
/// the HKLM Edge enterprise policies (which survive Edge updates, unlike the
/// in-app toggles). Intuitive Enabled/Disabled: <c>DesiredOn</c> = the
/// boost/background behavior enabled; recommended OFF. HKLM write needs elevation.
///
/// <para>This does not block Edge itself -- it still launches on demand and
/// WebView2-dependent apps keep working.</para>
/// </summary>
public sealed class EdgeBackgroundMonitor : IMonitoredSetting
{
public string Id => "debloat.edge";
private const string PolicyKey = @"SOFTWARE\Policies\Microsoft\Edge";
private const string StartupBoost = "StartupBoostEnabled";
private const string BackgroundMode = "BackgroundModeEnabled";

public IEnumerable<DriftItem> CheckDrift(AppConfig config)
{
var pref = config.Global.EdgeBackground;
var current = ReadCurrent();
if (current is null) yield break;
if (current.Value == pref.DesiredOn) yield break;

bool desired = pref.DesiredOn;
yield return new DriftItem(
SettingId: Id,
DisplayKey: "debloat",
DisplayLabel: "Debloat",
Description: "Edge startup boost & background mode",
CurrentValue: current.Value ? "Enabled" : "Disabled",
DesiredValue: desired ? "Enabled" : "Disabled",
AutoApply: pref.AutoApply,
Apply: () => Task.Run(() => Apply(desired)),
IsMonitored: pref.Monitor,
RawBefore: current.Value ? "(default / Edge prelaunch on)" : "StartupBoostEnabled=0, BackgroundModeEnabled=0",
RawDesired: desired ? "(policies deleted / Windows default)" : "StartupBoostEnabled=0, BackgroundModeEnabled=0");
}

/// <summary>True (on) unless the startup-boost policy explicitly disables it.</summary>
public static bool? ReadCurrent()
{
try
{
using var k = Registry.LocalMachine.OpenSubKey(PolicyKey, writable: false);
return (k?.GetValue(StartupBoost) as int?) != 0;
}
catch { return null; }

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}

public static void Apply(bool on)
{
if (on)
ElevatedRegistry.DeleteHklmMulti(new[]
{
(PolicyKey, StartupBoost),
(PolicyKey, BackgroundMode),
});
else
ElevatedRegistry.SetHklmMulti(new[]
{
(PolicyKey, StartupBoost, "REG_DWORD", "0"),
(PolicyKey, BackgroundMode, "REG_DWORD", "0"),
});
}
}
68 changes: 68 additions & 0 deletions src/GamerGuardian/Monitors/ExplorerAdsMonitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using GamerGuardian.Models;
using Microsoft.Win32;

namespace GamerGuardian.Monitors;

/// <summary>
/// File Explorer "sync provider" notifications -- the OneDrive / Microsoft 365
/// upsell banners that appear in the Explorer navigation pane and status bar.
/// Single per-user Explorer\Advanced DWORD (0 = off). Intuitive Enabled/Disabled:
/// <c>DesiredOn</c> = the banners enabled; recommended OFF.
/// </summary>
public sealed class ExplorerAdsMonitor : IMonitoredSetting
{
public string Id => "debloat.explorerads";
private const string SubKey = @"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced";
private const string ValueName = "ShowSyncProviderNotifications";

public IEnumerable<DriftItem> CheckDrift(AppConfig config)
{
var pref = config.Global.ExplorerAds;
var current = ReadCurrent();
if (current is null) yield break;
if (current.Value == pref.DesiredOn) yield break;

bool desired = pref.DesiredOn;
yield return new DriftItem(
SettingId: Id,
DisplayKey: "debloat",
DisplayLabel: "Debloat",
Description: "File Explorer OneDrive / Office ad banners",
CurrentValue: current.Value ? "Enabled" : "Disabled",
DesiredValue: desired ? "Enabled" : "Disabled",
AutoApply: pref.AutoApply,
Apply: () => Task.Run(() => Apply(desired)),
IsMonitored: pref.Monitor,
RawBefore: current.Value ? "(default / banners on)" : "ShowSyncProviderNotifications=0",
RawDesired: desired ? "(deleted / Windows default)" : "ShowSyncProviderNotifications=0");
}

/// <summary>True (on) unless the value is explicitly 0. Absent = Windows default on.</summary>
public static bool? ReadCurrent()
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(SubKey, writable: false);
return (k?.GetValue(ValueName) as int?) != 0;
}
catch { return null; }

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}

public static void Apply(bool on)
{
try
{
if (on)
{
using var k = Registry.CurrentUser.OpenSubKey(SubKey, writable: true);
k?.DeleteValue(ValueName, throwOnMissingValue: false);
}
else
{
using var k = Registry.CurrentUser.CreateSubKey(SubKey, writable: true)!;
k.SetValue(ValueName, 0, RegistryValueKind.DWord);
}
}
catch { }

Check notice

Code scanning / CodeQL

Poor error handling: empty catch block Note

Poor error handling: empty catch block.

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}
}
71 changes: 71 additions & 0 deletions src/GamerGuardian/Monitors/FeedbackNagMonitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using GamerGuardian.Models;
using Microsoft.Win32;

namespace GamerGuardian.Monitors;

/// <summary>
/// Windows Feedback request frequency -- the periodic "rate your experience"
/// dialogs the OS pops (and which can fire often on fresh installs). Controlled
/// by the per-user Siuf\Rules\NumberOfSIUFInPeriod DWORD; 0 = never ask. We also
/// remove PeriodInNanoSeconds, whose presence overrides the count. Intuitive
/// Enabled/Disabled: <c>DesiredOn</c> = feedback prompts enabled; recommended OFF.
/// </summary>
public sealed class FeedbackNagMonitor : IMonitoredSetting
{
public string Id => "debloat.feedback";
private const string SubKey = @"Software\Microsoft\Siuf\Rules";
private const string CountVal = "NumberOfSIUFInPeriod";
private const string PeriodVal = "PeriodInNanoSeconds";

public IEnumerable<DriftItem> CheckDrift(AppConfig config)
{
var pref = config.Global.FeedbackNag;
var current = ReadCurrent();
if (current is null) yield break;
if (current.Value == pref.DesiredOn) yield break;

bool desired = pref.DesiredOn;
yield return new DriftItem(
SettingId: Id,
DisplayKey: "debloat",
DisplayLabel: "Debloat",
Description: "Windows feedback request popups",
CurrentValue: current.Value ? "Enabled" : "Disabled",
DesiredValue: desired ? "Enabled" : "Disabled",
AutoApply: pref.AutoApply,
Apply: () => Task.Run(() => Apply(desired)),
IsMonitored: pref.Monitor,
RawBefore: current.Value ? "(default / Windows asks for feedback)" : "NumberOfSIUFInPeriod=0",
RawDesired: desired ? "(deleted / Windows default)" : "NumberOfSIUFInPeriod=0 (PeriodInNanoSeconds removed)");
}

/// <summary>True (on) unless the count is explicitly 0.</summary>
public static bool? ReadCurrent()
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(SubKey, writable: false);
return (k?.GetValue(CountVal) as int?) != 0;
}
catch { return null; }

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}

public static void Apply(bool on)
{
try
{
if (on)
{
using var k = Registry.CurrentUser.OpenSubKey(SubKey, writable: true);
k?.DeleteValue(CountVal, throwOnMissingValue: false);
}
else
{
using var k = Registry.CurrentUser.CreateSubKey(SubKey, writable: true)!;
k.SetValue(CountVal, 0, RegistryValueKind.DWord);
k.DeleteValue(PeriodVal, throwOnMissingValue: false);
}
}
catch { }

Check notice

Code scanning / CodeQL

Poor error handling: empty catch block Note

Poor error handling: empty catch block.

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}
}
80 changes: 80 additions & 0 deletions src/GamerGuardian/Monitors/FinishSetupNagMonitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using GamerGuardian.Models;
using Microsoft.Win32;

namespace GamerGuardian.Monitors;

/// <summary>
/// The "Let's finish setting up your device" / SCOOBE full-screen and
/// notification nags that prompt to set up OneDrive, a Microsoft account, or a
/// Microsoft 365 subscription -- and resurface after feature updates. Driven by
/// the per-user UserProfileEngagement flag plus the device-setup notification
/// under ContentDeliveryManager. Intuitive Enabled/Disabled: <c>DesiredOn</c> =
/// the nag enabled; recommended OFF.
/// </summary>
public sealed class FinishSetupNagMonitor : IMonitoredSetting
{
public string Id => "debloat.finishsetup";
private const string EngagementKey = @"Software\Microsoft\Windows\CurrentVersion\UserProfileEngagement";
private const string EngagementVal = "ScoobeSystemSettingEnabled";
private const string CdmKey = @"Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager";
private const string CdmVal = "SubscribedContent-310093Enabled"; // device-setup "finish setup" notifications

public IEnumerable<DriftItem> CheckDrift(AppConfig config)
{
var pref = config.Global.FinishSetupNag;
var current = ReadCurrent();
if (current is null) yield break;
if (current.Value == pref.DesiredOn) yield break;

bool desired = pref.DesiredOn;
yield return new DriftItem(
SettingId: Id,
DisplayKey: "debloat",
DisplayLabel: "Debloat",
Description: "\"Finish setting up your device\" nag",
CurrentValue: current.Value ? "Enabled" : "Disabled",
DesiredValue: desired ? "Enabled" : "Disabled",
AutoApply: pref.AutoApply,
Apply: () => Task.Run(() => Apply(desired)),
IsMonitored: pref.Monitor,
RawBefore: current.Value ? "(default / nag on)" : "ScoobeSystemSettingEnabled=0, SubscribedContent-310093Enabled=0",
RawDesired: desired ? "(deleted / Windows default)" : "ScoobeSystemSettingEnabled=0, SubscribedContent-310093Enabled=0");
}

/// <summary>True (on) unless both nag flags are explicitly 0. Absent values
/// count as on (the SCOOBE default), so the toggle applies on a fresh machine.</summary>
public static bool? ReadCurrent()
{
try
{
using var e = Registry.CurrentUser.OpenSubKey(EngagementKey, writable: false);
if ((e?.GetValue(EngagementVal) as int?) != 0) return true;
using var c = Registry.CurrentUser.OpenSubKey(CdmKey, writable: false);
if ((c?.GetValue(CdmVal) as int?) != 0) return true;
return false;
}
catch { return null; }

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}

public static void Apply(bool on)
{
try
{
if (on)
{
using (var e = Registry.CurrentUser.OpenSubKey(EngagementKey, writable: true))
e?.DeleteValue(EngagementVal, throwOnMissingValue: false);
using (var c = Registry.CurrentUser.OpenSubKey(CdmKey, writable: true))
c?.DeleteValue(CdmVal, throwOnMissingValue: false);
}
else
{
using (var e = Registry.CurrentUser.CreateSubKey(EngagementKey, writable: true)!)
e.SetValue(EngagementVal, 0, RegistryValueKind.DWord);
using (var c = Registry.CurrentUser.CreateSubKey(CdmKey, writable: true)!)
c.SetValue(CdmVal, 0, RegistryValueKind.DWord);
}
}
catch { }

Check notice

Code scanning / CodeQL

Poor error handling: empty catch block Note

Poor error handling: empty catch block.

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}
}
Loading