Tweaking Emacs on Android via Termux: xclip, xdg-open, syncthing conflicts
Posted: - Modified: | android, emacsUpdate: Fixed reference to termux.properties
I’m planning to leave my laptop at home during our 3-week trip, so I’ve been shifting more things to my phone in preparation. Orgzly is handy for reviewing my agenda and adding items on the go, but nothing beats Emacs for full flexibility. Runnng Emacs via Termux works pretty well. I can resize by pinch-zooming, scroll by swiping, and even enter in more text by swiping right on the row of virtual keyboard buttons.
Here’s what I’ve configured in ~/.termux/termux.properties
:
extra-keys = [['ESC','/','-','HOME','UP','END','PGUP','F5'],['TAB','CTRL','ALT','LEFT','DOWN','RIGHT','PGDN','F6']]
and here’s what that looks like:
I patched my Termux to allow the use of function keys in the extra keys shortcut bar. It’s been merged upstream, but the new version hasn’t been released yet, so I’m still running the one I compiled from source. It would be nice to fix accidental keypresses when swiping extra keys to get to the input field, but that can wait a bit.
I set up Syncthing to synchronize files with my server and laptop, Termux:API to make storage accessible, and symlinks in my home directory to replicate the main parts of my setup. I set up Orgzly to auto-sync with a local repository (synchronized with my server and laptop via Syncthing) and the same Org files set up in my agenda in Emacs on Termux. That way, I can hop between Orgzly and Emacs as quickly as I want. Here are a few tweaks that made Emacs even better.
First, a little bit of code for phone-specific config, taking advantage of the weird automatic username I have there.
(defun my/phone-p () (and (equal (system-name) "localhost") (not (equal user-login-name "sacha"))))
For Emacs News, I wanted to be able to easily open Org Mode links to webpages by tapping on them. This code makes that work (and in general, anything that involves opening webpages):
(setq browse-url-browser-function 'browse-url-xdg-open)
This piece of code cleans up my Inbox.org file so that it’s easier to skim in Orgzly. It archives all the done tasks and sorts them.
(defun my/org-clean-up-inbox () "Archive all DONE tasks and sort the remainder by TODO order." (interactive) (with-current-buffer (find-file my/org-inbox-file) (my/org-archive-done-tasks 'file) (goto-char (point-min)) (if (org-at-heading-p) (save-excursion (insert "\n"))) (org-sort-entries nil ?p) (goto-char (point-min)) (org-sort-entries nil ?o) (save-buffer))) (defun my/org-archive-done-tasks (&optional scope) "Archive finished or cancelled tasks. SCOPE can be 'file or 'tree." (interactive) (org-map-entries (lambda () (org-archive-subtree) (setq org-map-continue-from (outline-previous-heading))) "TODO=\"DONE\"|TODO=\"CANCELLED\"" (or scope (if (org-before-first-heading-p) 'file 'tree))))
I also sometimes wanted to copy and paste between Termux and Emacs by using the keyboard, so I submitted a patch for xclip.el so that it would detect and work with termux-clipboard-get/set
. That code is now in xclip 1.9 in ELPA, so if you M-x package-install
xclip, you should be able to turn on xclip-mode
and have it copy and paste between applications. In my config, that looks like:
(when (my/phone-p) (use-package xclip :config (xclip-mode 1)))
Because I use Orgzly and Termux to edit my Org files and I also edit the files on my laptop, I occasionally get synchronization errors. I came across this handy bit of code to find Syncthing conflicts and resolve them. I just had to change some of the code (in bold below) in order to make it work, and I needed to pkg install diffutils
to solve the diff errors. Here’s the fixed code below, along with a convenience function that checks my Orgzly directory:
(defun my/resolve-orgzly-syncthing () (interactive) (ibizaman/syncthing-resolve-conflicts "~/cloud/orgzly")) (defun ibizaman/syncthing-resolve-conflicts (directory) "Resolve all conflicts under given DIRECTORY." (interactive "D") (let* ((all (ibizaman/syncthing--get-sync-conflicts directory)) (chosen (ibizaman/syncthing--pick-a-conflict all))) (ibizaman/syncthing-resolve-conflict chosen))) (defun ibizaman/syncthing-show-conflicts-dired (directory) "Open dired buffer at DIRECTORY showing all syncthing conflicts." (interactive "D") (find-name-dired directory "*.sync-conflict-*")) (defun ibizaman/syncthing-resolve-conflict-dired (&optional arg) "Resolve conflict of first marked file in dired or close to point with ARG." (interactive "P") (let ((chosen (car (dired-get-marked-files nil arg)))) (ibizaman/syncthing-resolve-conflict chosen))) (defun ibizaman/syncthing-resolve-conflict (conflict) "Resolve CONFLICT file using ediff." (let* ((normal (ibizaman/syncthing--get-normal-filename conflict))) (ibizaman/ediff-files (list conflict normal) `(lambda () (when (y-or-n-p "Delete conflict file? ") (kill-buffer (get-file-buffer ,conflict)) (delete-file ,conflict)))))) (defun ibizaman/syncthing--get-sync-conflicts (directory) "Return a list of all sync conflict files in a DIRECTORY." (directory-files-recursively directory "\\.sync-conflict-")) (defvar ibizaman/syncthing--conflict-history nil "Completion conflict history") (defun ibizaman/syncthing--pick-a-conflict (conflicts) "Let user choose the next conflict from CONFLICTS to investigate." (completing-read "Choose the conflict to investigate: " conflicts nil t nil ibizaman/syncthing--conflict-history)) (defun ibizaman/syncthing--get-normal-filename (conflict) "Get non-conflict filename matching the given CONFLICT." (replace-regexp-in-string "\\.sync-conflict-.*\\(\\..*\\)$" "\\1" conflict)) (defun ibizaman/ediff-files (&optional files quit-hook) (interactive) (lexical-let ((files (or files (dired-get-marked-files))) (quit-hook quit-hook) (wnd (current-window-configuration))) (if (<= (length files) 2) (let ((file1 (car files)) (file2 (if (cdr files) (cadr files) (read-file-name "file: " (dired-dwim-target-directory))))) (if (file-newer-than-file-p file1 file2) (ediff-files file2 file1) (ediff-files file1 file2)) (add-hook 'ediff-after-quit-hook-internal (lambda () (setq ediff-after-quit-hook-internal nil) (when quit-hook (funcall quit-hook)) (set-window-configuration wnd)))) (error "no more than 2 files should be marked"))))
If you use Emacs on Android via Termux, I hope some of these tweaks help you too!
9 comments
smitty
2019-07-13T21:22:31ZI also have a bluetooth keyboard. I just use git and push files off to a private github repo.
Also sideloaded on a Kindle Fire, which has way more real estate than my Galaxy S8 phone.
sachac
2019-07-15T21:22:18ZThat sounds like a nice setup! :)
razorgoto
2019-07-19T11:47:48ZI did the same and it works well.
Jean Jordaan
2019-08-02T06:07:43ZOne vote for Logitech K380, just because I use it daily and it works great.
ShalokShalom
2019-09-04T07:13:17ZAnd why are you not using the official physical keyboard from Samsung?
https://www.samsung.com/us/...
Rafael
2019-07-14T01:42:18ZPlease note that the config file is called termux.properties (not termux-properties).
sachac
2019-07-15T21:21:55ZWhoops, thanks for catching that, Rafael!
ShalokShalom
2019-09-04T07:07:49ZThats golden.
Will try out this one with the physical keyboard on my S8.
I looked for something like the extra key row for quite some time, since symbols such as = [ ] { } and so on, happen to be not on it and I want to use it to code, just to see Emacs supports it out of the box so no separate application is needed.
Javierinsitu
2020-02-14T20:12:45Zhello,
Can you sync Emacs on Termux via webdav protocol (like Orgzly)?
Great work, BTW :D .