Monthly Archives: 2月 2025

2025-02-28

Android版のEmacsを試す

Android版のEmacsがリリースされたので初めて使ってみました。

参考資料

apkのダウンロード

端末や使い方によってダウンロードするapkが異なる(今回私が使った端末はPixel7/Android 15なので -29-arm64-v8a を選ぶ)。

Emacsを単体で使う場合:

Emacsと一緒に配布されているtermux-appと連携させる場合:

GitHub版のtermux-appと連携させる場合(再署名が必要):

上記リンクはこの記事を書いた時点でのものなので、最新の状況はSourceForgeのfilesページ(Emacs)GitHubのリリースページ(termux-app)を確認すると良い。

再署名

EmacsとTermuxを連携させたいなら(お互いのファイルにアクセスできるようにしたいなら)両者のapkを同じ鍵で署名する必要がある。

Emacsと一緒に配布されているtermux-app(termux-app_apt-android-7-release_universal.apk) を使う場合はすでに同じ鍵で署名されているので再署名は不要。

それ以外から入手したtermux-app(GitHub版の最新版や自分でビルドしたもの)を使う場合は再署名するのが手っ取り早い(正直Android版Emacsのビルドはやりたくないので)。

手順:

  • 注1: 既に私のPCに入っていたAndroid Studio(とSDK)を使用。
  • 注2: 署名に使う鍵は既に作成済み。
  • 注3: 以下はMSYS2のBash上でのコマンドライン。環境毎に適宜読み替えること。
  1. 署名の削除

    zip -d emacs-30.1-29-arm64-v8a.apk "META-INF/*"
    

    (unzipしてからzipしてもよい?)

    (termux-app_v0.118.1+github-debug_arm64-v8a.apkの方は削除しなくてもよい?)

  2. zipalign

    c:/Users/****/AppData/Local/Android/Sdk/build-tools/35.0.1/zipalign.exe -v -p 4 emacs-30.1-29-arm64-v8a.apk emacs-30.1-29-arm64-v8a-aligned.apk
    c:/Users/****/AppData/Local/Android/Sdk/build-tools/35.0.1/zipalign.exe -v -p 4 termux-app_v0.118.1+github-debug_arm64-v8a.apk termux-app_v0.118.1+github-debug_arm64-v8a-aligned.apk
    
  3. 署名

    予め鍵を生成して <keystore-path><key-alias> の部分に情報を埋めること。

    export JAVA_HOME="c:/Program Files/Android/Android Studio/jbr"
    c:/Users/****/AppData/Local/Android/Sdk/build-tools/35.0.1/apksigner.bat sign --ks <keystore-path> --ks-key-alias <key-alias> --out emacs-30.1-29-arm64-v8a-signed.apk emacs-30.1-29-arm64-v8a-aligned.apk
    c:/Users/****/AppData/Local/Android/Sdk/build-tools/35.0.1/apksigner.bat sign --ks <keystore-path> --ks-key-alias <key-alias> --out termux-app_v0.118.1+github-debug_arm64-v8a-signed.apk termux-app_v0.118.1+github-debug_arm64-v8a-aligned.apk
    

インストール

既に署名が異なるTermux関連アプリがインストールされている場合は先に全てアンインストールする。もちろん必要に応じてバックアップを取ること。

Android上でダウンロードしたapkをブラウザから直接インストールするか、再署名したのであればPCからadbでインストールするかAndroid上で動くファイル管理ソフト(MiXplorer等)を使ってコピーしてインストールする。先に身元不明のアプリをインストールできるようにする設定が必要かも。

基本的にストアを介さない野良アプリ扱いなので色々警告が出るが自己責任で。気になるなら専用端末に入れた方が良いかもしれない。どちらかと言えばTermuxの方が不安かも。Termuxが使いたくなければネットワーク越しのやりとりは全部url-retrieveで書けば良いと思う。

仮想キーボードのインストール

既に入っていたHacker's Keyboardを使用したが、こういうCtrl+何かのキーが押せるキーボードが無いとつらい。

もしくは物理キーボードを繋げるか。それはそれで配列のカスタマイズがつらい。(追記: 実際に繋げてみたが日本語IMEの起動方法が分からない)

Termuxの設定

pkg update
pkg upgrade

SSH越しのGitリポジトリにアクセスするなら:

pkg install openssh
pkg install git
ssh-keygen -t ed25519 -C "your_name@example.com"
  • ~/.ssh/id_ed25519.pub をサーバにセット
  • .bashrcに . source-ssh-agent と書いておくと良いかも
  • GitリポジトリからEmacsの設定を取り出したり色々できる

Termuxから /sdcard にアクセスするなら:

termux-setup-storage

Emacsの設定

/sdcardへのアクセス

Emacsから /sdcard にアクセスできないようなら「設定→アプリ→特別なアプリアクセス→Emacs」が必要かも? 

もしくは M-x android-request-storage-access ? (何回か入れ直したのでよく分からなくなってる)

Android判定

Emacs LispからAndroid版のEmacsで動いているかを判定するには次の式を使う。

(eq system-type 'android)

この式を使って、普段使っているearly-init.elやinit.elにAndroid専用の設定を追加していく。

環境変数の設定

Termux側のbinへPATHを通す。

(when (eq system-type 'android) ;; Android版
  (let ((termux-bin "/data/data/com.termux/files/usr/bin"))
    (when (file-directory-p termux-bin) ;; 一応アクセスできるか確認する
      (setenv "PATH" (concat termux-bin ":" (getenv "PATH")))
      (push termux-bin exec-path))))

これによって M-x shell から様々なコマンドが使えるようになった。gitも実行できるのでMagitも使えた(やけに遅かったけど)。

ちなみにHOMEの位置は:

  • Termux側: /data/data/com.termux/files/home
  • Emacs側: /data/data/org.gnu.emacs/files

同じ鍵で署名してあれば双方どちらにもアクセスできるはず。

ツールバーの表示

押せるものは何でも欲しい。

default-frame-alisttool-bar-mode でツールバーを消していたので消さないようにした。

メニューバーは元々消さずに使っているが、Androidでは絶対あった方が良いと思う。「Buffers」を押すだけでバッファリストが出る。

default-frame-alist をいじるついでに left-fringe と right-fringe を大きくしても良いかも。なぜか滅茶苦茶細いので。

modifier-bar-modeなんてのもあるらしい。secondary-tool-bar? え、global-window-tool-bar-mode? 何それ! M-xやC-x、C-x C-、C-cなどがワンボタンで押せると良いんだけど。その辺りは追々。

フォント

~/fonts/ (/data/data/org.gnu.emacs/files/fonts/) に.ttfファイルを置くとそれを使えるようになる。認識していれば (font-family-list) の評価結果に現れる。そのフォント名で (set-frame-font "<family-name>-<size>") とするか、early-init.elあたりで default-frame-alist(font . "<family-name>-<size>") として指定してやるか。

~/fontsディレクトリを表示しているところ(dired-details-rとnerd-icons-dired)
図1: ~/fontsディレクトリを表示しているところ(dired-details-rとnerd-icons-dired)

nerd-icons も M-x nerd-icons-install-fonts でこのディレクトリにインストールすれば普通に使える。

ただしSVG内からは参照できない気がする。というかSVGテキストが表示されないような気が。

仮想キーボードの表示

デフォルトだと読み取り専用の場所で仮想キーボードが消えてしまう。Dired等でHacker's Keyboardによる操作ができなくなってしまう。

(when (eq system-type 'android)
  (setq touch-screen-display-keyboard t))

これでも戻るボタンを押せば消せるのであまり邪魔にはならないと思う。とは言えスクロールして読むだけのバッファなら非表示の方が良いと思うので、モードによって切り替えた方が良いのかもしれない。

通常の日本語IMEとの切り替えは画面右下に切り替えボタンが出るのでそれで十分かなと(端末に依って異なるかも)。

おしまい

子フレームで補完候補を表示しているところ(ちなみにこの補完候補はタッチで選択出来ない)
図2: 子フレームで補完候補を表示しているところ(ちなみにこの補完候補はタッチで選択出来ない)

というわけで普段PCで使っているのとほとんど同じ物がAndroid上に現れてしまいました。GUI版のEmacsがそのまま動いているのはすごいですね。設定もPCで使っている物からほとんど変えていません。

後はどうやって使いやすくするか、あるいは慣れるか。

私としては今は軽いノートPCを持っているのでわざわざキーボードを繋げて使うことはあまりないと思います。あくまでタッチ操作で色々出来るようにしたいところ。

とりあえずタッチイベントを調べてみようかな。

作図ツール(少し問題あり)
図3: 作図ツール(少し問題あり)
2025-02-24

Emacs 30.1の設定(MS-Windows)

リリースされたので入れ替え。と言っても最近は30.0.9xをずっと使っていたのでトラブルなど無く、例によってネイティブコンパイルまわりの対処くらい。

1. ダウンロード

https://ftp.gnu.org/gnu/emacs/

  • emacs-30.1.zip
  • emacs-30.1.tar.xz (展開してfind-function-C-source-directory変数に指定し、describe-functionからソースコードを追えるようにするため)

2. zipを展開して適当な場所に置く

3. 起動してみる

パッと見問題なし。

4. 補う必要のあるファイルを確認する

  • 相変わらず libgccjit 関連のファイルは含まれていないのでネイティブコンパイルはそのままでは出来ない((native-comp-available-p) はnilを返す)
  • gdk_pixbuf関連のファイルはlibrsvgが自前のデコーダを持つようになったので不要

5. MSYS2で必要なファイルを取り寄せる

pacman -S mingw-w64-x86_64-libgccjit

MSYS2自体もアップデートして最新にした。

(ちなみにMSYS2のEmacsパッケージを使っていないのは、以前MSYS2版だけCPU100%不具合があったから。その時ノートPCでだけMSYS2版を使っていたのだけど、その不具合でファンがきゅいーん!と鳴ってうるさかった。多分もう直っているので、そもそも楽に使いたければMSYS2版を使った方が良いと思う。あ、それとテストのために古いバージョンのEmacsをいつでも使えるようにしておきたいという理由もある)

6. ネイティブコンパイルできるようにする

次のファイルをコピー。

  • emacs-30.1/binへ
    • msys64/mingw64/binから
      • libgccjit-0.dll
  • emacs-30.1/lib/gccへ
    • msys64/mingw64/binから
      • as.exe
      • ld.exe
    • msys64/mingw64/libから
      • crtbegin.o
      • crtend.o
      • dllcrt2.o
      • libadvapi32.a
      • libgcc_s.a
      • libkernel32.a
      • libmingw32.a
      • libmingwex.a
      • libmoldname.a
      • libmsvcrt.a
      • libpthread.a
      • libshell32.a
      • libuser32.a
    • msys64/mingw64/lib/gcc/x86_64-w64-mingw32/14.2.0/から
      • libgcc.a

libgccjit-0.dll以外のdllはemacs-30.1.zipの中に既に含まれていた。

.aや.oは全部必要なのか、また、(何かの条件で)不足するものが無いのかは確認していない。

~/.emacs.d/early-init.el には次の設定を入れている。

(when (and (eq system-type 'windows-nt) ;; Windowsの場合
           (fboundp #'native-comp-available-p) ;; emacs-28以降
           (native-comp-available-p)) ;; libgccjitが使える
  ;; -B で emacs-??/lib/gcc/ ディレクトリを指定する。
  (let ((gcc-dir (expand-file-name
                  (file-name-concat invocation-directory "../lib/gcc"))))
    (when (file-directory-p gcc-dir)
      (setq native-comp-driver-options (list "-B" gcc-dir)))))

この設定が無くても PATH を最小限にして runemacs -Q で起動して試したらちゃんとネイティブコンパイルが成功していたので不要かと思ったが、普段使っている環境だと大量にエラーが出たのでこの設定を入れた。ucrt64環境のldやasが参照された?

パッケージディレクトリの切り替え

バージョンが変わるとバイトコンパイルされたEmacs Lispで色々問題が起きるので、最近は次のような設定を入れている。

;; Emacsのバージョン毎にパッケージ格納先を切り替える。
;; ~/.emacs.d/elpa/30 のようにする。
;; `package-enable-at-startup'が非nil(デフォルト)のとき、early-init.el
;; の後、init.elの前にパッケージの初期化が行われるので、early-init.el
;; で切り替えておく必要がある。
(when (boundp 'package-user-dir)
  (setq package-user-dir
        (locate-user-emacs-file (format "elpa/%d" emacs-major-version))))
2025-02-21

Emacsでメールファイル(.eml等)をそのまま読む

昔から疑問に思っていて未だによく分かっていないことなのですが、Emacsから1件のメールのデータが入ったファイルを開いてその内容を読むにはどうしたら良いのでしょうか。

Web検索でちょっと調べてみても、既存のメールを取り扱うEmacs用のシステムに取りこんで読めというようなことばかりが引っかかり、単純に1件のメールだけが入ったファイルを普通のファイルを開くように読む方法がなかなか出てこないんですよね。

ここで言うメールが入ったファイルというのは沢山のメールが一つに詰め込まれたmbox形式とかではなくて、一般的なメールクライアントがメッセージを外部ファイルとして保存するときに.emlという拡張子を付けて出力するような先頭にメールヘッダーが入っているようなファイルのことです(ええと何形式というんだっけ)。内容はMIMEでエンコードされていて普通はそのままでは読めません(……ひょっとしてASCII言語圏ではそのまま読めるのでしょうか? だからあまり問題になっていない?)。

私は普段Emacs上ではWanderlustを使っているのですが、それが依存しているsemiというライブラリにMIMEエンコードされたバッファをプレビューするコマンドが含まれています。

その名もmime-view-buffer。メールファイルを開いて M-x mime-view-buffer を実行すれば、メールの内容が 別バッファに 人間が読める状態で表示されます。素晴らしい。

ちなみに検索して見つかった別の方法としては次のものがあります。これはGnusの関数(gnus-article-prepare-display)を使います。

Any way to just render an email file on disk? : r/emacs

(defun my/render-mime-message ()
  "Render the current buffer as a Gnus article."
  (interactive)
  (gnus-article-prepare-display))

これで読めると言えば読めるのですが、どうにも釈然としません。普通にEmacsからファイルを開く要領で読めるようにならないのでしょうか。要するにfind-fileで画像ファイルやpdfファイルを開いたら中身のバイナリではなく人間が読めるようなものが出るのと同じようにしたいわけです。

と、今更そのようなことを思い出したのは、先日いくつかのメールへのリンクをorg-modeに書きたくなって久しぶりにol-wl.elを使ったのがきっかけでした。メールへのリンクをorg-mode文書に書きたくなることはほとんどなく、近年はorg-contribが別パッケージに分かれたこともあってインストールすらされていませんでした。ol-wl.elを使うと wl: リンクタイプが追加されるのですが、そのパスにはWanderlustが管理するフォルダ表記とメッセージIDを指定します。そのリンクをC-c C-oで開くとWanderlustが起動してその中でメールの内容が表示されます。それを見て、単純に file: リンクタイプでメールが入っているファイルへリンクしたらダメなのかな? と思ったわけです。

画像やPDFを開くノリでできないかと思ったのですが、あれはバッファ全体をdisplayテキストプロパティ(またはオーバーレイプロパティ)で画像に置き換えることで解決しています。今回やりたいのは画像では無くテキストで置き換えること。displayプロパティで別のテキストに置き換えるとそのテキストの中にポイントを置けないのであまり望ましくないでしょう。なので、どちらかと言えばhexl-modeがファイルの中身をHEXダンプに置き換える時の手法が近そうです。

ただ、これはちゃんと実装しないと置き換えた後のテキストで元のファイルを上書きしてしまうリスクがあります。

とりあえず今回必要だったのはorg-modeからリンクを張ることなので、必ずしも律儀にそのファイルに関連付けられたバッファで開く必要はありません。org-modeではリンクを開くときの動作を条件毎にカスタマイズできるようになっているので、そこでmime-view-bufferで開くような関数を指定してしまえば良いのです。

(defun my-mime-view-file (file _original-link-path)
  "mime-view-bufferを使ってメールFILEのプレビューを開く。"
  (with-temp-buffer
    ;; PreviewバッファのカレントディレクトリはFILEがある場所にする。
    (setq default-directory
          (file-name-directory (expand-file-name file)))
    (insert-file-contents file)
    (mime-view-buffer
     nil
     ;; バッファ名にファイル名を入れる。
     (format "*MIME View: %s*" (file-name-nondirectory file)))))

;; Wanderlust用のメールが格納されているディレクトリにあるファイルを開
;; くときは my-mime-view-file を使う(例)。
(add-to-list 'org-file-apps '("c:/my-wl-mail-dir/" . my-mime-view-file)))

後は [[file:c:/my-wl-mail-dir/inbox/123]] のようなリンクを書けば、それをC-c C-oで開こうとすると上の関数の働きによって人間が読めるものがすぐに開くというわけです。

とりあえずこれでお茶を濁しておきますが、そのうちhexl-mode的な手法で内容を表示するmajor-modeを作りたい所。

メールが入ったファイルを読むだけのことがなんでこんなに面倒なんだろう。