Search code examples
c#cygwinansi-escape

24-Bit Console Color ANSI Codes in Cygwin


I wrote this simple C#/.NET Core console app code which outputs a set of color cubes of 7x7x7, testing 24-bit color rather than 256-color mode, as well as a custom TTF font I use that's derived from the "Ultimate Old School PC Font Pack" to include some extra Unicode block characters.

It works great in the Windows 10 terminal as seen, but tanks in Cygwin, even though it should be supported according to Github (https://gist.github.com/XVilka/8346728).

Any ideas on what could be wrong, if there's something in the code using [38m or [48m codes that might be somehow less compatible with Cygwin using either Cygwin.bat or mintty from Cygwin?

However, as you can see from the third picture it looks just fine in Mintty from Mingw64/MSYS2, but I would prefer to use Cygwin. It somehow mangles the triangle characters in the third picture as you can see, even when I set the character set setup as UTF-8 in mintty.

using System;
using System.Text;

namespace ConsoleColor
{
    public class App
    {
        //int[] colorMeta1 = { 0, 85, 170, 255 };
        int[] colorMeta2 = { 0, 43, 85, 127, 170, 213, 255 };

        public App()
        {
            int length = colorMeta2.Length;
            int index = 0;
            Encoding defaultEncoder = Console.OutputEncoding;
            Console.OutputEncoding = Encoding.UTF8;
            for (int r=0;r<length;r++)
            {
                for(int g=0;g<length;g++)
                {
                    for(int b=0;b<length;b++)
                    {
                        int r2 = colorMeta2[r];
                        int g2 = colorMeta2[g];
                        int b2 = colorMeta2[b];
                        int foregroundColor = (r2 == 255 || g2 == 255 || b2 == 255) ? 0 : 255;
                        Console.Write($"\u001b[38;2;{r2};{g2};{b2}m█");
                        Console.Write($"\u001b[38;2;{foregroundColor};{foregroundColor};{foregroundColor}m\u001b[48;2;{r2};{g2};{b2}m({r2.ToString().PadLeft(3, ' ')},{g2.ToString().PadLeft(3, ' ')},{b2.ToString().PadLeft(3, ' ')})");
                        Console.Write($"\u001b[38;2;{r2};{g2};{b2}m█");
                        Console.Write($"\u001b[38;2;{170};{170};{170}m");
                        Console.Write($"\u001b[48;2;{0};{0};{0}m");
                        index++;
                    }
                    Console.WriteLine();
                }
            }
            Console.WriteLine($"{index} total colors.");
            for (int a = 0x2580; a <= 0x259F; a++)
                Console.Write($"{(char)a}");
            for (int a = 0x25E2; a <= 0x25E5; a++)
                Console.Write($"{(char)a}");
            Console.WriteLine();
            Console.OutputEncoding = defaultEncoder;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            App app = new App();
        }
    }
}

Command Prompt: Command Prompt

Cygwin Command Prompt or Mintty 3.1.6 from Cygwin in Windows 10: Cygwin and Mintty

Mintty 3.1.6 in Mingw64 from MSYS2 in Windows 10: Mintty from MSYS


Solution

  • The following blog article shows what needs to be done. Apparently some win32 calls still need to be made in .NET Core console apps on Windows 10 for Cygwin/Mintty to work properly with them.

    https://www.jerriepelser.com/blog/using-ansi-color-codes-in-net-console-apps/

    Code:

    private const int STD_OUTPUT_HANDLE = -11;
    private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
    private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
    
    [DllImport("kernel32.dll")]
    private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
    
    [DllImport("kernel32.dll")]
    private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
    
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetStdHandle(int nStdHandle);
    
    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
    

    ...

    public void ConsoleInit()
    {
        var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
        if (!GetConsoleMode(iStdOut, out uint outConsoleMode))
        {
            Console.WriteLine("failed to get output console mode");
            Console.ReadKey();
            return;
        }
    
        outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
        if (!SetConsoleMode(iStdOut, outConsoleMode))
        {
            Console.WriteLine($"failed to set output console mode, error code: {GetLastError()}");
            Console.ReadKey();
            return;
        }
    }
    

    This line is of particular importance:

    outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;