I mainly use vterm inside of Emacs, usually via Equake, but sometimes I do want to spawn a terminal outside of Emacs, and so I’ve been curious about the properties of different terminals, including the ability to be used across a wide range of hardware.
A few weeks ago, I came across Zutty, which describes itself as:
Zutty is a terminal emulator for the X Window System, functionally similar to several other X terminal emulators such as xterm, rxvt and countless others. It is also similar to other, much more modern, GPU-accelerated terminal emulators such as Alacritty and Kitty. What really sets Zutty apart is its radically simple, yet extremely efficient rendering implementation, coupled with a sufficiently complete feature set to make it useful for a wide range of users. Zutty offers high throughput with low latency, and strives to conform to relevant (published or de-facto) standards.
Zutty is written in straightforward C++ and only relies on OpenGL ES 3.1 for rendering…
The Zutty page includes a link to a blog post “A totally biased comparison of Zutty (to some better-known X terminal emulators)", which includes some interesting discussion of features of different terminal emulators, including comparison of VT support levels, and discussion of why this could be important. A number of terminal emulators, perhaps leaning towards “minimalism”, only implement support up to VT220, or even lower, which creates inefficiencies in certain cases:
The difference between implementing a VT400 or VT500 terminal (provided the implementations are correct) is relatively inconsequential. The same cannot be said of the gap between these and VT220-level terminals. Programs running in a less capable virtual terminal must sometimes use longer series of basic escape sequences to achieve the results of a fewer number of more modern ones. For example, the DECLRMM control sequence (set left-right margin mode) is available from the VT420 and up, and will be used to restrict scrolling to the active part of two horizontally split tmux panes. On less capable terminals, tmux is forced to perform more work to achieve the same result. This is quite similar to how extended instructions on modern CPUs allow machine code to be more efficient than code compiled for an older machine. In other words, not implementing a modern VT variant goes directly counter to greater efficiency. And there is yet another downgrade from VT220 to those terminals that only claim to support VT100/VT102.
It is surprising how unambitious the newer terminals Alacritty and Kitty are in this regard. I would have hoped that new, supposedly state-of-the-art entrants would take the effort to add support for modern VT standards. Switching from xterm to Alacritty (or any other modern terminal) should not be a major downgrade.
Inspired by this, I copy/supplement the table on that page with some additional comparison information, and a few additional terminal emulators. As per the Zutty post, I use VTTEST (originally written in 1983-1985 by Per Lindberg at the Stockholm University Computing Center; in 1996 a new version written by Thomas E. Dickey, the maintainer of xterm). to check for reported VT support level. The below table includes also whether the terminal emulator is GPU-accelerated or not, and what the mininum OpenGL hardware support is, as well as what display server is targeted (X11 or Wayland or both).
program | tested version | VT self-id | GPU-accel | minimum OpenGL | DS |
---|---|---|---|---|---|
gnome-terminal | 3.30.2 | VT525 | no? | n/a | both |
zutty | 0.6 | VT520 | yes | ES 3.1 | X11 |
terminology | 1.12.1 | VT510 | when possible | n/a | X11 |
wezterm | 20220624 | VT500 | yes | 3.3 (?) | both |
xterm | 344 | VT420 | no | n/a | X11 |
kitty | 0.19.3 | VT220 | yes | 3.3 | both |
foot | 1.12.1 | VT220 | no | n/a | wayland |
alacritty | 0.4.3 | VT102 | yes | 3.3 | both |
st | 0.8.2 | VT102 | no | n/a | X11 |
urxt | v9.22 | VT102 | no | n/a | X11 |
(lib)vterm | 0.1.4 | VT100/VT102 | n/a | n/a | n/a |
Foot is another interesting new terminal emulator - targetting Wayland only, and not GPU-accelerated, but with some design choices that make it faster than GPU-accelerated terminal emulators in some cases, as described in the wiki “When is foot fast, and when is it not?". One of the things it does is “damage tracking”, i.e. only rendering cells that have been updated, whereas e.g. alacritty rerenders everything (though, as noted, “Alacritty renders empty cells really fast”). But foot only supports up to VT220.
The terminal emulator I end up using most is based on libvterm, this is the library underlying Emacs’ vterm, and claims to “implement[] a VT220 or xterm-like terminal emulator”, but VTTEST seems to report as VT102/VT100, at least inside of Emacs.
There’s no clear “best choice” here, even putting vterm aside (vterm
isn’t particularly fast, at least inside of Emacs (though it’s faster
than other Emacs terminal emulator choices like ansi-term
), doesn’t
have “very high” VT support; but it runs inside in Emacs, which offers
me a lot of advantages). Though alacritty, st, and urxvt implement
only up to VT102 level features.
Foot, Zutty, Kitty, and Alacritty are all fast, at least sometimes (the Zutty biased comparison post above offers some additional discussion on this, not including foot). They’re all limited in various ways (hardware or display server - though Zutty apparently has experimental support for software rendering), with only Zutty having “very high” VT support. (And Zutty has some bells-and-whistles limitations: no plans for transparency support, ligatures, bitmap images (e.g. SIXEL).)
Foot runs on pretty much any hardware and has some nice features, but is Wayland only (and limited in VT level support).
Kitty and Alacritty require OpenGL 3.3 (on Intel hardware, this means at least Intel HD Graphics 3000 (so 2011-era **20 ThinkPads or later)).
Zutty “runs on a wider range of graphics hardware by virtue of only requiring OpenGL ES as opposed to “desktop” OpenGL, and its resource demands are otherwise minimal”, but it requires ES 3.1, which isn’t supported by, for instance, Intel HD Graphics 3000.
Terminology, a terminal emulator which doesn’t seem to get talked about as much, actually turns out to be a good choice across different hardware. It can use GPU-acceleration, rendering using OpenGL or OpenGL-ES2, but this is not a hard requirement. It offers VT510 level support, and has lots of bells and whistles. I haven’t seen many speed test comparisons with it, but in use it feels fast. (No native Wayland support though, if that matters to you.)
[Edit: Added Wezterm (another one written in Rust) which I haven’t really had a chance to play with much, but it has good VT feature levels. It seems to have the same OpenGL requirements as Kitty or Alacritty.]
Still, I’m likely to be mainly using vterm and eshell. Because Emacs.
]]>The frequent label “Quake-style” does seem to suggest at least part of the origin in the computer game Quake (1996), or at least that the drop-down console in Quake was the most prominent/remembered example of this sort of UI.[0]
On Linux/Unix, a number of terminal emulators have been designed with Quake-style drop-down interaction, and other platforms now seem to have these as well. As far as I can tell, drop-down terminal emulators first appeared on Linux, and probably in the early 2000s. (Though I wonder about this, both the KDE project started in 1996 (same year as Quake) and the GNOME project shortly after and it almost feels like someone must have thought about doing something of this sort between 1996-2000.)
The earliest surviving drop-down terminals on Linux seem to be Yakuake (QT/KDE), Tilda (GTK), and Guake (GTK).
Yakuake seems to have been released in the early 2000s, certainly by 2005[1], but perhaps a bit earlier. Tilda was released by 2006[2]. Guake was explicitly inspired by the original author seeing Yakuake[3], itself being released in 2007.
But “Yakuake” stands for “yet another Kuake”, and indeed the earliest drop-down terminal I can find on Linux is Kuake, released probably by 2003, with the last update in 2004[4].
Happy to hear about further history of early drop-down Quake-style terminals on any platform.
[0] https://en.wikipedia.org/wiki/Console_(computer_games)
[1] https://web.archive.org/web/20051016011941/http://yakuake.uv.ro/
[2] https://web.archive.org/web/20061028182927/http://tilda.sourceforge.net/wiki/index.php/Main_Page
[3] https://news.ycombinator.com/item?id=22524552
[4] https://web.archive.org/web/20040219101722/http://www.nemohackers.org/kuake.php
]]>First, the stock configuration has a horrible touchpad - which shouldn’t matter if you don’t use the touchpad, but the horribleness of it is that the physical buttons that should be on the top of the touchpad, and are on the touchpads of models preceding and following the **40 line, are not there. But one can replace it, and so I did.
The T440p is nice in that servicing the fan and other internals of the machine is a relatively easy affair compared to say an X230. Just undo two screws on the bottom of the laptop and slide off the back panel, and you have access to memory, drives, the CPU, and so on. And so swapping in a different CPU was really easy and painless.
On the other hand, changing the touchpad was a very involved affair. But it can be done.
However, still the mouse-cursor experience for the machine continued to be horrible. Firstly, there was significant “drift” of the TrackPoint. I.e., even once pressure is released, it keeps moving, for a long time.
However, this ends up being solvable via
sudo -s echo 'ACTION=="add",SUBSYSTEM=="input",ATTR{name}=="TPPS/2 IBM TrackPoint",ATTR{device/drift_time}="30"' > /etc/udev/rules.d/10-trackpoint.rules
(If you’re trying to do this in Guix, something like:
(define %trackpoint-drift-rule
(udev-rule
"10-trackpoint.rules"
(string-append "ACTION==\"add\",SUBSYSTEM==\"input\",ATTR{name}==\"TPPS/2 IBM TrackPoint\",ATTR{device/drift_time}=\"25\""))
(define %my-desktop-services
(cons (udev-rules-service 'trackpoint-drift %trackpoint-drift-rule)
(modify-services %desktop-services
....) ; other modifications here
))
(operating-system
....
(services
(append
(list
(service openssh-service-type)
(service cups-service-type)
(service nix-service-type)
....)
%my-desktop-services))
....)
instead.)
But, unfortunately, this doesn’t solve what is actually the most horrible issue: the mouse cursor sometimes, when in use, just teleports around the edges of the screen and starts randomly clicking on things.
I finally turned up some discussion of this issue (though not for the T440p specifically) at: https://bugzilla.kernel.org/show_bug.cgi?format=multiple&id=209167 (The discussion suggests that it should somehow be solved in the kernel, but that is not my experience, even running kernel 5.17.13.)
And found that adding psmouse.proto=imps
to the kernel arguments and
disabling the touchpad in the stock BIOS solves the “possessed mouse
cursor” issue. For better or worse, it seem to make the TrackPoint be
detected as a generic PS/2 mouse. Which means that drift_time
can no
longer be set, and I do get a little bit of drift from time to time,
but it’s not too bad and certainly is far less maddening than the
“possessed mouse cursor” behaviour.
For Guix, the way to implement this is something along the lines of:
(operating-system
...
(kernel-arguments (cons* "modprobe.blacklist=pcspkr,snd_pcsp" "psmouse.proto=imps" "acpi_osi=Linux" %default-kernel-arguments))
...)
(The other kernel arguments are simply the other ones I use, and are not directly connected with this issue.)
Depending on the model of ThinkPad and/or the environment, using
psmouse.proto=bare
instead may work better (see discussion at:
https://www.reddit.com/r/thinkpad/comments/v7vn0o/thinkpad_t440p_trackpoint_occasionally_going_crazy/icjkk0o/
).
So, perhaps not an entirely satisfactory solution, this impish exorcism, but better than alternatives.
]]>Jeff Kowalski added code for a “close Equake frame on loss of focus feature” (similar to the Tilda feature) and a number of bug fixes and code-cleanup.
Further: I’m (half-)jokingly calling this the Geas on Gnomish Smiths release as I’ve finally figured out how to make it behave properly under GNOME Shell Wayland.
To my knowledge, the only other currently working drop-down terminal for GNOME Shell Wayland is the JavaScript GNOME extension ddterm. Wayland (at least GNOME Shell’s Wayland) seems very restrictive in how non-user-initiated events can affect window properties. E.g., it is difficult to programmatically affect window focus in GNOME Shell under Wayland. The default “hide window” behaviour of Equake doesn’t work.
However, I did have a legacy feature where closing/hiding Equake involved destroying the frame rather than hiding it. This is somewhat complicated, as re-invoking Equake then involves creating a new frame and restoring the tabs and buffer history. This is how Equake originally worked, but it is simpler (and slightly faster) just to hide the frame on “close” and then unhide it on “re-open”, and this became the default behaviour for Equake some time ago. But I left the old code in place as another option.
However, various changes apparently ended up with the restoration behaviour not working quite properly. I have fixed this (more or less, see https://gitlab.com/emacsomancer/equake/-/issues/26). With that fix in place, we can then trick GNOME Shell under Wayland into behaving properly. Rather than hiding the Equake frame on “close”, we destroy it and then recreate it on “open”. This involves quite a bit of trickery behind the scenes. Essentially, opening a new window under GNOME Shell Wayland can result in that window being placed on top of any existing windows (though to do it properly, employing a it of Elisp (select-frame-set-input-focus (selected-frame))
seems to result in the new Emacs client frame being on top (and focussed) [this is a potentially useful trick for opening regular new Emacsclient frames as well].
But, the usual way that Equake frames are created internally is via
Emacs make-frame
, which doesn’t automatically focus the new
frame. So rather than doing this, we need to have the user call emacsclient -c
and then transform that frame into an Equake frame. A new Equake function equake--transform-existing-frame-into-equake-frame
takes care of this. The transformation and the old code restoring tabs and history doesn’t quite take care of everything though as the point/cursor is left by default at the top of the frame, which is not the user-expected behaviour. So an addition bit of elisp (goto-char (1- (point-max)))
generally moves the point/cursor to the end of the buffer where the user might expect it.
So I have a bit of shell script as a helper function which is what one should create a keybinding for in GNOME Shell Wayland:
#!/bin/sh
equakestatus=$(emacsclient -n -e '(frame-live-p (alist-get (equake--get-monitor) equake--frame))')
if [ "$equakestatus" = "nil" ]; then
emacsclient -c -e "(progn (select-frame-set-input-focus (selected-frame))
(equake--transform-existing-frame-into-equake-frame)
(goto-char (1- (point-max))))"
else
emacsclient -n -e '(progn (setq equake-use-frame-hide nil)
(equake-invoke))'
fi
This checks if there is a live Equake frame; if there is, it is hidden; otherwise a new frame is created and transformed.
Finally, in order to be able to get the “Always on Top” behaviour, the new Equake frame transmutation function calls (shell-command "wmctrl -r :ACTIVE: -b toggle,above")
(so one needs wmctrl
installed), which seems to trigger the “Always on Top” feature at least for Xwayland windows.
And, presto, change-o, voilà, we have a version of Equake which exhibits its normal X11 behaviour in GNOME Shell Wayland.
(As long as Emacs is run as an Xwayland application; otherwise the “always on top” behaviour doesn’t work properly.)
]]>I’ve played with the Tridactyl extension for a few years, but Firefox
limitations in part have kept me from using it more extensively. But I
stumbled across a relatively easy way of “unreserving” reserved
Firefox keys (like <C-p>
, <C-f>
etc.) via an offhand comment at
Lobste.rs, which points to a 2 line bit of code to remove reserved
keybindings. I repeat it here:
#!/usr/bin/env sh
sudo perl -i -pne 's/reserved="true"/ /g' /usr/lib/firefox/browser/omni.ja
find ~/.cache/mozilla/firefox -type d -name startupCache | xargs rm -rf
This now frees up any key as a potential target for Tridactyl bindings. It’s still not perfect: the ability to remap keys in StumpWM is still easier, but it gives me a better possibility for customisation on machines not running StumpWM.
One thing I’ve added to all machines is a better way of dealing with
Tridactyl’s function to edit browser text areas in an external browser
(bound to C-i
by default). In Tridactyl I run in the command area
:set editorcmd emacsclient -c %f
. This opens up an Emacs frame
connected to the default daemon. However, I wanted a quick way of
“entering”/“saving” the text and closing the frame. An ad-hoc major
mode seemed like an easy way, allowing for save and close-frame with
C-c C-c
:
;; tridactyl mode
(define-derived-mode tridactyl-mode text-mode "tridactyl"
"A major mode to edit tridactyl-spawned 'editor' text.")
(define-key tridactyl-mode-map (kbd "C-c C-c")
'(lambda ()
"save and exit quickly"
(interactive)
(save-buffer)
(clipboard-kill-ring-save (point-min) (point-max)) ; just in case
(sleep-for 1)
(delete-frame)))
(provide 'tridactyl-mode)
However, the editorcmd interface seems to have problems running -e '(func)'
type calls (maybe there’s a way to get it to work, but it
wasn’t obvious to me), so I additionally added an auto-mode-alist
regexp to auto-detect tridactyl temporary files:
(add-to-list 'auto-mode-alist '("/tmp/tmp.*\\.txt$" . tridactyl-mode))
And this seems to work well. I still can’t quite get in-Firefox text editing to have Emacs-style bindings, but for anything non-trivial, I can just edit it in Emacs directly in this way.
(Addendum: though I note that at least a number of text boxes seem to require some additional input afterwards to “fix” the editorcmd text; otherwise they revert back to their previous state. A bit irritating. I’ve added a save-to-clipboard bit in the function above as a safety precaution against lost text.)
]]>Since Org-roam v2 creates a top properties drawer (with an :ID:
tag) anyway, it is nice to stick other information there as well. Specifically, information that could be useful in some situation, but which usually we don’t want to see, like :AUTHOR:
(it’s probably you, and you know who you are), :CREATION_TIME:
(and why not use Unix epoch time?), and so on. I have org drawers fold themselves automatically, so the normally-useless information doesn’t distract me.
We can do this by leveraging Org-roam’s org-roam-capture-new-node-hook
, and some org-roam-add-property
function calls, as below.
But, while we’re at it, we might also record where a note was made from. There are a number of ways we might do this, but an easy one (only requiring curl
and an active Internet connection) is using ipinfo.io. curl ipinfo.io
will give you a bunch of information in JSON format about your internet provider, including latitude and longitude, which will likely be at least somewhere near your present location. And curl ipinfo.io/loc
will return just latitude,longitude.
(defun bms/add-other-auto-props-to-org-roam-properties ()
;; if the file already exists, don't do anything, otherwise...
(unless (file-exists-p (buffer-file-name))
;; if there's also a CREATION_TIME property, don't modify it
(unless (org-find-property "CREATION_TIME")
;; otherwise, add a Unix epoch timestamp for CREATION_TIME prop
;; (this is what "%s" does - see http://doc.endlessparentheses.com/Fun/format-time-string )
(org-roam-add-property
(format-time-string "%s"
(nth 5
(file-attributes (buffer-file-name))))
"CREATION_TIME"))
;; similarly for AUTHOR and MAIL properties
(unless (org-find-property "AUTHOR")
(org-roam-add-property roam-user "AUTHOR"))
(unless (org-find-property "MAIL")
(org-roam-add-property roam-email "MAIL"))
;; also add the latitude and longitude
(unless (org-find-property "LAT_LONG")
;; recheck location:
(bms/get-lat-long-from-ipinfo)
(org-roam-add-property (concat (number-to-string calendar-latitude) "," (number-to-string calendar-longitude)) "LAT-LONG"))))
;; hook to be run whenever an org-roam capture completes
(add-hook 'org-roam-capture-new-node-hook #'bms/add-other-auto-props-to-org-roam-properties)
;; function to find latitude & longitude
;; (requires curl to be installed on system)
(setq calendar-latitude 0)
(setq calendar-longitude 0)
(defun bms/get-lat-long-from-ipinfo ()
(let*
((latlong (substring
(shell-command-to-string "curl -s 'ipinfo.io/loc'")
0 -1))
(latlong-list (split-string latlong ",")))
(setq calendar-latitude (string-to-number (car latlong-list)))
(setq calendar-longitude (string-to-number (cadr latlong-list)))))
You might also calculate/set calendar-latitude
and calendar-longitude
in other ways. Including just hard-coding them for stationary machines. On Android, we could in theory make use of the Termux command termux-location
, which queries the device’s GPS. But unfortunately it doesn’t always work (if it can’t find a good connection to a GPS satellite) and even when it does work it’s slow, so it’s not something you’d want to call every time you made a note. GeoClue would be another possible source.
(If you’re using a VPN, you’ll want to escape from it somehow to get something closer to your real location. How you do this will vary based on your VPN provider and other factors. (If you’re calling from Emacs, and you use something like Mullvad, you may want to revise the shell-command-to-string
to call up a bash session/script, then exclude that specific bash session/script from the VPN, and then call curl
, so that the call references your “real” IP. E.g. if you’re using Mullvad, then:
#!/bin/bash
PID=`echo $$`
mullvad split-tunnel pid add "${PID}"
curl ipinfo.io/loc # for lat/long ; `curl ipinfo.io` for full info
might give you a start on something.))
Let me know if you think of other properties that could be useful to automatically add to Org-roam file properties.
]]>It’s pretty useful to be able to have access to these notes, and be able to quickly add notes, on mobile as well. I thought it might be useful to include here some notes on how to do, since (especially since v2 of Org-roam) there are some hurdles.
On Android/LineageOS install the F-Droid app store, and then from
there install Termux. Open Termux and install four things we’ll need
(strictly speaking you don’t need curl and ripgrep, but they’ll
be useful): Emacs, sqlite, curl, and ripgrep via pkg install emacs sqlite curl ripgrep
.
You can then open up Emacs via emacs
and get started.
I include here a commented partial version of my ~/.emacs.d/init.el
configuration file for my Termux/Emacs Org-roam setup:
;; BASIC SETUP:
;; package setup - bootstrap the package system
(require 'package)
(setq package-enable-at-startup nil)
(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")
(setq package-archives
'(("GNU ELPA" . "https://elpa.gnu.org/packages/")
("ORG" . "https://orgmode.org/elpa/")
("MELPA Stable" . "https://stable.melpa.org/packages/")
("MELPA" . "https://melpa.org/packages/"))
package-archive-priorities
'(("ORG" . 20)
("MELPA" . 15)
("MELPA Stable" . 10)
("GNU ELPA" . 5)))
(package-initialize)
;; Bootstrap `use-package'
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(eval-when-compile
(require 'use-package))
;; for Termux-specific things; useful if you want to share
;; configs across platforms
(defvar termux-p
(not (null (getenv "ANDROID_ROOT")))
"If non-nil, GNU Emacs is running on Termux.")
(when termux-p
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package)))
;; This makes Emacs in Termux use your Android browser for opening urls
(setq browse-url-browser-function 'browse-url-xdg-open)
;; mouse
;; enable mouse reporting for terminal emulators
;; this lets you scroll around by swiping
(unless window-system
(xterm-mouse-mode 1)
(global-set-key [mouse-4] (lambda ()
(interactive)
(scroll-down 1)))
(global-set-key [mouse-5] (lambda ()
(interactive)
(scroll-up 1))))
;; ORG
(use-package org
:ensure t
:ensure org-plus-contrib
:init
(setq org-src-fontify-natively t)
:config
;; (add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode))
(define-key org-mode-map (kbd "M-p") 'org-metaup)
(define-key org-mode-map (kbd "M-n") 'org-metadown)
(setq org-catch-invisible-edits 'show-and-error)
(setq org-cycle-separator-lines -1)
(setq org-return-follows-link t)
(setq org-export-with-toc 'nil)
(setq org-startup-folded 'content)
(setq org-ellipsis "⇣")
;; **** use regular android apps to view pdfs & images *****
(when termux-p
(add-to-list 'org-file-apps '("\\.pdf\\'" . "termux-open %s"))
(add-to-list 'org-file-apps '("\\.png\\'" . "termux-open %s"))
(add-to-list 'org-file-apps '("\\.jpg\\'" . "termux-open %s"))
(add-to-list 'org-file-apps '("\\.jpeg\\'" . "termux-open %s")))
;; needed for <s etc. expansion of code-blocks
(require 'org-tempo))
;; define our Org-roam user and their email (set to your desired name/email)
(defvar roam-user "Some User"
"The name of the Org-roam note author.")
(defvar roam-email "roman@mode.org"
"The public email of that author.")
(setq org-roam-v2-ack t)
;; we need this package for v2 of Org-oram
(use-package emacsql-sqlite3
:ensure t)
;; If you've replicated my setup; otherwise change to the Termux
;; local path.
(setq org-roam-directory (file-truename "~/Documents/Org/org-roam/"))
;; org-roam
(use-package org-roam
:ensure t
:custom
(setq org-roam-db-location (file-truename "~"))
(org-roam-directory (file-truename "~/Documents/Org/org-roam/"))
:bind (("C-c n l" . org-roam-buffer-toggle)
("C-c n f" . org-roam-node-find)
("C-c n r" . org-roam-graph)
("C-c n i" . org-roam-node-insert)
("C-c n c" . org-roam-capture)
("C-c n g" . org-id-get-create)
;; Dailies
("C-c n n" . org-roam-dailies-capture-today)
("C-c n d" . org-roam-dailies-goto-today) ; find toDay
("C-c n v" . org-roam-dailies-goto-date)
("C-c n f" . org-roam-dailies-goto-next-note)
("C-c n b" . org-roam-dailies-goto-previous-note))
:config
;; this is a chunglak's hack to get sqlite to work on Android with org-roam v2:
;; from: https://github.com/org-roam/org-roam/issues/1605#issuecomment-885997237
(defun org-roam-db ()
"Entrypoint to the Org-roam sqlite database.
Initializes and stores the database, and the database connection.
Performs a database upgrade when required."
(unless (and (org-roam-db--get-connection)
(emacsql-live-p (org-roam-db--get-connection)))
(let ((init-db (not (file-exists-p org-roam-db-location))))
(make-directory (file-name-directory org-roam-db-location) t)
(let ((conn (emacsql-sqlite3 org-roam-db-location)))
(emacsql conn [:pragma (= foreign_keys ON)])
(set-process-query-on-exit-flag (emacsql-process conn) nil)
(puthash (expand-file-name org-roam-directory)
conn
org-roam-db--connection)
(when init-db
(org-roam-db--init conn))
(let* ((version (caar (emacsql conn "PRAGMA user_version")))
(version (org-roam-db--upgrade-maybe conn version)))
(cond
((> version org-roam-db-version)
(emacsql-close conn)
(user-error
"The Org-roam database was created with a newer Org-roam version. "
"You need to update the Org-roam package"))
((< version org-roam-db-version)
(emacsql-close conn)
(error "BUG: The Org-roam database scheme changed %s"
"and there is no upgrade path")))))))
(org-roam-db--get-connection))
(defun org-roam-db--init (db)
"Initialize database DB with the correct schema and user version."
(emacsql-with-transaction db
(emacsql db "PRAGMA foreign_keys = ON") ;; added
(emacsql db [:pragma (= foreign_keys ON)])
(pcase-dolist (`(,table ,schema) org-roam-db--table-schemata)
(emacsql db [:create-table $i1 $S2] table schema))
(pcase-dolist (`(,index-name ,table ,columns) org-roam-db--table-indices)
(emacsql db [:create-index $i1 :on $i2 $S3] index-name table columns))
(emacsql db (format "PRAGMA user_version = %s" org-roam-db-version))))
;; end chunglak hack
(org-roam-setup)
;; If using org-roam-protocol
(require 'org-roam-protocol))
;; These are my capture templates:
(setq org-roam-capture-templates
`(("d" "default" plain "%?" :if-new
(file+head "%<%Y%m%d%H%M%S>-${slug}.org"
,(concat "#+title: ${title}\n"
"#+date: %U\n\n"))
:unnarrowed t)))
(setq org-roam-dailies-directory "~/Documents/Org/org-roam/daily")
(setq org-roam-dailies-capture-templates
`(("d" "default" entry "* %?" :if-new
(file+head "%(concat org-roam-dailies-directory \"/%<%Y-%m-%d>.org\")"
,(concat "#+title: %<%Y-%m-%d>" "\n"
"#+filetags: :daily_journal:\n\n")))))
;; deft - one way to search Org-roam notes, but not the fastest (see below)
(use-package deft
:ensure t
:config
:after org
:bind
("C-c r d" . deft)
:custom
(deft-recursive t)
(deft-use-filter-string-for-filename t)
(deft-default-extension "org")
(deft-directory "~/Documents/Org/org-roam/"))
;; Here end the basic setup, but....
;; SOME OTHER THINGS YOU MIGHT ADD
;; bars seems pointless here, but if you like, don't do this
(menu-bar-mode -1)
(tool-bar-mode -1)
;; You could use a different theme
(use-package cyberpunk-theme
:ensure t
:config
(load-theme 'cyberpunk))
;;;;;;;;;;;;;;;;;;;;;
;; Spell-checking ;;;
;;;;;;;;;;;;;;;;;;;;;
(require 'flymake)
(setq ispell-program-name "hunspell") ; could be ispell as well, depending on your preferences
(setq ispell-dictionary "en_GB") ; this can obviously be set to any language your spell-checking program supports
;; I installed the en_GB ones, but these don't come in Termux by default. To add arbitrary hunspell languages, see:
;; https://www.reddit.com/r/termux/comments/k5o6mp/new_hunspell_dictionaries/?
;; in summary:
;; - how to add new: copy .aff and .dic files in /data/data/com.termux/files/usr/share/hunspell/
;; - where to get new: https://www.freeoffice.com/en/download/dictionaries
(dolist (hook '(org-mode-hook))
(add-hook hook (lambda () (flyspell-mode 1))))
(add-hook 'org-mode-hook (lambda () (setq ispell-parser 'tex))) ; make orgmode recognise LaTeX syntax [from http://stackoverflow.com/questions/11646880/flyspell-in-org-mode-recognize-latex-syntax-like-auctex ]
(add-hook 'text-mode-hook #'flyspell-mode)
;;;;;;;;;;;;;;;
;; Undo-Tree ;; - a new undo package
;;;;;;;;;;;;;;;
(use-package undo-tree
:ensure t
:config
;; (setq undo-tree-auto-save-history 1)
;; Each node in the undo tree should have a timestamp.
(setq undo-tree-visualizer-timestamps t)
;; Show a diff window displaying changes between undo nodes.
(setq undo-tree-visualizer-diff t)
(global-undo-tree-mode))
;; display time and date in modeline, if you like
(setq display-time-day-and-date t)
(display-time-mode 1)
;; prettier bullets
(use-package org-bullets
:ensure t
:config
(add-hook 'org-mode-hook (lambda () (org-bullets-mode 1)))
(setq org-bullets-bullet-list '("⋇" "∴" "∵" "∷" "∺")))
;; A nice way of quickly adding links.
;; (Though in Termux, you first must paste from your
;; Android clipboard and then copy/kill via Emacs before
;; it'll work.)
(use-package org-cliplink
:ensure t
:config
(define-key org-mode-map (kbd "C-c o c") #'org-cliplink))
;; This is also not needed, but adds some (dubiously) useful properties
;; to the Org-roam file's property drawer.
;; First, set up a system for getting location
;; (we could also try to leverage termux's built-in
;; GPS location abilities via `termux-location`, but
;; it seems a bit slow and doesn't even always work if
;; your device can't get a good satellite connection.)
(setq calendar-latitude 0)
(setq calendar-longitude 0)
(defun bms/get-lat-long-from-ipinfo ()
(let*
((latlong (substring
(shell-command-to-string "curl -s 'ipinfo.io/loc'") 0 -1))
(latlong-list (split-string latlong ",")))
(setq calendar-latitude (string-to-number (car latlong-list)))
(setq calendar-longitude (string-to-number (cadr latlong-list)))))
(defun bms/add-other-auto-props-to-org-roam-properties ()
(unless (file-exists-p (buffer-file-name))
(unless (org-find-property "CREATION_TIME")
(org-roam-add-property (format-time-string "%s"
(nth 5
(file-attributes (buffer-file-name))))
"CREATION_TIME"))
(unless (org-find-property "AUTHOR")
(org-roam-add-property roam-user "AUTHOR"))
(unless (org-find-property "MAIL")
(org-roam-add-property roam-email "MAIL"))
(unless (org-find-property "LAT_LONG")
(bms/get-lat-long-from-ipinfo)
(org-roam-add-property (concat (number-to-string calendar-latitude) "," (number-to-string calendar-longitude)) "LAT-LONG"))))
(add-hook 'org-roam-capture-new-node-hook #'bms/add-other-auto-props-to-org-roam-properties)
;; You could use Ivy or Helm or the default, but I
;; like Selectrum, Consult & friends. Plus we can leverage
;; Consult for a nice alternative to deft for note-searching.
;; You'll need this to use my ripgrep note searching feature below.
;; selectrum
(use-package selectrum
:ensure t
:config
(selectrum-mode +1))
;; ;; prescient - T9
(use-package prescient
:ensure t
:config
(setq prescient-persist-mode t)
(setq prescient-filter-method '(literal regexp initialism fuzzy))) ;; added fuzzy
(use-package orderless
:ensure t
:init (icomplete-mode) ; optional but recommended!
:custom (completion-styles '(orderless))
:config
(setq orderless-matching-styles '(orderless-flex))
;; This means that the company-capf backend will automatically use orderless, but following issue exists:
;; Pressing SPC takes you out of completion, so with the default separator you are limited to one component,
;; which is no fun. To fix this add a separator that is allowed to occur in identifiers, for example, for
;; Emacs Lisp code you could use an ampersand:
(setq orderless-component-separator "[ &]")
;; The matching portions of candidates aren’t highlighted. But while you can’t get different faces for
;; different components, you can at least get the matches highlighted in the sole available face with this configuration
(defun just-one-face (fn &rest args)
(let ((orderless-match-faces [completions-common-part]))
(apply fn args)))
(advice-add 'company-capf--candidates :around #'just-one-face))
(use-package selectrum-prescient
:ensure t
:config
;; to make sorting and filtering more intelligent
(selectrum-prescient-mode +1)
;; Filtering with orderless
(setq selectrum-refine-candidates-function #'orderless-filter)
(setq selectrum-highlight-candidates-function #'orderless-highlight-matches)
;; If you also configure `completion-styles` for orderless you might want to use the
;; following advice because orderless isn't well suited for initial gathering of
;; candidates by completion in region.
(advice-add #'completion--category-override :filter-return
(defun completion-in-region-style-setup+ (res)
"Fallback to default styles for region completions with orderless."
(or res
;; Don't use orderless for initial candidate gathering.
(and completion-in-region-mode-predicate
(not (minibufferp))
(equal '(orderless) completion-styles)
'(basic partial-completion emacs22)))))
;; Minibuffer-actions with embark
;; You should bind embark commands like embark-act, embark-act-noexit
;; and embark-export in minibuffer-local-map (as embark commands are not selectrum specific).
;; For available commands and other embark configurations see the embark documentation and its wiki.
(defun current-candidate+category ()
(when selectrum-is-active
(cons (selectrum--get-meta 'category)
(selectrum-get-current-candidate))))
(add-hook 'embark-target-finders #'current-candidate+category)
(defun current-candidates+category ()
(when selectrum-is-active
(cons (selectrum--get-meta 'category)
(selectrum-get-current-candidates
;; Pass relative file names for dired.
minibuffer-completing-file-name))))
(add-hook 'embark-candidate-collectors #'current-candidates+category)
;; No unnecessary computation delay after injection.
(add-hook 'embark-setup-hook 'selectrum-set-selected-candidate)
;; The following is not selectrum specific but included here for convenience.
;; If you don't want to use which-key as a key prompter skip the following code.
(setq embark-action-indicator
(lambda (map) (which-key--show-keymap "Embark" map nil nil 'no-paging)
#'which-key--hide-popup-ignore-command)
embark-become-indicator embark-action-indicator)
;; to save your command history on disk, so the sorting gets more
;; intelligent over time
(prescient-persist-mode +1))
;; Example configuration for Consult
(use-package consult
;; Replace bindings. Lazily loaded due by `use-package'.
:bind (("C-x M-:" . consult-complex-command)
("C-c h" . consult-history)
("C-c m" . consult-mode-command)
("C-x b" . consult-buffer)
("C-x 4 b" . consult-buffer-other-window)
("C-x 5 b" . consult-buffer-other-frame)
("C-x r x" . consult-register)
("C-x r b" . consult-bookmark)
("M-g g" . consult-goto-line)
("M-g M-g" . consult-goto-line)
("M-g o" . consult-outline) ;; "M-s o" is a good alternative.
("M-g l" . consult-line) ;; "M-s l" is a good alternative.
("M-g m" . consult-mark) ;; I recommend to bind Consult navigation
("M-g k" . consult-global-mark) ;; commands under the "M-g" prefix.
("M-g r" . consult-ripgrep) ;; or consult-grep, consult-ripgrep
("M-g f" . consult-find) ;; or consult-locate, my-fdfind
("M-g i" . consult-project-imenu) ;; or consult-imenu
("M-g e" . consult-error)
("M-s m" . consult-multi-occur)
("M-y" . consult-yank-pop)
("<help> a" . consult-apropos))
;; The :init configuration is always executed (Not lazy!)
:init
;; Custom command wrappers. It is generally encouraged to write your own
;; commands based on the Consult commands. Some commands have arguments which
;; allow tweaking. Furthermore global configuration variables can be set
;; locally in a let-binding.
(defun my-fdfind (&optional dir)
(interactive "P")
(let ((consult-find-command '("fdfind" "--color=never" "--full-path")))
(consult-find dir)))
;; Replace `multi-occur' with `consult-multi-occur', which is a drop-in replacement.
(fset 'multi-occur #'consult-multi-occur)
;; Configure other variables and modes in the :config section, after lazily loading the package
:config
;; Configure preview. Note that the preview-key can also be configured on a
;; per-command basis via `consult-config'.
;; (setq consult-preview-key 'any) ;; any key triggers preview, the default
;; Optionally configure narrowing key.
;; Both < and C-+ work reasonably well.
(setq consult-narrow-key "<") ;; (kbd "C-+")
;; Optionally make narrowing help available in the minibuffer.
;; Probably not needed if you are using which-key.
;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help)
;; Optional configure a view library to be used by `consult-buffer'.
;; The view library must provide two functions, one to open the view by name,
;; and one function which must return a list of views as strings.
;; Example: https://github.com/minad/bookmark-view/
;; (setq consult-view-open-function #'bookmark-jump
;; consult-view-list-function #'bookmark-view-names)
;; Optionally configure a function which returns the project root directory
;; (autoload 'projectile-project-root "projectile")
;; (setq consult-project-root-function #'projectile-project-root)
)
;; Optionally add the `consult-flycheck' command.
(use-package consult-flycheck
:bind (:map flycheck-command-map
("!" . consult-flycheck)))
;; Optionally enable richer annotations using the Marginalia package
(use-package marginalia
:ensure t
;; The :init configuration is always executed (Not lazy!)
:init
;; Must be in the :init section of use-package such that the mode gets
;; enabled right away. Note that this forces loading the package.
(marginalia-mode))
(use-package embark
:ensure t
:bind
("C-S-a" . embark-act) ; pick some comfortable binding
:config
;; For Selectrum users:
(defun current-candidate+category ()
(when selectrum-is-active
(cons (selectrum--get-meta 'category)
(selectrum-get-current-candidate))))
(add-hook 'embark-target-finders #'current-candidate+category)
(defun current-candidates+category ()
(when selectrum-is-active
(cons (selectrum--get-meta 'category)
(selectrum-get-current-candidates
;; Pass relative file names for dired.
minibuffer-completing-file-name))))
(add-hook 'embark-candidate-collectors #'current-candidates+category)
;; No unnecessary computation delay after injection.
(add-hook 'embark-setup-hook 'selectrum-set-selected-candidate))
;; org-roam-rg-search - this is a much faster way to search Org-roam notes:
;; requires the Selectrum+Consult setup immediately preceding.
;; Use C-c r r to search notes via consult's ripgrep interface
(defun bms/org-roam-rg-search ()
"Search org-roam directory using consult-ripgrep. With live-preview."
(interactive)
(let ((consult-ripgrep "rg --null --ignore-case --type org --line-buffered --color=always --max-columns=500 --no-heading --line-number . -e ARG OPTS"))
(consult-ripgrep org-roam-directory)))
(global-set-key (kbd "C-c rr") 'bms/org-roam-rg-search)
;; speed-keys - see https://github.com/alhassy/emacs.d#manipulating-sections
(setq org-use-speed-commands t)
;; On an org-heading, C-a goes to after the star, heading markers. To use speed keys, run C-a C-a to get to the star markers.
;; C-e goes to the end of the heading, not including the tags.
(setq org-special-ctrl-a/e t)
;;drag images into orgmode
(use-package org-download
:ensure t
:config
(add-hook 'dired-mode-hook 'org-download-enable)
(global-set-key (kbd "C-c o i") #'org-download-yank)
(setq org-download-method 'attach))
(defun bms/org-attach-insert-link (&optional in-emacs)
"Insert attachment from list."
(interactive "P")
(let ((attach-dir (org-attach-dir)))
(if attach-dir
(let* ((file (pcase (org-attach-file-list attach-dir)
(`(,file) file)
(files (completing-read "Insert attachment: "
(mapcar #'list files) nil t))))
(path (expand-file-name file attach-dir))
(desc (file-name-nondirectory path)))
(let ((initial-input
(cond
((not org-link-make-description-function) desc)
(t (condition-case nil
(funcall org-link-make-description-function link desc)
(error
(message "Can't get link description from %S"
(symbol-name org-link-make-description-function))
(sit-for 2)
nil))))))
(setq desc (if (called-interactively-p 'any)
(read-string "Description: " initial-input)
initial-input))
(org-insert-link nil path (concat "attachment:" desc))))
(error "No attachment directory exist"))))
(define-key org-mode-map (kbd "C-c o l") #'bms/org-attach-insert-link)
;; in case you want some things not in melpa
;; you'll need it for the remaining things below
(use-package quelpa
:ensure t)
(use-package quelpa-use-package
:ensure t)
;; A bit of sugar for the visual appearance of Org syntax
;; Use if you like.
(use-package org-appear
:ensure t
:quelpa (org-appear :fetcher github :repo "awth13/org-appear")
:config
(setq org-hide-emphasis-markers t)
(add-hook 'org-mode-hook 'org-appear-mode))
;; You don't need this, but it's cool and it does work on Android:
;; see https://github.com/org-roam/org-roam-ui for features
(use-package org-roam-ui
:ensure t
:quelpa (org-roam-ui :fetcher github :repo "org-roam/org-roam-ui" :branch "main" :files ("*.el" "out"))
:after org-roam
:hook (org-roam . org-roam-ui-mode))
An excellent way of keeping Org notes (and files more generally) in sync between desktop, laptop, and mobile devices is Syncthing. On Android I recommend using the Syncthing-Fork app (via F-Droid), which has various improvements over the default Syncthing app on Android, including better file-access features. (On iOS there is now a third-party solution for syncing via Syncthing: Möbius-Sync. I have no idea how to use Emacs/Org-mode on iOS though, but I recall hearing about some ways of running a Linux shell on iOS like iSH, so possibly there’s some way.)
I have Syncthing sync my Org files to a directory in my main “home”
directory on Android Documents/Org
and then in Termux created a
Documents
directory and inside of that directory created a symlink to
my actual Org directory via ln -s storage/shared/Documents/Org Org
. I’ve found that is easier for allowing Syncthing to have access
to the files in order to keep them in sync. (And having my Org files
live at ~/Documents/Org
in Termux mimics the directory structure on my
Linux boxes, which makes lots of things easier in terms of sharing
configurations.)
pdf-tools
can be used to add annotations to a PDF
document. It can be useful to have multiple annotation colours though,
and be able to set these on the fly.
Here’s an example of how to do it with four colours:
;; annotation colours
(defun bms/pdf-annot-colour-blue ()
(interactive)
(setq pdf-annot-default-markup-annotation-properties
'((label . "") (color . "blue") (popup-is-open)))
(message "%s" (propertize "Annotation colour set to blue." 'face '(:foreground "blue"))))
(defun bms/pdf-annot-colour-yellow ()
(interactive)
(setq pdf-annot-default-markup-annotation-properties
'((label . "") (color . "yellow") (popup-is-open)))
(message "%s" (propertize "Annotation colour set to yellow." 'face '(:foreground "yellow"))))
(defun bms/pdf-annot-colour-red ()
(interactive)
(setq pdf-annot-default-markup-annotation-properties
'((label . "") (color . "red") (popup-is-open)))
(message "%s" (propertize "Annotation colour set to red." 'face '(:foreground "red"))))
(defun bms/pdf-annot-colour-orange ()
(interactive)
(setq pdf-annot-default-markup-annotation-properties
'((label . "") (color . "orange") (popup-is-open)))
(message "%s" (propertize "Annotation colour set to orange." 'face '(:foreground "orange"))))
;; rebind keys for pdf-tools
(defun bms/pdf-tools-mode-config ()
"Set pdf-tools keybindings."
(local-set-key (kbd "R") #'bms/pdf-annot-colour-red)
(local-set-key (kbd "L") #'bms/pdf-annot-colour-blue)
(local-set-key (kbd "O") #'bms/pdf-annot-colour-orange)
(local-set-key (kbd "Y") #'bms/pdf-annot-colour-yellow))
;; add to pdf-view-mode-hook
(add-hook 'pdf-view-mode-hook #'bms/pdf-tools-mode-config)
Example in use (though some of the colours might be better chosen, e.g. a lighter shade of blue):
]]>I’ve long been intrigued by this one-hand, non-tethered input method and finally got a Twiddler 3. I was unfortunately stymied on progress early on due to a loose battery (requiring cracking the entire device open), but have got it back working and am slowly trying to integrate it into my workflow.
One place where it would be particularly useful is with a mobile device (e.g. smart phone), especially as one can run full Emacs in Termux on Android.
The Twiddler is a configurable device, and obviously using it with Emacs calls for some Emacs-specific configuration. Here is my current working configuration:
M-x tab-space: Emacs-centric layout for the Twiddler 3
With important Emacs combinations like C-x
, M-x
, C-g
mapped to
chords (along with a chord prefix, s-F
for my personal StumpWM
config), and some chords set up for comfortable navigating with God
Mode and avy. And lots of space for expansion.
Here’s my fix:
;; Global 'last focussed window'
(setf *global-earlier-focussed-window* 'nil)
(setf *global-prev-focussed-window* 'nil)
(setf *global-cur-focussed-window* 'nil)
(defun panrecord-of-last-focussed-window (currwin lastwin)
"Record last visited windows and their group."
(unless (or (search "*EQUAKE*[" (window-name currwin))
(equal (cons (current-window) (current-group)) *global-cur-focussed-window*))
(when (find-window-globally
(car *global-prev-focussed-window*) (screen-groups (current-screen)))
(setf *global-earlier-focussed-window* *global-prev-focussed-window*))
(when (find-window-globally
(car *global-cur-focussed-window*) (screen-groups (current-screen)))
(setf *global-prev-focussed-window* *global-cur-focussed-window*))
(setf *global-cur-focussed-window* (cons currwin (current-group)))))
(defun find-window-globally (window group-list)
"Check for presence of window in all groups."
(if (equal (car group-list) 'nil)
'nil
(if (member window (group-windows (car group-list)))
window
(find-window-globally window (cdr group-list)))))
(defcommand switch-to-last-focussed-window () ()
"Switch to last focussed window, irrespective of which group it is in and
what group we're currently in."
(let ((switch-to-win
(or
(find-window-globally
(car *global-prev-focussed-window*) (screen-groups (current-screen)))
(find-window-globally
(car *global-earlier-focussed-window*) (screen-groups (current-screen))))))
(if switch-to-win
(progn
(switch-to-group (cdr *global-prev-focussed-window*))
(focus-window (car *global-prev-focussed-window*) t))
(message "No window to switch to."))))
(stumpwm:add-hook stumpwm:*focus-window-hook* 'panrecord-of-last-focussed-window)
(define-key *root-map* (kbd "s-f") "switch-to-last-focussed-window")
The unless
statement in panrecord-of-last-focussed-window
prevents
my drop-down terminal Equake “window” from “counting” for history
tracking purposes.
switch-to-last-focussed-window
essentially just switches to the last
focussed window, after making sure it still exists. (If not, the
window which was focussed before that one, or else don’t switch and
display message to user.)
(define-key *root-map* (kbd "s-f") "switch-to-last-focussed-window")
means that I can double tap s-f
to switch to the last focussed window, no matter which group it
belongs to.
I continue to really enjoy the power that StumpWM’s Common Lisp underpinnings provides the user!
]]>The choice of typeface is particularly relevant for using XeLaTeX or LuaLaTeX, where the standard default unicode typeface family is Latin Modern, based on Donald Knuth‘s original Computer Modern family of typefaces designed in METAFONT, and the general recommendation is to use Latin Modern rather than the earlier Computer Modern Unicode.
Generally reasons given for preferring Latin Modern include the fact
that Latin Modern involves handmade vectorisation, with revised
metrics, and additional glyph coverage, including particularly
diacritic characters; whereas the Computer Modern Unicode fonts were
converted from METAFONT sources using mftrace
with autotrace
backend
and fontforge
(former pfaedit) automatically.
Some of the revised metrics of Latin Modern include arguably better positioning of acute and grave accents, more appropriately-sized umlauts, and (here tastes may vary) a better looking Eszett (ß).
[Note that throughout this post, you can right-click on the images and select “View Image” to see a larger/zoomable version of the image.]
However, in my tests, Computer Modern Unicode actually performs better at least with respect to handling diacritics. In fact, Computer Modern Unicode is one of a handful of typefaces which actually properly handle a large range of diacritics and combinations of diacritics, as can be seen in the image below, showing a variety of serif, sans serif, and monospace typefaces, respectively, typeset in XeLaTeX:
[The source file from which all of these samples were generated is included below in this footnote: 1.]
Note that in many typefaces, including all of the Latin Modern faces, combinations of acute accents and macrons (e.g. the first character in the tests: ā́, also not handled well by IM Fell Great Primer, the font this blog used to use for its body text; now switched to Computer Modern Unicode Sans) are very poorly handled, often with the accent and macron being overlaid, rather than the accent properly stacking on top of the macron.
Even fonts that properly handle this often fail for other diacritics: note that EB Garamond and Liberation Serif don’t seem to properly handle underrings (e.g. R̥r̥, also not handled that well by IM Fell Great Primer).
Even the Linux Libertine faces (here represented by their Libertinus continuations), which overall fare well, don’t handle the acute+macron combination well in the italic version ā́.
Thus the only serif faces which handle the full range of characters/diacritics are the didone (a style arising in the late 18th-century, popular in the 19th-c.) Computer Modern Unicode; Junicode (based on a 17th-century font used for George Hickes‘s Linguarum Vett. Septentrionalium Thesaurus); and the early 20th-century styled Times New Romanesque Noto Serif.
For the sans serif faces, in addition to the Computer Modern Unicode Sans Serif face (in some ways a ‘skeletal’, un-didone-ised version of Computer Modern Roman), Mozilla’s Fira Sans (here represented by FiraGO, an updated/extended version), and Noto Sans – the latter two both being vaguely ‘humanist’ sans serifs, though Noto Sans perhaps has some ‘american gothic/grotesque’ features – also manage the full range of characters/diacritics, but doesn’t have an italic face.
For the monospace faces, again the Computer Modern Typewriter face (an ‘Egyptian’ typewriter face, not unlike Courier) manages the full range of characters/diacritics, as do Noto Sans Mono and the Iosevka faces (here represented by my own customised version, Iosevka Oak Vise).
Not all of these typefaces are of equal beauty. Here are some examples of running text, single sentences first:
And full paragraphs (for a subset of the typefaces; only including those that manage at least a majority of the characters and diacritic combinations):2
Only a few typefaces handle the full range of characters and diacritic combinations. For the serif faces:
For the sans faces:
For the monospaced faces:
Here is a comparison only of these “winners” (and Noto Sans Mono perhaps is not a winner, lacking an italic face):
So, if you want to use a font family with good performance across the full range of fonts, Computer Modern Unicode (CMU) or Noto seem like the best bets, and the former is much more aesthetically-pleasing than the latter (in my opinion), which also lacks an italic font for its mono version. Junicode is also a beautiful and very functional serif face, which I often use.
The Latin Modern faces turn out, despite the received wisdom, to be inferior in terms of diacritic coverage, and switching from Latin Modern to Computer Modern Unicode (CMU) has significantly reduced the amount of frustration I have in trying to typeset my papers.
The editor font is the one I spend most of my time staring at, so I also want something good here too.
Despite DejaVu Sans Mono apparently not properly rendering all character/diacritic combinations in LaTeX, it handles all of these perfectly well when used as the default font in Emacs:
DejaVu Sans Mono was for many years my preferred font for Emacs (and I assume DejaVu Sans Mono-derived fonts like Menlo or Hack will also behave well).
Noto Sans Mono also works fine, but it’s not my favourite:
CMU Typewriter, despite rendering well in LaTeX, has problems with certain combination when used as the Emacs font, and anyway doesn’t look as good as the other choices as an editor font for whatever reason:3
These days, I prefer to use a customised version of Iosevka, which also handles all of the characters/diacritic combinations perfectly:
I plan to write more extensively about Iosevka in another post.
For typesetting papers requiring a full-range of unicode roman characters and diacritics (for non-roman, the Noto fonts are great), the Computer Modern Unicode (CMU) faces4 are the best bets, along with Junicode.
For editor work requiring a full-range of unicode roman characters and diacritics, use a DejaVu Sans Mono font or derivative, or one of the Iosevka variants.
I’m not sure why Latin Modern Typewriter ends up with a ragged right margin. ↩︎
Ubuntu Mono, when I tried setting it as the default font in Emacs and opening the .tex
file from which the pdf screenshots shown here were taken, caused Emacs to crash! ↩︎
E.g., CMU Serif, CMU Sans Serif, CMU Typewriter; there are a number of others, including also CMU Concrete Roman, used below for the non-title text (the title text is set in Latin Modern Dunhill): ↩︎
Both lists below have the games hyperlinked to their Interactive Fiction Database page, which generally includes reviews and game files to download (or links/suggestions of where to get the game).
First, my personal list of favourites/best text adventures/interactive fiction that I’ve completed or at least played most (er, at least half?) of, ordered roughly in the order I encountered them:
Game | Year | Author | Publisher |
---|---|---|---|
Zork I | 1980 | Marc Blank & Dave Lebling | Infocom |
Zork II | 1981 | Dave Lebling & Marc Blank | Infocom |
Enchanter | 1983 | Marc Blank & Dave Lebling | Infocom |
Planetfall | 1983 | Steve Meretzky | Infocom |
The Hitchhiker’s Guide to the Galaxy | 1984 | Douglas Adams & Steve Meretzky | Infocom |
Wishbringer | 1985 | Brian Moriarty | Infocom |
The Pawn | 1985 | Rob Steggles et al. | Magnetic Scrolls |
Spellbreaker | 1985 | Dave Lebling | Infocom |
The Guild of Thieves | 1987 | Rob Steggles | Magnetic Scrolls |
Knight Orc | 1987 | Pete Austin | Level 9 |
Dunnet | 1982 | Ron Schnell | |
Curses! | 1993 | Graham Nelson | |
Spider & Web | 1998 | Andrew Plotkin | |
Counterfeit Monkey | 2012 | Emily Short | |
Coloratura | 2013 | Lynnea Glasser | |
Mentula Mancanus: Apocolocyntosis | 2011 | Adam Thornton |
Some of these may be being boosted by nostalgia, but I think they all pretty much hold up and are interesting/innovative in some fashion.
Looking over the author names, I remember when playing Infocom games back in the 80s suspecting that “Marc Blank” was a pseudonym….i.e. Last Name: [blank].
Dunnet is an interesting entry also in that it was originally written
in Maclisp for the DECSYSTEM-20, then ported to Emacs Lisp in 1992,
and it is the Emacs version (M-x dunnet
) that I’m familiar with.
A list of games I still need to play (never played before) or finish (haven’t made significant progress, though I spent a pretty long time with the Silicon Dreams games, Anchorhead, and Varicella, but don’t think I’ve made much progress (or I’ve forgotten what progress I made)), roughly in order of precedence (i.e. how soon I plan to play):
Game | Year | Author | Publisher |
---|---|---|---|
Anchorhead | 1998 | Michael Gentry | |
Cragne Manor | 2018 | [numerous] | |
Galatea | 2000 | Emily Short | |
Savoir-Faire | 2002 | Emily Short | |
Hadean Lands | 2014 | Andrew Plotkin | |
Varicella | 1999 | Adam Cadre | |
Endless, Nameless | 2012 | Adam Cadre | |
Bronze | 2006 | Emily Short | |
Balances | 1994 | Graham Nelson | |
Jigsaw | 1995 | Graham Nelson | |
Blue Lacuna | 2008 | Aaron Reed | |
A Beauty Cold and Austere | 2017 | Mike Spivey | |
Lime Ergot | 2014 | Caleb Wilson (as Rust Blight) | |
Suveh Nux | 2007 | David Fisher | |
Delusions | 1996 | C. E. Forman | |
Slouching Towards Bedlam | 2003 | Star Foster & Daniel Ravipinto | |
A Mind Forever Voyaging | 1985 | Steve Meretzky | Infocom |
Trinity | 1986 | Brian Moriarty | Infocom |
Snowball [Silicon Dreams] | 1983 | Mike Austin et al. | Level 9 |
Return to Eden [Silicon Dreams] | 1984 | Nick Austin et al. | Level 9 |
The Worm in Paradise [Silicon Dreams] | 1985 | Mike Austin et al. | Level 9 |
The Shadow in the Cathedral | 2009 | Ian Finley & John Ingold | |
Make It Good | 2009 | Jon Ingold | |
1893: A World’s Fair Mystery | 2002 | Peter Nepstad | |
Red Moon | 1985 | David Williamson et al. | Level 9 |
Shade | 2000 | Andrew Plotkin | |
Stationfall | 1987 | Steve Meretzky | Infocom |
Rameses | 2000 | Stephen Bond | |
The Moonlit Tower | 2002 | Yoon Ha Lee | |
Bureaucracy | 1987 | Douglas Adams | Infocom |
All of the games listed here, in either list, are playable in either Malyon or Gargoyle (with the exception, I think, of Hadean Lands), an admittedly rather arbitrary criterion.
]]>pdfpc
is a fantastic application for presenting PDF slides,
including perhaps especially those produced using LaTeX Beamer. It
creates two (full-screen) windows, one a presenter viewer which shows
the time elapsed and a preview of the next slide, and one the
presentation view which is what is shown to the audience. It also has
a bunch of other cool features like being able to draw on slides;
highlight areas of slides, &c.
Here is an example, with the presenter’s view shown on the left; the audience’s view on the right:
In many environments pdfpc
is pretty smart about getting the
presentation view to the external display, but in StumpWM both end up
getting created in the same frame in the same display.
(General tip: in StumpWM, after connecting/activating an external
display, you may need to run refresh-heads
to get StumpWM to display
things properly.)
For whatever reason, I can’t get any of the Screen-related things in
StumpWM to behave like I have more than one
screen. E.g. *screen-list*
shows a singleton list even when an
external display is connected and activated:
STUMPWM> *screen-list*
(#S<screen #<XLIB:SCREEN :0.0 1366x768x24 TRUE-COLOR>>)
A passable solution is to use define-frame-preference
, adding the
following to your init.lisp
StumpWM configuration:
;; pdfpc rule - for 'Default' group
;; move to frame '1';
;; non-focussing ('nil) the presentation view;
;; not only matching (t) the target-group ('Default');
;; moving the 'pdfpc' 'presentation' window
(define-frame-preference "Default"
(1 nil t :instance "pdfpc" :role "presentation"))
This should properly move the presentation window to the external
display when pdfpc
is run.
There may (likely is) some way of programmatically setting the X
Windows PATH variable in Guix System (née GuixSD) via the base
configuration (e.g. config.scm
), but I haven’t been able to uncover
anything that works. This is relevant for being able to use locally
installed static binaries or local shell scripts via the window
manager.
As a window-manager-specific workaround, in StumpWM, one can
programmatically set PATH variables via (setf (getenv "VARIABLE_NAME") "variable-value")
. Thus, if you store local static
binaries and shell scripts in ~/bin
, the following (which you could
include in StumpWM’s init.lisp
) will add that to your PATH variable:
(setf (getenv "PATH") (concat "/home/YOURUSERNAME/bin:" (getenv "PATH")))
I use this with a static Haskell binary greenclip, which adds clipboard functionality to rofi, and with shell scripts that give “pretty names” to Flatpak run commands.
For example, the literate/natural-language-based programming
interactive fiction design language Inform7 (which is due to be
open-sourced sometime this year) is now conveniently available as a
Flatpak. But the run command after installing is flatpak run com.inform7.IDE
, which is non-ideal. So I made a simple shell script
named inform7
placed in ~/bin
:
#!/bin/sh
flatpak run com.inform7.IDE
Nix can be installed as a standalone package manage on top of other distros, including Guix System, which is useful for be able to obtain software currently lacking in Guix System (including, ironically, Hugo, used by this blog, which is present in Nix). Packages available in Nix but not in Guix include Gargoyle, a very nice interactive fiction front-end client that supports a number of different backends, including Frotz and Glulxe. One of the benefits of Gargoyle is that it “cares about typography”. However, Nix applications by default seem to have trouble finding/seeing fonts, including system fonts, local fonts, and even fonts installed via Nix.
This can be fixed by (1) setting the FONTCONFIG_PATH
and
FONTCONFIG_FILE
, e.g. in StumpWM this can be done with:
(setf (getenv "FONTCONFIG_PATH") "/home/YOURUSERNAME/.config/fontconfig/")
(setf (getenv "FONTCONFIG_FILE") "fonts.conf")
And (2) forcing Nix to look in the right places by manual
specification in ~/.config/fontconfig/fonts.conf
, adding right
before the final </fontconfig>
(as appropriate):
<cachedir prefix="xdg">fontconfig</cachedir>
<dir>/home/YOURUSERNAME/.local/share/fonts/</dir>
<dir>/home/YOURUSERNAME/.nix-profile/share/fonts/</dir>
<dir>/home/YOURUSERNAME/.guix-profile/share/fonts/</dir>
<dir>/usr/share/fonts</dir>
And regenerating the font cache (via fc-cache -fv
) [possibly you may
need to install Nix’s fontconfig
package].
grub-probe
not knowing how to properly
find the root drive) which I figured out ways around, as well as
discovering that Void’s zfs-0.8.0
package was
missing a
python3
dependency which caused ZFS DKMS builds to fail.
The scripts are more or less automated if you’re installing in a particular fashion. They’ll ask for user input along the way for configuring/customising certain things. What isn’t covered is a setup with multiple vdevs or UEFI or musl, but if you want these things you’ll probably be able to patch the scripts accordingly and perhaps these options could be accommodated in a future version.
The scripts live here: https://gitlab.com/emacsomancer/full-zfs-and-full-luks-encryption-on-void-linux , where you’ll find additional instructions and information.
(I do recommend using the Ubuntu Live ISO as your installer ‘host’ for
ease and reduction of the installation time: the ‘host’ for the
installation doesn’t really matter: basically it’s just being used to
run the initial cryptsetup
for the LUKS partition and initial ZFS
pool creation and the host for the Void chroot. The Ubuntu Live CD has
ZFS baked in, so you don’t have to wait twice(!) for DKMS to build ZFS
modules.)
Using ZFS for the entire system, from /
to /home
to /boot
also
has the advantage of not requiring you to decide how much space to
allocation ahead of time. With /boot
on a separate partition, I’ve
sometimes encountered issues of running out of space on /boot
because of maintaining multiple kernels, or else having to massively
overshoot in terms of how much space to give to /boot
. A full ZFS
install avoids this issue, and allows for easy snapshots of the
/boot
directory.
ZFS is a great file-system if you care about your data. ZFS is most
impressive file-system, and it has a number of other wonderful
features aside from data-integrity, and once you’re used to it, you’ll
want it everywhere. For instance, the ‘default’ lz4 compression is
effectively ‘free’, in terms of CPU usage (minor CPU hit for dealing
with compression is offset by the need to process smaller chunks of
data), and can be significant: on my root dataset
(dozer/ROOT/system
) I’m currently getting 1.79x compression: so 22.7G
of logical data is written in 13.6G, and even my dataset full of PDFs
has a more modest 1.03x compression ratio, but this means I save over
3G.
ZFS 0.8.0 also brings with it native encryption. I’ve not chosen to use this at the moment, as LUKS makes a full-disk setup easier at this point, but native encryption could be used in conjunction with LUKS encryption (potentially useful if, say, you want to backup up particular ZFS datasets to a remote and not entirely trusted machine:– natively encrypted ZFS snapshots can be sent without decrypting the data).
All of these various features, such as compression and encryption can be enabled per dataset, which allows for great flexibility. After suffering bit rot, which filtered through and rendered pointless my carefully maintained versioned backups, I really don’t like trusting my data to any other file-system.
]]>.tex
source) for
the past few days,1 I thought about providing automated pdf-tags
indicating creation tools used for my TeX-produced documents. Real,
professionally-typeset documents deserve to have the tools used to
produce them properly recognised in their metadata. So here’s a
yasnippet which generates auto-populated hyperref
options to
generate a pdf-creator
tag indicating the version of Emacs, AUCTeX,
and distro used:
# -*- mode: snippet -*-
# name: version-hyperref
# key: hyperv
# --
\usepackage[pdfusetitle,
pdfcreator={`(replace-regexp-in-string "_" "-"
(concat (replace-regexp-in-string "\n" "" (substring (emacs-version) 0 (1- (cl-search "(" (emacs-version))) ))
" with AUCTeX " (pkg-info-version-info 'auctex)
(if (shell-command-to-string "type -p lsb_release > /dev/null")
(concat
" on "
(substring (shell-command-to-string "lsb_release -sd") 1 (- (length (shell-command-to-string "lsb_release -sd")) 2))
" ("
(substring (shell-command-to-string "lsb_release -sr") 0 (- (length (shell-command-to-string "lsb_release -sr")) 1))
" '"
(substring (shell-command-to-string "lsb_release -sc") 0 (- (length (shell-command-to-string "lsb_release -sc")) 1))
"'" " release, using the "
(replace-regexp-in-string "\n$" "" (shell-command-to-string "uname -r")) " kernel)")
(if (shell-command-to-string "type -p guix > /dev/null")
(concat
"on Guix System "
(shell-command-to-string "guix system -V | awk 'NR==1{printf $5}'")
" (using the "
(replace-regexp-in-string "\n$" "" (shell-command-to-string "uname -r")) " kernel)")))))`}]{hyperref}
Snippets can execute elisp code placed between `...`
. The version of
Emacs (with some of the additional less-relevant information removed
via substring
combined with cl-search
(for “(", the beginning of
the additional information)), and AUCTeX can easily be done using
Emacs-internal functions, (emacs-version)
and
(pkg-info-version-info 'auctex)
, respectively.
For the operating-system information, we rely on external calls to the
shell via Emacs’s shell-command-to-string
function. Many distros
will have lsb_release
application (from the Linux Standard Base
project) available (and presumably usually part of the
base-install). Running lsb_release --help
reveals a helpful set of
options:
SYNOPSIS
lsb_release [OPTION]...
OPTIONS
−v, −−version
Display the version of the LSB specification against which the distribution is compliant.
−i, −−id
Display the string id of the distributor.
−d, −−description
Display the single line text description of the distribution.
−r, −−release
Display the release number of the distribution.
−c, −−codename
Display the codename according to the distribution release.
−a, −−all
Display all of the above information.
−s, −−short
Display all of the above information in short output format.
−h, −−help
Display this message.
LSB Version: 1.0
And we can get various bits of information about the distribution out
via passing different flags to lsb_release
(combined with the -s
“short description” flag).
Some distros don’t use the lsb_release
, and looking at various
methods neofetch
employs to manage to suss out the distro was
instructive. I added a method to allow for detecting the version of
Guix System distribution, and it could be easily extended (in
neofetch-style) to employ other methods for additional distros. (But
the lsb_release
method should handle the majority of distros.)
Finally, we get can the kernel version out via the ubiquitous uname
application, via uname -r
.
Thus calling this snippet on Void Linux results in a TeX block like this:
\usepackage[pdfusetitle,
pdfcreator={GNU Emacs 26.2 with AUCTeX 12.1.2 on Void Linux
(rolling 'void' release, using the 5.1.5-1 kernel)}]{hyperref}
(The hyperref
option pdfusetitle
will make use of the values of
the TeX-defined \author{...}
and \title={...}
commands to populate
the author and title pdf metadata. If you do anything funky with these
fields (like adding a \thanks{...}
command to your title), you may
need to take out the pdfusetitle
option and manually specify these
tags via pdfauthor={...}
etc.)
On Guix System (née GuixSD) the result will be something like:
\usepackage[pdfusetitle,
pdfcreator={GNU Emacs 26.2 with AUCTeX 12.1.2
on Guix System 1.0.1-1.8204295 (using the 5.1.2-gnu kernel)}]{hyperref}
An amusing side-discovery of putting this snippet together is that
Arch Linux multiply – and insistently – reminds the uname
caller of
the distro the kernel belongs to. I.e., using this snippet on my Arch
machine resulted in:
\usepackage[pdfusetitle,
pdfcreator={GNU Emacs 26.2 with AUCTeX 12.1.2 on
Arch Linux (rolling 'n/a' release, using the 5.1.5-arch1-2-ARCH kernel)}]{hyperref}
The horrifying reality of “professional” desktop publishing software stands in contrast to real professional documents produced in (La)TeX; and any resulting professionalism in the output of the former results from squeezing additional unpaid labour from researchers who have to correct the errors of “professional” typesetters. ↩︎
youtube-dl
) and
returns the best quality mp4 version (in case you need to deal with a
device that doesn’t like modern video encodings/containers), with the
download time as the file’s modification time timestamp (useful if
you have a directory of downloaded videos and want to quickly see the
last N files you downloaded, rather than the files being sorted by
upload time).
Add to your ~/.bashrc
or other shell configuration file as appropriate:
youtubemp4() {
youtube-dl -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4' "$1"
touch "$(youtube-dl -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4' --get-filename $1)"
}
Then call in the command-line, e.g. youtubemp4 https://www.youtube.com/watch?v=Gnnb6sjgk3A
.
The StumpWM #'invoke-equake
command hides (using StumpWM native
hide-window
, rather than Emacs’s make-frame-invisible
as the
latter creates various issues in finding and fetching the Equake
window) the Equake frame if it’s the currently active window; it
searches through all windows in all groups on the current
screen/monitor, and calls emacsclient -n -e '(equake-invoke)'
to
create an Equake frame if no extant Equake window is found; and if an
Equake window does already exist for the current screen, it is yanked
into the current group, pulled into the current frame, and unhidden (if
necessary).
This seems to work pretty well, though I haven’t figured out how to have StumpWM ‘skip’ the Equake frame when cycling through Emacs windows. (And, of course, having a proper partial height floating Equake window would be ideal.)
EDIT: Here’s a better, more Quake-like set-up, which actually makes use of recently added partial-height floating windows in non-floating groups:
(defun calc-equake-width ()
(let ((screen-width (caddr (with-input-from-stringp (s (run-shell-command "emacsclient -n -e '(equake-find-workarea-of-current-screen (equake-calculate-mouse-location (display-monitor-attributes-list)) (display-monitor-attributes-list))'" t)) (read s))))
(desired-width-perc (read-from-string (run-shell-command "emacsclient -n -e 'equake-size-width'" t))))
(truncate (* screen-width desired-width-perc))))
(defun calc-equake-height ()
(let ((screen-height (cadddr (with-input-from-string (s (run-shell-command "emacsclient -n -e '(equake-find-workarea-of-current-screen (equake-calculate-mouse-location (display-monitor-attributes-list)) (display-monitor-attributes-list))'" t)) (read s))))
(desired-height-perc (read-from-string (run-shell-command "emacsclient -n -e 'equake-size-height'" t))))
(truncate (* screen-height desired-height-perc))))
(setq *equake-width* 1368) ; TODO: programmatically get screen dimensions before Emacs starts
(setq *equake-height* 768)
(defcommand invoke-equake () ()
(let* ((on-top-windows (group-on-top-windows (current-group)))
(equake-on-top (find-equake-in-group on-top-windows)))
(if equake-on-top
(progn (setf (group-on-top-windows (current-group)) (remove equake-on-top on-top-windows))
(unfloat-window equake-on-top (current-group))
(hide-window equake-on-top)) ;; then hide Equake window via native Stumpwm method.)
(let ((found-equake (find-equake-globally (screen-groups (current-screen))))) ; Otherwise, search all groups of current screen for Equake window:
(if (not found-equake) ; If Equake cannot be found,
(progn
(run-shell-command "emacsclient -n -e '(equake-invoke)'") ; then invoke Equake via emacs function.
(setq *equake-height* (calc-equake-height)) ; delay calculation of height & width setting until 1st time equake invoked
(setq *equake-width* (calc-equake-width))) ; (otherwise Emacs may not be fully loaded)
(progn (raise-window found-equake)
(move-window-to-group found-equake (current-group)) ; But if Equake window is found, move it to the current group,
(unhide-window found-equake) ; unhide window, in case hidden
(float-window found-equake (current-group)) ; float window
(float-window-move-resize (find-equake-globally (screen-groups (current-screen))) :width *equake-width* :height *equake-height*) ; set size
(focus-window found-equake)
(toggle-always-on-top))))))) ; make on top
(defun find-equake-in-group (windows-list)
"Search through WINDOWS-LIST, i.e. all windows of a group, for an Equake window. Sub-component of '#find-equake-globally."
(let ((current-searched-window (car windows-list)))
(if (equal current-searched-window 'nil)
'nil
(if (search "*EQUAKE*[" (window-name current-searched-window))
current-searched-window
(find-equake-in-group (cdr windows-list))))))
(defun find-equake-globally (group-list)
"Recursively search through GROUP-LIST, a list of all groups on current screen, for an Equake window."
(if (equal (car group-list) 'nil)
'nil
(let ((equake-window (find-equake-in-group (list-windows (car group-list)))))
(if equake-window
equake-window ; stop if found and return window
(find-equake-globally (cdr group-list))))))
;; Set the mouse focus policy; - :click is best for proper Equake functioning
(setf *mouse-focus-policy* :click) ;; also StumpWM default (I think)
;; Keybindings
;; bind to an appropriate key
(define-key *top-map* (kbd "F12") "invoke-equake")
This is what it looks like:
]]>equake
. It is not yet on Melpa,
but is accessible at
https://gitlab.com/emacsomancer/equake.1
equake
, written fully in Emacs Lisp, is designed as a ‘classic’
drop-down console interface like Yakuake, inspired by ‘cheat’ consoles
in games like Quake. It provides access to various ‘shells’
implemented in Emacs, including shell
(an Emacs wrapper around the
current system shell), term
and ansi-term
, (both terminal
emulators, emulating VT100-style ANSI escape codes, like xterm
does), and eshell
(a shell written entirely in Emacs Lisp). equake
allows for multiple ‘tabs’ (which can be running different shells),
and allows tabs to be re-ordered and renamed.
My impetus for creating equake
was to hijack my own workflow into
using John Wiegley’s fantastic eshell
, an Emacs module which
‘translates “shell-like” syntax into Lisp in order to exercise the
kernel in the same manner as typical system shells’, allowing for a
similar working environment even in hostile, alien OSes like w32
.
eshell
is somewhat sparsely documented, but some useful resources
exist, including a fairly extensive video overview done by Howard
Abrams which I highly recommend. It has a number of great features,
either inherently or via additional Emacs packages, including features
from Plan 9‘s terminal, as well as Fish shell-like auto-suggestions.
equake
has been successful in my personal goal of using eshell
for
99% of my terminal work, and I am looking forward to making further
using of a shell which can handle Lisp syntax as well.
Most of the equake
code is keeping track of tabs, and frames for
multi-monitor set-ups. This is trickier than it would seem at first,
especially as each screen/monitor can have it own set of tabs (this
allows me to recreate at least a part of AwesomeWM’s
screen-independence in other environments). In theory equake
should
work fairly well across platforms, as it makes use of the frame.el
,
which includes code for MacOS and Windows (but I haven’t test either
platform). Probably more work needs to be done to get equake
to work
properly on tiling window managers like AwesomeWM or StumpWM. But it
seems to currently work fine in KDE Plasma and Gnome Shell (both X11
and Wayland).2
I learned a good deal about how Emacs manages frames. I originally
used Emacs’ make-frame-(in)visible
functions to hide/show the
equake
frames. However, the implementation of these is very
odd. Applying make-frame-invisible
to a frame once appears to render
it invisible, but Emacs still considers it to be visible, which means
that, for instance, frame-visible-p
will still report the frame as
being visible and, worse, functions like make-frame-visible
and
raise-frame
will have no effect whatsoever upon the frame in
question, because Emacs treats it as ‘visible’. Only a second
application of make-frame-invisible
will register the frame as
reportably invisible to Emacs. This is easily enough worked around
simply by using a ‘double tap’ of make-frame-invisible
to the
relevant frame. However, I ran into numerous other issues in the use
of make-frame-(in)visible
, including the fact that frames set at
less than 100% width end up re-appearing in a position other than
their original position, and frames sometimes spontaneously resize
when re-appearing or being un-fullscreened. I tried for a long time to
work around these issues, but found that even trying to force the
frames into doing what I wanted them to do via applications of
set-frame-position
was a non-starter, as application of this
function to malpositioned frames resulted in significant lag – which
defeated the purpose of using make-frame-(in)visible
in the first
place, which was to gain a slight performance improvement over
destroying and recreating frames. In the end, using destroy-frame
and make-frame
to ‘hide’ and ‘show’ the drop-down console ended up
being the most performent solution.
Destroying and recreating frames means also worrying about remembering
the last buffer used in a frame as well as the window-history for the
frame, so these also make up a decent part of the equake
code.
On single-screen set-ups, equake
, once installed, is designed to
have a equake
console frame toggled to drop down or be rolled up by
executing a command which invokes the (equake-invoke)
function bound
to a key like F12
via:
emacsclient -n -e '(equake-invoke)'
Getting equake
to work well on multi-monitor setups ended up being
rather challenging, since Emacs doesn’t know exactly which screen is
‘active’ unless that screen also has an active Emacs frame. The
solution, which I managed to get to be nearly as fast as the simpler
non-multi-monitor solution, is to launch ‘Emacs probes’ which are used
to determine which monitor is ‘active’ and are then destroyed, invoked
via:
emacsclient -n -c -e '(equake-invoke)' -F '((title . "*transient*") (alpha . (0 . 0)) (width . (text-pixels . 0)) (height . (text-pixels . 0)) (left . 0) (top . 0))'
(The title is important as it’s the key to being able to quickly destroy these ‘Emacs-probes’, and the other frame-settings are there to minimise the visibility of the probe-frame during its brief existence.)
If you’re looking for a drop-down for things other than
shells/terminals, alphapapa has a similar Emacs package designed as a
general-purpose drop-down (e.g. for Org mode buffers etc.); yequake
;
and a specialised version for org-capture.
I hope to add a few more features to equake
, but at this point it
seems stable and is usable for what I wanted to use it for, being a
Lisp console for a Lisp shell. Comments and suggestions are, of
course, most welcome.
Where installation via
quelpa-use-package
is described. This method is nearly as easy using
the plain use-package
package to pull from Melpa. ↩︎
Another part of the impetus for equake
is wanting
to increase my machines’ ‘Lisp quotient’ after moving a number of
machines from AwesomeWM to KDE Plasma, rather than to StumpWM as I had
originally planned. Of course, I could replace Kwin with StumpWM, and
I plan to experiment with this, but I rather like some the eye-candy
Kwin provides. ↩︎
However, John Mercouris has been developing Next Browser (originally styled nEXT Browser), a browser with a Common Lisp front-end, allowing for customisability and extensibility along Conkeror/Emacs lines:
The back-ends are – if I understand correctly – planned to be Blink for the QT port and WebkitGTK+ for the GTK port, with the Mac port of Webkit for the Mac version. But the front-end, the user-facing side, is Common Lisp.
John is currently running an Indiegogo campaign to properly port it to Linux and other non-Mac Unix variants (it apparently runs well already on the Mac, John’s main platform it seems [there’s no accounting for taste ;) ]). The raised money would be used in part to pay a professional C/C++ developer for their time.
Ambrevar is currently working on packaging Next Browser for Guix, which is exciting and promises to add to the amount of Lisp front-end software we’ll be able to use. Currently I’m running Emacs (elisp) for the majority of my non-browser productivity (writing papers & creating class slides using AUCTeX; reading composing email with mu4e; note-taking and scheduling with Org mode; &c. &c.) and, at least on one machine, StumpWM (Common Lisp window manager) for my ‘desktop environment’; and GNU GuixSD with a Guile-based package manager, Guile-based cron (mcron), and Guile-based init/daemon-manager (Shepherd). A functional, configurable, Lisp-based browser would be a most welcome addition. As excellent as Firefox is, especially its backend, I do really miss the halcyon days of Conkeror, and Next Browser could represent a return to those heady days of configurable browsing Emacs-style.
So, if this sort of thing appeals to you (i.e. if you like Lisp, Emacs, and/or highly-extendable browsers), you might want to support the Linux/Unix-port of Next Browser: https://www.indiegogo.com/projects/next-browser-nix-support
There’s only about a week left in the campaign.
]]>Fortunately, some years ago, Matthew Russotto created Confusion, “a MDL interpreter which works just well enough to play the original Zork all the way through” and runs on Linux/Unix and presumably macOS. I’d tried to install this from Arch’s AUR a couple of years ago, without any luck. Inspecting the package build, the AUR Confusion tries to get around errors by using an ancient version of gcc. I found that a few fairly trivial fixes were enough to get it to compile with a recent version of gcc, and so was finally able to try the original Zork:
The most fun I always had in text adventures was exploring, and I’ve always found Infocom’s various “time-limit” elements (batteries running out, health deteriorating, &c.) somewhat irritating. I remembered that there’s a torch in Zork I, and that is also true in the original Zork, and I managed to obtain it early on so as to save my (trusty brass) lamp’s batteries. I then cheated somewhat wildly (though, to be fair, a lot of the puzzles I had figured out once upon a time in Zork I or Zork II; and the bank puzzle and some of the others I wouldn’t have patience for anymore).
The original code has some bugs in it which made things more interesting.
Narrow Ledge
You are on a narrow ledge overlooking the inside of an old dormant
volcano. This ledge appears to be about in the middle between the
floor below and the rim above. There is an exit here to the south.
There is a very large and extremely heavy wicker basket with a cloth
bag here. Inside the basket is a metal receptacle of some kind.
Attached to the basket on the outside is a piece of wire.
The basket contains:
A cloth bag
A braided wire
A receptacle
The receptacle contains:
A newspaper
A blue label
There is a small hook attached to the rock here.
> untie braided wire from hook
*ERROR*
"FIRST-ARG-WRONG-TYPE"
"First arg to NTH must be structured"
LISTENING-AT-LEVEL 2 PROCESS 1
Atom REP has neither LVAL nor GVAL
I had a similar issue involving the result of a disagreement with a
suspicious-looking individual holding a bag. It turns out (thanks to
Matthew Russotto for the following information) that this has to do
with issues in saving and restoring files, and (failure of) either
properly recording or decoding certain values. The balloon issue has
to do with a record about the object burning in the receptacle. It is
saved as BINF!-FLAG
, which should be a boolean-type flag, but at
some point it became an object (recording what is burning) and
apparently isn’t decoded properly on a restore. Saving-and-restoring
during a battle with the suspicious-looking individual produces a
similar error to the balloon-burnable error, due to the
THIEF-ENGROSSED!-FLAG
(which apparently really is a flag) not
being saved properly. The upshot (for a player) is that you shouldn’t
save during either of these bits of the game.
With these additional lurking grues out of the way, I was able to make it to the end of the game:
Speaking of saving, MDL Zork only has a single restore file. It is
stored in the MTRZORK
directory and is named ZORK.SAVE
. Of course
you can create additional saves by copying out this save file to
different names.
If you too want to play the original Zork using the original PDP-10 MDL source code, you should be able to build the MDL interpreter necessary from the release at Matthew Russotto’s site (where my minor patches should be incorporated): http://www.russotto.net/git/mrussotto/confusion/releases or from the git repo with my patches at https://gitlab.com/emacsomancer/confusion-mdl.
Or, the easiest of all, install it via Guix (which can installed on
top of your current distro as a standalone package manager), as I have
created a Guix package which was accepted upstream (the package name
in Guix is confusion-mdl
).
Since I played through this right before designing the propositional logic homework for my Semantics class, my students are currently “enjoying” doing Zork-flavoured propositional logic translations, e.g.:
run-or-raise
command associated with xterm, but it starts from the
first xterm window and usually I want the last – something else to
figure out how to do). It’s nice to have a ‘working terminal’ that one
can quickly summon and dismiss.
There’s a number of issues for creating a Quake-type drop-down in StumpWM (at least based on my current knowledge). The first is fairly easily dealt with - I don’t want more than one Quake terminal around.
run-or-raise
will prevent multiple instances from being created:
(defcommand urxvt () ()
"Start an urxvt instance or switch to it, if it is already running."
(run-or-raise "urxvt"'(:title "urxvt")))
(I’m cheating here a bit because I’m not quite sure how to handle title-management. I know windows can be retitled, and I initially tried setting the window title in the above function to “quake”, but somehow I ended up also sporadically re-titling other windows as “quake”, so I’ve side-stepped the whole issue by just using urxvt for my drop-down terminal, with my regular terminal being xterm.)
What we want then is a function that toggles our Quake terminal
on-and-off: essentially if the current window is not urxvt, then call
the urxvt
function defined above, and if the current window is urxvt
then switch back to the last used window before urxvt was called. The
basic thing we need for this is (if (equal (window-title (current-window)) "urxvt")
.
The trouble is that StumpWM has a nice feature which is similar to
doing C-x b RET
in Emacs: it essentially switches to the last window
used, so it can be a nice feature for switching back and forth between
two different windows you’re working in. The function is
pull-hidden-other
and it is invoked with “prefix prefix”, which for
me is s-f s-f
(Super+F, twice), but by default is C-t C-
(Ctrl+t
twice). Why this creates a problem is because our Quake terminal will
muck that up because it will often end up being the last used
window, and that’s not what I want. I want pull-hidden-other
to
switch to the last used window that’s not the Quake drop-down.
I tried a number of different things, and this may still not be the best solution, but it works:
(defcommand rxvt-quake () ()
"Toggle rxvt-quake window."
(if (equal (window-title (current-window)) "urxvt") ; if the current window is quake
(progn
(other-window) ; switch back to window quake was called from
(select-window-by-number *real-other-window*) ; switch to the 'real' "other-window"
(other-window)) ; switch back to the original window - this way after quake finishes, the original configuration is restored
(progn ; otherwise, if the current window is NOT quake
(other-window) ; first switch the current "other-window"
(if (not (equal (window-title (current-window)) "urxvt")) ; if the current
; "other-window" is
; quake itself, do
; nothing
(setf *real-other-window* (window-number (current-window)))) ; otherwise store the window-number of the current other-window
(other-window) ; switch back to the window originally called from
(urxvt)))) ; run-or-raise urxvt
So this function indeed checks to see whether the current window is
our Quake drop-down terminal. If it’s not, it first switches to the
currently last used window with (other-window)
and stores the number
of this window in *real-other-window*
(but only if this
last-used-window isn’t itself urxvt - otherwise we’ll sometime end up
‘over-writing’ the actual last-used-window we want to keep). It then
switches back to window that was active when rxvt-quake
was invoked,
and then it calls the urxvt
function which either launches a new
urxvt (if none currently exists), or raises the currently running
urxvt window. Storing the value in *real-other-window*
gives us a
way to remember what the ‘real’ last-used window should be.
If rxvt-quake
is invoked while the Quake urxvt drop-down is the
currently active window, then first (other-window)
is invoked,
switching us back to the window that was active before we called the
drop-down terminal, then we switch to our stored window (which was the
‘real’ last-used window before Quake was invoked), and then we switch
back with (other-window)
to the window that was active when Quake
was first invoked.
This way the window configuration that existed before we summoned our
Quake drop-down terminal is restored, and pull-hidden-other
effectively ignores the Quake drop-drop.
Bind this command to the traditional F12 with:
(define-key *top-map* (kbd "F12") "rxvt-quake")
And you’re got a ‘traditional’ Quake drop-down terminal in StumpWM.
It is a ‘full-length’ drop-down terminal, however. It might be nice to have it be a fraction of the screen like a more traditional Quake drop-down.
]]>nix-env -i firefox
) with the following trick, create a
file ~/.local/bin/firefox
with the following content:
# Wrapper to run the Firefox built and packaged by Nix
MESA_LIB=$(dirname $(realpath /run/current-system/profile/lib/libGL.so)) #To get webgl working
export LD_LIBRARY_PATH="$MESA_LIB${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH"
#export FONTCONFIG_PATH="$(guix build fontconfig)/etc/fonts${FONTCONFIG_PATH:+:}$FONTCONFIG_PATH"
export FONTCONFIG_PATH="$(guix build fontconfig)/etc/fonts"
exec -a "$0" "/nix/var/nix/profiles/per-user/$USER/profile/bin/firefox" "$@"
And then add ~/.local/bin
to your $PATH
in ~/.profile
.
Unfortunately I haven’t been able to get that to work, though I could be doing something daft.
I figured out the following (elaborate) alternative workaround for Firefox (no luck for Chromium):
sudo dockerd
.# firefox in a docker container
# the following line will start firefox in the container, thus
# video and sound will be played on the host machine
FROM alpine:edge
MAINTAINER slade@jnanam.net
# add testing repo + install packages + add user and group
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing/" >> /etc/apk/repositories \
&& apk add --no-cache \
xz \
dbus-x11 \
ttf-dejavu \ # a bunch of fonts; you may not want all of these
ttf-freefont \
ttf-ubuntu-font-family \
font-noto \
font-noto-extra \
font-noto-emoji \
font-noto-oriya \
font-noto-tamil \
font-noto-avestan \
font-noto-gothic \
font-noto-myanmar \
font-noto-telugu \
font-noto-deseret \
font-noto-bengali \
font-noto-kannada \
font-noto-ethiopic \
font-noto-armenian \
font-noto-tibetan \
font-noto-sinhala \
font-noto-gurmukhi \
font-noto-malayalam \
font-noto-gujarati \
font-noto-devanagari \
font-noto-thai \
font-noto-adlam \
font-noto-nko \
font-noto-lisu \
font-noto-carian \
font-noto-buhid \
font-noto-osage \
font-noto-hebrew \
font-noto-arabic \
font-noto-chakma \
font-noto-gothic \
font-noto-khmer \
font-noto-cypriot \
font-noto-kayahli \
font-noto-mandaic \
font-noto-olchiki \
font-noto-thaana \
font-noto-georgian \
font-noto-shavian \
font-noto-cherokee \
font-noto-oldturkic \
font-noto-osmanya \
font-noto-glagolitic \
font-noto-tifinagh \
font-noto-adlamunjoined \
font-noto-nko \
font-noto-lao \
arc-theme \
hunspell \
hunspell-en \
firefox \
libcanberra-gtk2 \
pulseaudio \
&& rm -fr /var/cache/apk/* \
&& adduser -D -u 1000 -g 1000 user
# add user's work
WORKDIR /home/user
# switch to user
USER user
ENTRYPOINT ["/usr/bin/firefox", "--no-remote"]
Save the above out to a file Dockerfile
in some directory. cd
to
that directory. Then create a Docker container with docker build -t someprefix/firefox .
(with someprefix
being whatever you like).
~/.local/bin/firefox
:#!/bin/sh
# Wrapper to run the Chromium built and packaged by Nix
xhost +local:docker@; \
docker run --rm -it -e DISPLAY=$DISPLAY -v /tmp/.X11-unix/:/tmp/.X11-unix \
-v /dev/snd:/dev/snd \
-v /run/user/$USER_UID/pulse:/run/pulse:ro \
-v /home/$USER/.mozilla:/home/user/.mozilla \
-v /home/$USER/.cache/mozilla:/home/user/.cache/mozilla \
-v /tmp/Downloads:/tmp/Downloads \
-v /home/$USER/.gtkrc-2.0:/home/user/.gtkrc-2.0 \
-v /home/$USER/.config/gtk-3.0/:/home/user/.config/gtk-3.0 \
-v /home/$USER/.nix-profile/share/fonts/truetype:/usr/share/fonts/truetype-nix \ # for nix fonts, if you have them here
-v /home/$USER/.nix-profile/share/fonts/ubuntu:/usr/share/fonts/ubuntu-nix \ # ditto
-v /home/$USER/.nix-profile/share/fonts/noto:/usr/share/fonts/noto-nix \ # ditto
-v /home/$USER/.guix-profile/share/fonts/truetype:/user/share/fonts/truetype-guix \ # for guix fonts, if you have them here
-v /home/$USER/.guix-profile/share/fonts/opentype:/usr/share/fonts/opentype-guix \ # ditto
--shm-size 2g --privileged someprefix/firefox # substitute your prefix here for "someprefix"
Where again someprefix
is whatever you chose before. chmod +x ~/.local/bin/firefox
and then you can run Firefox with
firefox
. (You’ll have to make sure dockerd
is running.)
Perhaps this could be useful elsewhere. It has the advantage of being a nice Alpine Linux musl-libc/libressl build of Firefox running inside your GuixSD (or whatever you’re using).
What doesn’t work:
/tmp/Downloads
(but this is
a feature really).But I thought: it would be great to have a quick way of pulling up etymological information within Emacs itself. So, with a little help from Links (i.e. you’ll need to have Links installed), here is a first attempt at such a function:
(defun slade/etymonbuf ()
"Look up word etymology notes from http://www.etymonline.com. Requires [[http://links.twibright.com/][links] to be installed."
(interactive)
(let ($p1 $p2 $input $result) ;; lifted from Xah Lee's 2018-08-16: "emacs, create link of word etymology"
(if (use-region-p)
(progn (setq $p1 (region-beginning))
(setq $p2 (region-end)))
(progn (setq $p1 (line-beginning-position))
(setq $p2 (line-end-position))))
(setq $input (buffer-substring-no-properties $p1 $p2)) ;;
(generate-new-buffer $input) ; generate new buffer titled with entry word
(switch-to-buffer (last-buffer)) ; switch to that new buffer
(insert (shell-command-to-string (concat "links -dump http://www.etymonline.com/word/" $input))) ; dump text via links from etymonline
(goto-char (point-min)) ; go to beginning
(if (re-search-forward "Error 404 (Not Found)" nil t) ; for non-word or words with no etymonline entry
(slade/word-not-found) ; if 404, kill buffer and display minibuffer message
(slade/trim-etymonbuf)))) ; otherwise trim off unneeded text from header and footer
(defun slade/trim-etymonbuf ()
"Trim off unneeded text from top and bottom."
(goto-char (point-min)) ; goto beginning of buffer
(search-forward "[\s\s]") ; the text output of links always delivers text with a "[ ]" right before the text we want
(let ((begdel (point)))
(delete-region 1 begdel)) ; delete from beginning of buffer until "[ ]"
(goto-char (point-max)) ; goto end of buffer
(search-backward-regexp "Share[[:space:]]*Share on FacebookShare") ; the unneeded text at the bottom starts with a "Share" section
(let ((enddel (point)))
(delete-region (point-max) enddel)) ; delete from end of buffer until Share section
(goto-char (point-min))) ; move point back to beginning
(defun slade/word-not-found ()
"Delete buffer and display minibuffer message."
(kill-buffer)
(message "Sorry, word not found."))
After eval
'ing the above, one day, if you have an idle fancy to know
the etymology of a word, you can select it and call slade/etymonbuf
and have a quick peek at what Etymonline has to say. Here’s what you
get if you try this on “lisp”:
lisp (n.)
"act or habit of lisping," 1620s, from lisp (v.).
lisp (v.)
sometimes lipse, late 14c. alteration of wlisp, from late Old English
awlyspian "to lisp, to pronounce 's' and 'z' imperfectly," from wlisp
(adj.) "lisping," which is probably imitative (compare Middle Dutch, Old
High German lispen, Danish læspe, Swedish läspa). General sense "speak
imperfectly or childishly" is from 17c. Transitive sense from 1610s.
Related: Lisped; lisping. Suggestive of effeminacy from 14c.
Probably not the lisp you were looking for, but interesting none the less.
Of course it would be nice to retain italics and not to have to depend on an external application, so there’s more that can be done.
]]>I’m somewhat habituated to (my) AwesomeWM keybindings, which involve
the Super key in combination with various other keys, including say
s-1
for tag/workspace 1, s-3
for tag/workspace 3, &c., and
s-E
(i.e. hold Super and Shift and press e
) to launch an
emacsclient (see below on the Emacs client/daemon
configuration). StumpWM could be configured in a somewhat similar
fashion (though it doesn’t seem to quite use tag/workspaces in the
same fashion), but the ‘tradition’ seems to be to use a prefix, which
is by default C-t
(that is, hold Control and press t
), which
is then released and followed with another key or key combination. I
don’t really like using Control for windows management since it tends
to conflict with bindings in Emacs and elsewhere, so I’m testing out
s-F
(hold Super, press f
) as a prefix (though whether I’ll
stick with prefixed bindings or go back to single action bindings, I’m
not yet certain).
From browsing other stumpwm configs, I came across a useful bit of configuration:
(defun run-or-raise-prefer-group (cmd win-cls)
"If there are windows in the same class, cycle in those. Otherwise call
run-or-raise with group search t."
(let ((windows (group-windows (current-group))))
(if (member win-cls (mapcar #'window-class windows) :test #'string-equal)
(run-or-raise cmd `(:class ,win-cls) nil T)
(run-or-raise cmd `(:class ,win-cls) T T))))
This function can then be used with specific applications, e.g.:
(defcommand run-or-raise-icecat () ()
(run-or-raise-prefer-group "icecat" "Icecat"))
The above function leverages run-or-raise-prefer-group
to either
launch Icecat, if it is not already running, or else focus the Icecat
window, and successive calls will cycle through multiple Icecat
windows/windows if more than one Icecat window exists. This is extremely
useful as it’s much less cognitively-tasking than figuring out which
window number Icecat currently is associated with.
This can then be assigned a binding(s) like:
(define-key *root-map* (kbd "W") "run-or-raise-icecat")
(define-key *root-map* (kbd "C-W") "run-or-raise-icecat")
(define-key *root-map* (kbd "s-W") "run-or-raise-icecat")
This means that one first presses the prefix (s-f
for me) followed
by either W
or C-W
or s-W
(that is, Shift+w
,
Control-Shift+w
or Super-Shift+w
) to either launch Icecat or
focus/cycle through existing Icecat windows/windows.
Now, the way I typically use Emacs is to invoke it as a daemon
(i.e. emacs --daemon
) and then connect Emacsclients to this daemon
(i.e. emacsclient -c
for the ‘windowed’ gtk-application,
emacsclient -t
in the terminal). However, if we define a parallel
function for Emacs, a particular edge-case arises:
(defcommand run-or-raise-emacsclient () ()
(run-or-raise-prefer-group "emacs" "Emacs"))
This works rather like the Icecat one as long as at least one
Emacsclient window exists (so if there are multiple window,
successive calls of run-or-raise-emacsclient
will cycle through
them). However, if no emacsclient is currently open, it will launch an
undaemon’ed Emacs (requiring loading the entire init.el
), even
if/though an Emacs daemon is currently running and thus could be
attached to.
At least one solution to this is the following function (with relevant keybindings):
(defcommand decide-on-emacsclient () ()
(if (equal (run-shell-command "pgrep \"emacsclient\"" t) "")
(run-shell-command "emacsclient -c")
(run-or-raise-emacsclient)))
(define-key *root-map* (kbd "s-e") "decide-on-emacsclient")
(define-key *root-map* (kbd "C-e") "decide-on-emacsclient")
(define-key *root-map* (kbd "e") "decide-on-emacsclient")
The above function executes the shell-command pgrep "emacsclient"
and evaluates whether the output of that shell-command is equal to the
empty string (which will be the case only when no emacsclient is
running). Where at least one emacsclient is running, it executes the
run-or-raise-emacsclient
function defined earlier,
focussing/cycling through running emacsclients. Where no emacsclient
is currently running, it executes instead emacsclient -c
, opening
a windowed emacsclient. And the bindings let me press the prefix and
then either Super+e
, Control+e
or simply e
to execute this
new function.
I also have the following definitions:
(defcommand emacsclient-launch () ()
(run-shell-command "emacsclient -c"))
(define-key *root-map* (kbd "s-E") "emacsclient-launch")
(define-key *root-map* (kbd "C-E") "emacsclient-launch")
(define-key *root-map* (kbd "E") "emacsclient-launch")
So that if I want to launch a new emacsclient window instead of switching to an
existing one, I can do so using one of series of keybindings parallel
to the previous set, but with a capital E
rather than a lowercase
one.
This is working well for me, and is a nice example of the power of using Lisp-based ‘desktop environment’.
]]>It’s a fascinating distro, modelled on Nix, but implemented in Guile. It’s not been exactly easy to get running (one of the videos on GuixSD from Fosdem 2017 included the line “[GuixSD] is like Gentoo for grown ups”), in part because its architecture is rather different from what I’ve experienced with other Linux distros, which use different package managers perhaps and sometimes even different libc’s, but generally follow a similar design philosophy. Rather than spreading out configuration across lots of different pieces, GuixSD seems to largely try to concentrate it in specific configuration files which are transactional in that they can be rolled back (and thus the system can be rolled back to known working states).
It is a GNU-blessed distro, and does take the FSF’s hard line (and to my eyes sometimes weird line) approach to software. So no proprietary software is included in the Guix repos, including firmware (and it runs on the linux-libre kernel). That by itself is fine, but it means the state of affairs for Guix-packaged browsers is pretty poor. No Chromium, no Firefox. IceCat 52 is essentially what’s currently available (if IceCat were up to the latest Firefox ESR 60, it might be easier) in terms of browsers which might be considered secure.
This led me to try to use the Nix installer by itself* to try to install Firefox and Chromium. Sadly, I can’t get Nix’s Chromium to work at all on GuixSD, and while Firefox works fine, I can’t get it to see my locally installed fonts (or other fonts I’ve installed via Nix).
Hopefully at some point Next Browser will be packaged for Guix, to bring in another major component written in (Common) Lisp. And when (if?) IceCat 60 comes out, that will alleviate the pain somewhat. (I was a long-time Conkeror user, and I briefly tried it again in GuixSD, but I’m not certain of its security and uBlock Origin no long works with it, which I believe is why I stopped using it in the first place).
Other interesting Lispy pieces include mcron, a cron which accepts (as
well as Vixie cron style, I think) Guile config files. The examples in
Guix manual I couldn’t really get to work. But via the help-guix
listserv I found that one can put simple guile scripts in
~/.config/cron/job.guile
. Working out how to do a ‘run this every N
minutes’ was not immediately obvious, but I figured out how to do it,
e.g.:
; execute run_me every 5 minutes
(job '(next-minute (range 0 60 5)) "run_me")
; run execute_me every 2 hours
(job '(next-hour (range 0 24 2)) "execute_me")
One of the other great things about GuixSD is that its init manager, GNU Shepherd, is also written in Guile Scheme. I’ve only had a chance to play with it a little bit, but it seems very nice and it’s good to find other innovative init managers (I would mention here also runit and s6) which take very different approaches to systemd (another innovative init, or perhaps init+, but one that creates more problems than it solves in the end, in my experience).
On the Guix package manager itself: I learned the hard way that
searching for packages in Guix is really only comfortable within
Emacs: so do guix package -i guix-emacs
and then do everything else
from guix-emacs within Emacs ( M-x guix-search-by-name
to search
package names by regex; and M-x guix-search-by-regex
to search
names+descriptions by regex). The results returned by guix package -s ....
in a terminal are not very browseable (though I tried valiantly
for some time). But if you’re interested in Guix, you’ll likely
interested in Emacs anyway.
What I’m trying to build on this machine is something with lots of Lisp. Of course the kernel is still a kernel written in C, as are lots of the other pieces like the terminal &c., but much of the user-facing things: the package manager, the windows manager, the init, the job scheduler (=cron), and (most importantly perhaps) the ‘text editor’ (read: document composer, email interface, irc interface, twitter interface, blog post interface, code editor …) are all largely written in and interacted with using some form of Lisp (Guile Scheme, Common Lisp, Emacs Lisp).
Guix is a bit like Emacs, I think. It’s an incredibly powerful tool with lots of interesting possibilities, but when you start using it you’re presented with an empty screen with little indication of what you can do. I’ll be sticking with it, I think. Now I’ve got to get to grips with StumpWM and figure out how to configure polybar…
[And if you’re curious about why Guix is pronounced like “geeks”, have a look at this post over on my linguistics blog.]
* As well as being the package managers of both of their respective distros, both the Nix and Guix package managers can be used on top of other distros. Nix doesn’t have quite the same hard line approach to software licences as Guix.
]]>ox-hugo
, an exporter from native Org mode format to
Hugo-ready markdown files, as well his theme/configuration for Hugo,
hugo-refined
(which the theme used here is largely based on), I
finally have an ideal Emacs-centric blogging environment, though I’m
sure I’ll continue to tweak things a bit.
Hugo itself is a static website generator, which interprets markdown files. It’s written in Go, a language developed at Google, which is notable for including Ken Thompson and Rob Pike among its creators.
This is more or less a continuation of my old blog, of a similar name, and will largely address Emacs/elisp and other lispy things, and Linux/UNIX/*nix and free/open source software and operating systems, but, like my old blog, probably also occasionally games and pre-20th century technology, as well as whatever else catches my interest (other than natural language things, which I’ll keep on my professional site).
]]>