Stimulator

機械学習とか好きな技術話とかエンジニア的な話とかを書く

Rustでterminalのwindow sizeを取得する

はじめに

RustでCLIツールを作る際にターミナル等のwindow size変更に応じて表示を変更したい場合がある。

その際に画面サイズの取得について調査したログ。

 

 

画面サイズの取得

画面サイズの取得には、libcのioctlを利用し、TIOCGWINSZをシステムコールする。
Cargo.tomlにはlibcを追記する。

[dependencies]
libc = "0.2"

 
ioctlにはファイルディスクリプタを渡す必要があり、libc::STDOUT_FILENOが使えるが、アプリケーション側にSIGWINCHが上手く送られない場合(CLIツール等を作成している場合)に、Terminalを制御するウィンドウのサイズ変更に動的に対応できない。そのため"/dev/tty"を読む方法も用意する。

use libc::{ioctl, winsize, TIOCGWINSZ, STDOUT_FILENO};
use std::{mem, fs::File, os::unix::io::IntoRawFd};
use std::{thread, time};


pub fn terminal_size() -> Option<winsize> {
    // STDOUT_FILENOか/dev/ttyを利用する
    let fd = if let Ok(file) = File::open("/dev/tty"){
        file.into_raw_fd()
    }else {
        STDOUT_FILENO
    };

    // ファイルディスクリプタに対してTIOCGWINSZをシステムコール
    let mut ws: winsize = unsafe { mem::zeroed() };
    if unsafe { ioctl(fd, TIOCGWINSZ, &mut ws) } == -1 {
        None
    } else {
        Some(ws)
    }
}

// 1秒毎に画面サイズを表示
fn main(){
    loop{
        match terminal_size(){
            Some(ws) => println!("h:{},w:{}", ws.ws_row, ws.ws_col),
            None => println!("failure")
        }
        thread::sleep(time::Duration::from_millis(1000));
    }
}

 
実際の実行例は以下のようになる。

f:id:vaaaaaanquish:20200323000859g:plain
vim terminal上での実行例


 

調査ログ

2020/03/23に調査したログ。

terminalサイズの取得を行うライブラリを調べると以下が出てくる。

大体実装は同じだが、全てSTDOUT_FILENOを使っている。
クロスプラットフォーム観点でWindows対応の参考になる。

rustのターミナルだと以下が出てくる

NixSignalやlibcを使って画面サイズが変化したSignal(SIGWINCH)をキャッチして画面サイズ変更に対応している。そういう方法もあるか。

 
最終的に参考にしたのは以下のクロスプラットフォームターミナルの実装。
github.com

 
以下のブログやissueも読んだ。
hermanradtke.com
github.com