---
title: "Ubuntu 24.04でKleopatraをNautilusに統合する"
url: "https://blog.101ta28.com/posts/ubuntu-24-04-kleopatra-nautilus/"
language: "ja"
description: "Ubuntu 24.04 LTSでKleopatraのMIME関連付けを修正し、Nautilusの右クリックにOpenPGPメニューを追加する。暗号化、署名、復号、検証、鍵インポートに対応するPython拡張のコードと導入手順。"
---

# Ubuntu 24.04でKleopatraをNautilusに統合する

> Ubuntu 24.04 LTSでKleopatraのMIME関連付けを修正し、Nautilusの右クリックにOpenPGPメニューを追加する。暗号化、署名、復号、検証、鍵インポートに対応するPython拡張のコードと導入手順。

Kleopatraがインストール済みなのに、GNOME Files（Nautilus）で`.gpg`をダブルクリックしても復号できない。

この場合は、既定アプリの指定に加えて、起動方法を定義する`.desktop`ファイルを確認する。
対応MIME型とファイル引数の指定が欠けていれば、ユーザー用の`.desktop`で補うことで、復号と署名確認へファイルを渡せるようになる。

右クリックから暗号化や署名を始めるには、NautilusのPython拡張で`OpenPGP`サブメニューを追加する。
WindowsのGpg4win / GpgEXのように、ファイルを選んだ場所からKleopatraの操作画面を開ける構成になる。



## 対象環境と事前確認

対象はUbuntu 24.04 LTSのGNOMEデスクトップで、Kleopatraがインストール済みの環境である。
暗号化や署名には、普段使っているGnuPG環境の鍵を利用する。

以下の構成で、ダブルクリックからの復号と署名確認、Python拡張からの暗号化、署名、暗号化と署名の実行を確認済みである。

```text
OS: Ubuntu 24.04.4 LTS (Noble Numbat)
ファイルマネージャー: GNOME Files / Nautilus（46系）
暗号操作のGUI: Kleopatra
拡張API: Nautilus 4.0（python3-nautilus）
```

まず、端末でKleopatraが見つかることと、利用できるオプションを確認する。
`command -v`で実行ファイルのパスが、`--help`で操作の一覧が表示されれば、関連付けの確認へ進める。

```bash
command -v kleopatra
kleopatra --help
nautilus --version
dpkg-query -W kleopatra nautilus
```

`command -v`で何も表示されない場合は、Kleopatraのインストール先と`PATH`を確認する。
パッケージのバージョンは、最後の`dpkg-query`で確認できる。

## 既定アプリなのにファイルを開けない理由

ファイルをダブルクリックしたとき、デスクトップはファイルの種類を表す**MIME型**と、その型の既定アプリを使って起動先を選ぶ。
アプリ側の`.desktop`には、処理できる型を示す`MimeType=`と、起動コマンドを示す`Exec=`がある。
既定アプリの指定だけでは、起動コマンドへファイルを渡す方法までは補えない。

次のコマンドで、Kleopatraの`.desktop`ファイルと、暗号化ファイルおよび署名ファイルの関連付けを確認する。

```bash
ls /usr/share/applications/ | grep -i kleopatra
xdg-mime query default application/pgp-encrypted
gio mime application/pgp-encrypted
gio mime application/pgp-signature
```

問題が起きた環境では、次のように表示された。

```text
kleopatra_import.desktop
org.kde.kleopatra.desktop

org.kde.kleopatra.desktop

“application/pgp-encrypted” のデフォルトのアプリケーション: org.kde.kleopatra.desktop
登録されているアプリケーションはありません
推奨されているアプリケーションはありません

“application/pgp-signature” のデフォルトのアプリケーション: org.kde.kleopatra.desktop
登録されているアプリケーションはありません
推奨されているアプリケーションはありません
```

この出力では、既定アプリは`org.kde.kleopatra.desktop`だが、登録アプリの一覧が空になっている。
続いて、その`.desktop`に対応MIME型とファイル引数の指定があるかを調べる。

```bash
grep -E '^(Name|Exec|MimeType|NoDisplay|Hidden)=' \
  /usr/share/applications/org.kde.kleopatra.desktop
```

設定が不足している場合の出力例は次のとおり。

```text
Exec=kleopatra
Name=Kleopatra
```

`Exec=kleopatra`には、選択したファイルを引数として渡す指定がない。
複数のファイルを渡すには`%F`を使うと、それぞれのパスが独立した引数になる。
この出力なら、`MimeType=`の追加と`Exec=`の変更が必要になる。

対応MIME型と既定アプリの優先順位は別の設定である。
それぞれの役割と`%F`の意味は、freedesktop.orgの[対応MIME型の仕様](https://specifications.freedesktop.org/desktop-entry/latest/mime-types.html)と[Execの仕様](https://specifications.freedesktop.org/desktop-entry/latest/exec-variables.html)で確認できる。

すでに両方のMIME型とファイル引数が設定されているなら、これらを補う作業は省略できる。
`xdg-mime query filetype`で対象ファイルのMIME型を調べ、既定アプリの設定と一致するかを確認する。

## ユーザー用の関連付けを設定する

`/usr/share/applications/`のファイルはパッケージ管理下にあり、更新で上書きされる可能性がある。
同じ名前のファイルを`~/.local/share/applications/`へ置き、自分のユーザーだけで上書きする。

パッケージ更新後もユーザー側のコピーが優先されるため、更新を機に動かなくなった場合は、配布版の`.desktop`と差分を確認する。

後で設定を戻せるよう、変更前の関連付けと既存のユーザー用ファイルを保存してから編集する。
以下は初回設定時に実行するコマンドである。
設定をやり直す場合はバックアップを上書きせず、末尾の`vim`コマンドから再開する。

```bash
mkdir -p ~/.local/share/nautilus-openpgp-backup
mkdir -p ~/.local/share/applications

xdg-mime query default application/pgp-encrypted \
  > ~/.local/share/nautilus-openpgp-backup/pgp-encrypted.default
xdg-mime query default application/pgp-signature \
  > ~/.local/share/nautilus-openpgp-backup/pgp-signature.default

if [ -f ~/.local/share/applications/org.kde.kleopatra.desktop ]; then
  cp -p ~/.local/share/applications/org.kde.kleopatra.desktop \
    ~/.local/share/nautilus-openpgp-backup/org.kde.kleopatra.desktop
fi

cp /usr/share/applications/org.kde.kleopatra.desktop \
  ~/.local/share/applications/org.kde.kleopatra.desktop
vim ~/.local/share/applications/org.kde.kleopatra.desktop
```

`[Desktop Entry]`内の`Exec=`を次の値に変更し、`MimeType=`を追加する。
下の3行は変更箇所の抜粋で、既存の`Type=`、`Name=`、`Icon=`、`Categories=`などはそのまま残す。
`MimeType=`がすでにある場合は、既存の型を残して必要な型を追記する。

```ini
[Desktop Entry]
Exec=kleopatra --decrypt-verify %F
MimeType=application/pgp-encrypted;application/pgp-signature;
```

`--decrypt-verify`は、渡したファイルの復号または署名確認を行うKleopatraのオプションである。
暗号化ファイルと署名ファイルを同じ起動コマンドで扱える。

オプションの一覧は[Kleopatraの公式マニュアル](https://docs.kde.org/trunk_kf6/en/kleopatra/kleopatra/commandline-options.html)にあるが、対象環境で利用できる指定は`kleopatra --help`でも確認する。

アプリのMIME対応表を更新し、既定アプリを設定する。
`update-desktop-database`がない場合は、先に`sudo apt install desktop-file-utils`で導入する。

```bash
update-desktop-database ~/.local/share/applications
xdg-mime default org.kde.kleopatra.desktop application/pgp-encrypted
xdg-mime default org.kde.kleopatra.desktop application/pgp-signature

gio mime application/pgp-encrypted
gio mime application/pgp-signature
nautilus -q
```

`nautilus -q`で開いているFilesのウィンドウが閉じるので、改めてFilesを起動する。
`.gpg`をダブルクリックし、Kleopatraの復号と署名確認の画面が開くことを確かめる。

画面が開かない場合は、対象ファイルがどのMIME型として認識されているかを調べる。
次の`./document.gpg`を、開けないファイルのパスに置き換える。

```bash
xdg-mime query filetype ./document.gpg
gio info -a standard::content-type ./document.gpg
```

表示されたMIME型が`application/pgp-encrypted`なら、上で設定した関連付けの対象になる。
別の型なら、その型の既定アプリを`gio mime`で確認する。

`.asc`には鍵や署名も入るため、一律に復号へ関連付けず、右クリックの拡張で内容に応じた操作を選ぶ。

## OpenPGPサブメニューの設計

ダブルクリックで復号できても、通常のPDFやテキストには暗号化を始める操作が必要になる。
Python拡張で`OpenPGP`メニューを追加すれば、選んだファイルに応じて暗号化、復号、署名確認、鍵のインポートを表示できる。

![Nautilusでsample.txtを右クリックすると表示されるOpenPGPメニュー](openpgp-submenu.png)

Nautilusへ右クリック項目を提供するのが、**Nautilus.MenuProvider**である。
`python3-nautilus`を使うと、選択されたファイルの一覧をPythonで受け取り、`Nautilus.MenuItem`を返せる。

拡張はファイルを分類して操作候補を表示し、選んだ操作とファイルをKleopatraへ渡す。
暗号処理と鍵管理はKleopatra / GnuPGに任せ、パスフレーズやPINの入力も既存の仕組みを使う。

スマートカードやYubiKeyを使う場合も、Kleopatraから利用できるよう設定済みなら、拡張側にカード操作を実装する必要はない。

| 選択したファイル | 判定 | 表示する操作 |
| --- | --- | --- |
| `.txt`、`.pdf`など | 通常ファイル | 暗号化、署名、暗号化して署名 |
| `.gpg`、`.pgp` | 拡張子で分類 | 復号 / 署名を確認 |
| `.sig` | 分離署名として分類 | 署名を確認 |
| `.asc` | `BEGIN PGP MESSAGE` | 復号 / 署名を確認 |
| `.asc` | `BEGIN PGP SIGNATURE` | 署名を確認 |
| `.asc` | `BEGIN PGP SIGNED MESSAGE` | 署名を確認 |
| `.asc`の公開鍵 | `BEGIN PGP PUBLIC KEY BLOCK` | 公開鍵をインポート |
| `.asc`の秘密鍵 | `BEGIN PGP PRIVATE KEY BLOCK` | 秘密鍵をインポート |
| 判別できない`.asc` | 既知のヘッダーなし、または読み込み失敗 | 復号 / 署名を確認 |
| 元ファイルと分離署名 | `.sig`または署名の`.asc`を一緒に選択 | 署名を確認 |

同じ`.asc`でも、公開鍵と署名では必要な操作が違う。
たとえば`-----BEGIN PGP PUBLIC KEY BLOCK-----`で始まるファイルなら、公開鍵のインポートを表示する。
この開始行は、OpenPGPデータをテキストで表現する**ASCII Armor**のヘッダーである。

拡張は先頭を最大64 KiB読み、ヘッダーから操作候補を選ぶ。
実際のデータの読み取りや署名の検証はKleopatraに任せる。
`PGP MESSAGE`も暗号化の有無をヘッダーだけでは断定できないため、復号と検証を兼ねる操作へ渡す。

複数選択は100件までとし、ディレクトリや`file:`以外のURIが含まれる場合はメニューを出さない。

分類が混在した場合は、通常ファイルと分離署名を含む選択を検証へ、それ以外の未対応の組み合わせを復号と検証へ渡す。
鍵をインポートするときは、公開鍵だけ、または秘密鍵だけを選ぶと、それぞれのインポート項目が表示される。

## Python拡張のインストール

拡張の読み込みに`python3-nautilus`、起動失敗時の通知に`libnotify-bin`を使う。
システムのPythonから読み込まれるため、仮想環境への`pip install`ではなくAPTで導入する。

```bash
sudo apt install python3-nautilus libnotify-bin
mkdir -p ~/.local/share/nautilus-python/extensions
vim ~/.local/share/nautilus-python/extensions/openpgp.py
```

次のコード全体を`openpgp.py`に保存する。
NautilusがPythonモジュールとして読み込むため、実行権限の付与は不要である。

```python
#!/usr/bin/env python3
"""OpenPGP integration for Nautilus API 4.0 using Kleopatra."""

from __future__ import annotations

import logging
import shutil
import subprocess
from enum import Enum, auto
from pathlib import Path
from typing import Sequence
from urllib.parse import unquote, urlparse

import gi

gi.require_version("Nautilus", "4.0")
from gi.repository import GObject, Nautilus


APP_NAME = "Nautilus OpenPGP"
LOG_DIR = Path.home() / ".cache"
LOG_FILE = LOG_DIR / "nautilus-openpgp.log"
ARMOR_READ_LIMIT = 64 * 1024
MAX_SELECTION = 100

ENCRYPTED_SUFFIXES = {".gpg", ".pgp"}
SIGNATURE_SUFFIXES = {".sig"}
ASCII_ARMOR_SUFFIXES = {".asc"}

try:
    LOG_DIR.mkdir(parents=True, exist_ok=True)
    logging.basicConfig(
        filename=LOG_FILE,
        level=logging.INFO,
        format="%(asctime)s %(levelname)s %(name)s: %(message)s",
    )
except OSError:
    logging.basicConfig(level=logging.WARNING)

logger = logging.getLogger("nautilus-openpgp")


class FileKind(Enum):
    NORMAL = auto()
    ENCRYPTED = auto()
    DETACHED_SIGNATURE = auto()
    ASCII_ENCRYPTED = auto()
    ASCII_SIGNATURE = auto()
    ASCII_SIGNED_MESSAGE = auto()
    PUBLIC_KEY = auto()
    PRIVATE_KEY = auto()
    UNKNOWN_ASCII = auto()


ASCII_HEADERS: tuple[tuple[bytes, FileKind], ...] = (
    (b"-----BEGIN PGP MESSAGE-----", FileKind.ASCII_ENCRYPTED),
    (b"-----BEGIN PGP SIGNED MESSAGE-----", FileKind.ASCII_SIGNED_MESSAGE),
    (b"-----BEGIN PGP SIGNATURE-----", FileKind.ASCII_SIGNATURE),
    (b"-----BEGIN PGP PUBLIC KEY BLOCK-----", FileKind.PUBLIC_KEY),
    (b"-----BEGIN PGP PRIVATE KEY BLOCK-----", FileKind.PRIVATE_KEY),
)


class OpenPGPMenuProvider(GObject.GObject, Nautilus.MenuProvider):
    """KleopatraをNautilusのコンテキストメニューへ統合する。"""

    def __init__(self) -> None:
        super().__init__()
        self.kleopatra = shutil.which("kleopatra")
        self.notify_send = shutil.which("notify-send")
        if self.kleopatra:
            logger.info("Kleopatra detected: %s", self.kleopatra)
        else:
            logger.error("Kleopatra was not found in PATH")

    def _notify(self, title: str, message: str) -> None:
        logger.warning("%s: %s", title, message)
        if not self.notify_send:
            return
        try:
            subprocess.Popen(
                [self.notify_send, "--app-name", APP_NAME, title, message],
                start_new_session=True,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
        except OSError:
            logger.exception("Failed to launch notify-send")

    @staticmethod
    def _file_info_to_path(file_info: Nautilus.FileInfo) -> Path | None:
        try:
            if file_info.get_uri_scheme() != "file":
                return None
            if file_info.is_directory():
                return None
            parsed = urlparse(file_info.get_uri())
            if parsed.scheme != "file" or not parsed.path:
                return None
            return Path(unquote(parsed.path))
        except Exception:
            logger.exception("Failed to convert FileInfo to Path")
            return None

    def _get_local_paths(
        self, files: Sequence[Nautilus.FileInfo]
    ) -> list[Path]:
        paths: list[Path] = []
        for file_info in files:
            path = self._file_info_to_path(file_info)
            if path is not None:
                paths.append(path)
        return paths

    @staticmethod
    def _inspect_ascii_armor(path: Path) -> FileKind | None:
        try:
            with path.open("rb") as file:
                data = file.read(ARMOR_READ_LIMIT)
        except OSError:
            logger.exception(
                "Could not read file for ASCII Armor detection: %s", path
            )
            return None
        stripped = data.lstrip(b"\xef\xbb\xbf\r\n\t ")
        for header, kind in ASCII_HEADERS:
            if stripped.startswith(header):
                return kind
        return None

    def _classify_file(self, path: Path) -> FileKind:
        suffix = path.suffix.lower()
        if suffix in ASCII_ARMOR_SUFFIXES:
            armor_kind = self._inspect_ascii_armor(path)
            return armor_kind if armor_kind is not None else FileKind.UNKNOWN_ASCII
        if suffix in SIGNATURE_SUFFIXES:
            return FileKind.DETACHED_SIGNATURE
        if suffix in ENCRYPTED_SUFFIXES:
            return FileKind.ENCRYPTED
        return FileKind.NORMAL

    def _run_kleopatra_paths(
        self, operation: str, paths: Sequence[Path]
    ) -> None:
        if not self.kleopatra:
            self._notify("OpenPGP", "Kleopatraが見つかりません。")
            return
        if not paths:
            return
        command = [
            self.kleopatra, "--openpgp", operation,
            *[str(path) for path in paths],
        ]
        logger.info("Executing: %r", command)
        try:
            subprocess.Popen(
                command,
                start_new_session=True,
                stdin=subprocess.DEVNULL,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                close_fds=True,
            )
        except OSError as exc:
            logger.exception("Failed to launch Kleopatra")
            self._notify("OpenPGP", f"Kleopatraの起動に失敗しました: {exc}")

    def _run_operation(
        self,
        _menu_item: Nautilus.MenuItem,
        operation: str,
        files: Sequence[Nautilus.FileInfo],
    ) -> None:
        self._run_kleopatra_paths(operation, self._get_local_paths(files))

    @staticmethod
    def _new_item(*, name: str, label: str, tip: str) -> Nautilus.MenuItem:
        return Nautilus.MenuItem(
            name=f"OpenPGP::{name}", label=label, tip=tip
        )

    def _append_operation(
        self,
        submenu: Nautilus.Menu,
        files: Sequence[Nautilus.FileInfo],
        *,
        name: str,
        label: str,
        tip: str,
        operation: str,
    ) -> None:
        item = self._new_item(name=name, label=label, tip=tip)
        item.connect("activate", self._run_operation, operation, files)
        submenu.append_item(item)

    def _build_normal_menu(
        self, submenu: Nautilus.Menu, files: Sequence[Nautilus.FileInfo]
    ) -> None:
        self._append_operation(
            submenu, files, name="Encrypt", label="暗号化…",
            tip="選択したファイルをOpenPGPで暗号化します",
            operation="--encrypt",
        )
        self._append_operation(
            submenu, files, name="Sign", label="署名…",
            tip="選択したファイルにOpenPGP署名を作成します",
            operation="--sign",
        )
        self._append_operation(
            submenu, files, name="EncryptSign", label="暗号化して署名…",
            tip="選択したファイルを暗号化して署名します",
            operation="--encrypt-sign",
        )

    def _build_decrypt_menu(
        self, submenu: Nautilus.Menu, files: Sequence[Nautilus.FileInfo]
    ) -> None:
        self._append_operation(
            submenu, files, name="DecryptVerify", label="復号 / 署名を確認…",
            tip="暗号化されたファイルを復号し、署名があれば確認します",
            operation="--decrypt-verify",
        )

    def _build_verify_menu(
        self, submenu: Nautilus.Menu, files: Sequence[Nautilus.FileInfo]
    ) -> None:
        self._append_operation(
            submenu, files, name="Verify", label="署名を確認…",
            tip="OpenPGP署名を確認します", operation="--verify",
        )

    def _build_import_menu(
        self,
        submenu: Nautilus.Menu,
        files: Sequence[Nautilus.FileInfo],
        *,
        secret: bool,
    ) -> None:
        self._append_operation(
            submenu, files,
            name="ImportPrivateKey" if secret else "ImportPublicKey",
            label="秘密鍵をインポート…" if secret else "公開鍵をインポート…",
            tip=(
                "選択したOpenPGP秘密鍵をKleopatraにインポートします"
                if secret
                else "選択したOpenPGP公開鍵をKleopatraにインポートします"
            ),
            operation="--import-certificate",
        )

    def get_file_items(
        self, files: Sequence[Nautilus.FileInfo]
    ) -> list[Nautilus.MenuItem]:
        if not files or not self.kleopatra or len(files) > MAX_SELECTION:
            return []
        paths = self._get_local_paths(files)
        if len(paths) != len(files):
            return []
        kinds = {self._classify_file(path) for path in paths}
        root_item = self._new_item(
            name="Root", label="OpenPGP",
            tip="OpenPGPの暗号化、署名、復号、検証",
        )
        submenu = Nautilus.Menu()
        if kinds == {FileKind.NORMAL}:
            self._build_normal_menu(submenu, files)
        elif kinds <= {FileKind.ENCRYPTED, FileKind.ASCII_ENCRYPTED}:
            self._build_decrypt_menu(submenu, files)
        elif kinds <= {
            FileKind.DETACHED_SIGNATURE,
            FileKind.ASCII_SIGNATURE,
            FileKind.ASCII_SIGNED_MESSAGE,
        }:
            self._build_verify_menu(submenu, files)
        elif kinds == {FileKind.PUBLIC_KEY}:
            self._build_import_menu(submenu, files, secret=False)
        elif kinds == {FileKind.PRIVATE_KEY}:
            self._build_import_menu(submenu, files, secret=True)
        elif kinds == {FileKind.UNKNOWN_ASCII}:
            self._build_decrypt_menu(submenu, files)
        elif FileKind.NORMAL in kinds and (
            FileKind.DETACHED_SIGNATURE in kinds
            or FileKind.ASCII_SIGNATURE in kinds
        ):
            self._build_verify_menu(submenu, files)
        else:
            self._build_decrypt_menu(submenu, files)
        root_item.set_submenu(submenu)
        return [root_item]

    def get_background_items(
        self, current_folder: Nautilus.FileInfo
    ) -> list[Nautilus.MenuItem]:
        return []
```

対象には、ローカルに保存したテキストやPDFなどの通常ファイルを選ぶ。
`.asc`は右クリック時に先頭を読み込むため、応答の遅いマウント先ではメニュー表示も待たされることがある。
FIFO（名前付きパイプ）などの特殊ファイルは自動で除外されず、読み込みで待ち続けるおそれがあるので対象にしない。

Kleopatraの起動には、シェルを介さず引数のリストを`subprocess.Popen`へ渡す。
そのためファイル名に空白があっても分割されず、シェルの記号としても解釈されない。

## 拡張を有効にして動作を確認する

保存後に構文を確認し、Nautilusを終了して起動し直す。
Ubuntu標準のPythonで確認するため、ここでは`/usr/bin/python3`を指定する。

```bash
/usr/bin/python3 -m py_compile \
  ~/.local/share/nautilus-python/extensions/openpgp.py
nautilus -q
```

構文エラーが出なければ、Filesを開いてテスト用のテキストファイルを選ぶ。
自分で復号できるよう、暗号化の宛先には自分の公開鍵を含め、対応する秘密鍵を利用できる状態にしておく。

通常ファイルの`sample.txt`では、`OpenPGP`を開くと「暗号化…」「署名…」「暗号化して署名…」が並ぶ。

![sample.txtのOpenPGPサブメニューに暗号化、署名、暗号化して署名が表示されている](openpgp-submenu-sign-encrypt.png)

暗号化した`sample.txt.gpg`を選ぶと、メニューは「復号 / 署名を確認…」に切り替わる。

![sample.txt.gpgのOpenPGPサブメニューに復号 / 署名を確認が表示されている](openpgp-menu-decrypt-verify.png)

メニューが表示されることに加え、復号後の内容と署名の検証結果まで確かめる。

1. 通常ファイルの右クリックに`OpenPGP`が現れ、暗号化、署名、暗号化して署名を選べることを確認する。
2. 暗号化した`.gpg`をダブルクリックし、Kleopatraで復号して元の内容に戻ることを確認する。
3. 署名を作成し、署名ファイルから検証してKleopatraの結果を確認する。
4. 暗号化して署名したファイルを復号し、署名の検証結果も確認する。
5. 空白を含む名前や複数選択でも、意図したファイルがKleopatraへ渡ることを確認する。

分離署名の検証には元ファイルも必要になる。
たとえば`document.pdf`と`document.pdf.sig`を同じフォルダーに置き、2つを選んで「署名を確認…」を実行する。

元ファイルの指定を求められたら、署名の対象になった`document.pdf`を選ぶ。

`.asc`では、公開鍵、秘密鍵、署名、署名付きテキストごとに、表のメニューが出ることも確認する。
鍵を取り込む必要がなければ、表示の確認だけでよい。

署名を信頼してよいか判断する際は、検証結果に加え、公開鍵のフィンガープリントが署名者から確認した値と一致するかを確かめる。

## ログと起動しない場合の切り分け

拡張のログは次のコマンドで確認する。

```bash
tail -f ~/.cache/nautilus-openpgp.log
```

Kleopatraの検出結果、起動コマンド、読み込みや起動に失敗した際の例外を記録する。

起動コマンドには選択したファイルの絶対パスが含まれるため、ログを共有する際はファイル名も確認する。
ログは自動で世代交代せず追記されるため、大きくなったら不要な記録を削除する。

| 症状 | 確認する箇所 |
| --- | --- |
| `OpenPGP`が出ない | 配置先、Pythonの構文、Nautilusの再起動、`python3-nautilus`の導入 |
| 一部の選択で出ない | ディレクトリ、`file:`以外のURI、101件以上の選択がないか |
| Kleopatra未検出のログが出る | Nautilusから見える`PATH`に`kleopatra`があるか |
| メニューから起動できない | ログの`Failed to launch Kleopatra`とデスクトップ通知 |
| 起動するが処理できない | Kleopatraの画面、対象ファイル、必要な鍵、検証時の元ファイル |
| ログファイルが作られない | キャッシュへの書き込み権限、拡張の読み込みエラー |

メニューが出ず通知もない場合は、ログに`Kleopatra was not found in PATH`がないかを確認する。
Kleopatraが見つからないと、拡張はメニューを表示しない。

メニューを選んだ後にプロセスを起動できなかった場合は、ログに加えてデスクトップ通知にもエラーが出る。

Kleopatraが開いた後のエラーは、Kleopatraの画面で確認する。
拡張のログにはKleopatraの標準出力と標準エラーが残らないため、詳しい出力が必要なら端末から同じ操作を試す。

```bash
kleopatra --openpgp --decrypt-verify ./document.gpg
```

ログファイル自体が作られない場合は、Nautilusを終了して端末から起動し、拡張の読み込みエラーが出ていないかを確認する。
別の拡張によるログ設定が優先され、指定したファイルへ記録されない場合もある。

```bash
nautilus -q
nautilus
```

## アンインストールと関連付けの復元

右クリック拡張だけを外す場合は、`openpgp.py`を削除してNautilusを起動し直す。
この操作でKleopatraやGnuPGの鍵は削除されない。

```bash
rm -i -- ~/.local/share/nautilus-python/extensions/openpgp.py
nautilus -q
```

ダブルクリックの設定も戻す場合は、ユーザー用の`.desktop`を元に戻す。
設定前のコピーがある場合は復元し、もともとなかった場合は今回のコピーを削除する。

```bash
if [ -f ~/.local/share/nautilus-openpgp-backup/org.kde.kleopatra.desktop ]; then
  cp -p ~/.local/share/nautilus-openpgp-backup/org.kde.kleopatra.desktop \
    ~/.local/share/applications/org.kde.kleopatra.desktop
else
  rm -i -- ~/.local/share/applications/org.kde.kleopatra.desktop
fi
update-desktop-database ~/.local/share/applications
```

既定アプリも、設定前に記録した値へ戻す。
以下は保存値がある型だけを復元する。

```bash
for pgp_type in pgp-encrypted pgp-signature; do
  previous_app=$(cat "$HOME/.local/share/nautilus-openpgp-backup/$pgp_type.default")
  if [ -n "$previous_app" ]; then
    xdg-mime default "$previous_app" "application/$pgp_type"
  fi
done
nautilus -q
```

バックアップした`.default`ファイルが空の場合は、追加した関連付けを手動で外す。
保存値がないため、上のコマンドでは復元できない。

`~/.config/mimeapps.list`など、設定時に変更されたユーザー用の`mimeapps.list`を開く。
`application/pgp-encrypted`と`application/pgp-signature`から追加した指定だけを外し、他の型や同じ行にある別アプリの指定は残す。

ログが不要なら、次のファイルも削除できる。
`python3-nautilus`と`libnotify-bin`は他の拡張からも利用されるため、そのまま残してよい。

```bash
rm -i -- ~/.cache/nautilus-openpgp.log
```

## Filesから普段の鍵を使う

設定後は、暗号化済みのファイルをダブルクリックして復号し、通常ファイルは右クリックの`OpenPGP`から暗号化や署名を選べる。
公開鍵の`.asc`なら、同じメニューからインポートを始められる。

宛先や署名鍵の選択、処理結果の確認にはKleopatraを使う。
普段の鍵管理を保ったまま、Filesで選んだファイルに対してOpenPGPの操作を始められる。

## 補足：Python拡張の前に試したNautilus Scripts

Python拡張を作る前に、Nautilus Scriptsで選択したファイルをKleopatraへ渡せるかを試した。
短いシェルスクリプトだけで、暗号化、署名、暗号化と署名が動くことを確認できた。

この検証で、暗号操作の画面はKleopatraに任せ、Nautilus側ではファイルを渡せばよいと分かった。

試したのは以下の3本である。
呼び出しだけを切り分けて試す際にも使えるが、Python拡張の導入にこれらの作成は必要ない。

```bash
mkdir -p ~/.local/share/nautilus/scripts

cat > ~/.local/share/nautilus/scripts/暗号化 <<'EOF'
#!/usr/bin/env bash
kleopatra --openpgp --encrypt "$@"
EOF

cat > ~/.local/share/nautilus/scripts/署名 <<'EOF'
#!/usr/bin/env bash
kleopatra --openpgp --sign "$@"
EOF

cat > ~/.local/share/nautilus/scripts/暗号化＋署名 <<'EOF'
#!/usr/bin/env bash
kleopatra --openpgp --encrypt-sign "$@"
EOF

chmod +x \
  ~/.local/share/nautilus/scripts/暗号化 \
  ~/.local/share/nautilus/scripts/署名 \
  ~/.local/share/nautilus/scripts/暗号化＋署名
nautilus -q
```

Filesでファイルを選ぶと、右クリックの「スクリプト」以下に3つの操作が現れる。
`"$@"`を使うことで、空白を含むファイル名も引数の区切りを保って渡せる。
各項目を選び、Kleopatraで宛先や署名鍵を選択する画面が開けば、ファイルを渡せている。

ただ、この3本では暗号化済みのファイルを選んでも、メニューは暗号化と署名のままである。
選んだファイルに合わせて復号や鍵のインポートを表示するため、専用の`OpenPGP`メニューを作れるPython拡張へ移行した。

### 試したScripts版の片付け

この補足のScripts版を試した場合は、Python拡張への移行後に3ファイルを削除できる。
同名の別スクリプトを使っている場合は削除対象に含めない。

```bash
ls -l \
  ~/.local/share/nautilus/scripts/暗号化 \
  ~/.local/share/nautilus/scripts/署名 \
  ~/.local/share/nautilus/scripts/暗号化＋署名

rm -i -- \
  ~/.local/share/nautilus/scripts/暗号化 \
  ~/.local/share/nautilus/scripts/署名 \
  ~/.local/share/nautilus/scripts/暗号化＋署名
nautilus -q
```
