_Tips: ターミナルのカーソル位置を取得する

Tips: ターミナルのカーソル位置を取得する

(expanded from ターミナルのカーソル位置を取得 このページは編集しないでください)

(2018/3/21)

ESC [ 6 n というエスケープシーケンスを利用してターミナル上のカーソル位置を取得する話

VT100のようなターミナルのカーソルを動かすために ESC [ ... というエスケープシーケンスが広く利用されている
MacのTerminal.appをはじめ、現在ほぼすべてのターミナルエミュレータはVT100互換(というかANSI)なので同じシーケンスを使うことができる
エスケープシーケンスは普通はカーソルを動かすために使われるのだが、カーソル位置を知るためのシーケンスも用意されており、これを使ってターミナル上のカーソル位置を知るプログラムを書くことができる
ESC [ 6 n という問い合わせシーケンスを送るとき tty からエコーが返るのは嫌なのでエコーは禁止しておくのが良いと思われる
ioctl(2) で制御できると思ったのだがどうもうまくいかない
tcgetattr(3) でできるということを教えてもらってやっとうまくいった
このシーケンスの話はこちらで知った

エスケープシーケンスだの ioctl() だのには前世紀におおいに苦労したものだが、Web時代だとそんな情報ぐらい楽々手に入るだろうと思ったら意外に大変だった
今回はFacebookで質問して小飼氏に教えてもらってやっと解決した
話題が古いせいかサンプルプログラムもほとんどみつからなかった

cursorpos.c
Copied!
#include <stdio.h>
#include <unistd.h>
#include <termios.h>

struct termios state, oldstate;

void echo_off()
{
tcgetattr(0, &oldstate);
state = oldstate;
state.c_lflag &= ~(ICANON | ECHO);
tcsetattr(0, TCSANOW, &state);
}

void echo_on()
{
tcsetattr(0, TCSANOW, &oldstate);
}

int main(){
char buf[10];
char *p = buf;
char c;

echo_off();
write(0,"\x1b[6n",4);

for (int i=0;;i++){
read(0,&c,1);
if(c >= 0x30 && c <= 0x39) *p++ = c;
if(c == ';') *p++ = ' ';
if(c == 'R') break;
}
*p = '\0';

echo_on();

printf("%s\n",buf);
}

2018/3/21 09:59

Powered by Helpfeel