using Microsoft.Win32; using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Principal; using System.Threading; namespace Bedtimer { public sealed class Config { public int BedMinutes { get; set; } = 22 * 60 + 30; // 10:30 PM public int WarnLead { get; set; } = 60; public int FlashLead { get; set; } = 20; public int LockDelay { get; set; } = 5; private const string RegPath = @"SOFTWARE\Whitlocktech\Bedtimer"; // --- Public API ------------------------------------------------------- public static Config LoadOrCreate() { // Read only; do NOT write defaults here. using var k = OpenHKLM().OpenSubKey(RegPath, writable: false); if (k == null) return new Config(); // defaults in memory only int Get(string n, int d) => (k.GetValue(n) is int v) ? v : d; return new Config { BedMinutes = Get(nameof(BedMinutes), 22 * 60 + 30), WarnLead = Get(nameof(WarnLead), 60), FlashLead = Get(nameof(FlashLead), 20), LockDelay = Get(nameof(LockDelay), 5), }; } public void Save() { try { using var root = OpenHKLM(); using var k = root.CreateSubKey(RegPath, writable: true); k.SetValue(nameof(BedMinutes), BedMinutes, RegistryValueKind.DWord); k.SetValue(nameof(WarnLead), WarnLead, RegistryValueKind.DWord); k.SetValue(nameof(FlashLead), FlashLead, RegistryValueKind.DWord); k.SetValue(nameof(LockDelay), LockDelay, RegistryValueKind.DWord); Signals.RaiseConfigChanged(); } catch (UnauthorizedAccessException) { // Relaunch self elevated to write HKLM in the SAME registry view var exe = Environment.ProcessPath!; var args = $"--save-admin {BedMinutes} {WarnLead} {FlashLead} {LockDelay}"; Process.Start(new ProcessStartInfo(exe, args) { UseShellExecute = true, Verb = "runas" }); } } public static void SaveFromArgs(string[] args) { // Accept: Bedtimer.exe --save-admin // Find the flag and read the 4 numbers after it. int i = Array.FindIndex(args, a => string.Equals(a, "--save-admin", StringComparison.OrdinalIgnoreCase)); if (i < 0 || args.Length < i + 5) return; var cfg = new Config { BedMinutes = int.Parse(args[i + 1]), WarnLead = int.Parse(args[i + 2]), FlashLead = int.Parse(args[i + 3]), LockDelay = int.Parse(args[i + 4]), }; using var root = OpenHKLM(); using var k = root.CreateSubKey(RegPath, writable: true); k.SetValue(nameof(BedMinutes), cfg.BedMinutes, RegistryValueKind.DWord); k.SetValue(nameof(WarnLead), cfg.WarnLead, RegistryValueKind.DWord); k.SetValue(nameof(FlashLead), cfg.FlashLead, RegistryValueKind.DWord); k.SetValue(nameof(LockDelay), cfg.LockDelay, RegistryValueKind.DWord); Signals.RaiseConfigChanged(); } // Keep %AppData% for state/logs public static string Dir => System.IO.Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Whitlocktech", "Bedtimer"); // --- Helpers ---------------------------------------------------------- private static RegistryKey OpenHKLM() { // Always open the correct view explicitly to avoid WOW6432 redirection issues var view = Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32; return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view); } public static bool IsProcessElevated() { using var id = WindowsIdentity.GetCurrent(); var p = new WindowsPrincipal(id); return p.IsInRole(WindowsBuiltInRole.Administrator); } } public static class Signals { public const string ConfigChangedName = @"Local\Bedtimer_Config_Changed"; public static void RaiseConfigChanged() { using var ev = new EventWaitHandle(false, EventResetMode.AutoReset, ConfigChangedName); ev.Set(); } public static EventWaitHandle SubscribeConfigChanged() => new EventWaitHandle(false, EventResetMode.AutoReset, ConfigChangedName); } }