OmarchyにHowdy Nextを入れて、顔だけでロック解除できるようにした

Omarchyのロック画面を、パスワードを入力せずに顔認証で解除できるようにした。

使ったのはHowdy Next。Linux向けの顔認証ソフトウェアで、HowdyをC++で再実装したものだ。

インストールと顔の登録だけで終わるかと思ったが、今回はロック画面側にも変更が必要だった。最終的には、Omarchyのロック画面プラグインをユーザー用に複製し、顔認証とパスワード認証を別々に動かす構成にした。

この記事は2026年10月15日に手元の環境で設定した記録。Omarchyのバージョンやロック画面の実装が異なる場合は、そのまま適用できるとは限らない。

環境

  • Omarchy 4.0.4
  • Howdy Next 3.4.2(AURパッケージは howdy-next 3.4.2-1)
  • Omarchy標準のQuickshell製ロック画面
  • 使用カメラ:Anker AnkerWork C310(Linux上のデバイス名:AnkerWork C310 Webcam)

今回設定したのは、ログイン後の画面ロック解除。ログイン画面や sudo の顔認証設定は変更していない。

Howdy Nextをインストールする

公式READMEでは、Arch Linux向けにAURの howdy-next と howdy-next-git が案内されている。今回はリリース版の howdy-next を使った。

Omarchyでは次のコマンドで導入できる。

omarchy pkg aur add howdy-next

今回のビルドでは、パッケージに含まれる104件のテストがすべて成功した。

バージョン確認は次のとおり。--version ではなく、version サブコマンドを使う。

howdy version
Howdy Next 3.4.2 (554051d399)

導入したAURパッケージには、polkitのヘルパーがカメラなどにアクセスできるようにするsystemdの設定も含まれていた。

[Service]
PrivateDevices=no
DeviceAllow=char-video4linux rw
DeviceAllow=/dev/uinput rw

これはpolkitヘルパーのサンドボックス設定を一部緩和する変更になる。今回のロック画面用設定では、後述する入力注入のworkaroundは使っていない。

カメラを設定する

使用したのは、USB接続の Anker AnkerWork C310。以下はこのカメラで実際に設定し、ロック解除まで動作を確認した手順になる。

まず、カメラのデバイスを確認した。

ls -l /dev/v4l/by-id/

手元のカメラには video-index0 と video-index1 の2つがあった。v4l2-ctl で確認し、映像キャプチャ用の video-index0 を使うことにした。

v4l2-ctl --device /dev/video0 --all

/dev/video0 の番号は接続順などで変わる可能性があるので、設定には /dev/v4l/by-id/ 以下のパスを使った。

以下の <カメラのID> は、自分の環境で確認した名前に置き換える。

sudo howdy set device_path /dev/v4l/by-id/<カメラのID>-video-index0

設定ファイルは /etc/howdy/config.ini。変更後の該当部分は次のようになる。

[video]
device_path = /dev/v4l/by-id/<カメラのID>-video-index0

howdy config でエディタが見つからないと言われた

設定ファイルを開こうとして、最初にここでつまずいた。

sudo howdy config
Error: No suitable text editor found.
Set EDITOR to an executable name or path, or install one of: micro, nano, vi.

手元にはNeovimが入っていたので、EDITOR を明示すれば開ける。

sudo EDITOR=nvim howdy config

今回はカメラパスの変更だけだったので、実際の設定には howdy set を使った。

モデルを取得し、顔を登録する

続いて認識用モデルをダウンロードし、自分の顔を登録する。

sudo howdy download-models
sudo howdy add
sudo howdy test

顔の登録状況は次のコマンドで確認できる。<ユーザー名> は実際にロック解除するユーザーを指定する。

sudo howdy -U <ユーザー名> list

ここまでで、Howdy側のカメラ設定と顔登録ができた。

PAMに追加するだけでは、期待する動作にならなかった

この環境のOmarchyロック画面は、パスワード認証に次のPAM設定を使っていた。

/etc/pam.d/omarchy-lock-password

最初はここに次の行を追加した。

auth sufficient pam_howdy.so

このとき、追加する位置にも注意が必要だった。ファイルの末尾に置くと、それより前のパスワード認証失敗処理で終了し、Howdyまで到達しない。

pam_faillock.so preauth の後、パスワード認証の前に移動すると、カメラによる認証は動くようになった。

しかし、まだ期待した使い勝手ではなかった。

ロック画面で何かを入力してEnterを押してから、カメラが動く。

見た目には「パスワードを入れてから、さらに顔認証を求められる」ように感じる。実際には顔認証が成功すれば入力したパスワードの正しさは要求されないが、入力自体が必要だった。

ロック画面が空欄の送信を受け付けていなかった

ロック画面の実装を確認すると、次の条件があった。

function submitPassword(value) {
  var password = String(value || "")
  if (!lockRequested || authenticatingPassword || password.length === 0) return

  // この後で passwordPam.start() を呼ぶ
}

空文字の場合は認証処理を始めずに戻る。つまり、空欄でEnterを押してもPAMは呼ばれず、Howdyも動かない。

パスワード用のPAM設定にHowdyを追加するだけでは、「画面をロックしたら自動で顔認証」という動作にはならないことが分かった。

顔認証とパスワード認証を分ける

最終的には、次の構成に変更した。

認証方法 開始するタイミング PAMサービス
顔認証 セッションのロックが確立したとき omarchy-lock-face
パスワード認証 パスワードを入力して送信したとき omarchy-lock-password

どちらか一方が成功すれば解除する。顔認証が動いている間も、パスワード入力は受け付ける。

顔認証専用のPAM設定

/etc/pam.d/omarchy-lock-face を作成した。

#%PAM-1.0
auth requisite pam_faillock.so preauth silent deny=10 unlock_time=120
auth requisite pam_howdy.so
auth required pam_faillock.so authsucc
account include system-local-login

既存のパスワード側に合わせて、失敗回数によるアカウントロックを確認する。顔認証に成功したときだけ、authsucc へ進むようにした。

顔認証の不一致を繰り返すだけでパスワードまでロックされないよう、この顔認証専用スタックには pam_faillock.so authfail を追加していない。

一方、omarchy-lock-password からは、途中で追加した pam_howdy.so の行を削除した。パスワード認証がカメラを待つことを避けるためだ。

PAM設定は変更前にバックアップを取った。認証設定を編集するときは、作業中のターミナルを残し、解除できることを確認してから閉じるようにしたい。

ロック画面をユーザー用プラグインにする

Omarchy本体の次のファイルを直接変更すると、更新で上書きされる。

/usr/share/omarchy/shell/plugins/lock/Service.qml

そこで、ロック画面をユーザー用プラグインとして複製した。今回の配置先は次のとおり。

~/.config/omarchy/plugins/polidog.lock/

Omarchyには複製用コマンドがある。

omarchy plugin clone omarchy.lock

ただし、今回はこのコマンドによる再スキャン中にシェルのIPCが応答しなくなり、複製処理が失敗した。そのため、作業用ディレクトリで変更を仕上げ、ユーザープラグインの場所に配置してからシェルを再起動した。

以下は最終的な設定内容。複製コマンドだけで最後まで完了したわけではない。

manifest.json では、複製元を clonedFrom で指定した。

{
  "schemaVersion": 1,
  "id": "polidog.lock",
  "name": "Lock Screen with Face Authentication",
  "version": "1.0.0",
  "author": "Omarchy",
  "description": "Custom lock screen with independent facial authentication.",
  "omarchy": {
    "capabilities": ["authentication"],
    "clonedFrom": "omarchy.lock"
  },
  "kinds": ["service"],
  "keepLoaded": true,
  "entryPoints": {
    "service": "Service.qml"
  }
}

shell.json では、既存の設定を残しつつ、次の項目を追加した。これは設定全体ではなく、追加部分の抜粋。

{
  "plugins": [{ "id": "polidog.lock" }],
  "disabledPlugins": ["omarchy.lock"],
  "cloneSourceRestores": ["polidog.lock"]
}

プラグインには Service.qml だけでなく、元のロック画面に含まれる LockView.qml などの関連ファイルも配置した。

顔認証用の PamContext を追加する

Service.qml のルートに、顔認証が実行中かどうかを保持するプロパティを追加した。

property bool faceAuthenticating: false

認証の開始処理は次のようにした。画面のロックが確立していることを確認してから開始し、重複した認証処理は走らせない。

function startFace() {
  if (!lockRequested || !sessionLock.secure || facePam.active || faceAuthenticating) return
  faceAuthenticating = true
  if (!facePam.start()) {
    faceAuthenticating = false
    faceRetryTimer.restart()
  }
}

顔認証用の PamContext と再試行用のタイマーも追加した。

PamContext {
  id: facePam
  config: "omarchy-lock-face"
  user: root.userName

  onResponseRequiredChanged: {
    if (responseRequired) respond("")
  }

  onCompleted: function(result) {
    root.faceAuthenticating = false
    if (!root.lockRequested) return
    if (result === PamResult.Success) root.finishUnlock()
    else faceRetryTimer.restart()
  }

  onError: function(error) {
    root.faceAuthenticating = false
    if (root.lockRequested) faceRetryTimer.restart()
  }
}

Timer {
  id: faceRetryTimer
  interval: 2000
  repeat: false
  onTriggered: root.startFace()
}

顔認証専用のPAMにはパスワード認証を含めていない。ここでの respond("") はパスワードを空で通す処理ではなく、PAM側から応答要求があった場合に空文字を返すもの。解除できるかどうかはPAMの成功結果で判断する。

再試行は「2秒ごとに必ずスキャンする」という意味ではない。1回の認証が失敗またはエラーで終了してから、2秒後に次を開始する。

ロック確立後に開始し、解除時に止める

既存の WlSessionLock にある onSecureStateChanged の if (secure) ブロックへ、次の呼び出しを追加した。

root.startFace()

指紋認証を開始している箇所と同じタイミングになる。

また、既存の resetAuthenticationState() に次を追加した。

faceRetryTimer.stop()
faceAuthenticating = false
if (facePam.active) facePam.abort()

解除された後もカメラ認証や再試行が残らないようにするためだ。

既存のパスワード処理は維持した。顔認証とパスワード認証のどちらからでも、成功したら既存の finishUnlock() へ進む構成になっている。

反映と確認

設定を配置した後、画面がロックされていない状態でシェルを再起動した。

omarchy restart shell

今回の環境では、起動直後のIPC応答に時間がかかることがあった。状態確認には待ち時間を長めに指定した。

OMARCHY_SHELL_IPC_TIMEOUT=20s omarchy-shell lock status

カスタマイズしたステータスに face: true が表示され、新しいロック画面が読み込まれていることを確認できた。この face 項目は今回追加した表示で、Howdyの設定やモデルを自動診断するものではない。

最後に実際に画面をロックして、何も入力せず、カメラに顔を向けるだけで解除できることを確認した。

気をつけたいこと

Howdy Nextの公式READMEでは、顔認証はパスワードより弱く、似た顔や写真などにだまされる可能性があると説明されている。IRカメラはなりすまし対策に役立つが、今回はIRを使う構成の確認までは行っていない。

今回もパスワード解除を残している。また、顔認証はロック中に再試行するため、カメラが繰り返し動作する構成になる。

ユーザープラグインはOmarchyの更新で直接上書きされない一方、標準ロック画面に入った修正も自動では取り込まれない。特に認証やロックの修正が入ったときは、元のプラグインとの差分を確認して追従する必要がある。

おわりに

一番のつまずきは、Howdyの認証設定と、ロック画面が認証を開始するタイミングが別だったこと。

PAMにHowdyを追加すれば顔認証は使える。しかし、今回のOmarchyではパスワード欄への入力が認証開始の条件だったため、それだけでは操作なしの解除にならなかった。

顔認証用のPAMと PamContext を分け、ロックの確立後に自動で開始するようにしたことで、ようやく「カメラに顔を向けるだけ」の解除ができるようになった。

参考

カテゴリ