diff --git a/RunCat365/ContextMenuManager.cs b/RunCat365/ContextMenuManager.cs index 979139c3..5226035d 100644 --- a/RunCat365/ContextMenuManager.cs +++ b/RunCat365/ContextMenuManager.cs @@ -33,6 +33,8 @@ internal ContextMenuManager( Action setManualTheme, Func getFPSMaxLimit, Action setFPSMaxLimit, + Func getLaunchAtStartup, + Func toggleLaunchAtStartup, Action openRepository, Action onExit ) @@ -91,10 +93,17 @@ Action onExit _ => null ); + var launchAtStartupMenu = new CustomToolStripMenuItem("Launch at startup") + { + Checked = getLaunchAtStartup() + }; + launchAtStartupMenu.Click += (sender, e) => HandleStartupMenuClick(sender, toggleLaunchAtStartup); + var settingsMenu = new CustomToolStripMenuItem("Settings"); settingsMenu.DropDownItems.AddRange( themeMenu, - fpsMaxLimitMenu + fpsMaxLimitMenu, + launchAtStartupMenu ); var endlessGameMenu = new CustomToolStripMenuItem("Endless Game"); @@ -187,6 +196,24 @@ internal void SetIcons(Theme systemTheme, Theme manualTheme, Runner runner) icons.AddRange(list); } + private static void HandleStartupMenuClick(object? sender, Func toggleLaunchAtStartup) + { + if (sender is null) return; + var item = (ToolStripMenuItem)sender; + try + { + if (toggleLaunchAtStartup(item.Checked)) + { + item.Checked = !item.Checked; + } + } + catch (InvalidOperationException ex) + { + MessageBox.Show(ex.Message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + + } + private void ShowOrActivateGameWindow(Func getSystemTheme) { if (endlessGameForm is null) diff --git a/RunCat365/LaunchAtStartupManager.cs b/RunCat365/LaunchAtStartupManager.cs new file mode 100644 index 00000000..9646436b --- /dev/null +++ b/RunCat365/LaunchAtStartupManager.cs @@ -0,0 +1,141 @@ +// Copyright 2025 Takuto Nakamura +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Microsoft.Win32; +using Windows.ApplicationModel; + +namespace RunCat365 +{ + + internal interface ILaunchAtStartupManager + { + bool GetEnabled(); + bool SetEnabled(bool enabled); + } + + internal sealed class PackagedLaunchAtStartupManager : ILaunchAtStartupManager + { + private static StartupTask? startupTask; + + public bool GetEnabled() + { + if (startupTask is null) startupTask = Task.Run(async () => await StartupTask.GetAsync("RunCatStartup")).Result; + if (startupTask is null) return false; + if (startupTask.State == StartupTaskState.Enabled) return true; + return false; + } + + public bool SetEnabled(bool enabled) + { + var changeCheck = false; + if (startupTask is null) startupTask = Task.Run(async () => await StartupTask.GetAsync("RunCatStartup")).Result; + if (!enabled) + { + switch (startupTask.State) + { + case StartupTaskState.Enabled: + changeCheck = true; + break; + case StartupTaskState.Disabled: + StartupTaskState newStartupState = Task.Run(async () => await startupTask.RequestEnableAsync()).Result; + if (newStartupState == StartupTaskState.Enabled) + { + changeCheck = true; + } + else + { + throw new InvalidOperationException("Launch at Startup could not be activated."); + } + break; + case StartupTaskState.DisabledByUser: + throw new InvalidOperationException("Launch at startup was disabled by the user, enable it in Task Manager > Startup, search RunCat 365 and enable it."); + case StartupTaskState.DisabledByPolicy: + throw new InvalidOperationException("Launch at startup was disabled by policy."); + } + } + else + { + if (startupTask.State == StartupTaskState.Enabled) startupTask.Disable(); + changeCheck = true; + } + return changeCheck; + } + } + + internal sealed class UnpackagedLaunchAtStartupManager : ILaunchAtStartupManager + { + public bool GetEnabled() + { + var keyName = @"Software\Microsoft\Windows\CurrentVersion\Run"; + using var rKey = Registry.CurrentUser.OpenSubKey(keyName); + if (rKey is null) return false; + var value = (rKey.GetValue(Application.ProductName) is not null); + rKey.Close(); + return value; + } + + public bool SetEnabled(bool enabled) + { + var productName = Application.ProductName; + if (productName is null) return false; + var keyName = @"Software\Microsoft\Windows\CurrentVersion\Run"; + using var rKey = Registry.CurrentUser.OpenSubKey(keyName, true); + if (rKey is null) return false; + if (enabled) + { + rKey.DeleteValue(productName, false); + } + else + { + var fileName = Environment.ProcessPath; + if (fileName != null) + { + rKey.SetValue(productName, fileName); + } + } + rKey.Close(); + return true; + } + } + + internal class LaunchAtStartupManager + { + + private readonly ILaunchAtStartupManager _launchAtStartupManager; + + public LaunchAtStartupManager() + { + _launchAtStartupManager = IsRunningAsPackaged() + ? new PackagedLaunchAtStartupManager() + : new UnpackagedLaunchAtStartupManager(); + } + + public bool GetStartup() => _launchAtStartupManager.GetEnabled(); + + public bool SetStartup(bool enabled) => _launchAtStartupManager.SetEnabled(enabled); + + private bool IsRunningAsPackaged() + { + try + { + var _ = Package.Current; + return true; + } + catch + { + return false; + } + } + } +} diff --git a/RunCat365/Program.cs b/RunCat365/Program.cs index e7b538a0..4772b22f 100644 --- a/RunCat365/Program.cs +++ b/RunCat365/Program.cs @@ -1,4 +1,4 @@ -// Copyright 2020 Takuto Nakamura +// Copyright 2020 Takuto Nakamura // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -using FormsTimer = System.Windows.Forms.Timer; using Microsoft.Win32; using RunCat365.Properties; using System.Diagnostics; +using FormsTimer = System.Windows.Forms.Timer; namespace RunCat365 { @@ -49,6 +49,7 @@ public class RunCat365ApplicationContext : ApplicationContext private readonly CPURepository cpuRepository; private readonly MemoryRepository memoryRepository; private readonly StorageRepository storageRepository; + private readonly LaunchAtStartupManager launchAtStartupManager; private readonly ContextMenuManager contextMenuManager; private readonly FormsTimer fetchTimer; private readonly FormsTimer animateTimer; @@ -70,6 +71,7 @@ public RunCat365ApplicationContext() cpuRepository = new CPURepository(); memoryRepository = new MemoryRepository(); storageRepository = new StorageRepository(); + launchAtStartupManager = new LaunchAtStartupManager(); contextMenuManager = new ContextMenuManager( () => runner, @@ -79,6 +81,8 @@ public RunCat365ApplicationContext() t => manualTheme = t, () => fpsMaxLimit, f => fpsMaxLimit = f, + () => launchAtStartupManager.GetStartup(), + s => launchAtStartupManager.SetStartup(s), () => OpenRepository(), () => Exit() ); diff --git a/WapForStore/Package.appxmanifest b/WapForStore/Package.appxmanifest index 34e439c9..18908e02 100644 --- a/WapForStore/Package.appxmanifest +++ b/WapForStore/Package.appxmanifest @@ -3,8 +3,10 @@ + IgnorableNamespaces="uap uap5 desktop rescap"> + + + + + +