KOReaderプラグイン開発
KOReaderのフロントエンドは、小さなウィジェットツールキット上で動作するプレーンなLua(LuaJIT)です。プラグインは <name>.koplugin/ という名前のディレクトリであり、main.lua(コード)と _meta.lua(表示情報)を含みます。ビルドステップは不要です。そのため、正しいプラグインを迅速に作成するには、以下に示す実績のある規約に従い、APIを推測する代わりにバンドルされたスクリプトで検証するのが最善の方法です。
公式ドキュメント:APIリファレンス(ソースから自動生成)およびKOReaderソースコード(plugins/ ディレクトリに約40の実プラグインがあります。plugins/hello.koplugin が最小構成です)。シグネチャが不明な場合は、モジュールのドキュメントページを確認するか、ソースを読んでください。KOReaderのAPIはリリース間で変更されるため、ドキュメントサイトが公式情報として優先されます。
ワークフロー
- スキャフォールディング(新規プラグイン):
python3 <skill>/scripts/new_plugin.py <id> "<フルネーム>" [--dir <保存先>]コマンドで、メニューエントリ、Dispatcherアクション、設定の永続化、ライフサイクルフックがあらかじめ組み込まれたテンプレートから<id>.koplugin/ディレクトリを作成します。ゼロから書き始めるのではなく、これを編集して開発を進めてください。 - 実装:このファイルに記載されているパターンを使用します。リファレンスファイルは必要なときだけ開いてください。
references/plugin-anatomy.md— ファイル構成、ローダールール、is_doc_only、初期化順序、メニューのsorting_hint値references/ui-widgets.md— InfoMessage、ConfirmBox、InputDialog、MultiInputDialog、Menu、ButtonDialog、SpinWidget、Notification の動作サンプル付きreferences/events-and-lifecycle.md—onXxxハンドラ、ブロードキャスト、スケジューリング、Dispatcher、リーダーとファイルマネージャーのコンテキストreferences/settings-and-storage.md— LuaSettings、G_reader_settings、ドキュメント単位の設定、DataStorage のパスreferences/testing-and-verification.md— エミュレータ(./kodev run)、luacheck、busted テスト、実機へのインストールとログの読み取りreferences/pitfalls.md— KOReaderをクラッシュさせたり、何も実行されない原因となるよくあるミス。実装を完了する前に一読してください。
- 検証(動作確認の前に必ず実行):
python3 <skill>/scripts/validate_plugin.py <path/to/x.koplugin>コマンドを実行します。このスクリプトは、プラグインの構造、_meta.lua、メニューの配線、gettextの使用、LuaJITの構文(luajit -bl)、および(利用可能な場合)luacheck をチェックします。すべてのERRORを修正し、すべてのWARNを確認してください。その後、何が検証され、何が検証されなかったかを正直に報告してください。バリデーターは実行時の動作を証明できません。動作確認にはエミュレータまたは実機が必要です(テストのリファレンスを参照)。
最小限のプラグイン
-- _meta.lua
local _ = require("gettext")
return {
fullname = _("My Plugin"),
description = _([[プラグイン管理に表示される1文。]]),
}
-- main.lua
local Dispatcher = require("dispatcher")
local InfoMessage = require("ui/widget/infomessage")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local _ = require("gettext")
local MyPlugin = WidgetContainer:extend{
name = "myplugin", -- 一意のID、ディレクトリプレフィックスと一致
is_doc_only = false, -- true = 本が開かれているときのみロード
}
function MyPlugin:onDispatcherRegisterActions()
Dispatcher:registerAction("myplugin_hello", {
category = "none", event = "MyPluginHello",
title = _("My Plugin: hello"), general = true,
})
end
function MyPlugin:init()
self:onDispatcherRegisterActions()
self.ui.menu:registerToMainMenu(self)
end
function MyPlugin:addToMainMenu(menu_items)
menu_items.myplugin = {
text = _("My Plugin"),
sorting_hint = "more_tools",
callback = function() self:onMyPluginHello() end,
}
end
function MyPlugin:onMyPluginHello()
UIManager:show(InfoMessage:new{ text = _("Hello from my plugin") })
return true -- イベントを消費
end
return MyPlugin
重要なルール(とその理由)
- ユーザーに表示されるすべての文字列を
_()で囲む — KOReaderはgettextを通じて翻訳されます。囲まれていない文字列はローカライズされません。文字列の補間にはT = require("ffi/util").templateを使用してください(T(_("ページ %1"), n)のように。..による連結は避けてください)。言語によって語順が異なるためです。 - イベントは
on<イベント名>という名前のメソッドです。イベントを消費した場合のみtrueを返してください。そうしないと、他のウィジェット(KOReaderのコア部分を含む)がそのイベントを受信できなくなります。 - UIスレッドをブロックしないでください。 ネットワーク呼び出し、大規模なファイルスキャン、
os.executeはシングルスレッドの電子インクデバイスをフリーズさせます。長時間の処理にはNetworkMgr:runWhenOnline、UIManager:scheduleIn、Trapper:wrap(サブプロセス+進捗表示)を使用してください。 - データの永続化には
LuaSettingsを使用し、意図的にフラッシュしてください。 設定ファイルはDataStorage:getSettingsDir()に保存されます。変更後(またはonFlushSettings内で)必ず:flush()を呼び出してください。呼び出さないと、クラッシュやバッテリー切れでデータが失われます。 - クリーンアップ:
onCloseWidget/onCloseDocument内で、タイマー(UIManager:unschedule(fn))や開いたウィジェットを必ず解放してください。スケジュールされたコールバックがリークすると、デバイスが頻繁に起動し、バッテリーを消耗します。 - 電子インク対応UI:
InfoMessageにtimeoutを設定して使用し、アニメーションは避け、リフレッシュは最小限にしてください(変更があったウィジェットにのみUIManager:setDirty(widget, "ui")を呼び出します)。 loggerを使用する(require("logger")し、logger.dbg/info/warn/errを使用)。printは使わないでください。loggerの出力はエミュレータでは標準出力に、実機ではcrash.logに出力されます。- オプショナルな依存関係はガードする(
self.ui.statistics、self.ui.document、Device:hasFrontlight()など)。同じプラグインがファイルマネージャーや、その機能を持たないデバイスで読み込まれる可能性があります。
報告
作成または変更した内容、実行した検証コマンドとその結果、そして未検証の項目(例:「エミュレータで実行していません」)を明記してください。バリデーターまたはluacheckが利用できなかった場合は、問題なくパスしたように見せかけず、その旨を報告してください。