,#+ARROYOEMACSMODULE: exwm-evil-firefox ,#+ARROYOMODULEWANTS: cce/evilmode.org ,#+ARROYOMODULEWANTS: cce/exwm.org
One of the things which was nearly lost during Firefox's Quantum migration1 were complete-makeover plugins like Pentadactyl and Vimperator. Relying on a single set of movement and control idioms across all of my systems allows myself a little bit more "working memory" in which to solve the problem I actually opened up the computer to solve. It's small, perhaps imperceptible, and on shorter timescales it's almost definitely not worth the time invested in achieving it. But I believe I am Developing Software for the Decades and so there is value in persuing things that provide mental- and time-space on longer scales.
Floating around my head at the same time, I had a thought that Emacs Nemesis System is probably worth it. I
had a realization after using the Emacs
macro subsystem to automate some web forms that
if I could use get Emacs to intercept and rewrite commands to EXWM-managed windows, I could implement custom
keybindings or perhaps even get Evil-Mode working with it.
And so, when Firefox Quantum came out and I had to use a normal Firefox, without any aide of Emacs or a macro system, it was tough, I felt out of sync with my keyboard, and I went looking for a solution to this that would fit in to the CCE. I'd found that someone already had implemented this EXWM/Firefox/Evil frankensteinian monster: walseb/exwm-firefox-evil. This, along with exwm-firefox-core provide a simple API for performing key-bound actions in Firefox from with emacs-lisp. From there, it's fairly straightforward to get to a set of Evil Mode bindings for those actions, and it's easy too to extend this.
It's a surprisingly effective solution, since it allows for you to have easy access to keybindings which I have otherwise had obscured by not being in Evil Normal State. Firefox additionally allows you to set keybindings for extensions' actions, which can be moved to more memorable single-character locations in this manner.
Having things which go in to insert state for a short period of time,
rather than stay there is nice. I have this hack to accomplish this, in
two parts: a function which can be advice'd around one of the
exwm-firefox-FOO functions which will set a
buffer-local variable to true. Then, I have
<return> re-bound in insert mode to a
function which sends a return key event to the Firefox window, and then,
if that variable was set to true, set it to false and enter normal mode.
It's a cheeky solution to a problem I invented.
defun exwm-firefox-intercept-next-ret () ( (interactive)t)) (setq-local exwm-firefox-next-ret-normal defun exwm-firefox-intercept-return () ( (interactive)aref (kbd "<return>") 0)) (exwm-input--fake-key (when (and (boundp 'exwm-firefox-next-ret-normal) ( exwm-firefox-next-ret-normal) (exwm-firefox-evil-normal)nil))) (setq-local exwm-firefox-next-ret-normal "<return>") 'exwm-firefox-intercept-return) (evil-define-key 'insert exwm-mode-map (kbd "<return>") 'exwm-firefox-intercept-return) (evil-define-key 'emacs exwm-mode-map (kbd push (aref (kbd "<return>") 0) exwm-input-prefix-keys)(
have defined here a helper macro which translates a set of mapped
keys in to an interactive function which plumbs the key through, and
then optionally dumps in to
This little macro will make the integrations I have far more
defmacro define-evil-firefox-key (command-name input-key mapped-key insert-after doc) (let ((fname (intern (format "exwm-firefox-%s" command-name)))) (progn `(defun ,fname () ( ,doc (interactive)aref ,mapped-key 0)) (exwm-input--fake-key (when insert-after ,( '(exwm-firefox-evil-insert))) (evil-define-key 'normal exwm-firefox-evil-mode-map ,input-key #',fname)when insert-after ,( `(advice-add #',fname :after #'exwm-firefox-intercept-next-ret)))))
f up to showing link-hints using
(define-evil-firefox-key show-link-hints"f") (kbd "C-m") true (kbd "Show link hints using https://addons.mozilla.org/en-US/firefox/addon/yet-another-hints-extension/")
C-k through to the client, but
turn to insert mode, return to normal mode after hitting enter. Chat
clients use this as the "room switcher" keybinding a lot of the time and
everyone loves chatting with their friends in a web browser.
(define-evil-firefox-key show-room-switcher"C-k") (kbd "C-k") true (kbd "Chat clients generally intercept C-k to show a room/chat switcher. This does that and moves to insert mode")
A to toggling Dark Reader
(define-evil-firefox-key toggle-dark-reader"A") (kbd "M-A") nil (kbd "Toggle between dark CSS and light CSS.")
e up to switching in to Firefox
(define-evil-firefox-key toggle-reader-mode"e") (kbd "C-M-r") nil (kbd "Toggle between firefox Reader Mode view.")
U up to restoring a closed
"U") 'exwm-firefox-core-tab-close-undo)(evil-define-key 'normal exwm-firefox-evil-mode-map (kbd
T up to toggling tree-style-tabs.
If the sidebar fails to toggle but other keybindings work, check that
the extension's toggle shortcut is set to
C-M-t; exwm's fake-input-key function doesnt
like when i feed it
(define-evil-firefox-key toggle-tree-tabs"T") (kbd "C-M-t") nil (kbd "toggle visibility of tree-style-tabs sidebar") (define-evil-firefox-key toggle-tree-tabs"st") (kbd "C-M-t") nil (kbd "toggle visibility of tree-style-tabs sidebar")
s up to Simple Tab Groups. This is
changed in the Extension Shortcuts setting which is synced between my
(define-evil-firefox-key simple-tab-sidebar"sg") (kbd "C-8") nil (kbd "show the simple tab group sidebar") (define-evil-firefox-key simple-tab-popup"sp") (kbd "C-M-8") t (kbd "show the simple tab group popup") (define-evil-firefox-key simple-tab-popup-manage"sm") (kbd "M-Y") nil (kbd "show the simple tab group manager") (define-evil-firefox-key simple-tab-prev-group"M-j") (kbd "C-M-u") nil (kbd "show the simple tab group popup") (define-evil-firefox-key simple-tab-next-group"M-k") (kbd "C-M-y") nil (kbd "show the simple tab group popup")
P up to a save Action in the
Wallabagger extension. This will save a URL and a snapshot of its
content to an application which I can read while I am offline. If this
doesn't work, validate the token in the plugin-options.
Validate as well that on "Manage Extensions Shortcut" page (found inside
of gear dropdown on
Wallabagger extension has either of its actions bound to
(define-evil-firefox-key wallabag-it"P") (kbd "M-w") nil (kbd "Save a page to a Wallabag instance.")
When I'm in insert mode, I don't have arrow keys bound any more, so
C-NPBF in insert mode.
"C-n") 'exwm-firefox-core-down) (evil-define-key 'insert exwm-firefox-evil-mode-map (kbd "C-p") 'exwm-firefox-core-up) (evil-define-key 'insert exwm-firefox-evil-mode-map (kbd "C-b") 'exwm-firefox-core-left) (evil-define-key 'insert exwm-firefox-evil-mode-map (kbd "C-f") 'exwm-firefox-core-right) (evil-define-key 'insert exwm-firefox-evil-mode-map (kbd "C-a") 'exwm-firefox-core-top) (evil-define-key 'insert exwm-firefox-evil-mode-map (kbd "C-e") 'exwm-firefox-core-bottom)(evil-define-key 'insert exwm-firefox-evil-mode-map (kbd
This sort of thing is what's so incredible to me about working inside of Emacs. Even my GUI browser becomes programmable in this way and in ways that the software itself doesn't allow. Firefox doesn't allow extensions to intercept or rebind certain keys (C-n and C-q, I'm looking at you), other things (like the Reader Mode and Pocket shortcuts) aren't even exposed as bindable or externally callable. Having a programmable windowing manager between you and the browser lets you do crazy shit like this.
This is the sort of pattern which could be applied to any GUI application, too, I just don't really use that many other than Firefox. And putting this together isn't even difficult. The basic setup is an afternoon's work for anyone who cares to find it.
use-package exwm-firefox-core) (use-package exwm-firefox-evil ( :after exwm :hook (exwm-manage-finish . exwm-firefox-evil-activate-if-firefox) :commands (exwm-firefox-evil-activate-if-firefox exwm-firefox-evil-mode) :configpush (aref (kbd "<escape>") 0) exwm-input-prefix-keys) ( ;; This may have to be required for smooth operation defun exwm-firefox-core-cancel () ("General cancel action." (interactive) (exwm-input--fake-key 'escape)) ;; Functionality for advising certain exwm-firefox commands ;; to only enter insert mode until return is hit. <<intercept-return>>;; Macro for aiding in rebind creation <<define-macro>> ;; Rebinds <<room-switcher>> <<toggle-dark-reader>> <<toggle-reader-mode>> <<undo-close-tab>> <<toggle-tree-tabs>> <<wallabag-it>> <<npbf-in-insert-mode>> <<simple-tabs>> <<show-link-hints>>)
I write a bit more about this as well in A Custom Firefox User Chrome and Web Browsing.↩︎