gfxbootのテーマの自作

まずgfxbootのテーマの作成とは、 プログラミングであると認識することが必要である。 また、ややこしいのにドキュメントが少ない。 かなりの根性がいるので要注意。

gfxbootパッケージの導入

openSuse辺りのパッケージのソースを入手し、 再度バイナリのパッケージをつくる。 ただ、そのままだとエラーになるので、 コンパイルオプションをちょっと変えておく。(Vine-4.2用) (gfxboot-4.0.14-option.patch , gfxboot.spec)

rpm -ivh gfxboot-4.0.14-4.1.src.rpm
mv gfxboot-4.0.14-option.patch ~/rpm/SOURCES/
rpm -bb gfxboot.spec

あとは導入

rpm -ivh ~/rpm/RPM/i386/gfxboot-4.0.14-4.1tpu0.i386.rpm ~/rpm/RPM/i386/gfxboot-devel-4.0.14-4.1tpu0.i386.rpm

デザイン

なにか作る前に、全体のイメージを明らかにしておくことが必要。 gfxbootの場合、画面内の何処に何を置くのか、 そのためのコードはどの様に分割するのかを決めておく。

画面配置

取りあえず最低限の要素として、「背景」「メニュー」「タイマ」を 準備することにする。アニメーションは今回は使わない。 全体タイトルや操作説明は背景に書込むことにする。 細かな操作が必要なら、素のgrubの側でやれば良い。

位置関係は以下の様にしてみる。

大体の配置

文字を書く場合のフォントは、メニュー用とタイマ用の2種類とする。

コード配置その他

コードは分割して次のようなファイルに配置することにする。

共通コード

型の名前、色の名前、キーコード、 画面設定関数等のよく使う名前や関数等を、 "common.inc"ファイルにまとめて書いておく。

背景

gimp等で640x480の画像を作成し、 タイトルと説明、メニュー枠を描き、JPEGで保存する。 ファイル名はここでは"back.jpg"とする。
背景画像サンプル (取りあえずこんな感じ)

取りあえずテスト

気軽にブートローダをテストできる環境 があるのなら、ここまでのところをテストできる。

まず、boot.configに以下の記述をする。

%% include common.inc
%% include init.inc

次に、init.incに以下の記述をする。 (common.incは既にあるものとする)

640 480 32 findmode setmode pop
0 0 moveto
"back.jpg" findfile setimage 0 0 image.size image

コンパイルする。

gfxboot-compile -O -c boot.config init

アーカイブして、gfxbootのテーマにする。

(echo init; echo back.jpg) |cpio -o > bootlogo

取りあえず、 試してみて背景画像が見えたらOK. どれかキーを押すか、タイムアウトで普通のgrubの画面に戻るはず。

ポイント: 画像ファイルを直接描くときは、findfileしてsetimageすると、 「今の画像」となるので、movetoで画面の適当の場所に移動してから、 imageで描画する。

メニュー

メニューでは、選択肢の表示とキー入力の処理が必要である。

選択肢の文字列などは、最初にMenuInit関数に飛んでくるので、 ここで保存等の処理をする。

キー入力はKeyEvent関数で処理することになるので、 この関数からメニュー処理の関数を呼ぶことにする。 (時間切れも0のキーのKeyEventとなることに注意)

MenuInit

menu.incにつぎのように書く.

やることは、 メニューの情報ををもつ3つのデータをスタックから取り出して、 使えるように覚えておくMenuInitと言う関数を定義することと、 覚えておいたメニューの情報を使うため、 その他の適当な関数を定義することである。

% MenuInit
% ( menu_entries_array cmdline_args_array defaultentry ) -> ( )
/MenuInit {
  /menu.default exch def
  /menu.args exch def
  /menu.txts exch def

  /menu.font font.normal def
  /menu.color gray def
  /menu.hicolor white def
  /menu.xoff 210 def
  /menu.yoff 170 def
  /menu.width 400 def
  /menu.dy    36 def
  /menu.current 0 def
  0 1 menu.txts length 1 sub {
    dup menu.txts exch get menu.default eq {
      /menu.current exch def
    } { pop } ifelse
  } for 

  menu.font setfont
  0 1 menu.txts length 1 sub {
    menu.txts exch get
    { 
      dup strsize pop menu.width le { exit } if
      dup dup length dup 3 le { pop pop exit } if
      over over 1 sub 0 put
      over over 2 sub '.' put
      over over 3 sub '.' put
      over over 4 sub '.' put
      pop pop
    } loop
    pop
  } for

  /menu.backs menu.txts length array def
  0 1 menu.txts length 1 sub {
    menu.xoff over menu.dy mul menu.yoff add moveto
    menu.width menu.dy savescreen
    menu.backs 3 1 roll put
  } for

  menu.show
} def

% () -> ()
/menu.show {
  menu.font setfont
  0 1 menu.txts length 1 sub {
      menu.xoff over menu.dy mul menu.yoff add moveto
      menu.backs over get restorescreen
      dup menu.current eq {
        menu.hicolor setcolor
      } {
        menu.color setcolor
      } ifelse
      menu.xoff over menu.dy mul menu.yoff add moveto
      menu.txts exch get show
  } for
} def

% () -> ()
/menu.next {
  menu.current menu.txts length 1 sub lt {
    /menu.current menu.current 1 add def
  } if
  menu.show
} def
% () -> ()
/menu.prev {
  menu.current 0 gt {
    /menu.current menu.current 1 sub def
  } if
  menu.show
} def

"menu.show"と言った名前の中の'.'に文字であるという以上の意味はない。 ただ、このような命名規則にすることで、 他のファイルで利用する辞書項目と混ざりにくくなる。

KeyEvent

keyevent.incにつぎのように書く。

% KeyEvent : input event handling
% ( key ) -> ( input_buffer menu_entry action )
% key
%  bit 0-7      ascii
%  bit 8-15     scan code
%  bit 16-32    status bits (ctrl, shift...)
% action
%    0:         ok, stay in input loop
%    1:         switch to text mode
%  >=2:         start linux
%
/KeyEvent {
  dup 0 eq { keyevent.selected return } if

  dup 0xff and /keyevent.key exch def
  dup 8 shr 0xff and /keyevent.keycode exch def
  16 shr 0xffff and /keyevent.keystat exch def

  keyevent.keycode KC_UP eq { menu.prev } if
  keyevent.keycode KC_DOWN eq { menu.next } if
  keyevent.key KEY_ESC eq { "" -1 1 return } if
  keyevent.key KEY_ENTER eq { keyevent.selected return } if

  "" -1 0 return
} def

%  keyevent.selected : 
% () -> (str int int)
/keyevent.selected {
  menu.current 0 lt menu.current menu.txts length ge or {
    "" -1 1 return 
  } if
  menu.args menu.current get menu.current 2 return
} def

KeyEvent関数は、1つの情報をスタックから取り出した後、 適切な3つの情報をスタックに積んでからリターンする必要があることに注意。

フォントの生成

メニューの文字を書くためのフォントをあらかじめ作成する必要がある。

今回は以下のようにして、VineのVL Pゴシックからフォントを生成する。

gfxboot-font -l 36 -p /usr/X11R6/lib/X11/fonts/TrueType -f VL-PGothic-Regular:size=32 normal.fnt

もし日本語等を使う場合には、フォントの作成時に アルファベットも含めて使う文字をオプションで指定する必要があるが、 今回はアルファベットのみなので特に指定しない。

また取りあえずテスト

ここまででもう一度テストする。

boot.configに以下の記述。

%% include common.inc

%% include menu.inc
%% include keyevent.inc

%% include init.inc

init.incに以下の記述。

% 640x480 32bpp mode 
640 480 32 findmode setmode pop

% set background image
"back.jpg" findfile setimage 0 0 image.size image

% font
/font.normal "normal.fnt" findfile def

コンパイル

gfxboot-compile -O -c boot.config init

アーカイブしてテーマファイル("bootlogo")の作成

(echo init; echo back.jpg; echo normal.fnt;) | cpio -o > bootlogo

これを使ってブートして、メニューの項目が見え、 上下の矢印で移動、Enterで決定できればOK。

ポイント: 文字を書きたいときは、setfontとsetcolorしてから、 movetoで書きたい位置に移動し、文字列をshowする。
ポイント: 使用するフォントはあらかじめ専用のコマンドで用意する必要がある。 複数のフォントが欲しければ、それぞれ用意する。
ポイント: KeyEvent関数はとっても重要。 同様のコールバック関数は全部で11個あるが、 これとMenuInit, Timeoutだけ扱えれば、見た目は整う。

タイマ

タイマーは、タイムアウトまでの時間を表示するためのものである。 Timeoutに定期的に飛んでくるだけであるので、この関数を記述すれば良い。

今回は残り時間を文字としてだけでなく、絵としても表示することにする。 ただ、あまり画像が大きいとgfxbootが扱える範囲を超えてしまうようなので、 画像を4分割して部品として使うことで大きさを節約している。

% Timeout :  Boot timeout counter.
% ( timeout time ) -> ( )
% timeout: total time in 18.2Hz steps, time: current value.
/Timeout {
  timeout.sec .undef eq { timeout.init } if

  /timeout.sec over 10 mul 150 add 182 div def
  /timeout.step over 8 mul 3 index 4 mul 6 div add 3 index div
    dup 8 gt { pop 8 } if def
  
  timeout.sec timeout.lastsec ne timeout.step timeout.laststep ne or {
    timeout.update
    /timeout.lastsec timeout.sec def
    /timeout.laststep timeout.step def
  } if
} def

% () -> ()
/timeout.init {
  /timeout.lastsec -1 def
  /timeout.laststep -1 def
  /timeout.xoff 20 def
  /timeout.yoff 200 def
  /timeout.image "timer.jpg" findfile def
  /timeout.alpha "timer-b.jpg" findfile def
  /timeout.width 160 def
  /timeout.height 160 def

  /timeout.strbuf 12 string def
  /timeout.font font.normal def
  /timeout.color gray def
  /timeout.strxoff 50 def
  /timeout.stryoff 270 def

  /timeout.stepimages 12 array def
  timeout.image setimage 
  0 1 11 {
    dup 6 mod timeout.width 2 div mul
    over 6 div timeout.height 2 div mul
    timeout.width 2 div timeout.height 2 div 
    timeout.image setimage unpackimage
    exch
    dup 6 mod timeout.width 2 div mul
    over 6 div timeout.height 2 div mul
    timeout.width 2 div timeout.height 2 div 
    timeout.alpha setimage unpackimage
    exch

    dup 2 mod timeout.width 2 div mul timeout.xoff add
    over 6 div timeout.height 2 div mul timeout.yoff add moveto 
    timeout.stepimages over
    timeout.width 2 div timeout.height 2 div savescreen put
    0 0 moveto 
    timeout.stepimages exch get 
    2 index exch 2 index exch
    blend
    free free
  } for
} def

% () -> ()
/timeout.update {
  timeout.step 8 ge {  1 1 0 timeout.drawquad } if
  timeout.step 7 eq {  3 1 0 timeout.drawquad } if
  timeout.step 6 le {  5 1 0 timeout.drawquad } if
  timeout.step 6 ge {  7 1 1 timeout.drawquad } if
  timeout.step 5 eq {  9 1 1 timeout.drawquad } if
  timeout.step 4 le { 11 1 1 timeout.drawquad } if
  timeout.step 4 ge {  6 0 1 timeout.drawquad } if
  timeout.step 3 eq {  8 0 1 timeout.drawquad } if
  timeout.step 2 le { 10 0 1 timeout.drawquad } if
  timeout.step 2 ge {  0 0 0 timeout.drawquad } if
  timeout.step 1 eq {  2 0 0 timeout.drawquad } if
  timeout.step 0 le {  4 0 0 timeout.drawquad } if

  timeout.font setfont
  timeout.color setcolor
  timeout.sec "%4ds" timeout.strbuf sprintf
  timeout.strxoff timeout.stryoff moveto
  timeout.strbuf show
} def

% (img_index x_index y_index) -> ()
/timeout.drawquad {
    exch timeout.width 2 div mul timeout.xoff add
    exch timeout.height 2 div mul timeout.yoff add
    moveto
    timeout.stepimages exch get restorescreen
} def

これが描画する画像("timer.jpg")。
タイマー画像

これが不透明率を設定する画像("timer-b.jpg")。
タイマー領域画像

またまた取りあえずテスト

boot.configに以下の記述

%% include common.inc

%% include menu.inc
%% include keyevent.inc
%% include timeout.inc

%% include init.inc

コンパイル

gfxboot-compile -O -c boot.config init

アーカイブしてテーマファイルの作成

( echo init ; echo back.jpg ; echo normal.fnt ; echo timer.jpg ; echo timer-b.jpg ) | cpio -o > bootlogo

ブート画面こんな感じになるはず

取りあえずこれでブートセレクタとして最低限のものは揃ったと思う。

ポイント: 画像を切り抜いて描画したいときは、 切抜用の画像も用意する。 その上で、「今の画像」からは unpackimageで、 画面からはsavescreenで取り出してからblendで混ぜて、 restorescreenで画面に描画する。 unpackimageもsavescreenも得られたバッファを使わなくなったら、 freeが必要なことに注意。

作業の自動化

あらかじめMakefileを作っておくと、 ファイルの中身を変えたときに"make"と打つだけで テーマのアーカイブまで作ってくれる。

MKBOOTMSG=gfxboot-compile
MKBLFONT=gfxboot-font

BOOTLOGO=bootlogo

IMG_FILES=back.jpg timer.jpg timer-b.jpg
INC_FILES=common.inc init.inc keyevent.inc menu.inc timeout.inc
FILES=init $(IMG_FILES) normal.fnt

all: $(FILES)
	echo $(FILES) | sed -e "s/ /\n/g" | cpio -o > $(BOOTLOGO)

init: boot.config $(INC_FILES)
	$(MKBOOTMSG) -O -l log -c boot.config init

normal.fnt:
	$(MKBLFONT) -v -l 36 \
		-p /usr/X11R6/lib/X11/fonts/TrueType \
		-f VL-PGothic-Regular:size=32 \
		normal.fnt > normal.fnt.log

small.fnt:
	$(MKBLFONT) -v -l 24 \
		-p /usr/X11R6/lib/X11/fonts/TrueType \
		-f VL-PGothic-Regular:size=23 \
		small.fnt > small.fnt.log

clean:
	rm *.log *.fnt init $(BOOTLOGO) || true

makeはタブとスペースを区別するので注意.

(2008/11/1)