How to update console window's taskbar progress in Windows Terminal

398 Views Asked by At

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

1

There are 1 best solutions below

0
Tim On BEST ANSWER

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

https://github.com/microsoft/terminal/issues/6700