I switched to Android a few weeks ago, and being an Emacs user, I immediately installed and started tweaking Emacsā new Android port (specifically the version which shares a UID with Termux, as uploaded by Po Lu to SourceForge).
Many things didnāt work out of the box, but I got large parts of my setup working under Android now, so I thought Iād share some stuff since I found little to no information online about any of this.
Running programs installed through Termux
This is covered well by the FAQ on SourceForge, but it seems to give some wrong advice for users of Android 7+: Setting LD_LIBRARY_PATH on these devices likely causes failures to link some executables installed through Termux.
Iām not sure what other ramifications doing this has, but I have been able to run all Termux programs (with a single exception, git-annex) through Emacs this way. To use git-annex through Emacs I run tmux on Termux, and connect to it from an Emacs terminal buffer. This could be somewhat automated using the āamā command. git-annex can be used through Emacs on Android 10+ by disabling Emacsā default behavior of running executables under system call tracing. You need to ensure every directory containing executable files is read only though (this can be done through emacs using It actually works for me without setting directories read-only (contrary to the FAQ).(set-file-modes DIR 320)
).
āinfoā and āmanā
You can use Emacsā built-in viewers for info manuals and manpages by setting INFOPATH and MANPATH appropriately. When setting INFOPATH, add the directory ā/assets/infoā otherwise you wonāt be able to see Emacsā manuals.
SSH and GPG Agents
After installing GPG through Termux, set āepg-pinentry-modeā to āloopbackā to prompt for the GPG passphrase through Emacs. The agent should start automatically, I didnāt do anything special other than this.
The SSH agent is a bit more tricky - it seems to not cleanup after itself when Termux and Emacs close, and for some reason doesnāt appear in the process list through Emacs when launched through Termux.
For these reasons, I decided to launch a new agent whenever I launch Emacs, and configure Termux to connect to it, or start a new agent only if the socket file doesnāt exist at all:
.profile:
export SSH_AUTH_SOCK="${SSH_AUTH_SOCK:-$HOME/.ssh/auth.socket}"
if [ ! -S "$SSH_AUTH_SOCK" ]; then
eval "$(ssh-agent -a "$SSH_AUTH_SOCK")" > /dev/null 2>&1
echo "$SSH_AGENT_PID" > "$HOME/.ssh/pid"
fi
export SSH_AGENT_PID="${SSH_AGENT_PID:-$(cat "$HOME/.ssh/pid")}"
profile.el:
(let* ((sr (expand-file-name ".ssh" "~"))
(ss (setenv "SSH_AUTH_SOCK" (expand-file-name "auth.socket" sr)))
p)
(unless (file-exists-p sr) (make-directory sr t))
(delete-file (expand-file-name "pid" sr))
(delete-file ss)
(with-temp-buffer
(call-process "ssh-agent" nil (current-buffer) nil "-a" ss)
(goto-char (point-min))
(forward-line)
(search-forward "SSH_AGENT_PID=")
(setq p (point))
(goto-char (line-end-position))
(search-backward "; export SSH_AGENT_PID;")
(setenv "SSH_AGENT_PID" (buffer-substring-no-properties p (point)))
(write-region p (point) (expand-file-name "pid" sr))))
And, as a bonus, hereās a pair of functions I use to prompt for the passphrase to an SSH key through Emacs when pulling/pushing Git repositories with VC: (it tries to use a graphical askpass program by default, which isnāt possible on Android)
(defun init-interfaces-ssh-add (keys)
"Attempt to add SSH KEYS to the agent interactively."
(dolist (key (cond ((and keys (atom keys)) (list keys))
((length= keys 1) keys)
(t (completing-read-multiple "SSH Keys: " keys))))
(when-let ((key (expand-file-name key "~/.ssh"))
((file-readable-p key)))
(with-temp-buffer
(insert-file-contents (concat key ".pub"))
(call-process "ssh-add" nil (current-buffer) nil "-L")
(unless (search-backward
(buffer-substring-no-properties (point) (line-end-position))
nil t)
(async-shell-command (format "ssh-add %s" key)
(current-buffer))
(setq key (get-buffer-process (current-buffer)))
(while (accept-process-output key)))))))
(defun init-interfaces-vc-add-ssh-key (&rest _)
"Ensure the SSH key of the current repository has been added to the agent."
(when-let ((root (vc-git-root default-directory))
(remote (vc-git-repository-url root))
((string-match-p "^\\(\\(rsync\\|ssh\\)://\\)?\\([[:alnum:]][[:alnum:]._-]+@\\)?[[:alnum:]+.-]+:.*" remote)))
(init-interfaces-ssh-add
(seq-remove (lambda (s) (string-match-p ".pub\\'" s))
(directory-files
(expand-file-name "~/.ssh") nil
(format ".*\\(git\\.\\)?%s.*\\'"
(string-trim-left
(cadr (string-split remote "[@:]"))
"git\\.")))))))
(advice-add 'vc-git--pushpull :before 'init-interfaces-vc-add-ssh-key)
Using Termux:API from Emacs
If you install Po Luās version of Termux (which is just Termux signed with Emacsā key) you wonāt be able to install Termux:API from F-Droid, as it needs to be signed by the same key used to sign Termux.
You can use ājarsignerā and āzipalignā to resign the APK (get the keystore from Emacsā source repository under the ājavaā directory, use the password āemacs1ā with alias āEmacs keystoreā).
Also, when running āemacsclientā through Termux (whether directly or through the API app) you need to ensure Termux can find the server socket.
The easiest way to do this is to set TMPDIR to Emacsā TMPDIR (ā/data/data/org.gnu.emacs/cacheā by default).
Here is an example of using ātermux-notificationā to display an interactive media notification for EMMS:
(defun init-emms-termux-notify-stopped ()
"Remove EMMS' Android notification."
(start-process "init-emms-notification-remove" nil
"termux-notification-remove" "emms"))
(add-hook 'emms-player-stopped-hook
#'init-emms-termux-notify-stopped)
(defun init-emms-termux-notify-killed ()
"Remove EMMS' Android notification (synchronously)."
(call-process "termux-notification-remove" nil nil nil "emms"))
(add-hook 'kill-emacs-hook #'init-emms-termux-notify-killed)
(defun init-emms-termux-notify-started ()
"Display a media notification for EMMS through Termux:API."
(let ((track (emms-playlist-current-selected-track))
(tmp (getenv "TMPDIR"))
(client (executable-find emacsclient-program-name)))
(start-process "init-emms-notification" nil "termux-notification"
"--id" "emms" "--alert-once" "--priority" "max"
"--on-delete"
(format "TMPDIR=%s %s --eval '(emms-stop)'" tmp client)
"--title" (emms-track-get track 'info-title)
"--content" (format "%s - %s"
(emms-track-get track 'info-artist)
(emms-track-get track 'info-album))
"--image-path"
(if (eq (emms-track-type track) 'file)
(expand-file-name "cover_med.png"
(file-name-directory
(emms-track-name track)))
(expand-file-name
(locate-user-emacs-file "images/emms.png")))
"--type" "media" "--media-next"
(format "TMPDIR=%s %s --eval '(emms-next)'"
tmp client)
"--media-previous"
(format "TMPDIR=%s %s --eval '(emms-previous)'"
tmp client)
"--media-pause"
(format "TMPDIR=%s %s --eval '(emms-pause)'"
tmp client)
"--media-play"
(format "TMPDIR=%s %s --eval '(emms-pause)'"
tmp client))))
(add-hook 'emms-player-started-hook #'init-emms-termux-notify-started)
Performing actions with the touchscreen
First of all, the following snippet will add a toggle for the on-screen keyboard to the Menu-bar -> Options -> Show/Hide menu:
(easy-menu-add-item nil '("Options" "Show/Hide")
["Keyboard" (if touch-screen-display-keyboard
(setopt touch-screen-display-keyboard nil)
(setopt touch-screen-display-keyboard t))
:visible (eq system-type 'android)
:style toggle
:selected touch-screen-display-keyboard])
Other than that, to bind commands to a touchscreen ātapā, use āmouse-1ā (which is translated automatically from the touchscreen events), and for a āholdā use ātouchscreen-holdā.
Prefer using ESC over Alt if possible, since Android doesnāt seem to support many Alt+ combinations (for example, M-:, āeval-expressionā). This is possibly only an issue with software keyboards (I havenāt tested with a hardware keyboard, I use Emacs with the Unexpected Keyboard).
Other stuff
Enable āconfirm-kill-emacsā to get a popup allowing you to prevent Emacs from being killed by the system in the background.
Sending mail using Emacsā built-in smtpmail package doesnāt work for me for some reason; I suspect this is solvable but I havenāt figured it out yet. Termux doesnāt seem to include a āsendmailā (but I havenāt looked too hard) so currently I have to use a separate app to send mail from my phone (oh no). This works, my configuration was wrong.
I made one attempt to compile Jinxā dynamic module and gave up when it failed. I highly suspect this is also possible. I got around to trying Jinx again and it just worked.
Many commands which read a character fail on Android since in most situations the ācharacterā returned will be ātext-conversionā. Every few times I update the app I notice a new command which now works correctly, so it seems there is an ongoing effort to fix these commands. I will say though, my own package Window Commander doesnāt really work on Android due to this and I tried very hard to make it work recently, so donāt be surprised by issues in external packages (as most probably arenāt even considering this yet).
Most of the code in this post (or code similar to it) can be found in my repository: https://git.sr.ht/~dsemy/emacs-config