diff --git a/ArchiSteamFarm/Core/NativeMethods.cs b/ArchiSteamFarm/Core/NativeMethods.cs index af1d27189..46238d057 100644 --- a/ArchiSteamFarm/Core/NativeMethods.cs +++ b/ArchiSteamFarm/Core/NativeMethods.cs @@ -1,18 +1,20 @@ +// ---------------------------------------------------------------------------------------------- // _ _ _ ____ _ _____ // / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ // / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| -// | +// ---------------------------------------------------------------------------------------------- +// // Copyright 2015-2024 Łukasz "JustArchi" Domeradzki // Contact: JustArchi@JustArchi.net -// | +// // 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. @@ -26,6 +28,12 @@ using System.Runtime.Versioning; namespace ArchiSteamFarm.Core; internal static partial class NativeMethods { + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [SupportedOSPlatform("Windows")] + [return: MarshalAs(UnmanagedType.Bool)] + [LibraryImport("user32.dll")] + internal static partial void FlashWindowEx(ref FlashWindowInfo pwfi); + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [SupportedOSPlatform("Windows")] [return: MarshalAs(UnmanagedType.Bool)] @@ -77,6 +85,16 @@ internal static partial class NativeMethods { Awake = SystemRequired | AwayModeRequired | Continuous } + [SupportedOSPlatform("Windows")] + [Flags] + internal enum EFlashFlags : uint { + Stop = 0, + Caption = 1, + Tray = 2, + All = Caption | Tray, + Timer = 4 + } + [SupportedOSPlatform("Windows")] internal enum EShowWindow : uint { Minimize = 6 @@ -86,4 +104,13 @@ internal static partial class NativeMethods { internal enum EStandardHandle { Input = -10 } + + [StructLayout(LayoutKind.Sequential)] + internal struct FlashWindowInfo { + public uint StructSize; + public nint WindowHandle; + public EFlashFlags Flags; + public uint Count; + public uint TimeoutBetweenFlashes; + } } diff --git a/ArchiSteamFarm/Core/OS.cs b/ArchiSteamFarm/Core/OS.cs index 11fba0c59..e1731270c 100644 --- a/ArchiSteamFarm/Core/OS.cs +++ b/ArchiSteamFarm/Core/OS.cs @@ -1,18 +1,20 @@ +// ---------------------------------------------------------------------------------------------- // _ _ _ ____ _ _____ // / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ // / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| -// | +// ---------------------------------------------------------------------------------------------- +// // Copyright 2015-2024 Łukasz "JustArchi" Domeradzki // Contact: JustArchi@JustArchi.net -// | +// // 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. @@ -84,11 +86,11 @@ internal static class OS { private static Mutex? SingleInstance; internal static void CoreInit(bool minimized, bool systemRequired) { - if (OperatingSystem.IsWindows()) { - if (minimized) { - WindowsMinimizeConsoleWindow(); - } + if (minimized) { + MinimizeConsoleWindow(); + } + if (OperatingSystem.IsWindows()) { if (systemRequired) { WindowsKeepSystemActive(); } @@ -227,6 +229,67 @@ internal static class OS { return false; } + [SupportedOSPlatform("Windows")] + internal static void WindowsStartFlashingConsoleWindow() { + if (!OperatingSystem.IsWindows()) { + throw new PlatformNotSupportedException(); + } + + using Process currentProcess = Process.GetCurrentProcess(); + nint handle = currentProcess.MainWindowHandle; + + if (handle == nint.Zero) { + return; + } + + NativeMethods.FlashWindowInfo flashInfo = new() { + StructSize = (uint) Marshal.SizeOf(), + Flags = NativeMethods.EFlashFlags.All | NativeMethods.EFlashFlags.Timer, + WindowHandle = handle, + Count = uint.MaxValue + }; + + NativeMethods.FlashWindowEx(ref flashInfo); + } + + [SupportedOSPlatform("Windows")] + internal static void WindowsStopFlashingConsoleWindow() { + if (!OperatingSystem.IsWindows()) { + throw new PlatformNotSupportedException(); + } + + using Process currentProcess = Process.GetCurrentProcess(); + nint handle = currentProcess.MainWindowHandle; + + if (handle == nint.Zero) { + return; + } + + NativeMethods.FlashWindowInfo flashInfo = new() { + StructSize = (uint) Marshal.SizeOf(), + Flags = NativeMethods.EFlashFlags.Stop, + WindowHandle = handle + }; + + NativeMethods.FlashWindowEx(ref flashInfo); + } + + private static void MinimizeConsoleWindow() { + // Will work if the terminal supports XTWINOPS sequences, reference: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html + Console.Write('\x1b' + @"[2;2;2t\r"); + + // Fallback if we're using conhost on Windows + if (OperatingSystem.IsWindows()) { + using Process process = Process.GetCurrentProcess(); + + nint windowHandle = process.MainWindowHandle; + + if (windowHandle != nint.Zero) { + NativeMethods.ShowWindow(windowHandle, NativeMethods.EShowWindow.Minimize); + } + } + } + [SupportedOSPlatform("Windows")] private static void WindowsDisableQuickEditMode() { if (!OperatingSystem.IsWindows()) { @@ -264,15 +327,4 @@ internal static class OS { ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, result)); } } - - [SupportedOSPlatform("Windows")] - private static void WindowsMinimizeConsoleWindow() { - if (!OperatingSystem.IsWindows()) { - throw new PlatformNotSupportedException(); - } - - using Process process = Process.GetCurrentProcess(); - - NativeMethods.ShowWindow(process.MainWindowHandle, NativeMethods.EShowWindow.Minimize); - } } diff --git a/ArchiSteamFarm/NLog/Logging.cs b/ArchiSteamFarm/NLog/Logging.cs index ceab6f449..e092a8f8d 100644 --- a/ArchiSteamFarm/NLog/Logging.cs +++ b/ArchiSteamFarm/NLog/Logging.cs @@ -1,18 +1,20 @@ +// ---------------------------------------------------------------------------------------------- // _ _ _ ____ _ _____ // / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ // / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| -// | +// ---------------------------------------------------------------------------------------------- +// // Copyright 2015-2024 Łukasz "JustArchi" Domeradzki // Contact: JustArchi@JustArchi.net -// | +// // 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. @@ -280,7 +282,7 @@ internal static class Logging { return; } - Console.Beep(); + Console.Write('\a'); } } @@ -292,9 +294,17 @@ internal static class Logging { Utilities.InBackground(() => BeepUntilCanceled(token)); + if (OperatingSystem.IsWindows()) { + OS.WindowsStartFlashingConsoleWindow(); + } + return Console.ReadLine(); } finally { cts.Cancel(); + + if (OperatingSystem.IsWindows()) { + OS.WindowsStopFlashingConsoleWindow(); + } } } @@ -306,6 +316,10 @@ internal static class Logging { Utilities.InBackground(() => BeepUntilCanceled(token)); + if (OperatingSystem.IsWindows()) { + OS.WindowsStartFlashingConsoleWindow(); + } + StringBuilder result = new(); while (true) { @@ -341,6 +355,10 @@ internal static class Logging { } } finally { cts.Cancel(); + + if (OperatingSystem.IsWindows()) { + OS.WindowsStopFlashingConsoleWindow(); + } } }