第47回
特殊な画面制御~コンソール入出力関数とエスケープシーケンス

エスケープシーケンスによる画面制御

コンソールモードのOSには、ANSI(米国標準協会――American National Standards Institute)の制定した画面制御機能が備わっています。これを使うと、文字に色を付けたり、表示位置を変更したりできます。

特殊記号で画面を制御する

コンピュータが登場した初期の頃には、さん孔式のカードやテープが基本的な入出力でした ※1 。後にディスプレイ(出力)とキーボード(入力)が誕生し、「ディスプレイにメッセージを表示し、それを受けてユーザーがキーボードからデータを入力する」という形式が標準となりました。

それ以降、ユーザーが操作しやすい親切な画面設計が重要になっていきます ※2 。単純な処理なら、画面の左端から、それも黒地に白でメッセージを表示するだけで済む場合もあるでしょう。しかし、専門家でない普通の人々が間違えないように入力を誘導するためには、メッセージの出力位置を画面の中央にしたり、エラーメッセージを目立つ色で表示したりするほうが親切です。

そこで、ディスプレイに特殊な記号を出力することで、表示位置や文字の色を変更する仕組みとして、エスケープシーケンスが用いられるようになりました。

コンソールモードでは1行が1バイト文字で80桁となっていますが、これはさん孔カードの仕様がそのまま受け継がれたものです
コンピュータが専門的な技能を持った人の扱う機械だった昔は、「間違いは人間の責任」だと捉えられていました。しかし、機械の性能が向上してディスプレイやキーボードが登場するようになると「人間が間違うのは当たり前。人間の間違いを防ぐことこそがコンピュータ技術である」という考え方が広まり、ヒューマンインターフェイスの研究が活発化します

エスケープシーケンス

エスケープシーケンスとは、エスケープコードで始まる画面制御のための文字列です。表2のように多彩な機能が提供されています。

表2:主なエスケープシーケンス
文字列 説明
画面制御 ESC[2 画面クリア
ESC[K カーソル位置から行末までをクリア 
ESC[nA カーソルを上にn行移動
ESC[nB カーソルを下にn行移動
ESC[nC カーソルを右にn桁移動
ESC[nD カーソルを左にn桁移動
ESC[r;cH カーソルをr行のn桁目に移動
文字属性 ESC[0m 属性を標準に戻す
ESC[1m 強調(太字)
ESC[4m 下線
ESC[7m 反転
ESC[nm 文字色(前景色)を指定
nの値は以下の通り
30:黒
31:赤
32:緑
33:黄
34:青
35:赤紫(マゼンタ)
36:水色(シアン)
37:白
ESC[39m 標準色に戻す
ESC[nm 背景色の指定
nの値は以下の通り
40:黒
41:赤
42:碧
43:黄
44:青
45:赤紫(マゼンタ)
46:水色(シアン)
47:白
ESC[49m 標準色に戻す

printfまたはputsで出力

これらエスケープシーケンスは、文字列としてディスプレイに送ることで機能します。Cの場合はprintfまたはputs関数で文字列を出力するのが一般的です。

文字列の中では、ESCコードは以下のように書き表します。
¥033 : 8進数で表記
¥x1b : 16進数で表記
どちらの書き方でも構いません。

たとえば以下のような動作を行うなら、リスト4のようなソースコードを記述すればよいでしょう。
(1) 画面をクリアする
(2) カーソルを5行/10桁目に移動する
(3) 文字色を黄色にする
(4) "Hello!"と出力する

リスト4:エスケープシーケンスを使って画面を制御する例~(ex4703.c)
#include <stdio.h>

int main(void)
{
  printf("¥033[2J");   /* 画面をクリア */
  printf("¥033[5;10H"); /* カーソルを移動 */
  printf("¥033[33m");   /* 文字色を黄色に */
  printf("Hello!¥n");  /* メッセージを出力 */

  printf("Press [Enter]...¥n");  /* メッセージを出力 */
  (void)getchar();     /* キー入力を待つ */
  printf("¥033[93m");   /* 文字色を標準に */
  return (0);
}

DOS窓では準備が必要

なお、エスケープシーケンスは、標準状態のWindowsのコマンドプロンプト(DOS窓)では使用できません(単に“[2J [5;10H"”などの文字列が表示されるだけです)。コマンドプロンプトでエスケープシーケンスを有効にするには、起動時に“ansi.sys”というドライバを読み込ませておく必要があります。

そのためには、起動ドライブのWindowsフォルダ(標準では“C:¥WINDOWS”)の“System32¥config.nt”に、リスト5のように以下の1行を挿入します。
device=%SystemRoot%¥system32¥ANSI.SYS

変更前のconfig.ntのコピーを作ってorg_config.ntなどといった名前を付け、いつでも元の状態に戻せるようにしてから、config.ntを書き換えるようにしましょう。

その後コマンドプロンプトを実行するとansi.sysが読み込まれ、エスケープシーケンスを使ったプログラムが正常に動作するようになります。

リスト5:config.ntにansi.sysを読み込むための1行を挿入する ※3
        :
REM     The EMM size is determined by pif file(either the one associated
REM     with your application or _default.pif). If the size from PIF file
REM     is zero, EMM will be disabled and the EMM line will be ignored.
REM
EMM=RAM
dos=high, umb
device=%SystemRoot%¥system32¥himem.sys
devicehigh=%SystemRoot%¥system32¥ntfont.sys
devicehigh=%SystemRoot%¥system32¥font_win.sys
devicehigh=%SystemRoot%¥system32¥$disp.sys /hs=%HardwareScroll%
devicehigh=%SystemRoot%¥system32¥disp_win.sys
devicehigh=%SystemRoot%¥system32¥kkcfunc.sys
files=40
device=%SystemRoot%¥system32¥MSIMEK.SYS /A1
devicehigh=%SystemRoot%¥system32¥MSIMEI.SYS /D*%SystemRoot%¥system32¥MSIMER.DIC /D%SystemRoot%¥system32¥MSIME.DIC /C1 /N /A1

device=%SystemRoot%¥system32¥ANSI.SYS
            ↑この1行を挿入
[K7QSCNLST]
c:¥windows¥system32¥config.nt=3
        :

config.ntの内容はOSのバージョンによって異なりますが、REMと書かれた行(注釈行)以外の箇所に挿入すれば問題はありません。こちらではWindows XPのconfig.ntを例に示します

エスケープシーケンスを使った例

エスケープシーケンスを使ったプログラムを紹介しておきます(リスト6)。

白い背景に緑色のバルタン星人もどきが現れ、腕を上下に動かし、膝を曲げながら横歩きする――というだけの他愛ないものです(画面1――はっきり言って、何のメリットもありません)。最後に[Enter]キーを押せば終了します。

動作速度は、記号定数“WAIT”の値で調整できます。
#define		WAIT	(10000000 / 2)
                         ↑この値を変更すれば速度が変わる

リスト6:エスケープシーケンスのサンプル――バルタン星人(もどき)が横歩きで体操する~ex4704.c
/*
  valtan.c -- バルタン星人(もどき)が体操する(^^;)
*/
#include    <stdio.h>

#define  ESC    0x1B
#define  WAIT   (10000000 / 2)  /* 待ち時間 */
#define  LOOP   200             /* 繰り返し回数 */

void	main(void)
{
  int    xpos, ypos = 8;   /* x, y 座標 */
  int    i = 0;
  long   j;

  printf("%c[2J", ESC);    /* 画面消去 */
  printf("%c[>5h", ESC);   /* カーソル消去 */
  printf("%c[33m", ESC);   /* 文字を黄色に */

  for(i=0 ; i<LOOP; i+=2) {
    xpos = i % 80 + 1;
    if (xpos == 1)    ypos++;
    /* 腕を上げる */
    printf("%c[%d;%dH  Yo¥¥oY", ESC, ypos,   xpos);
    printf("%c[%d;%dH    w   ", ESC, ypos+1, xpos);
    printf("%c[%d;%dH   | |  ", ESC, ypos+2, xpos);
    for (j=0; j<WAIT; j++) { }
    /* 腕を下げる */
    printf("%c[%d;%dH   o¥¥o ", ESC, ypos,   xpos+1);
    printf("%c[%d;%dH >- w -<", ESC, ypos+1, xpos+1);
    printf("%c[%d;%dH   < >  ", ESC, ypos+2, xpos+1);
    for (j=0; j<WAIT; j++) { }
  }
  printf("¥nPress [Enter]");
  (void)getchar();        /* [Enter]キーを押せば終了 */
  printf("%c[>5l", ESC);  /* カーソル表示 */
  printf("%c[39m", ESC);  /* 文字色を戻す */
}


あとがき

コンソール入出力とエスケープシーケンスを使えば、CUIのシンプルなディスプレイでも、それなりに操作しやすいユーザーインターフェイスを構築できます。

今回はLSI-Cを前提としたDOS用の画面制御を紹介しました。

次回はLinuxなどUNIX系OSで使用されるGCCでの画面制御を紹介します。