feat(app): 初始化自动关机工具首个版本

实现基于 WPF 的 AutoShutdown 主界面,支持关机、重启、睡眠、休眠、唤醒、锁屏和注销等电源任务。

支持指定时间和倒计时计划、执行前提醒、系统关机撤销、Windows 唤醒任务、托盘运行、自定义图标以及 OmniNotify 通知适配。

修复关闭到托盘时的运行提醒,并支持单击托盘图标打开或收起主界面。

补充 README、发布配置和 win-x64 Release 输出要求。

Release: win-x64
This commit is contained in:
2026-05-18 23:54:58 +08:00
commit d2d81a482b
17 changed files with 1611 additions and 0 deletions

95
Services/PowerService.cs Normal file
View File

@@ -0,0 +1,95 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using AutoShutdown.Models;
namespace AutoShutdown.Services;
internal sealed class PowerService
{
[DllImport("PowrProf.dll", SetLastError = true)]
private static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool LockWorkStation();
public void Execute(PowerAction action, int delaySeconds, bool forceApps)
{
switch (action)
{
case PowerAction.Shutdown:
RunShutdownCommand($"/s /t {Math.Max(0, delaySeconds)}{ForceFlag(forceApps)}");
break;
case PowerAction.Restart:
RunShutdownCommand($"/r /t {Math.Max(0, delaySeconds)}{ForceFlag(forceApps)}");
break;
case PowerAction.LogOff:
if (delaySeconds > 0)
{
using var timer = new System.Threading.Timer(_ => RunShutdownCommand($"/l{ForceFlag(forceApps)}"), null, TimeSpan.FromSeconds(delaySeconds), Timeout.InfiniteTimeSpan);
Thread.Sleep(TimeSpan.FromSeconds(delaySeconds + 1));
}
else
{
RunShutdownCommand($"/l{ForceFlag(forceApps)}");
}
break;
case PowerAction.Lock:
DelayThen(delaySeconds, () => LockWorkStation());
break;
case PowerAction.Sleep:
DelayThen(delaySeconds, () => SetSuspendState(false, forceApps, false));
break;
case PowerAction.Hibernate:
DelayThen(delaySeconds, () => SetSuspendState(true, forceApps, false));
break;
}
}
public bool CancelShutdown()
{
var result = RunProcess("shutdown.exe", "/a", showWindow: false);
return result == 0;
}
private static string ForceFlag(bool forceApps) => forceApps ? " /f" : string.Empty;
private static void DelayThen(int delaySeconds, Action action)
{
if (delaySeconds > 0)
{
Task.Delay(TimeSpan.FromSeconds(delaySeconds)).ContinueWith(_ => action(), TaskScheduler.Default);
return;
}
action();
}
private static void RunShutdownCommand(string arguments)
{
var code = RunProcess("shutdown.exe", arguments, showWindow: false);
if (code != 0)
{
throw new InvalidOperationException($"shutdown.exe 执行失败,退出码 {code}。");
}
}
private static int RunProcess(string fileName, string arguments, bool showWindow)
{
using var process = Process.Start(new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = !showWindow,
WindowStyle = showWindow ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden
});
if (process is null)
{
return -1;
}
process.WaitForExit();
return process.ExitCode;
}
}