Top

第6章 poiOSの機能拡張例

ここまでOSっぽいものを作る事を目指してUEFIファームウェアの機能の呼び出し方を説明してきました。題材として作ってきたpoiOSはまだまだ不完全なものなので、ぜひご自身で機能拡張を行ってみてください(あるいはフルスクラッチで作ってみてください)。

ここでは機能拡張の例を紹介します。この章で説明する全ての拡張を行ったサンプルを"sample_poios"のディレクトリに格納しています。

6.1 画像を表示してみる

フレームバッファのアドレスとピクセルフォーマットが分かっているので、フレームバッファへ書き込むことで画像を表示することもできます。

blt関数を追加

まず、指定されたバッファに格納されているピクセルデータをフレームバッファへ書き込む関数"blt(BLock Transfer)"をgraphics.cへ追加します(リスト6.1)。

リスト6.1: sample_poios/graphics.c(blt関数)

 1: void blt(unsigned char img[], unsigned int img_width, unsigned int img_height)
 2: {
 3:     unsigned char *fb;
 4:     unsigned int i, j, k, vr, hr, ofs = 0;
 5: 
 6:     fb = (unsigned char *)GOP->Mode->FrameBufferBase;
 7:     vr = GOP->Mode->Info->VerticalResolution;
 8:     hr = GOP->Mode->Info->HorizontalResolution;
 9: 
10:     for (i = 0; i < vr; i++) {
11:             if (i >= img_height)
12:                     break;
13:             for (j = 0; j < hr; j++) {
14:                     if (j >= img_width) {
15:                             fb += (hr - img_width) * 4;
16:                             break;
17:                     }
18:                     for (k = 0; k < 4; k++)
19:                             *fb++ = img[ofs++];
20:             }
21:     }
22: }

リスト6.1は単に引数imgのバイト列をピクセルデータとしてフレームバッファへ書き込んでいるだけです。なお、画像は常に原点(0,0)から描画されます。

シェルへ画像閲覧コマンド追加(view)

次に、blt関数を使用して画像ファイルを画面へ描画するコマンド"view"をshell.cへ追加します(リスト6.2)。なお、「画像ファイル」はUEFIに対応したピクセルフォーマットの生バイナリとします。

リスト6.2: sample_poios/shell.c

 1: /* ・・・省略・・・ */
 2: #define MAX_COMMAND_LEN     100
 3: 
 4: #define MAX_IMG_BUF 4194304 /* 4MB */       /* 追加 */
 5: 
 6: unsigned char img_buf[MAX_IMG_BUF]; /* 追加 */
 7: /* ・・・省略・・・ */
 8: void view(unsigned short *img_name)
 9: {
10:     unsigned long long buf_size = MAX_IMG_BUF;
11:     unsigned long long status;
12:     struct EFI_FILE_PROTOCOL *root;
13:     struct EFI_FILE_PROTOCOL *file;
14:     unsigned int vr = GOP->Mode->Info->VerticalResolution;
15:     unsigned int hr = GOP->Mode->Info->HorizontalResolution;
16: 
17:     status = SFSP->OpenVolume(SFSP, &root);
18:     assert(status, L"error: SFSP->OpenVolume");
19: 
20:     status = root->Open(root, &file, img_name, EFI_FILE_MODE_READ,
21:                         EFI_FILE_READ_ONLY);
22:     assert(status, L"error: root->Open");
23: 
24:     status = file->Read(file, &buf_size, (void *)img_buf);
25:     if (check_warn_error(status, L"warning:file->Read"))
26:             blt(img_buf, hr, vr);
27: 
28:     while (getc() != SC_ESC);
29: 
30:     status = file->Close(file);
31:     status = root->Close(root);
32: }
33: 
34: void shell(void)
35: {
36:     /* ・・・省略・・・ */
37: 
38:     while (!is_exit) {
39:             /* ・・・省略・・・ */
40:             else if (!strcmp(L"view", com)) {     /* 追加 */
41:                     view(L"img"); /* 追加 */
42:                     ST->ConOut->ClearScreen(ST->ConOut);   /* 追加 */
43:             } else
44:                     puts(L"Command not found.\r\n");
45:     }
46: }

リスト6.2について、viewコマンド実行時、"img"というファイル名を指定してview関数を実行します。view関数は引数で指定されたファイル名のファイルを開き、読み出したバイナリデータをバッファ"img_buf"へ格納しblt関数へ渡します。その後、ESCキー押下まで待機し、ESCキーが押下されるとview関数を抜けます。cat関数やedit関数もそうですが、shellの各コマンドの関数は「画面を散らかしたまま返す」が仕様なので、view関数も画面をクリアせずに呼び出し元へ戻ります。そのため、shell関数側でview関数実行後、ClearScreen関数を呼び出しています。

BGRA32ビットへの画像変換方法

BGRA32ビット(各8ビット)の生の画像データ(ヘッダ等が無いバイナリ)への変換は、ImageMagickのconvertコマンドで行えます。コマンド例は以下の通りです。

$ convert hoge.png -depth 8 yux.bgra

なお、現状のviewコマンドは画像のスクロールやリサイズは行うことができません。そのため、筆者は、自身のPCのUEFIファームウェアがデフォルトで認識している解像度(Width=640px)に合わせるように画像をリサイズしています。コマンド例は以下の通りです。

$ convert hoge.png -resize 640x -depth 8 yux.bgra

GUIモードへ画像ファイル表示機能追加

最後に、gui.cを拡張し、画像ファイルをクリックするとview関数を使用して画面へ画像を描画するようにします(リスト6.3)。なお、poiOSにおいては「"i"で始まるファイル名のファイル」を画像ファイルとすることにします。

リスト6.3: sample_poios/gui.c(gui関数内)

 1: /* ファイルアイコン処理ループ */
 2: for (idx = 0; idx < file_num; idx++) {
 3:     if (is_in_rect(px, py, file_list[idx].rect)) {
 4:             /* ・・・省略・・・ */
 5:             if (prev_lb && !s.LeftButton) {
 6:                     if (file_list[idx].name[0] != L'i')     /* 追加 */
 7:                             cat_gui(file_list[idx].name);
 8:                     else    /* 追加 */
 9:                             view(file_list[idx].name);      /* 追加 */
10:                     file_num = ls_gui();
11:             }
12:             /* ・・・省略・・・ */
13:     }
14: }

リスト6.3に示す通り、gui.cの変更箇所はgui関数内の"ファイルアイコン処理ループ"内だけです。左クリック時にcat_gui関数を呼び出していたところを、ファイル名が"i"で始まる場合にはview関数を呼び出すようにしています。

blt関数はUEFIの仕様に存在します

実は仕様上、EFI_GRAPHICS_OUTPUT_PROTOCOL内にBltという関数が存在します(仕様書"11.9.1 Blt Buffer(P.474)")。UEFIのBlt関数はバッファのデータをフレームバッファへ書き込む(EfiBltBufferToVideo)だけでなく、フレームバッファのデータをバッファへ格納する(EfiBltVideoToBltBuffer)、フレームバッファのある矩形領域内のデータを別の座標へコピーする(EfiBltVideoToVideo)等ができます。

ただし、筆者が動作確認に使用しているLenovo ThinkPad E450(UEFIバージョン2.3.1)ではこの機能を使うことができず*1、今回は自前で実装しました。なお、QEMU(OVMF、UEFIバージョン2.3.1)では動作しました。

[*1] ステータスはSuccessを返すが、画面には何も描画されていないという状況でした。

6.2 GUIモードとシェルの終了機能を追加する

シェル終了機能追加

exitコマンドで終了できるようにしてみます。

変更箇所はshell.cのshell関数内だけです(リスト6.4)。

リスト6.4: sample_poios/shell.c(shell関数へexit機能追加)

 1: void shell(void)
 2: {
 3:     unsigned short com[MAX_COMMAND_LEN];
 4:     struct RECT r = {10, 10, 100, 200};
 5:     unsigned char is_exit = FALSE;  /* 追加 */
 6: 
 7:     while (!is_exit) {      /* 変更 */
 8:             /* ・・・省略・・・ */
 9:             } else if (!strcmp(L"exit", com))     /* 追加 */
10:                     is_exit = TRUE; /* 追加 */
11:             else
12:                     puts(L"Command not found.\r\n");
13:     }
14: }

GUIモード終了機能追加

右上に[X]ボタンを配置し、このボタンをクリックすることでGUIモードを終了できるようにしてみます。

変更するソースコードはgui.cのみです。まず、[X]ボタンを配置するput_exit_button関数と、マウスカーソルに対して再描画やクリック判定を行うupdate_exit_button関数を追加します(リスト6.5)。

リスト6.5: sample_poios/gui.c(put_exit_buttonとupdate_exit_button)

 1: /* ・・・省略・・・ */
 2: #define EXIT_BUTTON_WIDTH   20
 3: #define EXIT_BUTTON_HEIGHT  20
 4: /* ・・・省略・・・ */
 5: struct FILE rect_exit_button;
 6: /* ・・・省略・・・ */
 7: void put_exit_button(void)
 8: {
 9:     unsigned int hr = GOP->Mode->Info->HorizontalResolution;
10:     unsigned int x;
11: 
12:     rect_exit_button.rect.x = hr - EXIT_BUTTON_WIDTH;
13:     rect_exit_button.rect.y = 0;
14:     rect_exit_button.rect.w = EXIT_BUTTON_WIDTH;
15:     rect_exit_button.rect.h = EXIT_BUTTON_HEIGHT;
16:     rect_exit_button.is_highlight = FALSE;
17:     draw_rect(rect_exit_button.rect, white);
18: 
19:     /* EXITボタン内の各ピクセルを走査(バッテンを描く) */
20:     for (x = 3; x < rect_exit_button.rect.w - 3; x++) {
21:             draw_pixel(x + rect_exit_button.rect.x, x, white);
22:             draw_pixel(x + rect_exit_button.rect.x,
23:                        rect_exit_button.rect.w - x, white);
24:     }
25: }
26: 
27: unsigned char update_exit_button(int px, int py, unsigned char is_clicked)
28: {
29:     unsigned char is_exit = FALSE;
30: 
31:     if (is_in_rect(px, py, rect_exit_button.rect)) {
32:             if (!rect_exit_button.is_highlight) {
33:                     draw_rect(rect_exit_button.rect, yellow);
34:                     rect_exit_button.is_highlight = TRUE;
35:             }
36:             if (is_clicked)
37:                     is_exit = TRUE;
38:     } else {
39:             if (rect_exit_button.is_highlight) {
40:                     draw_rect(rect_exit_button.rect, white);
41:                     rect_exit_button.is_highlight = FALSE;
42:             }
43:     }
44: 
45:     return is_exit;
46: }

リスト6.5について、put_exit_button関数では[X]ボタンを画面に描画すると同時に、グローバル変数"struct FILE rect_exit_button"へボタンの座標・大きさ・ハイライト状態を格納しています。また、update_exit_button関数は、ハイライト状態を更新するのに加え、ボタンクリック判定結果をTRUE/FALSEで返します。

次に、これらの関数を使用するようにgui関数へ機能追加を行います(リスト6.6)。

リスト6.6: sample_poios/gui.c(gui関数へ終了機能追加)

 1: void gui(void)
 2: {
 3:     /* ・・・省略・・・ */
 4:     unsigned char is_exit = FALSE;  /* 追加 */
 5: 
 6:     SPP->Reset(SPP, FALSE);
 7:     file_num = ls_gui();
 8:     put_exit_button();      /* 追加 */
 9: 
10:     while (!is_exit) {      /* 変更 */
11:             ST->BootServices->WaitForEvent(1, &(SPP->WaitForInput), &waitidx);
12:             status = SPP->GetState(SPP, &s);
13:             if (!status) {
14:                     /* ・・・省略・・・ */
15: 
16:                     /* ファイルアイコン処理ループ */
17:                     for (idx = 0; idx < file_num; idx++) {
18:                             if (is_in_rect(px, py, file_list[idx].rect)) {
19:                                     /* ・・・省略・・・ */
20:                                     if (prev_lb && !s.LeftButton) {
21:                                             /* ・・・省略・・・ */
22:                                             file_num = ls_gui();
23:                                             put_exit_button();  /* 追加 */
24:                                     }
25:                                     if (prev_rb && !s.RightButton) {
26:                                             edit(file_list[idx].name);
27:                                             file_num = ls_gui();
28:                                             put_exit_button();  /* 追加 */
29:                                             executed_rb = TRUE;
30:                                     }
31:                             } else {
32:                                     /* ・・・省略・・・ */
33:                             }
34:                     }
35: 
36:                     /* ファイル新規作成・編集 */
37:                     if ((prev_rb && !s.RightButton) && !executed_rb) {
38:                             /* アイコン外を右クリックした場合 */
39:                             dialogue_get_filename(file_num);
40:                             edit(file_list[file_num].name);
41:                             ST->ConOut->ClearScreen(ST->ConOut);
42:                             file_num = ls_gui();
43:                             put_exit_button();      /* 追加 */
44:                     }
45: 
46:                     /* 追加(ここから) */
47:                     /* 終了ボタン更新 */
48:                     is_exit = update_exit_button(px, py,
49:                                                  prev_lb && !s.LeftButton);
50:                     /* 追加(ここまで) */
51: 
52:                     /* ・・・省略・・・ */
53:             }
54:     }
55: }

リスト6.6では、最初の画面描画時とcatコマンド実行時等の画面再描画時にput_exit_button関数を実行するようにしています。また、マウス処理のループの最後に"終了ボタン更新"の処理を追加し、update_exit_buttonの戻り値に応じてgui関数のメインループを抜けるようにしています。

6.3 マウスを少し大きくする

1pxのマウスカーソルは、使えなくは無いのですが、あまりにも小さいので、少し大きく4x4pxにしてみます。

変更箇所はgui.cのみです(リスト6.7)。

リスト6.7: sample_poios/gui.c(マウスカーソルを大きくする)

 1: #define CURSOR_WIDTH        4       /* 追加 */
 2: #define CURSOR_HEIGHT       4       /* 追加 */
 3: /* ・・・省略・・・ */
 4: /* 追加(ここから) */
 5: struct EFI_GRAPHICS_OUTPUT_BLT_PIXEL cursor_tmp[CURSOR_HEIGHT][CURSOR_WIDTH] =
 6: {
 7:     {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
 8:     {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
 9:     {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
10:     {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}
11: };
12: int cursor_old_x;
13: int cursor_old_y;
14: /* 追加(ここまで) */
15: /* ・・・省略・・・ */
16: void draw_cursor(int x, int y)
17: {
18:     /* 変更(ここから) */
19:     int i, j;
20:     for (i = 0; i < CURSOR_HEIGHT; i++)
21:             for (j = 0; j < CURSOR_WIDTH; j++)
22:                     if ((i * j) < CURSOR_WIDTH)
23:                             draw_pixel(x + j, y + i, white);
24:     /* 変更(ここまで) */
25: }
26: 
27: void save_cursor_area(int x, int y)
28: {
29:     /* 変更(ここから) */
30:     int i, j;
31:     for (i = 0; i < CURSOR_HEIGHT; i++) {
32:             for (j = 0; j < CURSOR_WIDTH; j++) {
33:                     if ((i * j) < CURSOR_WIDTH) {
34:                             cursor_tmp[i][j] = get_pixel(x + j, y + i);
35:                             cursor_tmp[i][j].Reserved = 0xff;
36:                     }
37:             }
38:     }
39:     /* 変更(ここまで) */
40: }
41: 
42: void load_cursor_area(int x, int y)
43: {
44:     /* 変更(ここから) */
45:     int i, j;
46:     for (i = 0; i < CURSOR_HEIGHT; i++)
47:             for (j = 0; j < CURSOR_WIDTH; j++)
48:                     if ((i * j) < CURSOR_WIDTH)
49:                             draw_pixel(x + j, y + i, cursor_tmp[i][j]);
50:     /* 変更(ここまで) */
51: }
52: 
53: void put_cursor(int x, int y)
54: {
55:     if (cursor_tmp[0][0].Reserved)  /* 変更 */
56:             load_cursor_area(cursor_old_x, cursor_old_y);
57: 
58:     save_cursor_area(x, y);
59: 
60:     draw_cursor(x, y);
61: 
62:     cursor_old_x = x;
63:     cursor_old_y = y;
64: }
65: /* ・・・省略・・・ */

リスト6.7については、これまでの関数の枠組みは変わらず、各関数の処理の規模が少し大きくなっただけです。なお、マウスカーソルの形は、"(x * y) < CURSOR_WIDTH"が真となる座標(x,y)にのみドットを置く事で、"┏"を描いています。

6.4 機能拡張版poiOS実行の様子

機能拡張後のpoiOSを画面写真で紹介します(図6.1図6.2図6.3図6.4)。

GUIモードの状態

図6.1: GUIモードの状態

画像ファイルをクリックすると、画像を表示できます

図6.2: 画像ファイルをクリックすると、画像を表示できます

[X]ボタンクリックでGUIモード終了し、シェルモードへ戻ります

図6.3: [X]ボタンクリックでGUIモード終了し、シェルモードへ戻ります

exitコマンド実行でシェルモードとpoiOS終了し、ブートメニュー画面へ

図6.4: exitコマンド実行でシェルモードとpoiOS終了し、ブートメニュー画面へ


Top