Flash console window on input request on Windows (#3158)

* 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
This commit is contained in:
Vita Chumakova
2024-03-14 04:08:00 +04:00
committed by GitHub
parent c193858973
commit 8642b0775e
3 changed files with 125 additions and 28 deletions

View File

@@ -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;
}
}

View File

@@ -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<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()) {
@@ -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);
}
}

View File

@@ -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();
}
}
}