ここまでOSっぽいものを作る事を目指してUEFIファームウェアの機能の呼び出し方を説明してきました。題材として作ってきたpoiOSはまだまだ不完全なものなので、ぜひご自身で機能拡張を行ってみてください(あるいはフルスクラッチで作ってみてください)。
ここでは機能拡張の例を紹介します。この章で説明する全ての拡張を行ったサンプルを"sample_poios"のディレクトリに格納しています。
フレームバッファのアドレスとピクセルフォーマットが分かっているので、フレームバッファへ書き込むことで画像を表示することもできます。
まず、指定されたバッファに格納されているピクセルデータをフレームバッファへ書き込む関数"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)から描画されます。
次に、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ビット(各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.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関数を呼び出すようにしています。
実は仕様上、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を返すが、画面には何も描画されていないという状況でした。
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: }
右上に[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関数のメインループを抜けるようにしています。
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)にのみドットを置く事で、"┏"を描いています。
機能拡張後のpoiOSを画面写真で紹介します(図6.1、図6.2、図6.3、図6.4)。
図6.1: GUIモードの状態
図6.2: 画像ファイルをクリックすると、画像を表示できます
図6.3: [X]ボタンクリックでGUIモード終了し、シェルモードへ戻ります
図6.4: exitコマンド実行でシェルモードとpoiOS終了し、ブートメニュー画面へ