本文へスキップ
Fumiet

ブログ一覧へ

Kindle Paperwhite 2を、自分の新聞端末に作り替えた

English で読む

脱獄したKindle Paperwhite 2の純正UIをCで置き換え、LANサーバーで作ったテキスト版を端末側で組む新聞リーダーを作りました。Javaフレームワークを止めたまま電源ボタンを生かすために、powerdを逆アセンブルした話も書きます。

khomeのホーム画面、紙面、記事、設定画面

手元のKindle Paperwhite 2を、純正UIを捨てて自分のホーム画面が開く端末に作り替えました。紙面はNewspaperをタップすると、最後に取得した版が開きます。6インチのE-Ink、758×1024、16階調。本を読むには小さく、けれど「紙」としては、いま手元にある画面の中でいちばん新聞に近い。

khome は、純正UIをCの小さなランチャーに置き換えるプロジェクトです。紙面はLAN内のサーバーが作り、端末はそれを検証してキャッシュし、文字と罫線は端末側で組みます。コードはMITで公開しています。

何が変わるか

これまで khome
起動後 純正のLibrary khomeのホーム(時計・Apps・Favorites)
ニュース ブラウザで各サイトを巡回 端末に落ちた版を読む
オフライン 読めない 最後に取得した版が読める
純正に戻る 常にそこにいる /mnt/us/khome/disable を作って再起動

ホーム画面には時計、日付、通算日(NO. 296 / 365)、Apps、Favoritesが並びます。表示名はまだ仮のFOLIO、紙面の題字はKindle Timesです。

khomeのホーム画面

なぜ自分で描くのか

E-Inkで新聞を読みたいだけなら、既存のビューアで足りそうに見えます。ところが2段組の紙面となると、選択肢がありません。

  • 純正のKF8/AZW3はcolumn-countを無視する。2段組はScribeのKFX(横向き)くらい
  • KOReader(crengine)にも段組みのモデルがなく、EPUBのcolumn-countは効かない
  • 2段組を扱うビューアはSupernoteやAndroid向けで、少なくともこのKindleでは動かない

つまり紙面のレイアウトは自分で持つしかありませんでした。ならばページ画像を配るのではなく、テキストを端末に送って、端末側で組む。題字も罫線も見出しもページ番号も、Cで描いています。

どう動くか

サーバー側は、公開フィードを取得し、記事の本文を抽出し、チェックサム付きのtext-v1バンドルを作ります。RSSは目次であって本文ではないので、本文はHTMLから取り出します。

  • index.tsv — 記事1行ずつ(ID、ジャンル、配信元、公開時刻、文字数、見出し、リード)
  • articles/<id>.txt — 本文
  • manifest.json / SHA256SUMS / latest-text.json — 同一性とハッシュ

端末側はfetch.shでlatest-text.jsonを取り、SHA256SUMSとmanifest.jsonを検証します。バンドル全体が正しいときだけcurrentをpreviousに回すので、取得に失敗してもキャッシュ済みの版は無傷です。khomeは通信より先にキャッシュを描くため、最後に取った版はオフラインでも読めます。

今日、実機でNewspaperを開いたときは、その場で取得が走って20261026-1802 / 30 stories / 7 pages、終了コード0で版が入れ替わりました。端末側にタイマーはなく、取得はNewspaperを開いたときか、KUALのRefreshを実行したときです。版の生成はサーバーのcronが07:00と18:00に回します。

Kindle Timesの一面

山場1: 純正UIを止めると、電源ボタンが死ぬ

khomeはXと周辺の純正サービスを生かしたまま、Javaフレームワーク(cvm)を止めて動きます。cvmは1分ごとにステータスバーの時計を描き続けるので、画面をkhomeが持っている間は止める必要がありました。

ところがcvmを止めると、電源ボタンの入力が捨てられます。スリープに入れない。ここが最初の壁でした。

powerdを逆アセンブルすると、判定は1箇所に集約されていました。com.lab126.kafのframeworkStartedが取れて非0であること、そしてcom.lab126.blanketのloadにsplashが含まれないこと。どちらかが失敗すると、powerdは「スプラッシュ中」と判断して電源ボタンを捨てます。cvmが無いということは、まさにこの状態です。

そこでcom.lab126.kafを名乗り、frameworkStarted=1を返す小さな代役を書きました(fakekaf)。必要に応じてblanketのsplashを外し、FrameworkStartedイベントを送ります。実機では、cvmを止めたままfakekafを常駐させて電源ボタンを3回往復させ、3回ともスリープと復帰が効きました。深いサスペンドからの復帰だけは未確認です。

逆アセンブルのアドレスや根拠の命令は、docs/spikes/fakekaf.mdに全部残しています。アドレスと条件は実機のバイナリから読みました。設定値の一部は推定で、そこは資料に明記しています。

山場2: cvm無しでブラウザを全画面にする

同じことがブラウザにも起きます。cvmが無いとcom.lab126.chromebarも存在せず、pillowのtitleBar(41px)とsearchBar(75px)のウィンドウが作られません。結果としてブラウザは758×1024+0+0で開きます(このときawesomeのtopOffsetが0になっている、と推定されています)。config.xmlは触っていません。

もうひとつは起動スプラッシュです。blanket unload splashで外すだけで、白い画面の上にブラウザがそのまま出ます。

向きの切り替えはもっと厄介でした。winmgrのorientationはこのファームウェアでは読み取り専用で、orientationLockを書いても_O:の付かない窓は回りません。ブラウザの向きはブラウザ自身のlocalStorage、つまりSQLiteのItemTableにあるsettingsが持っています。

なので、ホームのトグルから次の順で書き換えます。

  1. ブラウザが動いていれば止める(WebKitが設定をメモリに持つため)
  2. ItemTableのsettingsを読み、hex()で現行値を控える
  3. json_setで$.orientationだけをlandscapeLeftかautoに差し替える
  4. 読み戻して意図と違えば、控えたhexをそのまま書き戻す

横向きの間はシートを出さず、画面の端からのプルで直接ホームに戻る。縦に戻ったことはフレームバッファの寸法で確認してから描く。この辺りは、失敗した試験と戻し方までdocs/adr/0001-...に残っています。

紙面をCで組む

描画は758×1024の8ビットキャンバスです。文字はstb_truetype、罫線は矩形と線、アイコンは事前に作ったPNG。描いたら/dev/fb0に転送し、FBInkにGC16かGL16のリフレッシュを頼みます。

16階調しかないので、グレーは2段まで。影、ぼかし、グラデーション、半透明は使いません。毎分変わる時計だけは、1つの矩形を部分リフレッシュします。E-Inkは全画面更新で閃くので、動くものを小さく閉じ込めるのがコツでした。

デザインは先にHTMLで3案作り、Cで再現できる範囲まで削ってから実装しました。その代わり、テストで描画を縛っています。

  • デザインモックとの差が1.2%以内(最大の差分塊が1,200px以下)
  • 実機ゴールデンとはピクセル一致
  • 出力は8ビット、16階調(0, 17, …, 255)のみ

意図しない描画変更はここで検出されます。

記事の本文表示

数字

部分 行数・枚数
端末の描画・入力(C、ヘッダ込み) 15,076行
端末のテスト(C / Python / shell) 11,439行
scripts/ 全体(抽出・配信・開発ツール) 3,976行
リポジトリ内のPNG(ゴールデン・モック・アイコン) 78枚

端末側にPythonは入れていません。取得も抽出もサーバーの仕事で、端末は描いて、タップを読んで、lipcで電源とWi-Fiを扱うだけ。この分担にしたことで、端末側にPythonもネットワーク処理も置かずに済んでいます。

まとめ

6インチのE-Inkは、通知も動画も要らない「読むためだけの画面」です。新聞にはちょうどいい。けれど純正UIを置き換えると、電源ボタンやブラウザのように、思わぬところでつまずきます。そのひとつひとつを実機で確認して潰したのが、このプロジェクトの本体でした。

いまはスリープから起こすと、khomeのホームがすぐ出ます。Newspaperをタップして紙面をめくると、見出しから本文が開く。それだけの端末ですが、毎朝の最初の数分は、たしかに気持ちがいい。

なお、この記事のスクリーンショットはテスト用の架空の記事(台風の号)を描画したものです。実機に入っている版は実際のニュースなので、そのままは載せていません。Wi-Fi名もHOME-5Gという架空の表記に差し替えてあります。

脱獄とシステムUIの置き換えは、端末を文鎮化させる可能性があります。復旧できる見込みのない端末では試さないでください。USBかSSHで/mnt/us/khome/disableを置いて再起動すれば、純正のLibraryに戻せます。