Skip to content

Latest commit

 

History

History
257 lines (184 loc) · 13.9 KB

File metadata and controls

257 lines (184 loc) · 13.9 KB

打鍵ログ・ビジュアライザ設計

この文書は TODO6.md を起点とした設計検討と、その後の実装済み仕様の記録である。実装済みの内容を「現在の仕様」、未実装の案を「将来」として区別する。

目的

MacTcode の入力操作を JSON Lines(JSONL)形式で記録し、ログからキーボードの打鍵履歴を可視化するアニメーションを生成する。

対象は次の二つである。

  1. MacTcode IME に組み込むキーロガー
  2. JSONL を読み、アニメーションを出力する Ruby 製 visualizer

キーボードの打鍵表示、未確定キー列に対する次打鍵候補、最近確定した文字列の表示を行う。

ロガーのライフサイクル

確定事項

  • メニューから記録を開始・停止する。
  • ロガーの有効/無効状態は IME プロセスのメモリ内だけで保持する。
  • IME プロセスの再起動後、ロガーは停止状態とする。設定ファイルへ状態を保存・復元しない。
  • 設定リロード時は、記録中であってもロガーを停止する。
  • 停止、設定リロード、アプリ終了時には、保留中の出力をフラッシュしてファイルを閉じる。
  • ログ書き込みに失敗しても通常の IME 入力は継続し、ロガーだけを停止する。
  • 記録中であることはメニューのチェック状態などで判別可能にする。

保存先とファイル名

  • 設定ディレクトリ配下の logs/ サブディレクトリへ保存する。
  • セッションごとに開始時刻を含む新規ファイルを作る。
  • 例: keystrokes-20260919-123456.jsonl
  • 同じ秒に複数のセッションを開始した場合は、既存ログを上書きせず keystrokes-20260919-123456-1.jsonl のように連番を付ける。
  • 初版では自動削除を行わない。将来、保存期間・件数・合計容量による削除設定を追加できるようにする。

固定ファイル名の上書きではなくセッション単位の新規作成とする。これにより、記録を失わず、複数セッションの比較や動画生成対象の選択が可能になる。

プライバシー

  • excludedApps に該当するアプリケーション上での入力は記録しない。
  • excluded アプリケーションのアプリ名、ウィンドウタイトル、キー入力、確定文字列をログへ書き込まない。
  • ログには入力内容が含まれるため、記録中であること、保存場所、ログを共有する際の注意をドキュメントに明記する。

JSONL 形式

1行を1イベントとする JSONL を使う。先頭にはセッションのメタデータを置き、以後に時系列イベントを記録する。

共通項目

各イベントは少なくとも次を持つ。

項目 説明
schemaVersion ログ形式のバージョン。初版は 1。
sequence セッション内で単調増加する連番。同一ミリ秒のイベント順を保証する。
timestamp 人が確認できる壁時計時刻(ISO 8601、ミリ秒・タイムゾーン付き)。
elapsedMilliseconds セッション開始からの経過ミリ秒。再生時刻の基準。
type イベント種別。

現在のセッション開始イベントにはアプリケーション版を記録する。visualizer は再生に Swift の変換ロジックや辞書を再実装しないことを原則とする。

記録するイベント

初版で次のイベントを記録する。

イベント 用途
keyInput 生のキー入力。キーボードの打鍵表示に使用する。
modeChanged T-Code、全角、半角カナ、行、部首合成、交ぜ書きなどのモード遷移を表す。
pendingChanged 未確定キー列と、次打鍵候補の変化を表す。
textCommitted 文字列の挿入・確定を表す。
textDeleted 後方削除など、文字列の削除を表す。
textReplaced 既存文字列を別の文字列へ置換したことを表す。
passthrough 直前の keyInput を IME が処理せず入力先へ渡したことを表す。
pendingKakuteiCancel PendingKakutei を取り消したことを表し、Cancel キーを強調する。
sessionStarted / sessionStopped 記録セッションの開始・終了を表す。

パススルーイベントも記録対象とする。現在は reason(directMode、modeForward、unhandled など)で、IME が入力を処理しなかった理由を記録する。直前の keyInput と組み合わせて再生するため、visualizer は passthrough 自体にキー情報を持たせない。

pendingChanged と nextChars

pendingChanged は次の項目を持つ。

項目 説明
keys pending 中のキーの文字列表現。
count pending のキー数。
nextChars 次の基本キーに対応する候補文字列の配列。

基本キーはファンクションキー以外の40個のキーを指す。nextChars は基本キーと同じ順序の40要素配列であり、各要素は次の基本キーを押したときに確定する文字列である。出力が未定義、アクション、または次のキーマップへ進む場合は空文字列とする。

{
  "type": "pendingChanged",
  "keys": "f",
  "count": 1,
  "nextChars": ["", "あ", "", "…"]
}

pending が空の場合は nextChars: [] を記録する。これは基本キー上の候補表示を消す指示である。

生キーと確定テキストを分離する理由

keyInput は物理的・論理的な打鍵表示のため、textCommitted、textDeleted、textReplaced は最近入力した文字列を再生するために使用する。キー列から Ruby 側で IME の変換を再実装して確定文字列を推測してはならない。

テキスト変更イベント

textCommitted

新しい文字列を確定・挿入した場合に用いる。

{
  "type": "textCommitted",
  "text": "あ",
  "source": "tcode"
}

textDeleted

Backspace などで確定済み文字列を削除した場合に用いる。削除対象の文字列を取得できる経路では text に記録する。取得できない経路では text を省略でき、その場合 visualizer は表示用バッファの末尾1文字を削除する。

{
  "type": "textDeleted",
  "text": "あ",
  "source": "backspace"
}

textReplaced

変換確定、変換確定の取消し、その他の置換を表す。削除イベントと確定イベントの連続へ分解せず、必ず1イベントで表す。

{
  "type": "textReplaced",
  "replacedText": "かんじ",
  "text": "漢字",
  "source": "mazegaki"
}

PendingKakutei の取消しは逆向きの置換として記録する。

{
  "type": "textReplaced",
  "replacedText": "漢字",
  "text": "かんじ",
  "source": "pendingKakuteiCancel"
}

PendingKakutei とテキスト記録

PendingKakuteiMode は、すべての確定文字列を通過する共通状態ではない。

  • 交ぜ書きは MRU 学習が有効な場合に PendingKakutei を作る。
  • 部首合成は自動学習が有効な場合に PendingKakutei を作る。
  • 通常の T-Code 入力、学習が無効な交ぜ書き・部首合成、全角、半角カナ、行、ダイレクトモード、自己挿入などは PendingKakutei を作らず、直接テキストを操作しうる。

PendingKakutei がある変換では、保持している変換前の yomiString と変換後の kakuteiString を用いて textReplaced を正確に作れる。また取消し時も同じ情報で逆方向の textReplaced を作れる。

ただし、テキスト変更ログ全体を PendingKakutei のみで記録してはならない。実装では次の二層に分ける。

  1. Client / ContextClient など、実際に挿入・削除・置換を要求する境界で、汎用的なテキスト変更を記録する。
  2. 部首合成、交ぜ書き、PendingKakutei は、source や置換前後の文字列を補足して変換の意味を記録する。

これにより、通常入力を取りこぼさず、変換操作についても意味のある再生ができる。

visualizer

実装と言語

  • Ruby スクリプトとして実装する。
  • ログ読み込み・状態再生、フレーム描画、エンコードを分離する。
  • 各フレームは SVG として描画し、エンコーダ固有の API へ描画ロジックを密結合させない。
  • タイトルには MacTcode 打鍵ログ と、sessionStarted.timestamp から得る録画開始時刻を YYYY-mm-dd HH:MM:SS 形式で表示する。
  • モード表示は Swift のクラス名をそのまま表示せず、名前空間と末尾の Mode を除いた小文字の名前を表示する。例: MacTcode.ZenkakuMode は zenkaku。

出力形式と依存コマンド

  • --type は gif または mp4 を受け付け、既定値は mp4。
  • GIF の生成には ImageMagick の magick コマンドを使用する。
  • MP4 は一時 GIF を経由して ffmpeg -y -i <gif> <mp4> で生成する。
  • MP4 出力時に ffmpeg がなければ、起動直後に Homebrew のインストール例を表示して終了する。
  • --output に拡張子がなければ、--type に応じて .gif または .mp4 を付加する。
  • MP4 用の中間 GIF は一時ディレクトリにのみ作成し、出力先には残さない。
  • SVG フレームはそれぞれ静止 GIF へ変換する。この変換は8ワーカーで並列に実行し、Converting frames: n/total の進捗を表示する。
  • 個別フレームの変換失敗はすべて収集し、1件でも失敗した場合は動画 GIF の合成・MP4 変換・最終出力の更新を行わない。
  • すべての静止 GIF の変換成功後、順番に並べて1回の magick コマンドで動画 GIF へ合成する。
形式 位置付け
GIF --type gif で出力する共有・確認用形式。
MP4 既定の出力形式。長時間ログに適する。
WebP 未実装の将来候補。

フレームとイベントの適用

  • フレーム時刻は 0, 1000 / fps, 2 * 1000 / fps, ... ミリ秒とする。
  • 各フレームでは、その時刻以下のイベントを sequence 順にすべて適用する。
  • 同一フレームに複数の打鍵がある場合も、各キーの強調終了時刻を個別に管理する。
  • 動画内の時刻表示は経過時間を秒単位で表示する。ミリ秒はログ内だけに保持する。

キーの赤色強調

打鍵後にキーを赤く表示する期間は、ログ形式ではなく visualizer のコマンドラインオプションで決める。

ruby scripts/visualize_keystrokes.rb INPUT.jsonl --fps 30 --key-highlight-frames 9
  • --fps の初期値は 30。
  • --key-highlight-frames の初期値は 9(30 FPS で 0.3 秒)。
  • 強調時間は key-highlight-frames / fps 秒となる。

描画内容と物理キー位置

  • 4行・40個の基本キーと、Sym、Space、Delete、Enter、Cancel のファンクションキーを描画する。Escape キーは描画しない。
  • 基本キーのキートップには通常のキーラベルを印字しない。ファンクションキーにはラベルを表示する。
  • [, ], -, =, ' はレイアウトへ含めない。
  • 基本キーの位置は KEYCODES 定数で定義した macOS virtual key code と対応付ける。ハイライト位置は入力文字列ではなく keyInput.keyCode から決めるため、Dvorak などの入力配列でも物理位置が正しい。
  • 打鍵キーは赤く強調する。
  • 経過時間、モード、pending を表示する。pendingChanged.keys の末尾がバックスラッシュの場合は Sym キーを強調する。
  • 最近入力した文字列はキーボード上方に表示し、Ruby の each_char 単位で末尾40文字までを維持する。

次打鍵候補とハイライト中の文字

  • pendingChanged.nextChars が40要素なら、各基本キーに対応する文字列を薄い色で描画する。
  • nextChars が空配列、存在しない、または40要素でない場合は、通常の候補文字をすべて消す。
  • 1打鍵目のキーが候補文字を持つ場合、そのキーが赤く強調されている間も候補文字は薄い色のまま表示する。
  • 2打鍵目では、押したキーに表示済みの候補文字をハイライト期間中保持する。直後に空の nextChars が届いても消さない。
  • printable な passthrough の場合は、基本キーのハイライト期間中に直前の keyInput.text を表示する。
  • ハイライト中に保持する文字は赤い背景上で見やすくするため、白・太字・通常候補より大きい文字で表示する。
  • pendingKakuteiCancel では Cancel キーを強調する。

最近入力した文字列

visualizer は textCommitted、textDeleted、textReplaced を順に適用して表示用のテキストバッファを維持する。

  • 表示は最大40文字とする。
  • 現在の visualizer では Ruby の each_char 単位で保持・切り詰める。
  • カーソル移動、範囲選択、任意位置への編集は初版の対象外とする。
  • 変換中の文字列ではなく、確定したテキストだけを表示対象とする。

今後の詳細設計

  • keyInput.modifiers の配列順序・将来追加する修飾キー名
  • passthrough の reason の列挙値を公開仕様として固定すること
  • セッション開始イベントに設定識別情報や辞書版を追加するか
  • WebP 出力の追加方法

これらは、本書のイベント種別とライフサイクルの確定事項を変更せずに決定する。