ここまでOSっぽいものを作る事を目指してUEFIファームウェアの機能の呼び出し方を説明してきました。題材として作ってきたpoiOSはまだまだ不完全なものなので、ぜひご自身で機能拡張を行ってみてください(あるいはフルスクラッチで作ってみてください)。
ここでは機能拡張の例を紹介します。この章で説明する全ての拡張を行ったサンプルを"sample_poios"のディレクトリに格納しています。
フレームバッファのアドレスとピクセルフォーマットが分かっているので、フレームバッファへ書き込むことで画像を表示することもできます。
まず、指定されたバッファに格納されているピクセルデータをフレームバッファへ書き込む関数"blt(BLock Transfer)"をgraphics.cへ追加します(リスト6.1)。
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に対応したピクセルフォーマットの生バイナリとします。
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"で始まるファイル名のファイル」を画像ファイルとすることにします。
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)。
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)。
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)。
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)。
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)。