Skip to content

Commit

Permalink
Flash console window on input request on Windows (#3158)
Browse files Browse the repository at this point in the history
* Flash console window on input request

* Use BELL character instead of Beep, fix flash struct, add support for minimizing and flashing with Windows Terminal

* cross-platform minimization, use alert char instead of number, fix struct again

* remove console window

* formatting

* use MainWindowHandle if it's set (fix flashing winterm if ASF is launched in conhost)

* fix build

* remove support for flashing winterm
  • Loading branch information
ezhevita authored Mar 14, 2024
1 parent c193858 commit 8642b07
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 28 deletions.
35 changes: 31 additions & 4 deletions ArchiSteamFarm/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// ----------------------------------------------------------------------------------------------
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: [email protected]
// |
//
// 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.
Expand All @@ -26,6 +28,12 @@
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)]
Expand Down Expand Up @@ -77,6 +85,16 @@ internal enum EExecutionState : uint {
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
Expand All @@ -86,4 +104,13 @@ internal enum EShowWindow : uint {
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;
}
}
90 changes: 71 additions & 19 deletions ArchiSteamFarm/Core/OS.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// ----------------------------------------------------------------------------------------------
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: [email protected]
// |
//
// 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.
Expand Down Expand Up @@ -84,11 +86,11 @@ internal static string Version {
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();
}
Expand Down Expand Up @@ -227,6 +229,67 @@ internal static bool VerifyEnvironment() {
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<NativeMethods.FlashWindowInfo>(),
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<NativeMethods.FlashWindowInfo>(),
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()) {
Expand Down Expand Up @@ -264,15 +327,4 @@ private static void WindowsKeepSystemActive() {
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);
}
}
28 changes: 23 additions & 5 deletions ArchiSteamFarm/NLog/Logging.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// ----------------------------------------------------------------------------------------------
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: [email protected]
// |
//
// 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.
Expand Down Expand Up @@ -280,7 +282,7 @@ private static async Task BeepUntilCanceled(CancellationToken cancellationToken,
return;
}

Console.Beep();
Console.Write('\a');
}
}

Expand All @@ -292,9 +294,17 @@ private static async Task BeepUntilCanceled(CancellationToken cancellationToken,

Utilities.InBackground(() => BeepUntilCanceled(token));

if (OperatingSystem.IsWindows()) {
OS.WindowsStartFlashingConsoleWindow();
}

return Console.ReadLine();
} finally {
cts.Cancel();

if (OperatingSystem.IsWindows()) {
OS.WindowsStopFlashingConsoleWindow();
}
}
}

Expand All @@ -306,6 +316,10 @@ private static string ConsoleReadLineMasked(char mask = '*') {

Utilities.InBackground(() => BeepUntilCanceled(token));

if (OperatingSystem.IsWindows()) {
OS.WindowsStartFlashingConsoleWindow();
}

StringBuilder result = new();

while (true) {
Expand Down Expand Up @@ -341,6 +355,10 @@ private static string ConsoleReadLineMasked(char mask = '*') {
}
} finally {
cts.Cancel();

if (OperatingSystem.IsWindows()) {
OS.WindowsStopFlashingConsoleWindow();
}
}
}

Expand Down

0 comments on commit 8642b07

Please sign in to comment.