diff --git a/dist/ap.exe b/dist/ap.exe index 75ba5dd..2fc3944 100644 Binary files a/dist/ap.exe and b/dist/ap.exe differ diff --git a/source/AutopilotManager.Client/Program.cs b/source/AutopilotManager.Client/Program.cs index b0d8694..822cafb 100644 --- a/source/AutopilotManager.Client/Program.cs +++ b/source/AutopilotManager.Client/Program.cs @@ -50,6 +50,8 @@ class Program private static bool _quietMode = false; private static bool _noWait = false; private static bool _skipHardwareHashUpload = false; + private static bool _autoCountdownEnabled = false; + private static bool _rebootEnabled = false; private const string _updateXmlUri = "https://github.com/okieselbach/Autopilot-Manager-Client/raw/master/dist/update.xml"; private static string _version = string.Empty; private static string _newVersion = string.Empty; @@ -227,7 +229,7 @@ static async Task Main(string[] args) var urlNoSelfService = new Url($"{_backendUrl}/Home/NoSelfService"); var qrCodeNoSelfServiceImage = QrCodeUtil.GenerateQrCode(urlNoSelfService.ToString()); - var form = new QrCodeForm(qrCodeImage, qrCodeNoSelfServiceImage, _systemInformation, _backendClient, _backendUrl, _preCheckErrorMessage, _endpointsValidationResult, _skipHardwareHashUpload); + var form = new QrCodeForm(qrCodeImage, qrCodeNoSelfServiceImage, _systemInformation, _backendClient, _backendUrl, _preCheckErrorMessage, _endpointsValidationResult, _skipHardwareHashUpload, _autoCountdownEnabled, _rebootEnabled); form.DisplayData(_quietMode, _noWait); if (form.DialogResult == DialogResult.Retry) @@ -324,9 +326,17 @@ private static string ParseCommandlineArgs(string[] args) _skipHardwareHashUpload = true; _logger.WriteDebug($"No Hardware Hash upload"); break; + case "a": + _autoCountdownEnabled = true; + _logger.WriteDebug($"Auto-countdown timer enabled"); + break; + case "r": + _rebootEnabled = true; + _logger.WriteDebug($"Reboot after countdown enabled"); + break; case "?": case "h": - _logger.WriteInfo($"2024 by Oliver Kieselbach (oliverkieselbach.com)"); + _logger.WriteInfo($"2025 by Oliver Kieselbach (oliverkieselbach.com)"); _logger.WriteInfo(""); _logger.WriteInfo($"USAGE: {Assembly.GetExecutingAssembly().GetName().Name} [options...]"); _logger.WriteInfo(""); @@ -343,6 +353,8 @@ private static string ParseCommandlineArgs(string[] args) _logger.WriteInfo($" deletion requests are only processed with enabled approval mode"); _logger.WriteInfo($"-g skip client version check on startup"); _logger.WriteInfo($"-u skip hardware hash harvesting and upload"); + _logger.WriteInfo($"-a, --auto, /a, /auto enable auto-countdown timer (15 seconds)"); + _logger.WriteInfo($"-r, --reboot, /r, /reboot enable reboot after countdown (combine with -a)"); Environment.Exit(0); break; default: diff --git a/source/AutopilotManager.Client/QrCodeForm.Designer.cs b/source/AutopilotManager.Client/QrCodeForm.Designer.cs index df2eb0c..e5366c8 100644 --- a/source/AutopilotManager.Client/QrCodeForm.Designer.cs +++ b/source/AutopilotManager.Client/QrCodeForm.Designer.cs @@ -17,6 +17,24 @@ protected override void Dispose(bool disposing) { components.Dispose(); } + + if (disposing) + { + // Stop Timer + if (_rebootCountdownTimer != null) + { + _rebootCountdownTimer.Stop(); + _rebootCountdownTimer.Dispose(); + _rebootCountdownTimer = null; + } + + if (_currentOverlayImage != null && _currentOverlayImage != AutopilotManager.Client.Properties.Resources.Win11Background) + { + _currentOverlayImage.Dispose(); + _currentOverlayImage = null; + } + } + base.Dispose(disposing); } diff --git a/source/AutopilotManager.Client/QrCodeForm.cs b/source/AutopilotManager.Client/QrCodeForm.cs index a6eae5f..2975a52 100644 --- a/source/AutopilotManager.Client/QrCodeForm.cs +++ b/source/AutopilotManager.Client/QrCodeForm.cs @@ -24,6 +24,12 @@ public partial class QrCodeForm : Form private bool _quietMode; private bool _noWait; private bool _skipHardwareHashUpload; + private Image _currentOverlayImage; // To track the current overlay image for memory cleanup + private System.Windows.Forms.Timer _rebootCountdownTimer; + private int _rebootCountdownSeconds = 15; + private string _originalRebootButtonText; + private bool _autoCountdownEnabled; + private bool _rebootEnabled; public QrCodeForm(Bitmap idQrCodeImage, Bitmap qrCodeNoSelfServiceImage, @@ -32,7 +38,9 @@ public QrCodeForm(Bitmap idQrCodeImage, string backendUrl, string preCheckErrorMessage, bool endpointsValidationResult, - bool skipHardwareHashUpload) + bool skipHardwareHashUpload, + bool autoCountdownEnabled = false, + bool rebootEnabled = false) { _idQrCodeImage = idQrCodeImage; _qrCodeNoSelfServiceImage = qrCodeNoSelfServiceImage; @@ -44,12 +52,213 @@ public QrCodeForm(Bitmap idQrCodeImage, _endpointsValidationResult = endpointsValidationResult; _quietMode = false; _skipHardwareHashUpload = skipHardwareHashUpload; + _autoCountdownEnabled = autoCountdownEnabled; + _rebootEnabled = rebootEnabled; _backendClient.ResultReceived += backendClient_ResultReceived; InitializeComponent(); } + /// + /// Handles the FormClosing event to stop the countdown + /// + protected override void OnFormClosing(FormClosingEventArgs e) + { + StopRebootCountdown(); + base.OnFormClosing(e); + } + + /// + /// Handles keyboard input - ESC stops the reboot countdown + /// + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + if (keyData == Keys.Escape && _rebootCountdownTimer != null) + { + StopRebootCountdown(); + buttonCancel.Text = "&Finish"; + return true; // Key was handled + } + return base.ProcessCmdKey(ref msg, keyData); + } + + /// + /// Sets a colored overlay over the background image + /// + /// The color of the overlay + /// The transparency of the overlay (0.0 - 1.0) + private void SetBackgroundColorOverlay(Color overlayColor, float opacity = 0.7f) + { + // Freigabe des vorherigen Overlay-Bildes + if (_currentOverlayImage != null && _currentOverlayImage != AutopilotManager.Client.Properties.Resources.Win11Background) + { + _currentOverlayImage.Dispose(); + } + + // Create a new bitmap based on the original background image + var originalImage = AutopilotManager.Client.Properties.Resources.Win11Background; + var overlayImage = new Bitmap(originalImage.Width, originalImage.Height); + + using (var graphics = Graphics.FromImage(overlayImage)) + { + // Draw the original background image + graphics.DrawImage(originalImage, 0, 0, originalImage.Width, originalImage.Height); + + // Create a colored overlay with transparency + using (var overlayBrush = new SolidBrush(Color.FromArgb((int)(255 * opacity), overlayColor))) + { + graphics.FillRectangle(overlayBrush, 0, 0, originalImage.Width, originalImage.Height); + } + } + + // Set the new image as background and track it for memory cleanup + _currentOverlayImage = overlayImage; + this.BackgroundImage = overlayImage; + tableLayoutPanel.BackgroundImage = overlayImage; + } + + /// + /// Removes the colored overlay and restores the original background image + /// + private void ClearBackgroundColorOverlay() + { + // Freigabe des aktuellen Overlay-Bildes + if (_currentOverlayImage != null && _currentOverlayImage != AutopilotManager.Client.Properties.Resources.Win11Background) + { + _currentOverlayImage.Dispose(); + _currentOverlayImage = null; + } + + var originalImage = AutopilotManager.Client.Properties.Resources.Win11Background; + this.BackgroundImage = originalImage; + tableLayoutPanel.BackgroundImage = originalImage; + } + + /// + /// Starts the automatic countdown for 15 seconds + /// + private void StartRebootCountdown() + { + _rebootCountdownSeconds = 15; + _originalRebootButtonText = buttonCancel.Text; + + // Create timer for countdown + _rebootCountdownTimer = new System.Windows.Forms.Timer(); + _rebootCountdownTimer.Interval = 1000; // 1 Sekunde + _rebootCountdownTimer.Tick += RebootCountdownTimer_Tick; + + // Button-Text initial setzen + UpdateRebootButtonText(); + + // Timer starten + _rebootCountdownTimer.Start(); + } + + /// + /// Stops the reboot countdown + /// + private void StopRebootCountdown() + { + if (_rebootCountdownTimer != null) + { + _rebootCountdownTimer.Stop(); + _rebootCountdownTimer.Dispose(); + _rebootCountdownTimer = null; + } + + // Restore original button text + if (!string.IsNullOrEmpty(_originalRebootButtonText)) + { + buttonCancel.Text = _originalRebootButtonText; + } + } + + /// + /// Aktualisiert den Button-Text mit dem Countdown + /// + private void UpdateRebootButtonText() + { + if (_rebootCountdownSeconds > 0) + { + if (_rebootEnabled) + { + buttonCancel.Text = $"&Reboot in {_rebootCountdownSeconds}s (Click or ESC to cancel)"; + } + else + { + buttonCancel.Text = $"&Close in {_rebootCountdownSeconds}s (Click or ESC to cancel)"; + } + } + else + { + if (_rebootEnabled) + { + buttonCancel.Text = "&Rebooting..."; + } + else + { + buttonCancel.Text = "&Closing..."; + } + } + } + + /// + /// Timer event for the countdown + /// + private void RebootCountdownTimer_Tick(object sender, EventArgs e) + { + _rebootCountdownSeconds--; + + if (_rebootCountdownSeconds <= 0) + { + // Countdown expired + _rebootCountdownTimer.Stop(); + UpdateRebootButtonText(); + + // Wait briefly so user can see the message + Application.DoEvents(); + System.Threading.Thread.Sleep(500); + + if (_rebootEnabled) + { + // Perform reboot when /reboot parameter is set + ExecuteReboot(); + } + else + { + // Only close application (default behavior) + this.Close(); + } + } + else + { + // Countdown aktualisieren + UpdateRebootButtonText(); + } + } + + /// + /// Executes the system restart + /// + private void ExecuteReboot() + { + try + { + Process.Start("shutdown.exe", "-r -t 0"); + } + catch (Exception ex) + { + // Fallback if shutdown.exe doesn't work + MessageBox.Show($"Automatic restart failed: {ex.Message}\nPlease restart manually.", + "Restart Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + finally + { + Application.Exit(); + } + } + public void DisplayData(bool quietMode, bool noWait) { _quietMode = quietMode; @@ -101,13 +310,20 @@ public void DisplayData(bool quietMode, bool noWait) private void buttonDone_Click(object sender, EventArgs e) { + // Stop countdown if running + StopRebootCountdown(); + if (buttonCancel.Text.StartsWith("&Reboot", StringComparison.OrdinalIgnoreCase) || - buttonCancel.Text.StartsWith("Reboot", StringComparison.OrdinalIgnoreCase)) + buttonCancel.Text.StartsWith("Reboot", StringComparison.OrdinalIgnoreCase) || + buttonCancel.Text.Contains("Reboot")) { - Process.Start("shutdown.exe", "-r -t 0"); + ExecuteReboot(); + } + else + { + Close(); + Dispose(); } - Close(); - Dispose(); } private async void QrCodeForm_Shown(object sender, EventArgs e) @@ -148,25 +364,44 @@ private async void QrCodeForm_Shown(object sender, EventArgs e) // SUCCESS - change colors success = true; - //BackColor = Color.FromArgb(0, 117, 51); + // Set green overlay for success + SetBackgroundColorOverlay(Color.FromArgb(0, 117, 51), 0.6f); if (_systemInformation.Action.StartsWith("import", StringComparison.OrdinalIgnoreCase) && !_skipHardwareHashUpload) { - buttonCancel.Text = "&Reboot"; + if (_rebootEnabled) + { + buttonCancel.Text = "&Reboot"; + } + else + { + buttonCancel.Text = "&Finish"; + } } else // Deletion request or Corporate Identifier upload processed { buttonCancel.Text = "&Finish"; } - //buttonCancel.BackColor = Color.FromArgb(0, 92, 40); - //labelCancel.ForeColor = Color.White; + buttonCancel.BackColor = Color.FromArgb(0, 92, 40); + + labelCancel.ForeColor = Color.White; labelCancel.Show(); if (_quietMode) { Application.Exit(); } + else + { + // Automatic countdown only for import operations and when enabled + if (_systemInformation.Action.StartsWith("import", StringComparison.OrdinalIgnoreCase) && + !_skipHardwareHashUpload && + _autoCountdownEnabled) + { + StartRebootCountdown(); + } + } } if ((labelRegisteredValue.Text.StartsWith("Queued", StringComparison.OrdinalIgnoreCase) && _noWait) || @@ -194,8 +429,8 @@ private async void QrCodeForm_Shown(object sender, EventArgs e) { // FAILURE - change colors - // pre-check went wrong like model/manufacturer or import failed - //BackColor = Color.FromArgb(91, 0, 7); + // Set red overlay for error + SetBackgroundColorOverlay(Color.FromArgb(91, 0, 7), 0.6f); if (_systemInformation.Action.StartsWith("import", StringComparison.OrdinalIgnoreCase)) { @@ -205,16 +440,16 @@ private async void QrCodeForm_Shown(object sender, EventArgs e) { buttonCancel.Text = "&Finish"; } - //buttonCancel.BackColor = Color.FromArgb(119, 0, 8); + + buttonCancel.BackColor = Color.FromArgb(119, 0, 8); - //buttonRetry.BackColor = Color.FromArgb(119, 0, 8); + buttonRetry.BackColor = Color.FromArgb(119, 0, 8); buttonRetry.Show(); - //labelCancel.ForeColor = Color.White; + labelCancel.ForeColor = Color.White; labelCancel.Show(); - //labelProvisioningInformation.ForeColor = Color.FromArgb(254, 197, 114); - labelProvisioningInformation.ForeColor = Color.FromArgb(255, 69, 0); + labelProvisioningInformation.ForeColor = Color.FromArgb(254, 197, 114); if (!string.IsNullOrEmpty(_preCheckErrorMessage)) {