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

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

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

対象環境と事前確認

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

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

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

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

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

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

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

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

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

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

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

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型とファイル引数の指定があるかを調べる。

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

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

Exec=kleopatra
Name=Kleopatra

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

対応MIME型と既定アプリの優先順位は別の設定である。 それぞれの役割と%Fの意味は、freedesktop.orgの対応MIME型の仕様Execの仕様 で確認できる。

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

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

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

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

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

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=がすでにある場合は、既存の型を残して必要な型を追記する。

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

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

オプションの一覧はKleopatraの公式マニュアル にあるが、対象環境で利用できる指定はkleopatra --helpでも確認する。

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

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を、開けないファイルのパスに置き換える。

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メニュー

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で導入する。

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モジュールとして読み込むため、実行権限の付与は不要である。

#!/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を指定する。

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

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

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

sample.txtのOpenPGPサブメニューに暗号化、署名、暗号化して署名が表示されている

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

sample.txt.gpgのOpenPGPサブメニューに復号 / 署名を確認が表示されている

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

nautilus -q
nautilus

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

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

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

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

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

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

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-encryptedapplication/pgp-signatureから追加した指定だけを外し、他の型や同じ行にある別アプリの指定は残す。

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

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

Filesから普段の鍵を使う

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

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

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

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

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

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

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ファイルを削除できる。 同名の別スクリプトを使っている場合は削除対象に含めない。

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