2026年1月19日

keyd を FreeBSD で使いたい (2) 【迷走編】

前回に引き続き、FreeBSD で keyd を使うべく模索します。今回の記事は読んでも何も得られませんので (汗)、手っ取り早く結果だけ知りたい方は、【(一応) 完結編】まで読み飛ばしてください。とはいえ試行錯誤の過程はあとで何かの役に立つかもしれないので、自分用の記録として残しておきます。

関連記事:

前回のおさらい

なんやかやあって最終的にわかったのは、コンソール環境だとダンマリだけど、X11/Wayland 環境なら問題なさそうということです。だったら X11/Wayland といっしょに keyd も起動するようにすればいいじゃない。ということでやってみます。

とりあえず思いつくままやる

でどうするかというと、 Wayland/Sway をインストールしたとき に作った ~/.config/sway/config の中で keyd を起動すればいいんじゃないかと思いました。

~/.config/sway/config
exec sudo /usr/local/bin/keyd

ちなみにこの exec は Sway 独自のコマンドであり、シェルのビルトインコマンドの exec とは無関係のようです。

visudo コマンドで wheel グループが keyd を実行できるようにします (実行するユーザーは wheel グループに所属しているものとします)。

/usr/local/etc/sudoers (追記)
%wheel ALL=(root) NOPASSWD: /usr/local/bin/keyd

で、前回 /etc/rc.conf に追加した行は削除またはコメントアウトしておきます。

/etc/rc.conf
# keyd_enable="YES"

また、もし /usr/local/etc/rc.d/keyd を作成してあった場合は削除します。

そしたら OS を再起動ます。ログインプロンプトが表示されたら、ユーザー名とパスワードを入力してログインします。この時点では keyd は起動していないので、ここは問題なくキーボードから入力できます。

問題は Sway が起動した後です。意図したとおりに keyd が起動してくれれば、ターミナルエミュレータを立ち上げて、CapsLock+J を入力したら、Ctrl+J (改行) にすり替えてくれるはずなんですが。まあそううまくはいかずに、むなしく「J」と表示されてしまいました。つまり keyd は起動していない感じです。実際、ps コマンドでも keyd のプロセスは見当たりませんでした。

試しに手動で起動してみます。

% sudo /usr/local/bin/keyd
mlockall: Cannot allocate memory

なんかエラーが出ました。ChatGPT に訊いてみたところ、ユーザーが 1 つのプロセスのためにロックできるメモリ容量に制限がかかっており、keyd がその上限を超えてメモリをロックしようとしているためとのことでした。実際、keyd のソースコードを確認すると、daemon.c の run_daemon() の中でメモリをロックしようとしています。

src/daemon.c
    if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
        perror("mlockall");
        exit(-1);
    }

メモリロックの制限を解除する

ユーザーがロックできるメモリ容量は ulimit -l コマンドで確認することができます。

% ulimit -l
64

単位はキロバイトらしいので、デフォルトだと 64 KB までってことになるみたいです。

で、この制限を緩和あるいは解除しなくてはならないわけです。やり方は、まず /etc/login.conf に「ログインクラス」というものを新しく定義します。

/etc/login.conf
mlock:\
        :memorylocked=unlimited:\
        :tc=default:

ここでは「mlock」という名前のログインクラスを定義しました。で、memorylocked=unlimited で制限を解除します。tc というのは Table Continued の略らしく、既に定義されている default クラスの項目を (memorylocked を除いて) 適用するという指示です。

ログインクラスのデータベース?を更新します。

# cap_mkdb /etc/login.conf

keyd を使用したいユーザーのログインクラスを mlock に変更します。

# pw usermod nobiru -L mlock

ログインクラスが変更されたか確認します。

% pw usershow nobiru
nobiru:*:1001:1001:mlock:0:0:nobiru:/home/nobiru:/usr/local/bin/zsh

よさそうです。ちなみにログインクラスを元に戻したい場合は、再度 default に変更してください。

# pw usermod nobiru -L default

ログインし直して、制限が解除できたか確認します。

% ulimit -l
unlimited

CapsLock+J を連打してみます。

%
%
%
%
%

できました、やったー。

余談ですが、adduser コマンドでユーザーを追加するときに訊かれるログインクラスってこれのことだったんですね。いつも「なんかしらんけど default でいいか」って思ってました。

が、

問題はまだありました。Sway の終了時に keyd も終了させるようにしないと、コンソール環境に戻ったときにまたダンマリが再発してしまうのでした。

Sway の設定ファイルから実行したプロセスは、Sway が終了するときに一緒に終了させてくれるものと思い込んでいたんですが、そうではないみたいです。

※ このときは作業に夢中で見落としてましたが、改めてこの投稿をよく読むと、 Sway から実行したプロセスを終了させる方法も書かれていますね。Sway の設定ファイルの「KillUserProcess=no」と書かれているところを yes にすればいいみたいです (真偽は未確認です)。

で、どうやって終了させればいいんですかね?ChatGPT に訊きながらああだこうだとやった中で (自分でも説明できないようなとんちんかんなことばっかりやってたんで、ここには掲載しません)、最終的にまあなんとかうまくいったのが、次のような Sway のラッパースクリプトでした。

Sway のラッパースクリプト
#!/bin/sh

sway &
SWAY_PID=$!
wait "$SWAY_PID"

KEYD_PID=$(pgrep -f keyd)
kill -TERM $KEYD_PID

Sway をバックグラウンドで起動し (3 行目)、終了するまで待って (5 行目)、終了したら keyd のプロセス ID を取得し (7 行目)、kill します。

keyd のプロセス ID を pgrep -f で取得しているのがポイントです。上のほうの ~/.config/sway/config の中で sudo /usr/local/bin/keyd を実行するようにしたので、次のように 2 つのプロセスが動いています。

root       1793   0.0  0.1  21136  9072  -  Is   00:20     0:00.01 sudo /usr/local/bin/keyd
root       1794   0.0  0.3  20976 21152  -  S    00:20     0:00.01 /usr/local/bin/keyd

pgrep は何もオプションを付けないとコマンド名にマッチするので、上の例だと 1793 のほうにはマッチせず、1794 のほうにだけマッチします (もし 1793 のほうにマッチさせたければ pgrep sudo とする) が、-f オプションを付けると両方にマッチするので、それを使って両方のプロセス ID を kill に渡すようにしました。

で、このスクリプトを ~/.zprofile から実行するようにしたら、期待したとおりコンソール環境でダンマリになる問題は解消されました。やったー。

~/.zprofile
[ "$(tty)" = "/dev/ttyv0" ] && ~/bin/start-sway.sh

あきらめるか…想像の100倍ややこしい:(

しかしなんかこう、釈然としないものを感じます。そもそも通常は制限されているメモリロックを解除したり、service(8) コマンドを介さずにデーモンを起動したりしなきゃならない点がイマイチというか、アプローチのしかたがなんか間違っている気がします。

あとプロセスをきれいに終了させるのって難しいんだなあと。開始するほうは .zshrc とか .xinitrc とかその他のなんたら rc とかに書く機会がありますけど、終了させるほうはあまり機会がなかったというか、考えたことがなかったというか、考えたことがあったとしても、適当にごまかしてきたと思うんで、そういうツケがこういうときに回ってくるんだなあと思いました。

そろそろあきらめるか…想像の100倍ややこしい:(

と ChatGPT に投げたら、「わかる……それ、正常な反応だと思うよ。(中略) 想像の100倍ややこしいのは、あなたのせいじゃない。」と慰めてくれました :)

そして完結へ…

それでも未練がましくマニュアルやソースコードを嗅ぎまわっていたところ、プロセス間通信 (IPC) を使ってなんかやっていることに気がつきました。

これを使えば外部アプリから keyd を有効にしたり無効にしたりできんじゃね?

そんな都合のいい話があるかい、と思いつつもさらに調べていくと、そのようなアプリを自分で作る必要すらなく、keyd に備わっている機能だけでやりたいことができそうだとわかりました。ポイントは 2 つありまして、

  1. monitorcheckreload などのサブコマンドを付けて keyd を実行すると、そのプロセスがクライアントになって、別口で起動しておいた keyd デーモンにリクエストを送る。keyd デーモンはリクエストされたサブコマンドを処理する。
  2. keyd の設定ファイル (/usr/local/etc/keyd/*.conf) が存在しない場合、keyd はすべての入力デバイスを無視する。

で、keyd reload というのは設定ファイルを再読み込みするコマンドなので、ちょっと泥臭いですけど

  • 無効にしたいとき (コンソール環境) は設定ファイルを別の場所に移動 (あるいは拡張子を .conf 以外に変更) してから reload する
  • 有効にしたいとき (GUI 環境) は設定ファイルを元に戻してから reload する

という運用にすれば、うまくくいきそうじゃんと思いました。

今から考えると、そのくらいのことは最初にマニュアルを読んだ段階で気づいてほしかったですが、それはさておき、泥臭いにはちがいないですが、これまでに試みたおかしなやり方に比べたら、はるかに腹落ちするのはたしかです。

という訳で完結編に続きます。