Waydroidで英語のときだけテンキーが効かない原因を、AndroidとWaydroidのソースから調べた

LinuxでAndroidを動かすWaydroidの中で、テンキーの数字が打てなくなった。キーボードをGboardの「日本語」にすると打てて、「English (US)」にすると打てない。上段の数字キーは、どちらでも打てる。ホスト(Arch Linux、sway)でテンキーを打つと、数字が出る。 swayの設定に input * xkb_numlock enabled を足したが、直らなかった。なぜ効かないのかを、ソースと実測で調べた。 先に結論 ホストのNumLockの状態は、Waydroidの中のAndroidに渡らない。swayの設定は、Waydroidには効かない。 AndroidのNumLockは、Waydroidの中で、入った直後に消されている可能性が高い。ソースの読みと実測が合っているが、消す判定の値そのものは、見ていない。 Androidのアプリで、テンキーを数字に割り当てるキーレイアウトを足したら、英語のままテンキーが打てた。 ソースで分かったこと 読んだのは、WaydroidのHWC(android_hardware_waydroidのhwcomposer/wayland-hwc.cpp)と、Android 13(android13-release)の入力まわり、Waydroidが当てるパッチ(android_vendor_waydroid)。 1. ホストのキーは、キーコードのまま渡される。修飾キーの状態は渡されない。 HWCは、Waydroidのウィンドウに入ったキー入力を、キーコードの形で、Android側のパイプ(/dev/input/wl_keyboard_events)に書く。Waylandの修飾キーの状態を受け取る関数 keyboard_handle_modifiers は、中身が空になっている。ホストのNumLockがオンでも、Androidはそれを知らない。 2. テンキーの数字は、AndroidのNumLockがオンのときだけ出る。 Android 13の Generic.kcm では、NUMPAD_1 は、base: fallback MOVE_END、numlock: '1' と定義されている。NumLockがオフなら、Endキーになる。 3. NumLockの状態は、キーを離したときに切り替わる。 Keyboard.cpp の toggleLockedMetaState は、キーを押したときは何もせず、離したときに、状態を反転する。 4. Waydroidが作る入力機器は、出せるキーを2つしか登録していない。 WaydroidのEventHubのパッチ(Android 13用の 0004-EventHub-Add-wayland-inputs-support.patch)は、wayland_keyboard の keyBitmask に、BTN_MISC と KEY_OK だけを登録する。 AndroidのEventHubの markSupportedKeyCodes は、キーレイアウトにそのキーがあっても、この登録に無いキーは、「機器に無いキー」と判定する。KeyboardInputMapper::updateLedState は、NumLockのキーが無い機器について、NumLockの状態を消す。 NumLock(コード69)は、この登録に入っていない。 実測 dumpsys input で、入力機器の状態を取った。キー入力の履歴を含まない部分だけを、次のように取り出せる。 sudo waydroid shell dumpsys input | sed -n '/Input Reader State/,/Input Dispatcher State/p' > out-1.txt Copy wayland_keyboard の項目の、MetaState(修飾キーとロックの状態)と、DownTime(最後にキーが押された時刻)を、見た。 ...

暗号化されたWaydroidのキャッシュを諦めて、PSNの非公開APIから友人とのチャット履歴を救出した話

古い友人とのPlayStation Network(PSN)上でのチャットを、消えてしまう前に手元に残しておきたくなった。使っているのはPCの中の[Waydroid]で動くPS App。まずはローカルのキャッシュファイルを直接読みに行こうとしたのだが、そこから思いのほか長い調査になった。 ローカルのキャッシュは暗号化されていた Waydroidのアプリごとのデータは~/.local/share/waydroid/data/data/<パッケージ名>/以下にAndroidの/dataパーティションと同じ構造で保存されている。PS Appのフォルダを覗くと、databases/の中にそれらしいファイルが並んでいた。 fileコマンドで確認すると「OpenPGP Public Key」という判定が出て面食らったが、実際には単に先頭バイトが平文のSQLiteの磁気文字列(SQLite format 3)ではなかっただけだった。xxdで生のバイト列を見ると、最初から意味のないランダムな値が並んでいる。つまり中身は暗号化されていて、復号する鍵はアプリ側が握っている。ここは早々に諦めた。 アプリの画面を手で遡るのは、そもそも選択肢に入らなかった PSNのメッセージはサーバー側に保存されていて、PS4・PS5・スマホのPS Appのどれからでも同じ会話を見られる。だからPS Appを開いて手で遡れば、6年分だろうと全部読めることは最初から分かっていた。 問題はそこじゃない。何年分もの会話を、スクロールしながらスクリーンショットで残していくのがあまりに面倒だった、というだけの話だ。だから、ローカルの暗号化ファイルを諦めた後の次の一手は「手で遡る」ではなく、サーバーから直接、プログラムでメッセージを取得する方法を探すことだった。 PSNは1対1のDMも「Group」として扱っている PSNには非公式ながら実用に足るPythonラッパー(psnawp)がある。ソースを読むと、興味深いことが分かった。 The Group class manages PSN group endpoints related to messages (Group and Direct Messages). PSN内部では、複数人のグループチャットも1対1のダイレクトメッセージも、どちらも同じ「Group」という概念で扱われている。つまり友人との会話を取得するには、「その人とのGroup」を見つければよく、専用のDM用APIを探す必要はなかった。 ページネーションのパラメータ名を、独立した別実装から確定させる get_conversation()というメソッドでメッセージを取得できるが、レスポンスにはprevious・nextというカーソルらしきフィールドとreachedEndOfPageという終端フラグが含まれていた。ページネーションができる構造だとは分かったが、次のページを取るときに渡すクエリパラメータの正式な名前がドキュメントに載っていない。 最初はnextという名前を推測で使ったが、動かなかった。ここで、同じPSNの非公式APIを別言語(PHP)で実装している、完全に独立した別プロジェクト(Tustin/psn-php)のソースコードを確認した。該当箇所にはこう書かれていた。 if ($cursor != null) { $params['before'] = $cursor; } ... $this->update($this->totalCount, $results->messages, $results->previous); Copy 正しいパラメータ名はnextではなく**beforeで、渡す値はレスポンスのprevious**フィールドだった。ドキュメントに載っていない挙動でも、独立した複数の実装を突き合わせれば、推測に頼らず答えを確定できる。 実行環境まわりの小さなつまずき Arch Linuxではpip install --userがexternally-managed-environmentエラーで弾かれる(PEP 668)。素直にpython -m venvで仮想環境を作ってそこにインストールする。 認証にはNPSSOという64文字のトークンを使うが、ライブラリのドキュメントには「これはパスワードと同等に扱うべき」と明記されている。人に見せない・チャットに貼らない前提で、ローカルの環境変数として渡す運用にした。 もう一つ、正直に書いておく。 PSNには公式に公開されたAPIは無く、psnawpのようなライブラリはPS Appの内部通信をリバースエンジニアリングしたものだ。これはPSNの利用規約が禁じる「許可されていない自動化ツールの使用」に該当し、規約違反になる。 読み取り専用だから軽い、書き込みより安全、という話ではない。規約違反であること自体は読み書き問わず同じで、それを「相対的にリスクが低い」という言い方でぼかすべきではなかった。 結果と、届かなかったケース こうして書いたスクリプトを実行すると、6年近く前、会話グループが作られた時点まで遡って、1万件を超えるメッセージを数分で取得できた。手で遡っていたら、この量は現実的な時間では終わらなかったはずだ。 一方で、もう何年も連絡を取っていない別の知人に同じ方法を試したところ、Request is rejected due to all targets' settingsというエラーで弾かれた。これは相手の側のプライバシー設定(あるいはブロック)によるもので、こちらの実装や理屈の間違いではない。API がどれだけ正しく動いても、相手側の設定という現実の壁は越えられない。技術的な調査がどこまでも通用するわけではない、という当たり前の事実を確認する形になった。 ...

Termux の sshd はどんなユーザー名でも同じユーザーで入れる ── ソースで確かめる

母艦から Pixel 6(Termux の sshd)へ画像を scp するために ~/.ssh/config に エントリを足したとき、User を書いた。あとで気づいたが、相手が Termux だと ssh banana@<phone> でも ssh root@<phone> でも、同じユーザーでログインできる。 ~/.ssh/config の User 行は効いていない。設定の問題ではなく、Termux の OpenSSH ビルドの話だった。 まず観察 $ ssh -p 8022 banana@100.65.202.69 'whoami' u0_a440 $ ssh -p 8022 root@100.65.202.69 'whoami' u0_a440 Copy banana も root も通って、着地するのは u0_a440(Termux アプリのユーザー)。 鍵は /data/data/com.termux/files/home/.ssh/authorized_keys だけを見ている。 通常の OpenSSH がユーザーをどう解決するか sshd は、接続してきたユーザー名を struct passwd に解決する。これをやるのが auth.c の getpwnamallow()。素の OpenSSH ではこう: pw = getpwnam(user); Copy user はクライアントが送ってきた名前。それを /etc/passwd(実際には NSS 経由で LDAP / SSSD なども)で引く。 ...