I have this code that works perfectly in cmd and PowerShell, but does nothing in Windows Terminal.
internal class TaskbarProgress : IDisposable
{
private IntPtr consoleWindowHandle = IntPtr.Zero;
[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
internal TaskbarProgress()
{
consoleWindowHandle = GetConsoleWindow();
if (consoleWindowHandle != IntPtr.Zero)
{
TaskbarProgressCom.SetState(consoleWindowHandle, TaskbarProgressState.Normal);
}
}
internal void SetProgress(ulong currentValue, ulong maximumValue)
{
if (consoleWindowHandle != IntPtr.Zero)
{
TaskbarProgressCom.SetValue(consoleWindowHandle, currentValue, maximumValue);
}
}
public void Dispose()
{
if (consoleWindowHandle != IntPtr.Zero)
{
TaskbarProgressCom.SetState(consoleWindowHandle, TaskbarProgressState.NoProgress);
consoleWindowHandle = IntPtr.Zero;
}
}
}
internal enum TaskbarProgressState
{
NoProgress = 0,
Indeterminate = 0x1,
Normal = 0x2,
Error = 0x4,
Paused = 0x8
}
internal static class TaskbarProgressCom
{
... // Removed for StackOverflow complaint of too much code, but basically the same as https://www.nuget.org/packages/Microsoft-WindowsAPICodePack-Shell
}
I thought maybe the console window is childed, so grab the root window:
[DllImport("user32.dll", ExactSpelling = true)]
private static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);
// ...
IntPtr rootOwnerHandle = GetAncestor(consoleWindowHandle, GetAncestorFlags.RootOwner);
if (rootOwnerHandle != IntPtr.Zero)
{
consoleWindowHandle = rootOwnerHandle;
}
But that didn't change anything. What am I missing?
Extra context: https://github.com/dotnet/BenchmarkDotNet/pull/2158
Thanks to folks on the Windows Terminal repo, I got the answer:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetConsoleMode(IntPtr hConsoleHandle, out ConsoleModes lpMode);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetConsoleMode(IntPtr hConsoleHandle, ConsoleModes dwMode);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
const int STD_OUTPUT_HANDLE = -11;
[Flags]
private enum ConsoleModes : uint
{
ENABLE_PROCESSED_INPUT = 0x0001,
ENABLE_LINE_INPUT = 0x0002,
ENABLE_ECHO_INPUT = 0x0004,
ENABLE_WINDOW_INPUT = 0x0008,
ENABLE_MOUSE_INPUT = 0x0010,
ENABLE_INSERT_MODE = 0x0020,
ENABLE_QUICK_EDIT_MODE = 0x0040,
ENABLE_EXTENDED_FLAGS = 0x0080,
ENABLE_AUTO_POSITION = 0x0100,
ENABLE_PROCESSED_OUTPUT = 0x0001,
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002,
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004,
DISABLE_NEWLINE_AUTO_RETURN = 0x0008,
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
}
static void Main(string[] args)
{
IntPtr handle = GetStdHandle(STD_OUTPUT_HANDLE);
ConsoleModes previousConsoleMode;
GetConsoleMode(handle, out previousConsoleMode);
SetConsoleMode(handle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT);
for (uint i = 0; i < 100; ++i)
{
// Set progress value (0-100).
Console.Write($"\x1b]9;4;1;{i}\x1b\\");
Thread.Sleep(100);
}
// Set state to no progress.
Console.Write($"\x1b]9;4;0;0\x1b\\");
SetConsoleMode(handle, previousConsoleMode);
}
The supported sequences are:
ESC ] 9 ; 4 ; st ; pr ST
Set progress state on Windows taskbar and tab. When `st` is:
0: remove progress.
1: set progress value to pr (number, 0-100).
2: set the taskbar to the "Error" state
3: set the taskbar to the "Indeterminate" state
4: set the taskbar to the "Warning" state