using System.ComponentModel; using System.Windows; using System.Windows.Threading; using AutoShutdown.Models; using AutoShutdown.Services; using Forms = System.Windows.Forms; namespace AutoShutdown; public partial class MainWindow : Window { private readonly PowerService _powerService = new(); private readonly WakeService _wakeService = new(); private readonly NotificationSettingsStore _settingsStore = new(); private readonly DispatcherTimer _timer = new() { Interval = TimeSpan.FromSeconds(1) }; private readonly Dictionary _actionButtons; private readonly Forms.NotifyIcon? _trayIcon; private readonly bool _enableTray; private NotificationSettings _notificationSettings; private NotificationService _notificationService; private PowerAction _selectedAction = PowerAction.Shutdown; private PowerAction? _scheduledAction; private DateTime? _scheduledAt; private bool _hasExecuted; private bool _reminderShown; private bool _allowExit; public MainWindow(bool enableTray = true) { _enableTray = enableTray; _notificationSettings = _settingsStore.Load(); _notificationService = new NotificationService(_notificationSettings); InitializeComponent(); _actionButtons = new Dictionary { [PowerAction.Shutdown] = ShutdownButton, [PowerAction.Restart] = RestartButton, [PowerAction.Sleep] = SleepButton, [PowerAction.Hibernate] = HibernateButton, [PowerAction.Wake] = WakeButton, [PowerAction.Lock] = LockButton, [PowerAction.LogOff] = LogOffButton }; foreach (var (action, button) in _actionButtons) { button.Tag = action; button.ToolTip = action.Description(); } DateInput.Text = DateTime.Today.ToString("yyyy/M/d"); TimeInput.Text = DateTime.Now.AddMinutes(30).ToString("HH:mm"); ForceCheck.ToolTip = "强制关闭未响应程序,仅用于关机、重启、注销等动作。"; _trayIcon = _enableTray ? CreateTrayIcon() : null; SelectAction(PowerAction.Shutdown); UpdateScheduleInputs(); UpdateStatus(); _timer.Tick += (_, _) => OnTick(); _timer.Start(); } protected override void OnClosing(CancelEventArgs e) { if (_enableTray && !_allowExit) { e.Cancel = true; HideToTray(); return; } _trayIcon?.Dispose(); base.OnClosing(e); } private Forms.NotifyIcon CreateTrayIcon() { var menu = new Forms.ContextMenuStrip(); menu.Items.Add("显示/收起主窗口", null, (_, _) => Dispatcher.Invoke(ToggleMainWindowFromTray)); menu.Items.Add("取消当前计划", null, async (_, _) => await Dispatcher.InvokeAsync(CancelCurrentPlanAsync)); menu.Items.Add("OmniNotify 适配设置", null, (_, _) => Dispatcher.Invoke(OpenNotificationSettings)); menu.Items.Add(new Forms.ToolStripSeparator()); menu.Items.Add("退出 AutoShutdown", null, (_, _) => ExitFromTray()); var icon = new Forms.NotifyIcon { Icon = LoadTrayIcon(), Text = "AutoShutdown", ContextMenuStrip = menu, Visible = true }; icon.MouseClick += (_, e) => { if (e.Button == Forms.MouseButtons.Left) { Dispatcher.Invoke(ToggleMainWindowFromTray); } }; return icon; } private static System.Drawing.Icon LoadTrayIcon() { var processPath = Environment.ProcessPath; return !string.IsNullOrWhiteSpace(processPath) && System.Drawing.Icon.ExtractAssociatedIcon(processPath) is { } icon ? icon : System.Drawing.SystemIcons.Application; } private void HideToTray(bool showHint = true) { Hide(); if (!showHint) { return; } if (_notificationSettings.Mode == NotificationMode.OmniNotify) { _ = _notificationService.NotifyAsync("AutoShutdown 已在托盘运行", "关闭窗口不会退出应用,可在托盘菜单中退出。"); return; } _trayIcon?.ShowBalloonTip(2500, "AutoShutdown 已在托盘运行", "关闭窗口不会退出应用,可在托盘菜单中退出。", Forms.ToolTipIcon.Info); } private void ToggleMainWindowFromTray() { if (IsVisible && WindowState != WindowState.Minimized) { HideToTray(showHint: false); return; } RestoreFromTray(); } private void RestoreFromTray() { Show(); WindowState = WindowState.Normal; Activate(); } private void ExitFromTray() { _allowExit = true; Dispatcher.Invoke(Close); } private void UpdateTrayText(string text) { if (_trayIcon is null) { return; } _trayIcon.Text = text.Length > 63 ? text[..63] : text; } private void ActionButton_Click(object sender, RoutedEventArgs e) { if (sender is System.Windows.Controls.Button { Tag: PowerAction action }) { SelectAction(action); } } private void ScheduleModeChanged(object sender, RoutedEventArgs e) => UpdateScheduleInputs(); private async void StartButton_Click(object sender, RoutedEventArgs e) => await StartPlanAsync(); private async void CancelButton_Click(object sender, RoutedEventArgs e) => await CancelPlanAsync(); private async void AbortButton_Click(object sender, RoutedEventArgs e) => await AbortSystemShutdownAsync(); private void SettingsButton_Click(object sender, RoutedEventArgs e) => OpenNotificationSettings(); private void OpenNotificationSettings() { var window = new NotificationSettingsWindow(_notificationSettings, _notificationService) { Owner = this }; if (window.ShowDialog() != true) { return; } _notificationSettings = window.Settings; _settingsStore.Save(_notificationSettings); _notificationService.UpdateSettings(_notificationSettings); UpdateStatus(); } private void SelectAction(PowerAction action) { _selectedAction = action; ActionTitleText.Text = action.Label(); ActionDescriptionText.Text = action.Description(); foreach (var (item, button) in _actionButtons) { var selected = item == action; button.Background = selected ? BrushFrom("#E2ECFF") : BrushFrom("#FFFFFF"); button.Foreground = selected ? BrushFrom("#2563EB") : BrushFrom("#111827"); button.BorderBrush = selected ? BrushFrom("#2563EB") : BrushFrom("#DEE6F2"); button.BorderThickness = selected ? new Thickness(2) : new Thickness(1); } } private async Task StartPlanAsync() { if (!TryResolveScheduleTime(out var scheduledAt)) { await _notificationService.NotifyAsync(this, "计划无效", "请检查时间或倒计时输入。", MessageBoxImage.Warning); return; } if (scheduledAt <= DateTime.Now.AddSeconds(1)) { await _notificationService.NotifyAsync(this, "计划无效", "请选择一个晚于当前时间的计划。", MessageBoxImage.Warning); return; } var scheduledAction = _selectedAction; var forceApps = ForceCheck.IsChecked == true; _scheduledAt = scheduledAt; _scheduledAction = scheduledAction; _hasExecuted = false; _reminderShown = false; UpdateStatus(); await Dispatcher.Yield(DispatcherPriority.Background); var seconds = (int)Math.Ceiling((scheduledAt - DateTime.Now).TotalSeconds); if (scheduledAction == PowerAction.Wake) { await TryRunAsync( () => _wakeService.ScheduleWake(scheduledAt), "计划已启动", "已创建 Windows 唤醒任务。请确保电源选项允许唤醒计时器。"); } else if (scheduledAction is PowerAction.Shutdown or PowerAction.Restart) { await TryRunAsync( () => _powerService.Execute(scheduledAction, seconds, forceApps), "计划已启动", "已交给 Windows 系统计划执行。"); } else { await _notificationService.NotifyAsync(this, "计划已启动", $"{scheduledAction.Label()} 将在 {scheduledAt:yyyy-MM-dd HH:mm} 执行。"); } } private async Task CancelPlanAsync() { var hadPlan = _scheduledAt is not null; var scheduledAction = _scheduledAction; _scheduledAt = null; _scheduledAction = null; _hasExecuted = false; _reminderShown = false; UpdateStatus(); if (scheduledAction == PowerAction.Wake) { await TryCancelWakeAsync(); } if (hadPlan && scheduledAction is not null) { await _notificationService.NotifyAsync(this, "计划已取消", $"已取消 {scheduledAction.Value.Label()} 计划。"); } } private Task CancelCurrentPlanAsync() { return _scheduledAction is PowerAction.Shutdown or PowerAction.Restart ? AbortSystemShutdownAsync() : CancelPlanAsync(); } private async Task AbortSystemShutdownAsync() { if (_powerService.CancelShutdown()) { _scheduledAt = null; _scheduledAction = null; _hasExecuted = false; _reminderShown = false; UpdateStatus(); await _notificationService.NotifyAsync(this, "已撤销", "已请求 Windows 撤销待执行的关机/重启任务。"); return; } await _notificationService.NotifyAsync(this, "无需撤销", "当前没有可撤销的系统关机/重启任务,或系统拒绝了该请求。"); } private async void OnTick() { if (_scheduledAt is null) { UpdateStatus(); return; } var remaining = _scheduledAt.Value - DateTime.Now; if (remaining.TotalSeconds <= 0 && !_hasExecuted) { _hasExecuted = true; var scheduledAction = _scheduledAction ?? _selectedAction; var forceApps = ForceCheck.IsChecked == true; if (scheduledAction is not (PowerAction.Shutdown or PowerAction.Restart or PowerAction.Wake)) { await TryRunAsync( () => _powerService.Execute(scheduledAction, 0, forceApps), "执行完成", $"{scheduledAction.Label()} 已执行。", showSuccess: false); } _scheduledAt = null; _scheduledAction = null; } else if (!_reminderShown && TryReadInt(ReminderInput.Text, 0, 120, out var reminder) && reminder > 0 && remaining.TotalMinutes <= reminder) { _reminderShown = true; var scheduledAction = _scheduledAction ?? _selectedAction; await _notificationService.NotifyAsync(this, "任务提醒", $"{scheduledAction.Label()} 将在 {FormatRemaining(remaining)} 后执行。"); } UpdateStatus(); } private bool TryResolveScheduleTime(out DateTime scheduledAt) { if (ClockRadio.IsChecked == true) { if (!DateTime.TryParse(DateInput.Text, out var date) || !TimeSpan.TryParse(TimeInput.Text, out var time)) { scheduledAt = default; return false; } scheduledAt = date.Date.Add(time); return true; } if (!TryReadInt(HoursInput.Text, 0, 168, out var hours) || !TryReadInt(MinutesInput.Text, 0, 59, out var minutes)) { scheduledAt = default; return false; } scheduledAt = DateTime.Now.AddHours(hours).AddMinutes(minutes); return true; } private void UpdateScheduleInputs() { if (DateInput is null || TimeInput is null || HoursInput is null || MinutesInput is null) { return; } var byClock = ClockRadio.IsChecked == true; DateInput.IsEnabled = byClock; TimeInput.IsEnabled = byClock; HoursInput.IsEnabled = !byClock; MinutesInput.IsEnabled = !byClock; } private void UpdateStatus() { UpdatePlanActionButtons(); if (_scheduledAt is null) { StatusText.Text = _notificationSettings.Mode == NotificationMode.OmniNotify ? $"当前没有运行中的计划。提示:OmniNotify / {_notificationSettings.Channel}" : "当前没有运行中的计划。"; CountdownText.Text = "待命"; UpdateTrayText("AutoShutdown - 待命"); return; } var remaining = _scheduledAt.Value - DateTime.Now; if (remaining < TimeSpan.Zero) { remaining = TimeSpan.Zero; } var scheduledAction = _scheduledAction ?? _selectedAction; StatusText.Text = $"下一次任务:{_scheduledAt:yyyy-MM-dd HH:mm} 执行 {scheduledAction.Label()}"; CountdownText.Text = FormatRemaining(remaining); UpdateTrayText($"AutoShutdown - {scheduledAction.Label()} {FormatRemaining(remaining)}"); } private void UpdatePlanActionButtons() { if (StartPlanButton is null || CancelPlanButton is null || AbortShutdownButton is null) { return; } var hasPlan = _scheduledAt is not null; var needsSystemAbort = hasPlan && _scheduledAction is PowerAction.Shutdown or PowerAction.Restart; StartPlanButton.Visibility = hasPlan ? Visibility.Collapsed : Visibility.Visible; CancelPlanButton.Visibility = hasPlan && !needsSystemAbort ? Visibility.Visible : Visibility.Collapsed; AbortShutdownButton.Visibility = needsSystemAbort ? Visibility.Visible : Visibility.Collapsed; } private async Task TryRunAsync(Action action, string title, string successMessage, bool showSuccess = true) { try { await Task.Run(action); if (showSuccess) { await _notificationService.NotifyAsync(this, title, successMessage); } } catch (Exception ex) { _scheduledAt = null; _scheduledAction = null; UpdateStatus(); await _notificationService.NotifyAsync(this, "启动失败", ex.Message, MessageBoxImage.Error); } } private async Task TryCancelWakeAsync() { try { await Task.Run(_wakeService.CancelWake); } catch { // 取消普通计划时不因为系统任务不存在或权限差异打断用户。 } } private static bool TryReadInt(string text, int min, int max, out int value) { return int.TryParse(text, out value) && value >= min && value <= max; } private static string FormatRemaining(TimeSpan value) { if (value.TotalDays >= 1) { return $"{(int)value.TotalDays}天 {value.Hours:D2}:{value.Minutes:D2}:{value.Seconds:D2}"; } return $"{Math.Max(0, (int)value.TotalHours):D2}:{value.Minutes:D2}:{value.Seconds:D2}"; } private static System.Windows.Media.Brush BrushFrom(string hex) { return (System.Windows.Media.Brush)new System.Windows.Media.BrushConverter().ConvertFromString(hex)!; } }