Monthly Archives: 1月 2022

2022-01-22 ,

TermuxでEmacsを使うためにやったこと

Termuxのインストール

次の方法があります。

必ずしもF-Droidアプリは必要なくTermuxパッケージのページから直接apkをダウンロードすることもできます。GitHubからも(Debug版ですが)apkファイルをダウンロードできます。ビルドはそれほど難しくは無いと思います。

署名の問題があるので別の供給元から入手したものは共存できません。切り替えるにはいったんアンインストールが必要です(必要に応じてバックアップすること)。

私は最終的に次の二つを自分でビルドしてインストールしました。

ソースをgit cloneで入手し、既にPCに入っていたAndroid Studioでプロジェクトを開いてRunボタンを押したらすんなり成功しました。

Termuxの分かりにくそうな操作

ドロワー

画面左端から右へスワイプすると、ドロワー(サイドバー)が出てきます。

KEYBOARDを 長押し すると、下部ツールバーの表示状態を切り替えられます。

ツールバーの左スワイプ

下部ツールバーを左スワイプすると、Text Input Viewが出てきます。ここはInput Methodが効くので日本語を入力するのに使えます。私は上部で入力できるように改造したので今はほぼ使っていません。

Volume Up+ソフトキーボード

色々ショートカットが割り当てられています。

Touch Keyboard - Termux Wiki

キーボードまわりの設定

先日書きました。

Termuxでハードウェアキーボードからスムーズに日本語入力したい | Misohena Blog

  • .termux/termux-properties
    • soft-keyboard-toggle-behaviour = enable/disable
    • disable-hardware-keyboard-shortcuts = true
    • bell-character = ignore
    • ctrl-space-workaround = true
  • キーレイアウト問題(CtrlとCaps、BACKSLASH、無変換)
  • Input Methodがらみは独自ビルドで解決

Emacsのインストール

pkg upgrade
pkg install emacs
emacs

普通に起動します!

SSHとGitのインストール

自分の設定を持ってくるためにsshとgitをインストールしました。

pkg install openssh
pkg install git

鍵を作ってサーバに設定。

ssh-agentは .bashrcに . source-ssh-agent を追加するだけ。簡単!

(https://www.reddit.com/r/termux/comments/b8il5p/sshagent_want_to_start_again_when_new_terminal_is/ より)

Emacsの設定を持ってくる

Gitで自分の設定を持ってきて……

git clone --recursive ssh://hogehoge/my-emacs-config.git
cp my-emacs-config/.emacs.el ~/.emacs.d/init.el
emacs

エラー箇所を修正。最近色々判定をサボっていたので少し出ましたが、それほど多くはありませんでした。(display-graphic-p)はnilなのでそのあたりの機能はごっそり無効化。

Termux上で動いているかの判別は次のようにしました。

(setq my-termux-p (not (null (executable-find "termux-info"))))

How to detect in a BASH script that I'm in Termux? : termux によればtermux-toolsパッケージは常に存在すると考えて良いみたいです。他にもパスにcom.termuxという特徴的な文字列が含まれているのでそれをチェックするのも(コマンドを検索するより速くて)良さそうです。

パッケージのインストール。

M-x package-refresh
M-x package-install-selected-packages
C-x C-c
emacs

その他Emacsの修正

emacsclientが動かない

TMPDIR"/data/data/com.termux/files/usr/tmp" なのに server-socket-dir のデフォルト値が "/data/data/com.termux/files/usr/var/run/<uid>" になっているのが原因みたいです。次のようにします。

(setq server-socket-dir
      (and (featurep 'make-network-process '(:family local))
           (format "%s/emacs%d" (or (getenv "TMPDIR") "/data/data/com.termux/files/usr/var/run") (user-uid))))

(emacsclient can not find server socket · Issue #4230 · termux/termux-packagesより)

パッチが当たらなくなったのかな?

browse-urlでブラウザが開かない

(setq browse-url-browser-function 'browse-url-xdg-open)

(How can I make Emacs function 'browse-url-at-point work on a tablet running Android? - Emacs Stack Exchangeより)

browse-url-xdg-openxdg-open コマンドを使います。Termuxから使えるようです。 termux-open-url というコマンドもあります。

Androidのクリップボードと連携する

事前にtermux-apiアプリの導入が必要です。私はtermux-appを自分でビルドしてしまったので、こちらも自分でビルドする必要がありました。

その上でTermux内にもパッケージのインストールが必要です。

pkg install termux-api

そうすると次の二つのコマンドが使えるようになります。

termux-clipboard-set <text>
termux-clipboard-get

Emacsからは xclip というパッケージを導入するとこれらのコマンドを使ってくれるようになります。

M-x package-install xclip
M-x xclip-mode

(Copy/paste between apps and Emacs in Termux · Issue #6266 · termux/termux-packagesより)

sdcardの読み書き

termux-setup-storage を実行し権限を許可。すると ~/storage が追加され、 /sdcard にもアクセスできるようになりました。

その他インストールしたツール

  • pkg install ripgrep : Emacsから検索の際に使用
  • pkg install wget : ファイルをダウンロードするときに使用
2022-01-21 , ,

Termuxの曖昧幅文字の全角化

TermuxでのEmacs環境の整備を続けています。

Termux 上で Emacs を普段使いする為の設定 - Qiita

を読んでいて、曖昧幅なんてものもあったなぁ……と、手元のorgファイルの表の部分を見てみると……ありましたありました。メチャクチャ崩れています。○△×とか書く欄が表にあったらもうダメですね。どうしたら良いんだろう。

と、上のページをよく読むと

Termux が純粋な Linux アプリであれば、wcwidh()のハックにより解消出来ますが、Termux は Java アプリなのでハックの仕方が分かりません…。 (ソースを修正すべき箇所は多分ここです→ https://github.com/termux/termux-app/blob/master/terminal-emulator/src/main/java/com/termux/terminal/WcWidth.java)

おおお、そんな場所があるとは。幸いTermuxはビルドしたばかりですから直せるかもしれません。

hamano/locale-eaw: East Asian Ambiguous Width問題と絵文字の横幅問題の修正ロケール

ええと、このページからたどれるeaw.elをダウンロードして……ふむふむ、曖昧幅の文字と全角確定の文字を合わせて、その範囲のリストを作れば良さそうですね。

(let* (
       ;; From https://github.com/hamano/locale-eaw/blob/master/eaw.el
       (east-asian-ambiguous
        '(
          #x00A1 ; Po         INVERTED EXCLAMATION MARK
          #x00A4 ; Sc         CURRENCY SIGN
          #x00A7 ; Po         SECTION SIGN
          ;;...略...
          ))
       ;; From WIDE_EASTASIAN in termux-app/terminal-emulator/src/main/java/com/termux/terminal/WcWidth.java
       (east-asian-wide
        '(
          (#x01100 . #x0115f) ;; // Hangul Choseong Kiyeok  ..Hangul Choseong Filler
          (#x0231a . #x0231b) ;; // Watch                   ..Hourglass
          ;;...略...
          (#x30000 . #x3fffd) ;; // (nil)                   ..(nil)
          ))
       (full-width-chars
         (sort
          (append
           (cl-loop for range in east-asian-wide
                    nconc (cl-loop for c from (car range) to (cdr range)
                                   collect c))
           east-asian-ambiguous)
          #'<)))

  (let ((p full-width-chars))
    (while p
      (let ((first (car p))
            (last (progn
                    (while (and (cadr p)
                                (or
                                 (= (car p) (cadr p)) ;;Omit duplication
                                 (= (1+ (car p)) (cadr p))))
                      (setq p (cdr p)))
                    (car p))))
        (insert (format "{0x%x, 0x%x},\n" first last))
        (setq p (cdr p))))))

結果はこんな感じ。

{0xa1, 0xa1},
{0xa4, 0xa4},
{0xa7, 0xa8},
{0xaa, 0xaa},
{0xad, 0xae},
{0xb0, 0xb4},
{0xb6, 0xba},
{0xbc, 0xbf},
{0xc6, 0xc6},
{0xd0, 0xd0},
{0xd7, 0xd8},
{0xde, 0xe1},
{0xe6, 0xe6},
{0xe8, 0xea},
{0xec, 0xed},
{0xf0, 0xf0},
{0xf2, 0xf3},
{0xf7, 0xfa},
{0xfc, 0xfc},
{0xfe, 0xfe},
{0x101, 0x101},
{0x111, 0x111},
{0x113, 0x113},
{0x11b, 0x11b},
{0x126, 0x127},
{0x12b, 0x12b},
{0x131, 0x133},
{0x138, 0x138},
{0x13f, 0x142},
{0x144, 0x144},
{0x148, 0x14b},
{0x14d, 0x14d},
{0x152, 0x153},
{0x166, 0x167},
{0x16b, 0x16b},
{0x1ce, 0x1ce},
{0x1d0, 0x1d0},
{0x1d2, 0x1d2},
{0x1d4, 0x1d4},
{0x1d6, 0x1d6},
{0x1d8, 0x1d8},
{0x1da, 0x1da},
{0x1dc, 0x1dc},
{0x251, 0x251},
{0x261, 0x261},
{0x2c4, 0x2c4},
{0x2c7, 0x2c7},
{0x2c9, 0x2cb},
{0x2cd, 0x2cd},
{0x2d0, 0x2d0},
{0x2d8, 0x2db},
{0x2dd, 0x2dd},
{0x2df, 0x2df},
{0x391, 0x3a1},
{0x3a3, 0x3a9},
{0x3b1, 0x3c1},
{0x3c3, 0x3c9},
{0x401, 0x401},
{0x410, 0x44f},
{0x451, 0x451},
{0x1100, 0x115f},
{0x2010, 0x2010},
{0x2013, 0x2016},
{0x2018, 0x2019},
{0x201c, 0x201d},
{0x2020, 0x2022},
{0x2024, 0x2027},
{0x2030, 0x2030},
{0x2032, 0x2033},
{0x2035, 0x2035},
{0x203b, 0x203b},
{0x203e, 0x203e},
{0x2074, 0x2074},
{0x207f, 0x207f},
{0x2081, 0x2084},
{0x20ac, 0x20ac},
{0x2103, 0x2103},
{0x2105, 0x2105},
{0x2109, 0x2109},
{0x2113, 0x2113},
{0x2116, 0x2116},
{0x2121, 0x2122},
{0x2126, 0x2126},
{0x212b, 0x212b},
{0x2153, 0x2154},
{0x215b, 0x215e},
{0x2160, 0x216b},
{0x2170, 0x2179},
{0x2189, 0x2189},
{0x2190, 0x2199},
{0x21b8, 0x21b9},
{0x21d2, 0x21d2},
{0x21d4, 0x21d4},
{0x21e7, 0x21e7},
{0x2200, 0x2200},
{0x2202, 0x2203},
{0x2207, 0x2208},
{0x220b, 0x220b},
{0x220f, 0x220f},
{0x2211, 0x2211},
{0x2215, 0x2215},
{0x221a, 0x221a},
{0x221d, 0x2220},
{0x2223, 0x2223},
{0x2225, 0x2225},
{0x2227, 0x222c},
{0x222e, 0x222e},
{0x2234, 0x2237},
{0x223c, 0x223d},
{0x2248, 0x2248},
{0x224c, 0x224c},
{0x2252, 0x2252},
{0x2260, 0x2261},
{0x2264, 0x2267},
{0x226a, 0x226b},
{0x226e, 0x226f},
{0x2282, 0x2283},
{0x2286, 0x2287},
{0x2295, 0x2295},
{0x2299, 0x2299},
{0x22a5, 0x22a5},
{0x22bf, 0x22bf},
{0x2312, 0x2312},
{0x231a, 0x231b},
{0x2329, 0x232a},
{0x23e9, 0x23ec},
{0x23f0, 0x23f0},
{0x23f3, 0x23f3},
{0x2460, 0x24e9},
{0x24eb, 0x254b},
{0x2550, 0x2573},
{0x2580, 0x258f},
{0x2592, 0x2595},
{0x25a0, 0x25a1},
{0x25a3, 0x25a9},
{0x25b2, 0x25b3},
{0x25b6, 0x25b7},
{0x25bc, 0x25bd},
{0x25c0, 0x25c1},
{0x25c6, 0x25c8},
{0x25cb, 0x25cb},
{0x25ce, 0x25d1},
{0x25e2, 0x25e5},
{0x25ef, 0x25ef},
{0x25fd, 0x25fe},
{0x2600, 0x27e5},
{0x27ee, 0x27ff},
{0x2b1b, 0x2b1c},
{0x2b50, 0x2b50},
{0x2b55, 0x2b59},
{0x2e80, 0x2e99},
{0x2e9b, 0x2ef3},
{0x2f00, 0x2fd5},
{0x2ff0, 0x2ffb},
{0x3000, 0x303e},
{0x3041, 0x3096},
{0x3099, 0x30ff},
{0x3105, 0x312f},
{0x3131, 0x318e},
{0x3190, 0x31e3},
{0x31f0, 0x321e},
{0x3220, 0x4dbf},
{0x4e00, 0xa48c},
{0xa490, 0xa4c6},
{0xa960, 0xa97c},
{0xac00, 0xd7a3},
{0xf900, 0xfaff},
{0xfe10, 0xfe19},
{0xfe30, 0xfe52},
{0xfe54, 0xfe66},
{0xfe68, 0xfe6b},
{0xff01, 0xff60},
{0xffe0, 0xffe6},
{0xfffd, 0xfffd},
{0x16fe0, 0x16fe4},
{0x16ff0, 0x16ff1},
{0x17000, 0x187f7},
{0x18800, 0x18cd5},
{0x18d00, 0x18d08},
{0x1b000, 0x1b11e},
{0x1b150, 0x1b152},
{0x1b164, 0x1b167},
{0x1b170, 0x1b2fb},
{0x1f000, 0x1f02b},
{0x1f030, 0x1f093},
{0x1f0a0, 0x1f0ae},
{0x1f0b1, 0x1f0bf},
{0x1f0c1, 0x1f0cf},
{0x1f0d1, 0x1f0f5},
{0x1f100, 0x1f1ad},
{0x1f1e6, 0x1f202},
{0x1f210, 0x1f23b},
{0x1f240, 0x1f248},
{0x1f250, 0x1f251},
{0x1f260, 0x1f265},
{0x1f300, 0x1f6d7},
{0x1f6e0, 0x1f6ec},
{0x1f6f0, 0x1f6fc},
{0x1f700, 0x1f773},
{0x1f780, 0x1f7d8},
{0x1f7e0, 0x1f7eb},
{0x1f800, 0x1f80b},
{0x1f810, 0x1f847},
{0x1f850, 0x1f859},
{0x1f860, 0x1f887},
{0x1f890, 0x1f8ad},
{0x1f8b0, 0x1f8b1},
{0x1f900, 0x1f978},
{0x1f97a, 0x1f9cb},
{0x1f9cd, 0x1fa53},
{0x1fa60, 0x1fa6d},
{0x1fa70, 0x1fa74},
{0x1fa78, 0x1fa7a},
{0x1fa80, 0x1fa86},
{0x1fa90, 0x1faa8},
{0x1fab0, 0x1fab6},
{0x1fac0, 0x1fac2},
{0x1fad0, 0x1fad6},
{0x1fb00, 0x1fb92},
{0x1fb94, 0x1fbca},
{0x1fbf0, 0x1fbf9},
{0x20000, 0x2fffd},
{0x30000, 0x3fffd},

これを termux-app/terminal-emulator/src/main/java/com/termux/terminal/WcWidth.javaWIDE_EASTASIAN の定義と差し替えます。

そしてビルドして実行してみると……ちゃんと綺麗に揃った幅で表示されました!

Emacsの方は (set-language-environment "Japanese") すればちゃんと幅が2になっているみたいですね。昔のEmacsは自分で設定しないといけなくて、でもいつの間にか設定しなくても良くなってた記憶があります。

フォントの方は前に作ったキメラのようなフォントを適用したところ問題なく表示されました。決して綺麗なフォントではありませんが、まぁ、ある意味カッチリしているというべきか……。ついline-spacingを指定しようとしてしまいましたが効くわけがありません(笑)

何はともあれ予想されていたもう一つの方法でも解決できたということで。

2022-01-21 , ,

TermuxのrcloneでDropboxと双方向同期

引き続きTermuxの環境構築をしています。

Dropboxが使いたかったのですが当然のことながら公式アプリで同期、なんてできません。検索したらrcloneというツールでクラウドストレージとやりとりできるそうです。

How to access dropbox files via termux? : termux

rcloneは pkg install rclone でインストールできて、設定も対話形式で簡単です。

ただ、双方向同期のような機能はありません。ああいうのはちゃんとやろうとすると結構大変ですからね。

代わりと言っては何ですが、diffとpatchで同期する仕組みを作ってみました。

  1. まずリモートの同期対象ディレクトリをrcloneでローカルの最新リモート置き場に取りこみます。
  2. 前回取りこんだ内容とdiffを取って、それをローカルのワーキングコピーにpatchします。
  3. うまく当たらないハンクがあったらここで終了。手動で解決してもらいます。
  4. うまく当たったら、最新のリモートとローカルのワーキングコピーのdiffを取って表示し、ユーザーに確認を促します。
  5. 問題ないようなら最新のリモートファイルを置く場所にローカルのワーキングコピーをコピーして、それをrcloneでアップロードします。

この作業の間に別PCがリモートを書き替えたら……THE ENDですw。

あとバイナリファイルとかもどうなるか知りません。

限定されたディレクトリで使う分には大丈夫でしょう。Dropboxなら消してしまってもすぐに気がつけば復元できるはずですしね。

#!/bin/bash

DROPBOXDIR=~/Dropbox
SYNCTARGET=sync-test-dir

set -eu

# Ensure Current Directory
cd "$(dirname "$0")"

# Pull Remote Files (Destructive)
# _remote(old) => _remote.before_pull
# remote => _remote
rm -fR _remote.before_pull
if [ -e _remote ]; then
    cp -a _remote _remote.before_pull
fi
rclone sync dropbox:${SYNCTARGET} _remote/${SYNCTARGET}

# Create Remote Diff (Non Destructive)
set +e
diff -urN _remote.before_pull/${SYNCTARGET} _remote/${SYNCTARGET} >_remote.diff
REMOTE_DIFF_STATUS=$?
if [ $REMOTE_DIFF_STATUS -ge 2 ] ; then
    exit 2
fi
set -e
#rm -fR _remote.before_pull

# Apply Remote Changes to Local (Destructive)
# _remote(new) => _last_pull
# (local) => _local.before_remote_change
# (remote changes) => (local)
if [ $REMOTE_DIFF_STATUS -eq 0 ] ; then
    echo "No Remote changes."
else
    # Save Last Pull State
    rm -fR _last_pull
    cp -a _remote _last_pull

    # Backup Local
    rm -fR _local.before_remote_change/${SYNCTARGET}
    if [ -e ${DROPBOXDIR}/${SYNCTARGET} ] ; then
        mkdir -p _local.before_remote_change/${SYNCTARGET}
        cp -a ${DROPBOXDIR}/${SYNCTARGET} _local.before_remote_change/${SYNCTARGET}
    fi

    # Apply Remote Changes to Local
    echo "Apply remote changes."
    mkdir -p ${DROPBOXDIR}/${SYNCTARGET}
    patch --set-time -p2 -d ${DROPBOXDIR}/${SYNCTARGET} < _remote.diff
fi

# Check Local Changes (Non Destructive)
set +e
diff -urN _remote/${SYNCTARGET} ${DROPBOXDIR}/${SYNCTARGET} >_local.diff
LOCAL_DIFF_STATUS=$?
if [ $LOCAL_DIFF_STATUS -ge 2 ] ; then
    exit 2
fi
if [ $LOCAL_DIFF_STATUS -eq 0 ] ; then
    echo "No local changes."
    exit $LOCAL_DIFF_STATUS
fi
# Confirm to Upload Local Changes
echo "You have local changes."

function confirm_local_changes {
    while true; do
        read -n1 -p "Upload? (Y/n/=): " yn
        case $yn in
            [Yy])
                return 0
                ;;
            [Nn])
                return 1
                ;;
            [=])
                emacsclient _local.diff
                ;;
        esac
    done
}

confirm_local_changes
if [ $? -ne 0 ]; then
    echo "Not uploaded."
    exit 1;
fi

# Apply Local Changes (Destructive)
# _remote(new) => _remote.before_local_change
# (local)(new) => _remote
# (local)(new) => _last_pull
rm -fR _remote.before_local_change/${SYNCTARGET}
mkdir -p _remote.before_local_change/${SYNCTARGET}
mv _remote/${SYNCTARGET} _remote.before_local_change/${SYNCTARGET}
mkdir -p _remote/${SYNCTARGET}
cp -a ${DROPBOXDIR}/${SYNCTARGET} _remote
rm -fR _last_pull/${SYNCTARGET}
mkdir -p _last_pull/${SYNCTARGET}
cp -a ${DROPBOXDIR}/${SYNCTARGET} _last_pull

# Upload Local Changes 
rclone sync -i _remote/${SYNCTARGET} dropbox:${SYNCTARGET}

双方向同期でなくてもこのrcloneは結構便利ですね。ちょっとしたファイルのやりとりが気軽にできます。