「30日でできる!OS自作入門」をRustで。24日目

Posted on August 5, 2019 , Tags: OS自作入門, OS, Rust

「30日でできる!OS自作入門 」のC言語の部分をできるだけRustですすめてみる。今回は24日目の内容。

ウィンドウの切り替え

ウィンドウが増えてきたので、切り替えたときに重ねあわせも切り替えられるようにする。

F11で切り替え

まずは、F11でコンソールとtask_aを切り替えられるようにする。

GIFだと少し動きが見にくいが、以下のように切り替えができるようになる。

F11での切り替え

マウスクリックでの切り替え

次にマウスクリックで切り替えができるようにする。
これまでtask_aウィンドウを移動するロジックを書いていたところを書きかえる。
これにより一時的にtask_aウィンドウは動かなくなる。

これでマウスクリックで切り替えられるようになった。見た目は上と変らないので省略する。

ウィンドウの移動

上記で一時的にウィンドウの移動ができなくなったので、できるようにする。

moving でウィンドウ移動モードかどうかを切り替え、ウィンドウ移動モードならマウスの移動分移動するようにした。

実行結果

例によってマウスの動作がPeekだとうまくいかないのでGIFではなくスクリーンショットを貼る。
無事にウィンドウの切り替えと移動がマウス操作でできるようになった。

ウィンドウの切り替えと移動

ウィンドウのクローズ

アプリケーションのウィンドウを「×」ボタンのクリックで閉じることができるようにする。

実行結果

以下の通り、×ボタンクリックでアプリケーションのウィンドウを閉じることができるようになった。

×ボタンクリック前

×ボタンクリック後

入力ウィンドウをアプリのウィンドウも含めて切り替え

入力ウィンドウ(ウィンドウの上部が青くなり、カーソル表示があるものではカーソルが点滅する)をアプリケーションで起動したウィンドウも含めて切り替えられるようにする。

まず、Sheetにフラグを追加する。

これを使ってlib.rsに処理を追加する。

// lib.rs
#[no_mangle]
#[start]
pub extern "C" fn hrmain() {
    // 省略
    let mut active_window: usize = shi_win;
    {
        let mut sheet_console = &mut sheet_manager.sheets_data[shi_console];
        sheet_console.task_index = console_task_index;
        sheet_console.cursor = true;
    }
    {
        let mut sheet_win = &mut sheet_manager.sheets_data[shi_win];
        sheet_win.cursor = true;
    }
    // 省略
    loop {
        // 省略
        if fifo.status() != 0 {
            // 省略
            let i = fifo.get().unwrap();
            sti();
            let active_sheet = sheet_manager.sheets_data[active_window];
            if active_sheet.flag == SheetFlag::AVAILABLE {
                // ウィンドウが閉じられたら一番上のウィンドウを入力ウィンドウにする
                if let Some(zmax) = sheet_manager.z_max {
                    active_window = sheet_manager.sheets[zmax - 1];
                    cursor_c = window_on(
                        sheet_manager,
                        task_manager,
                        active_window,
                        shi_win,
                        cursor_c,
                    );
                }
            }
            // 省略
                // Enterキー
                if key == 0x1c {
                    if active_window != shi_win { // active_windowをshi_winと比較するように変更
                        let ctask = task_manager.tasks_data[console_task_index];
                        let fifo = unsafe { &*(ctask.fifo_addr as *const Fifo) };
                        fifo.put(CONSOLE_ENTER + KEYBOARD_OFFSET).unwrap();
                    }
                }
            // 省略
                // タブ
                if key == 0x0f {
                    let sheet_win = sheet_manager.sheets_data[shi_win];
                    let sheet_console = sheet_manager.sheets_data[shi_console];
                    // 現在入力中のものをOFFにして、ひとつ下のものを入力中にする
                    cursor_c = window_off(
                        sheet_manager,
                        task_manager,
                        active_window,
                        shi_win,
                        cursor_c,
                        cursor_x as i32,
                    );
                    let mut j = active_sheet.z.unwrap() - 1;
                    if j == 0 && sheet_manager.z_max.is_some() && sheet_manager.z_max.unwrap() > 0 {
                        j = sheet_manager.z_max.unwrap() - 1;
                    }
                    active_window = sheet_manager.sheets[j];
                    cursor_c = window_on(
                        sheet_manager,
                        task_manager,
                        active_window,
                        shi_win,
                        cursor_c,
                    );
                }

window_off, window_on は、window.rsというファイルを作り、そちらに追加した。

// window.rs
pub fn window_on(
    sheet_manager: &mut SheetManager,
    task_manager: &TaskManager,
    sheet_index: usize,
    shi_win: usize,
    cursor_c: Color,
) -> Color {
    let sheet = sheet_manager.sheets_data[sheet_index];
    let mut cursor_c = cursor_c;
    toggle_title_color(sheet.buf_addr, sheet.width as usize, true);
    sheet_manager.refresh(sheet_index, 3, 3, sheet.width, 21);
    if sheet_index == shi_win {
        cursor_c = Color::Black;
        {
            let mut sheet_win = &mut sheet_manager.sheets_data[sheet_index];
            sheet_win.cursor = true;
        }
    } else {
        if sheet.cursor {
            let task = task_manager.tasks_data[sheet.task_index];
            let fifo = unsafe { &mut *(task.fifo_addr as *mut Fifo) };
            fifo.put(CONSOLE_CURSOR_ON).unwrap();
        }
    }
    cursor_c
}

pub fn window_off(
    sheet_manager: &mut SheetManager,
    task_manager: &TaskManager,
    sheet_index: usize,
    shi_win: usize,
    cursor_c: Color,
    cursor_x: i32,
) -> Color {
    let sheet_win = sheet_manager.sheets_data[sheet_index];
    let sheet = sheet_manager.sheets_data[sheet_index];
    let mut cursor_c = cursor_c;
    toggle_title_color(sheet.buf_addr, sheet.width as usize, false);
    sheet_manager.refresh(sheet_index, 3, 3, sheet.width, 21);
    if sheet_index == shi_win {
        cursor_c = Color::White;
        {
            let mut sheet_win = &mut sheet_manager.sheets_data[sheet_index];
            sheet_win.cursor = false;
        }
        boxfill(
            sheet_win.buf_addr,
            sheet_win.width as isize,
            Color::White,
            cursor_x as isize,
            28,
            cursor_x as isize + 7,
            43,
        );
    } else {
        if sheet.cursor {
            let task = task_manager.tasks_data[sheet.task_index];
            let fifo = unsafe { &mut *(task.fifo_addr as *mut Fifo) };
            fifo.put(CONSOLE_CURSOR_OFF).unwrap();
        }
    }
    cursor_c
}

またウィンドウを強制クローズするときに、アプリから起動したウィンドウかどうかを確認するようにした。

実行結果

以下の通り、タブでの入力ウィンドウの切り替えがアプリケーションウィンドウ含めてできるようになった。

入力ウィンドウ切り替え

マウスでの入力切り替え

次に、先ほどの入力切り替えをマウスでもできるようにする

マウスクリックでウィンドウを入れ替えていたところに少し追加するだけでできる。

実行結果

以下の通り、マウスクリックでウィンドウの切り替えができるようになった。

マウスでの入力ウィンドウ切り替え

マウスでの入力ウィンドウ切り替え

タイマーの実装

OSのタイマを使ってタイマー機能を実装する。

まず、タイマまわりのシステムコールを作る。

timerの関数をそのままラップしたようなシステムコールになっている。
これを使って、アプリケーションを実装する。

今後アプリケーションがどんどん増えていきそうなので、appsというディレクトリを作り、そちらにコピーしていく方式に変更した。

今回はapps/timer/src/lib.rsがメインのRustファイルになる。

// apps/timer/src/lib.rs

use core::fmt;
use core::panic::PanicInfo;

extern "C" {
    fn _api_initmalloc();
    fn _api_malloc(size: usize) -> usize;
    fn _api_free(addr: usize, size: usize);
    fn _api_linewin(sheet_index: usize, x0: i32, y0: i32, x1: i32, y1: i32, color: i32);
    fn _api_inittimer(timer_index: usize, data: i32);
    fn _api_settimer(timer_index: usize, timer: i32);
    fn _api_boxfilwin(sheet_index: usize, x0: i32, y0: i32, x1: i32, y1: i32, color: i32);
    fn _api_putstrwin(
        sheet_index: usize,
        x: i32,
        y: i32,
        color: i32,
        len: usize,
        string_addr: usize,
    );
}

const SHEET_UNREFRESH_OFFSET: usize = 256;

struct TimerMessage {
    pub message: [u8; 12],
    pub ptr: usize,
}

#[no_mangle]
#[start]
pub extern "C" fn hrmain() {
    use core::fmt::Write;
    unsafe {
        _api_initmalloc();
    }
    let buf_addr = unsafe { _api_malloc(150 * 50) };
    let sheet_index = open_window(buf_addr, 150, 50, -1, b"timer".as_ptr() as usize) as usize;
    let timer_index = alloc_timer();
    unsafe {
        _api_inittimer(timer_index.clone(), 128);
    }
    let mut h = 0;
    let mut m = 0;
    let mut s = 0;
    let mut timer_message = &mut TimerMessage {
        message: [0; 12],
        ptr: 0,
    };
    loop {
        write!(timer_message, "{:>5}:{:>02}:{:>02}", h, m, s).unwrap();
        unsafe {
            _api_boxfilwin(sheet_index, 28, 28, 115, 41, 7 /* 白 */);
            _api_putstrwin(
                sheet_index,
                28,
                27,
                0, /* 黒 */
                11,
                timer_message.message.as_ptr() as usize,
            );
            _api_settimer(timer_index, 100);
        }
        if get_key(1) != 128 {
            break;
        }
        s += 1;
        if s == 60 {
            s = 0;
            m += 1;
            if m == 60 {
                m = 0;
                h += 1;
            }
        }
        timer_message.ptr = 0;
    }
    end()
}

impl fmt::Write for TimerMessage {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let str_bytes = s.as_bytes();
        for i in 0..str_bytes.len() {
            if self.ptr > 11 {
                break;
            }
            self.message[self.ptr] = str_bytes[i];
            self.ptr += 1;
        }
        Ok(())
    }
}

インラインアセンブリはハマリどころが多く、今回Cの部分をRustで書きたいだけなので、今後はアセンブリ言語でのコードはできるだけ本の内容をそのままasmfunc.asmに書いて参照するようにする。

また、write!を使用するためにstructを定義してfmt::Writeを実装した。

このままでも一応動くが、タイマの後処理(終了後のアプリケーションにひもづいているタイマはキャンセルする処理)の必要がある。

タイマ用のstructにfrom_appというアプリから起動したかどうかをもたせる。

TimerManagercancelおよびcancel_allという関数を実装する。

このcancel_allをアプリの終了時に呼ぶ。

これでアプリ終了時にタイマもキャンセルされるようになった。

実行結果

以下の通りタイマーの実行および終了が問題なくできることが確認できた。

タイマー

24日目は以上となる。ここまでの内容のコードはyoshitsugu/hariboteos_in_rustのday24としてタグを打ってある。