10. コンソール入力の編集 -- termios を使う --
Linux のコンソールからの入力行を編集するには,単に標準入力から 取り込むだけでは実現できません.キーボードから入力する文字のうち カーソルを移動したり,文字の削除などに使うキー入力は表示する必要 がありません.またカーソルを移動するための方法も必要になります. このようにコンソールに対する入力を制御するために termios という 機能を使い,エスケープシーケンスと呼ぶ画面出力をコントロール するための特定の文字列を使用する必要があります. termios とエスケープシーケンスには非常に豊富な機能がありますが, ここでは行編集に使う部分だけを解説します.
termios を制御するために termios 構造体に必要な設定をします. termios 構造体は カーネルソースで次のように定義されています. (linux-2.4.0-test6/include/asm-i386/termbits.h の場合)
#define NCCS 19
struct termios {
tcflag_t c_iflag; /* 入力モードフラグ */
tcflag_t c_oflag; /* 出力モードフラグ */
tcflag_t c_cflag; /* 制御モードフラグ */
tcflag_t c_lflag; /* ローカルモードフラグ */
cc_t c_line; /* 通信制御 */
cc_t c_cc[NCCS]; /* 制御文字 */
};
入力行の編集機能を持つプログラムを作成する場合に問題となるのは ローカルエコーと呼ばれるキー入力をそのまま画面に表示するような デフォルトの設定です.カーソルを移動するキー入力などは画面への エコーを停止する必要があります.そのために termios 構造体の ローカルモードフラグ (c_lflag) と制御文字 (c_cc[]) を変更しますが, プログラム終了時に設定を元に戻しておく必要があります.
全体の流れは以下のようになります.
- 現在の端末設定を保存
- 端末を新しく設定
- 行編集処理
- 終了時に端末を保存してある設定に復帰
termios.c_lflag の設定のうち ISIG, ICANON, ECHO, ECHONL を設定します. ICANON を OFF にして Raw モードとし,ローカルエコーを OFF にするため に ECHO と ECHONL を OFF にします.また,ISIG を OFF にしてキー入力に よるシグナルの送信を止めます.以上の設定で read システムコールで キーボードからの入力をそのままプログラムで取得することができるように なります.また入力を 1 バイト毎に受け取るために,端末が Raw モードのとき read システムコールが 1 バイト受け取るまでリターンしないように 制御文字の配列の内 c_cc[VMIN] を 1, c_cc[VTIME] を 0 に設定します.
制御文字の初期設定は以下のようになっています.
| intr=^C | quit=^\ | erase=del | kill=^U |
| eof=^D | vtime=\0 | vmin=\1 | sxtc=\0 |
| start=^Q | stop=^S | susp=^Z | eol=\0 |
| reprint=^R | discard=^U | werase=^W | lnext=^V |
| eol2=\0 |
まず,端末の制御に必要な定数を Linux カーネルのヘッダファイルから 抽出して用意します.
;---------------------------------------------------------------------
; file : termios.inc
; created : 2000/08/19
; assembler : nasm 0.98
; description : termios structures and constants
; derived from : linux-2.4.0-test6/include/asm-i386/termbits.h,
; : termios.h, ioctl.h, ioctls.h
;
;Copyright (C) 2000 Jun Mizutani <mizutani.jun@nifty.ne.jp>
;---------------------------------------------------------------------
%ifndef __TERMIOS_INC
%define __TERMIOS_INC
%include "vartype.inc"
;---------------------------------------------------------------------
; /usr/src/linux/include/asm-i386/termbits.h
%assign NCCS 19
struc termios
.c_iflag UINT 1 ; input mode flags
.c_oflag UINT 1 ; output mode flags
.c_cflag UINT 1 ; control mode flags
.c_lflag UINT 1 ; local mode flags
.c_line UCHAR 1 ; line discipline
.c_cc UCHAR NCCS ; control characters
endstruc
; c_cc characters
%assign VINTR 0
%assign VQUIT 1
%assign VERASE 2
%assign VKILL 3
%assign VEOF 4
%assign VTIME 5
%assign VMIN 6
%assign VSWTC 7
%assign VSTART 8
%assign VSTOP 9
%assign VSUSP 10
%assign VEOL 11
%assign VREPRINT 12
%assign VDISCARD 13
%assign VWERASE 14
%assign VLNEXT 15
%assign VEOL2 16
; c_iflag bits
%assign IGNBRK 0000001q
%assign BRKINT 0000002q
%assign IGNPAR 0000004q
%assign PARMRK 0000010q
%assign INPCK 0000020q
%assign ISTRIP 0000040q
%assign INLCR 0000100q
%assign IGNCR 0000200q
%assign ICRNL 0000400q
%assign IUCLC 0001000q
%assign IXON 0002000q
%assign IXANY 0004000q
%assign IXOFF 0010000q
%assign IMAXBEL 0020000q
; c_oflag bits
%assign OPOST 0000001q
%assign OLCUC 0000002q
%assign ONLCR 0000004q
%assign OCRNL 0000010q
%assign ONOCR 0000020q
%assign ONLRET 0000040q
%assign OFILL 0000100q
%assign OFDEL 0000200q
%assign NLDLY 0000400q
%assign NL0 0000000q
%assign NL1 0000400q
%assign CRDLY 0003000q
%assign CR0 0000000q
%assign CR1 0001000q
%assign CR2 0002000q
%assign CR3 0003000q
%assign TABDLY 0014000q
%assign TAB0 0000000q
%assign TAB1 0004000q
%assign TAB2 0010000q
%assign TAB3 0014000q
%assign XTABS 0014000q
%assign BSDLY 0020000q
%assign BS0 0000000q
%assign BS1 0020000q
%assign VTDLY 0040000q
%assign VT0 0000000q
%assign VT1 0040000q
%assign FFDLY 0100000q
%assign FF0 0000000q
%assign FF1 0100000q
; c_cflag bit meaning
%assign CBAUD 0010017q
%assign B0 0000000q ; hang up
%assign B50 0000001q
%assign B75 0000002q
%assign B110 0000003q
%assign B134 0000004q
%assign B150 0000005q
%assign B200 0000006q
%assign B300 0000007q
%assign B600 0000010q
%assign B1200 0000011q
%assign B1800 0000012q
%assign B2400 0000013q
%assign B4800 0000014q
%assign B9600 0000015q
%assign B19200 0000016q
%assign B38400 0000017q
%assign EXTA B19200
%assign EXTB B38400
%assign CSIZE 0000060q
%assign CS5 0000000q
%assign CS6 0000020q
%assign CS7 0000040q
%assign CS8 0000060q
%assign CSTOPB 0000100q
%assign CREAD 0000200q
%assign PARENB 0000400q
%assign PARODD 0001000q
%assign HUPCL 0002000q
%assign CLOCAL 0004000q
%assign CBAUDEX 0010000q
%assign B57600 0010001q
%assign B115200 0010002q
%assign B230400 0010003q
%assign B460800 0010004q
%assign B500000 0010005q
%assign B576000 0010006q
%assign B921600 0010007q
%assign B1000000 0010010q
%assign B1152000 0010011q
%assign B1500000 0010012q
%assign B2000000 0010013q
%assign B2500000 0010014q
%assign B3000000 0010015q
%assign B3500000 0010016q
%assign B4000000 0010017
%assign CIBAUD 002003600000q ; input baud rate (not used)
%assign CMSPAR 010000000000q ; mark or space (stick) parity
%assign CRTSCTS 020000000000q ; flow control
; c_lflag bits
%assign ISIG 0000001q
%assign ICANON 0000002q
%assign XCASE 0000004q
%assign ECHO 0000010q
%assign ECHOE 0000020q
%assign ECHOK 0000040q
%assign ECHONL 0000100q
%assign NOFLSH 0000200q
%assign TOSTOP 0000400q
%assign ECHOCTL 0001000q
%assign ECHOPRT 0002000q
%assign ECHOKE 0004000q
%assign FLUSHO 0010000q
%assign PENDIN 0040000q
%assign IEXTEN 0100000q
; tcflow() and TCXONC use these
%assign TCOOFF 0
%assign TCOON 1
%assign TCIOFF 2
%assign TCION 3
; tcflush() and TCFLSH use these
%assign TCIFLUSH 0
%assign TCOFLUSH 1
%assign TCIOFLUSH 2
; tcsetattr uses these
%assign TCSANOW 0
%assign TCSADRAIN 1
%assign TCSAFLUSH 2
;---------------------------------------------------------------------
; /usr/src/linux/include/asm-i386/termios.h
struc winsize
.ws_row USHORT 1
.ws_col USHORT 1
.ws_xpixel USHORT 1
.ws_ypixel USHORT 1
endstruc
%assign NCC 8
struc termio
.c_iflag USHORT 1 ; input mode flags
.c_oflag USHORT 1 ; output mode flags
.c_cflag USHORT 1 ; control mode flags
.c_lflag USHORT 1 ; local mode flags
.c_line UCHAR 1 ; line discipline
.c_cc UCHAR NCC ; control characters
endstruc
; modem lines
%assign TIOCM_LE 0x001
%assign TIOCM_DTR 0x002
%assign TIOCM_RTS 0x004
%assign TIOCM_ST 0x008
%assign TIOCM_SR 0x010
%assign TIOCM_CTS 0x020
%assign TIOCM_CAR 0x040
%assign TIOCM_RNG 0x080
%assign TIOCM_DSR 0x100
%assign TIOCM_CD TIOCM_CAR
%assign TIOCM_RI TIOCM_RNG
%assign TIOCM_OUT1 0x2000
%assign TIOCM_OUT2 0x4000
%assign TIOCM_LOOP 0x8000
; ioctl (fd, TIOCSERGETLSR, &result) where result may be as below
; line disciplines
%assign N_TTY 0
%assign N_SLIP 1
%assign N_MOUSE 2
%assign N_PPP 3
%assign N_STRIP 4
%assign N_AX25 5
%assign N_X25 6 ; X.25 async
%assign N_6PACK 7
%assign N_MASC 8 ; Reserved for Mobitex module <kaz@cafe.net>
%assign N_R3964 9 ; Reserved for Simatic R3964 module
%assign N_PROFIBUS_FDL 10 ; Reserved for Profibus <Dave@mvhi.com>
; Linux IR - https://www.cs.uit.no/~dagb/irda/irda.html
%assign N_IRDA 11
; SMS block mode - for talking to GSM data cards about SMS messages
%assign N_SMSBLOCK 12
%assign N_HDLC 13 ; synchronous HDLC
%assign N_SYNC_PPP 14 ; synchronous PPP
;---------------------------------------------------------------------
; intr=^C quit=^\ erase=del kill=^U
; eof=^D vtime=\0 vmin=\1 sxtc=\0
; start=^Q stop=^S susp=^Z eol=\0
; reprint=^R discard=^U werase=^W lnext=^V
; eol2=\0
INIT_C_CC db "\003\034\177\025\004\0\1\0\021\023\032\0\022\017\027\026\0"
;---------------------------------------------------------------------
; /usr/src/linux/include/asm-i386/ioctl.h
%assign _IOC_NRBITS 8
%assign _IOC_TYPEBITS 8
%assign _IOC_SIZEBITS 14
%assign _IOC_DIRBITS 2
%assign _IOC_NRMASK ((1 << _IOC_NRBITS)-1)
%assign _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1)
%assign _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1)
%assign _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1)
%assign _IOC_NRSHIFT 0
%assign _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
%assign _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
%assign _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)
; Direction bits.
%assign _IOC_NONE 0
%assign _IOC_WRITE 1
%assign _IOC_READ 2
%define _IOC(dir,type,nr,size) (((dir) << _IOC_DIRSHIFT)|((type) << _IOC_TYPESHIFT)|
((nr) << _IOC_NRSHIFT)|((size) << _IOC_SIZESHIFT))
; used to create numbers
%define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
%define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(size))
%define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(size))
%define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(size))
; used to decode ioctl numbers..
%define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
%define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
%define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
%define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
; ...and for the drivers/sound files...
%assign IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT)
%assign IOC_OUT (_IOC_READ << _IOC_DIRSHIFT)
%assign IOC_INOUT ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
%assign IOCSIZE_MASK (_IOC_SIZEMASK << _IOC_SIZESHIFT)
%assign IOCSIZE_SHIFT (_IOC_SIZESHIFT)
;---------------------------------------------------------------------
; /usr/src/linux/include/asm-i386/ioctls.h
; 0x54 is just a magic number to make these relatively unique ('T')
%assign TCGETS 0x5401
%assign TCSETS 0x5402
%assign TCSETSW 0x5403
%assign TCSETSF 0x5404
%assign TCGETA 0x5405
%assign TCSETA 0x5406
%assign TCSETAW 0x5407
%assign TCSETAF 0x5408
%assign TCSBRK 0x5409
%assign TCXONC 0x540A
%assign TCFLSH 0x540B
%assign TIOCEXCL 0x540C
%assign TIOCNXCL 0x540D
%assign TIOCSCTTY 0x540E
%assign TIOCGPGRP 0x540F
%assign TIOCSPGRP 0x5410
%assign TIOCOUTQ 0x5411
%assign TIOCSTI 0x5412
%assign TIOCGWINSZ 0x5413
%assign TIOCSWINSZ 0x5414
%assign TIOCMGET 0x5415
%assign TIOCMBIS 0x5416
%assign TIOCMBIC 0x5417
%assign TIOCMSET 0x5418
%assign TIOCGSOFTCAR 0x5419
%assign TIOCSSOFTCAR 0x541A
%assign FIONREAD 0x541B
%assign TIOCINQ FIONREAD
%assign TIOCLINUX 0x541C
%assign TIOCCONS 0x541D
%assign TIOCGSERIAL 0x541E
%assign TIOCSSERIAL 0x541F
%assign TIOCPKT 0x5420
%assign FIONBIO 0x5421
%assign TIOCNOTTY 0x5422
%assign TIOCSETD 0x5423
%assign TIOCGETD 0x5424
%assign TCSBRKP 0x5425 ; Needed for POSIX tcsendbreak()
%assign TIOCTTYGSTRUCT 0x5426 ; For debugging only
%assign TIOCSBRK 0x5427 ; BSD compatibility
%assign TIOCCBRK 0x5428 ; BSD compatibility
%assign TIOCGSID 0x5429 ; Return the session ID of FD
; Get Pty Number (of pty-mux device)
%assign TIOCGPTN _IOR('T',0x30, 4)
; Lock/unlock Pty
%assign TIOCSPTLCK _IOW('T',0x31, 4)
%assign FIONCLEX 0x5450 ; these numbers need to be adjusted.
%assign FIOCLEX 0x5451
%assign FIOASYNC 0x5452
%assign TIOCSERCONFIG 0x5453
%assign TIOCSERGWILD 0x5454
%assign TIOCSERSWILD 0x5455
%assign TIOCGLCKTRMIOS 0x5456
%assign TIOCSLCKTRMIOS 0x5457
%assign TIOCSERGSTRUCT 0x5458 ; For debugging only
%assign TIOCSERGETLSR 0x5459 ; Get line status register
%assign TIOCSERGETMULTI 0x545A ; Get multiport config
%assign TIOCSERSETMULTI 0x545B ; Set multiport config
%assign TIOCMIWAIT 0x545C ; wait for a change on serial input line(s)
%assign TIOCGICOUNT 0x545D ; read serial port inline interrupt counts
%assign TIOCGHAYESESP 0x545E ; Get Hayes ESP configuration
%assign TIOCSHAYESESP 0x545F ; Set Hayes ESP configuration
; Used for packet mode
%assign TIOCPKT_DATA 0
%assign TIOCPKT_FLUSHREAD 1
%assign TIOCPKT_FLUSHWRITE 2
%assign TIOCPKT_STOP 4
%assign TIOCPKT_START 8
%assign TIOCPKT_NOSTOP 16
%assign TIOCPKT_DOSTOP 32
%assign TIOCSER_TEMT 0x01 ; Transmitter physically empty
%endif
今回は termios.inc のうちのほんの一部分しか使っていません.
つぎに termios.inc を使って行編集機能付きの 1 行入力ルーチンを作成 します.1 行のバイト数を eax に設定して,入力行の保存領域の先頭アドレス を ebx に設定して call readline のように呼び出します.入力されたバイト数が eax に返ります.端末のローカルエコーが OFF の状態で呼び出します. VT100系の端末(Linux端末,xterm, kterm 等) で実行できます. 入力行の保存領域の先頭アドレスを eax, 保存領域のサイズを ebx にセット して呼び出してください.入力バイト数が eax に返ります.
入力された文字を格納するバッファの内容と画面に表示されている文字列を 一致するような動作をするだけのサブルーチンです.
Linux のコンソール画面では VT102と同じ (ほとんど?) エスケープ シーケンスを使用します.今回の例では以下のコードを使用します.
| カーソル位置の保存 | ^[7 |
| カーソル位置の復帰 | ^[8 |
| カーソル左移動 | ^B |
| カーソル位置の削除と右側を移動 | ^[[1P |
| カーソル右左移動 | ^[[1C |
| カーソル位置から行末までの消去 | ^[[0K |
Linux のコンソールには非常に多く機能を実現するシーケンスがあります. 自分で実際に試したこともなく,すべて解説する根性も無いので書籍を 紹介しておきます. 「Linux プログラミング アスキー出版局 ISBN4-7561-2044-X」の 20 章 にLinux のコンソール機能に関する詳しい記述があります. カーネルソースの drivers/char/console.c を読むという方法もあります :-)
READ_LINE では入力行は次に示すキーで編集できます.bash の行入力と同じです.
| 1 文字削除 | ^D |
| バックスペース | ^H |
| カーソル右移動 | ^F → |
| カーソル左移動 | ^B ← |
;---------------------------------------------------------------------
; file : readline.inc
; created : 2000/08/20
; description : read a line with editting
;
; Copyright (C) 2000 Jun Mizutani <mizutani.jun@nifty.ne.jp>
;
; enter eax : BufferSize
; ebx : Buffer Address
; return eax : no. of char
;---------------------------------------------------------------------
%ifndef __READLINE_INC
%define __READLINE_INC
%include "stdio.inc"
%include "termios.inc"
READ_LINE:
push esi
push edi
push ebx
push ecx
push edx
mov edi, ebx ; Input Buffer
mov edx, eax ; BufferSize
xor ecx, ecx
mov esi, ecx ; current position
.next_char:
call InChar
cmp al, 0x1B ; ESC ?
jnz .bs
call translate_key_seq
.bs: cmp al, 0x08 ; BS ?
jnz .delete
test esi, esi ; if cp = 0 then next_char
jz .next_char
dec esi
mov ebx, CURSOR_LEFT
call OutPString
jmp short .del1
.delete:
cmp al, 0x04 ; ^D ?
jnz .cursor_left
.del1
cmp esi, ecx ; if cp < eol then del2
jb .del2
jmp short .next_char
.del2
dec ecx
push esi ; p = cp
.del3 cmp esi, ecx ; while(p<eol){buf[p]=buf[p+1]; p++}
je .del4
mov al, [edi+esi+1]
mov [edi + esi], al
inc esi
jmp short .del3
.del4
pop esi
mov ebx, DEL_AT_CURSOR
call OutPString
jmp short .next_char
.cursor_left:
cmp al, 0x02 ; ^B
jnz .cursor_right
test esi, esi ; if cp = 0 then next_char
jz .next_char
dec esi
mov ebx, CURSOR_LEFT
call OutPString
jmp short .next_char
.cursor_right:
cmp al, 0x06 ; ^F
jnz .enter_key
cmp ecx, esi ; if cp = eol then next_char
je .next_char
inc esi
mov ebx, CURSOR_RIGHT
call OutPString
jmp .next_char
.enter_key:
cmp al, 0x0A ; enter ?
jz .in_exit
cmp al, 0x20
jb near .next_char ; illegal chars
.in_printable:
inc ecx
inc esi
cmp ecx, edx ; buffer size
jae .in_toolong
cmp esi, ecx
jb .insert
call OutChar
mov [edi + esi-1], al
jmp .next_char
.insert
call OutChar
push eax
push ecx ; p = eol
dec ecx
.ins1 cmp esi, ecx ; while(p=>cp){buf[p]=buf[p-1]; p--}
ja .ins2
mov al, [edi + ecx - 1]
mov [edi + ecx], al
dec ecx
jmp short .ins1
.ins2
pop ecx
pop eax
mov [edi + esi - 1] ,al
call print_line_after_cp
jmp .next_char
.in_toolong:
dec ecx
dec esi
jmp .next_char
.in_exit:
mov byte [edi + ecx], 0
call NewLine
mov eax, ecx
pop edx
pop ecx
pop ebx
pop edi
pop esi
ret
print_line_after_cp:
push eax
push edx
mov ebx, SAVE_CURSOR
call OutPString
mov ebx, CLEAR_EOL
call OutPString
lea eax, [esi+edi]
mov edx, ecx
sub edx, esi
call OutString
mov ebx, RESTORE_CURSOR
call OutPString
pop edx
pop eax
ret
translate_key_seq:
call InChar
cmp al, '['
jnz .exit
call InChar
.tk0: cmp al, 'A'
jnz .tk1
mov al, 'P' - 0x40 ; ^P
ret
.tk1: cmp al, 'B'
jnz .tk2
mov al, 'N' - 0x40 ; ^N
ret
.tk2: cmp al, 'C'
jnz .tk3
mov al, 'F' - 0x40 ; ^F
ret
.tk3: cmp al, 'D'
jnz .exit
mov al, 'B' - 0x40 ; ^B
ret
.exit mov al, 0
ret
SAVE_CURSOR db 2, 0x1B, '7' ; ^[7
RESTORE_CURSOR db 2, 0x1B, '8' ; ^[8
DEL_AT_CURSOR db 4, 0x1B, "[1P" ; ^[[1P
CURSOR_RIGHT db 4, 0x1B, "[1C" ; ^[[1C
CURSOR_LEFT db 4, 0x1B, "[1D" ; ^[[1D
CLEAR_EOL db 4, 0x1B, "[0K" ; ^[[0K
%endif
termios.inc と readline.inc を使った簡単な例を作成します.行編集機能 を試すことができます.まずデフォルトの termios の c_cc 配列と変更後の c_cc 配列を表示します. 次に端末の設定を変更して入力バッファの内容が 画面に反映される状態で READ_LINE を呼び出し,その後入力結果を表示します. 次にウィンドウサイズを表示して終了します.
プログラムの全体の流れは以下のようになります.
- 現在の端末設定を保存
- 端末を新しく設定
- 編集機能付き 1 行入力
- 入力された行の内容を表示
- 端末の設定を復帰
- ウィンドウサイズを表示
- プログラム終了
;---------------------------------------------------------------------
; 2000/08/21
;
; nasm -f elf testterm.asm
; ld -s -o testterm testterm.o
;---------------------------------------------------------------------
section .text
global _start
%include "readline.inc"
;====================================
; メインルーチン
;====================================
_start:
; 端末のローカルエコーを OFF
; termios の保存と設定
call GET_TERMIOS
call SET_TERMIOS
; 新旧の termios の c_cc 配列を表示
call PRINT_TERMIOS
call NewLine
; 1 行入力
mov eax, MSG_EDIT
call OutAsciiZ
mov eax, 0x100
mov ebx, INBUFFER
call READ_LINE
; 入力内容の表示
mov edx, eax
mov eax, INBUFFER
call OutString
call NewLine
; termios の復帰
call RESTORE_TERMIOS
; ウィンドウサイズの表示
call NewLine
mov eax, MSG_WINSIZE
call OutAsciiZ
call PRINT_WINSIZE
call NewLine
; プログラム終了
call Exit
;------------------------------------
; ウィンドウサイズの表示
PRINT_WINSIZE:
pusha
call tcgetwinsize
mov ebx, wsize
xor eax, eax
mov ax, [ebx + winsize.ws_col]
call PrintLeft
mov eax, "x"
call OutChar
xor eax, eax
mov ax, [ebx + winsize.ws_row]
call PrintLeft
call NewLine
popa
ret
;------------------------------------
; ウィンドウサイズの設定
SET_WINSIZE:
pusha
mov ebx, wsize
mov ax, 80
mov [ebx + winsize.ws_col], ax
mov ax, 25
mov [ebx + winsize.ws_row], ax
call tcsetwinsize
popa
ret
;------------------------------------
; termios の表示
PRINT_TERMIOS:
pusha
mov eax, MSG_C_CC
call OutAsciiZ
mov ecx, new_termios - old_termios
mov esi, old_termios
.loop1: mov al, [esi]
call PrintHex2
inc esi
loop .loop1
call NewLine
mov ecx, new_termios_end - new_termios
mov esi, new_termios
.loop2: mov al, [esi]
call PrintHex2
inc esi
loop .loop2
call NewLine
popa
ret
;------------------------------------
; 現在の termios を保存
GET_TERMIOS:
pusha
mov ebx, old_termios
call tcgetattr
mov ecx, new_termios - old_termios
mov esi, old_termios
mov edi, new_termios
rep
movsb
popa
ret
;------------------------------------
; 新しい termios を設定
; Rawモード, ECHO 無し, ECHONL 無し
; VTIME=0, VMIN=1 : 1バイト読み取られるまで待機
SET_TERMIOS:
pusha
mov eax, [new_termios.c_lflag]
and eax, ~ICANON & ~ECHO & ~ECHONL & ~ISIG
mov [new_termios.c_lflag], eax
mov eax, 1
mov [new_termios.c_cc + VMIN], al
mov eax, 0
mov [new_termios.c_cc + VTIME], al
mov ebx, new_termios
call tcsetattr
popa
ret
;------------------------------------
; 保存されていた termios を復帰
RESTORE_TERMIOS:
pusha
mov ebx, old_termios
call tcsetattr
popa
ret
;------------------------------------
;ウィンドウサイズの取得と設定
; tcgetwinsize(&ws)
; tcsetwinsize(&ws)
; eax, ebx, ecx, edx : destroyed
tcgetwinsize:
mov ecx, TIOCGWINSZ
jmp short IOCTLWINSZ
tcsetwinsize:
mov ecx, TIOCSWINSZ
IOCTLWINSZ:
mov edx, wsize
mov eax, SYS_ioctl ; (54)
mov ebx, 0 ; to stdout
int 0x80 ; call kernel
ret
;------------------------------------
; 標準入力の termios の取得と設定
; tcgetattr(&termios)
; tcsetattr(&termios)
; eax : destroyed
; ebx : termios buffer adress
; ecx, edx : destroyed
tcgetattr:
mov eax, TCGETS
jmp short IOCTL
tcsetattr:
mov eax, TCSETS
;------------------------------------
; 標準入力の ioctl の実行
; sys_ioctl(unsigned int fd, unsigned int cmd,
; unsigned long arg)
; eax : cmd
; ebx : buffer adress
IOCTL:
mov ecx, eax ; set cmd
mov edx, ebx ; set arg
mov eax, 54 ; sys_ioctl
mov ebx, 0 ; to stdin
int 0x80 ; call kernel
ret
MSG_EDIT db 'Input a line (Del:^D, BS:^H, Back:^B, Forward:^F).', 0x0A, 0
MSG_C_CC db 'Termios c_cc[] : ', 0x0A, 0
MSG_WINSIZE db 'Window size : ', 0
;------------------------------------
section .bss
INBUFFER resb 0x100
old_termios istruc termios
.c_iflag UINT 1 ; input mode flags
.c_oflag UINT 1 ; output mode flags
.c_cflag UINT 1 ; control mode flags
.c_lflag UINT 1 ; local mode flags
.c_line UCHAR 1 ; line discipline
.c_cc UCHAR NCCS ; control characters
iend
new_termios istruc termios
.c_iflag UINT 1 ; input mode flags
.c_oflag UINT 1 ; output mode flags
.c_cflag UINT 1 ; control mode flags
.c_lflag UINT 1 ; local mode flags
.c_line UCHAR 1 ; line discipline
.c_cc UCHAR NCCS ; control characters
iend
new_termios_end
wsize istruc winsize
.ws_row USHORT 1
.ws_col USHORT 1
.ws_xpixel USHORT 1
.ws_ypixel USHORT 1
iend
;------------------------------------
実行例
jm:~/ex_asm$ asm testterm jm:~/ex_asm$ ./testterm Termios c_cc[] : 0005000005000000BD0000003B8A000000031C7F150400010011131A00120F1716000000 0005000005000000BD000000318A000000031C7F150400010011131A00120F1716000000 Input a line (Del:^D, BS:^H, Back:^B, Forward:^F). abcdefghij123klmnopqrstuvwxyzABCDEFGH4567IJKLMNOPQRST890UVWXYZ abcdefghij123klmnopqrstuvwxyzABCDEFGH4567IJKLMNOPQRST890UVWXYZ Window size : 80x40
testterm では日本語の入力はまったく考えていませんが,例えば Windows から telnet で起動すれば,日本語の入力も制限付きで可能です.
初期の MS-DOSプログラマはテキスト画面出力におけるハードウェアの違い を吸収するため,特定の文字コードが特殊な画面操作を行うように定義 されたエスケープシーケンスを使っていました. 場合によっては画面制御に使うエスケープシーケンスの方言を吸収する ために,さらに上位の抽象化されたコードを使う場合もありました. 例えば TurboPascal 3.0 のエディタでは非常に多くのハードウェアの差を 吸収するための機能があり,当時の日本で販売されていた種々のDOSマシン でも若干のコツが必要でしたが,英語版でも内蔵のエディタで日本語の テキストを使うことができました.その後,特定のハードウェアのシェアが 圧倒的になるにつれて, その特定のハードウェアの構成を前提とした ソフトウェアしか存在できなくなりました.その当時,特定のハードウェア の構成を前提とすることで,メモリに文字を書き込めば,即画面に反映する ことができました.そして画面上の好きな位置に好きな文字を高速に表示 することができるビデオメモリへの直接アクセスのメリットは,マイナーな 機種までもサポートするメリットより重要となってしまいました.
最近になって,ネットワークを経由して端末を制御する機会が増えて, ビデオメモリへの直接アクセスに比べて速度的に劣るシリアル端末を想定し た昔の方法が再び重要になってきました.ネットワーク経由でも動作する スクリーンエディタ (Linuxでは当たり前) を作成する時には面倒でも エスケープシーケンスを利用することになりますが,通信速度が高速化 されてきたため,画面更新の速度はエスケープシーケンスを使った場合でも 問題になりません.
とにかく,画面を思い通りに操るエスケープシーケンスを考えるのも 面白いものです.