2022年1月27日木曜日

azarakとzomelak

 現在のWindows 10/11の標準コンソール(conhost.exe)やWindows Terminal、その他の端末エミュレーターソフトやコンソールアプリケーションの大半がANSIエスケープシーケンスにより画面制御が行える。

 ANSIエスケープシーケンスは、Escコードを先頭にASCII文字からなる簡単なものなので、これを文字列に含ませることで、カーソル制御や表示属性の変更が可能だ。

 WSLのLinux(bash)やPowerShell(Windows PowerShellもPowerShell Coreのどちらも)も文字列にEscコードを埋め込むことができる。しかし、残念なことにcmd.exeの内部コマンド(cmd.exeが処理するコマンド)のechoやWindows付属のコマンドには、エスケープシーケンスを表示できるものがない。

 というわけで、エスケープシーケンスを表示できるコマンドを作って見た。cmd.exeとbashに組み込みコマンドでechoがあるので、さすがに同じ名前じゃまずい。「エコ(ー)」が2つなので、アザラク(azarak)とザメラク(zomelak)にした。

【追記】ダウンロードリンクを追加(2022年1月28日)

まずは、Windows Terminalなど、標準でANSIエスケープシーケンスが有効な場合向けのC#プログラムだ(ダウンロードリンクはページの最後)。

using System;
using System.Text.RegularExpressions;
namespace zomelak {
    class Program {
        static void Main(string[] args) {
            Console.WriteLine(Regex.Unescape(args[0]+@"\e[0m"));
        }
    }
}
 プログラムとしては単純で、1つめの引数(C#だとArgs[0])をRegexのUnescapeメソッドでエンコードする。C#では、“\e”などのエスケープ文字を文字列リテラルに記述できるが、ソースコードでは指定できても実際の値に変換するのはコンパイルのときなので、実行中にエスケープ文字を解釈することはない。このため、実行中に外部からもらう文字列は、エスケープ処理などが行われない。これを行うのが.NETの正規表現オブジェクト(Regex)のUnescapeメソッドだ。

Regex.Unescape(String) メソッド (System.Text.RegularExpressions) 

 なお、最後に属性などをリセットするための“\e[0m”を付けてある。これがないと、コマンド実行後に表示した属性がそのまま継続してしまう。

Windows標準コンソールで表示

 次は、標準コンソール用だが、基本的な考えは同じ。ただし、標準コンソールはデフォルトでは、ANSIエスケープシーケンスを解釈しないモードになっている。これを有効にするには、Win32APIのSetConsoleMode関数を使って、“ENABLE_VIRTUAL_TERMINAL_PROCESSING”(値としては0x0004)フラグを立ててやる必要がある。しかも、他のフラグが立ってるかもしれないので、現在のモードをGetConsoleModeで取得する必要がある。また、どちらの関数もコンソールウィンドウの標準出力ハンドルが必要である。というわけで3つのWin32APIを定義している(リストの8~10行目)。

using System;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
namespace azarak {
    class Program {
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr GetStdHandle(int nStdHandle);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
        private const int STD_OUTPUT_HANDLE = -11;
        private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
        static void Main(string[] args) {
            IntPtr handle=GetStdHandle(STD_OUTPUT_HANDLE);
            uint dmode;
            GetConsoleMode(handle,out dmode);
            dmode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
            SetConsoleMode(handle,dmode);
            Console.WriteLine(Regex.Unescape(args[0]+@"\e[0m"));
        }
    }
}

 それぞれの関数は、以下に解説がある。

GetStdHandle 関数

GetConsoleMode 関数

SetConsoleMode 関数

これらの関数は、エラーが起きる可能性があり、エラー値を戻す。手抜きだが、エラー処理は無視。どうせエラーが起きたら何もできないからどうしようもない。

ダウンロード

以下にそれぞれのzip圧縮ファイルのリンクを示す

Azarak

Zomelak

0 件のコメント:

コメントを投稿