,#+ARROYOEMACSMODULE: exwm-evil-firefox ,#+ARROYOMODULEWANTS: cce/evilmode.org ,#+ARROYOMODULEWANTS: cce/exwm.org
provide 'cce/exwm-evil-firefox) (
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 'insert exwm-mode-map (kbd "<return>") 'exwm-firefox-intercept-return)
(evil-define-key 'emacs exwm-mode-map (kbd "<return>") 'exwm-firefox-intercept-return)
(evil-define-key
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 insert
mode.
This little macro will make the integrations I have far more
legible.
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)))'normal exwm-firefox-evil-mode-map ,input-key #',fname)
(evil-define-key when insert-after
,(#',fname :after #'exwm-firefox-intercept-next-ret))))) `(advice-add
Wire f
up to showing link-hints using
this
Firefox add-on.
(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/")
Wire 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")
Wire up A
to toggling Dark Reader
extension
(define-evil-firefox-key toggle-dark-reader"A") (kbd "M-A") nil
(kbd "Toggle between dark CSS and light CSS.")
Wire e
up to switching in to Firefox
reader view.
(define-evil-firefox-key toggle-reader-mode"e") (kbd "C-M-r") nil
(kbd "Toggle between firefox Reader Mode view.")
Wire U
up to restoring a closed
tab.
'normal exwm-firefox-evil-mode-map (kbd "U") 'exwm-firefox-core-tab-close-undo) (evil-define-key
Wire 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 (kbd "F1")
(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")
Wire s
up to Simple Tab Groups. This is
changed in the Extension Shortcuts setting which is synced between my
devices.
(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")
Wire 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 about:addons
) the
Wallabagger extension has either of its actions bound to Shift-Alt-W
.
(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
use C-NPBF
in insert mode.
'insert exwm-firefox-evil-mode-map (kbd "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
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)'escape))
(exwm-input--fake-key
;; 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.↩︎