Jonathan Hunt

- jonhunt.uk -
07 Feb 2021

My Emacs config

What's Emacs?

For the uninitiated, Emacs is thought of as a text editor (usually compared against vi/vim as part of the great editor war) but it's actually a lisp interpreter, and text editing is just one of its capabilities. It's capable of countless different things, and the Emacs community is always adding new capabilities.

For anyone curious about learning Emacs, I would highly recommend watching UncleDave's tutorials. It's also a good idea to check out other people's config files or my own config on this page, and putting the parts you like into your own Emacs configuration file (usually /.emacs.d/init.el by default). There are also Emacs distributions where the configuration has already been done, such as Spacemacs and Doom Emacs. I personally recommend against those though as you will end up learning a lot more and having a much more personalised setup if you build up gradually from default Emacs.

How I use Emacs

Below lists all the things I use Emacs for and how:

Feature Packages Notes
Calculator scratch (built-in) Conventional calculator packages exist also, but scratch uses lisp expressions which are far more powerful
Configuration org-babel Org-babel allows me to configure any program or script (not just Emacs ones) within a single file, making organisation far simpler and more portable
EPUB reader nov.el  
Emails mu4e It works fully for Gmail, but I'm still figuring out how to configure it for other email accounts
File management dired (built-in), dired-x, openwith, all-the-icons-dired  
Launching non-emacs programs dmenu  
Music player mingus, mingus-header-mode  
PDF reader pdf-tools  
RSS elfeed, elfeed-org I have an elfeed webpage which lists the content I follow and explains how I use elfeed
Terminal emulation eshell, ansi-term, term (all built-in) I use eshell in most cases, and ansi-term for things eshell can't do well (e.g ncurses)
To-do lists and notes orgmode (built in)  
Torrent client mentor If I open it manually, it works as intended, but I can't get launching at startup to work
Web development org-static-blog, babel At some point I'll make a blogpost explaining how I made this website in Emacs
Window manager exwm  

I try to do as much as I can within Emacs as having everything integrated into a single program configured exactly as I like is really nice. Some things I don't currently do in Emacs but would like to in the future include:

Feature Packages
Cryptocurrency wallet balance tracker (displaying on the modeline) I've not found a package for this yet, but it may be possible to do by pulling the current value of crytocurrencies url.el and multipying them by the ammount of cryptocurrency I currently hold
Git management magit
Reading comics/manga (.CBR/.CBZ files) ? - haven't looked into yet
Remote file editing tramp
IRC erc
Private chat ? - haven't looked into yet
Search websites without a web browser ytel/ivy-youtube for YouTube. I haven't found equivalents for other websites
screenshot-thumb.png
Figure 1: An example of my current setup (this is the media workspace, with elfeed, mingus, dired and mentor open)

My config

The original source is an orgmode file, which is then automatically converted into 2 additional files:

  1. An elisp file so Emacs can run it. This is done via babel.
  2. The webpage you're currently viewing. This is done via org-static-blog.

Below are the configuration rules I use:

Preamble

Load ob-tangle early

This is required if you want to tangle this file from within Emacs itself. Without it, `org-babel-tangle-file` will error at startup because the function isn't yet defined.

(require 'ob-tangle)
(require 'cl-lib)

Increase rubish collection during startup:

(setq startup/gc-cons-threshold gc-cons-threshold)
(setq gc-cons-threshold most-positive-fixnum)
(defun startup/reset-gc () (setq gc-cons-threshold startup/gc-cons-threshold))
(add-hook 'emacs-startup-hook 'startup/reset-gc)

Initialise packages:

(require 'package)

(setq package-archives '(("org"       . "http://orgmode.org/elpa/")
                         ("gnu"       . "http://elpa.gnu.org/packages/")
                         ("melpa"     . "http://melpa.org/packages/")))
(package-initialize)

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

(require 'use-package)
(setq use-package-always-ensure t)

This section defines functions that allow Polybar to show the current workspace:

(defvar dw/current-polybar-workspace ""
  "Cached string of current EXWM workspace to send to Polybar.")

(defun dw/polybar-current-workspace ()
  "Return the cached EXWM workspace string for Polybar."
  dw/current-polybar-workspace)

(defun dw/update-polybar-workspace ()
  "Update `dw/current-polybar-workspace` to reflect current EXWM workspace."
  (setq dw/current-polybar-workspace
        (let* ((main-visible '(1 2 3 4))  ;; EXWM workspace indexes to display
               (current exwm-workspace-current-index)
               (output (mapconcat
                        (lambda (i)
                          (if (= i current)
                              (format "[%d]" i)
                            (format "%d" i)))
                        main-visible
                        " ")))
          (if (not (member current main-visible))
              (concat output " [+]")
            output))))

(defun dw/send-exwm-workspace-to-polybar ()
  "Update Polybar by refreshing EXWM workspace display."
  (dw/update-polybar-workspace)
  (start-process-shell-command
   "polybar-msg"
   nil
   "polybar-msg action '#exwm.hook.0'"))

Load up EXWM with use-package:

(use-package exwm
  :ensure t
  :config
  ;; Set the default number of workspaces
  (setq exwm-workspace-number 10)

  ;; Keys that should always pass through to Emacs
  (setq exwm-input-prefix-keys
     '(?\C-x
       ?\C-u
       ?\C-h
       ?\M-x
       ?\M-`
       ?\M-&
       ?\M-:
       ?\C-\M-j  ;; Buffer list
       ?\C-\     ;; Ctrl+Space
       ?\s-d))   ;; <-- ADD THIS
  ;; Ctrl+Q to send next key directly
  (define-key exwm-mode-map [?\C-q] 'exwm-input-send-next-key)

  ;; Global keybindings
  (setq exwm-input-global-keys
        `(
          ([?\s-r] . exwm-reset)
          ([s-left] . windmove-left)
          ([s-right] . windmove-right)
          ([s-up] . windmove-up)
          ([s-down] . windmove-down)
          ([?\s-&] . (lambda (command)
                       (interactive (list (read-shell-command "$ ")))
                       (start-process-shell-command command nil command)))
          ([?\s-w] . exwm-workspace-switch)
          ,@(mapcar (lambda (i)
                      `(,(kbd (format "s-%d" i)) .
                        (lambda ()
                          (interactive)
                          (exwm-workspace-switch-create ,i))))
                    (number-sequence 0 9))))

  ;; Start EXWM
  (exwm-enable)

  ;; Add these hooks AFTER (exwm-enable)
  (add-hook 'exwm-workspace-switch-hook #'dw/send-exwm-workspace-to-polybar)
  (add-hook 'exwm-init-hook #'dw/send-exwm-workspace-to-polybar))

;; Start Emacs server for emacsclient support
(require 'server)
(unless (server-running-p)
  (server-start))

Disable menubar, scrollbar, toolbar and fringe:

(menu-bar-mode -1)
(tool-bar-mode -1)
(fringe-mode -1)
(scroll-bar-mode -1)

Disable scratch buffer's initial message:

(setq initial-scratch-message "")

Disable menubar, scrollbar, toolbar and fringe:

(menu-bar-mode -1)
(tool-bar-mode -1)
(fringe-mode -1)
(scroll-bar-mode -1)

Disable scratch buffer's initial message:

(setq initial-scratch-message "")

Set UTF-8 encoding:

(setq locale-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(prefer-coding-system 'utf-8)

Line indicator options:

;;highlight current line
(global-hl-line-mode +1)

;; show line numbers only on code files
(add-hook 'prog-mode-hook #'display-line-numbers-mode)

Change and 'yes or no' prompts to 'y or n':

(fset 'yes-or-no-p 'y-or-n-p)

Load up solarized dark theme:

 (use-package solarized-theme
   :ensure t
   :init
   (load-theme 'solarized-dark t))

 ;; set default font with fallbacks
(defun my/set-default-font ()
  "Set default font if available."
  (cl-block nil
    (cl-dolist (font '("Iosevka" "JetBrains Mono" "Fira Code" "DejaVu Sans Mono"))
      (when (member font (font-family-list))
        (set-face-attribute 'default nil :font (format "%s-8" font))
        (cl-return)))))

(my/set-default-font)

Add clock in modeline (currently disabled as I use polybar to display time):

;; format
;;(setq display-time-24hr-format t)
;;(setq display-time-format "%H:%M - %d %B %Y")
;; enable clock
;;(display-time-mode 1)

Rename EXWM buffer to window's title or class:

 (defun my/exwm-clean-title (title class)
   "Remove redundant class name from the window title."
   (if (and title class
            (string-match (concat " - " (regexp-quote class) "\\'") title))
       (replace-regexp-in-string (concat " - " (regexp-quote class) "\\'") "" title)
     title))

 (defun my/exwm-rename-buffer ()
   "Rename EXWM buffer to 'Class: Cleaned Title'."
   (let* ((class (or exwm-class-name "EXWM"))
          (clean-title (my/exwm-clean-title exwm-title class)))
     (exwm-workspace-rename-buffer (format "%s: %s" class clean-title))))

 (add-hook 'exwm-update-class-hook #'my/exwm-rename-buffer)
(add-hook 'exwm-update-title-hook #'my/exwm-rename-buffer)

Set colors for packages:

(with-eval-after-load 'org
  (set-face-attribute 'org-block nil :background "#002b36" :foreground "#93a1a1")
  (set-face-attribute 'org-block-begin-line nil :background "#073642" :foreground "#586e75")
  (set-face-attribute 'org-block-end-line nil :background "#073642" :foreground "#586e75"))

When window is split, change focus to the new split:

(defun split-and-follow-horizontally ()
  (interactive)
  (split-window-below)
  (balance-windows)
  (other-window 1))
(global-set-key (kbd "C-x 2") 'split-and-follow-horizontally)

(defun split-and-follow-vertically ()
  (interactive)
  (split-window-right)
  (balance-windows)
  (other-window 1))
(global-set-key (kbd "C-x 3") 'split-and-follow-vertically)

Set C-c e to edit init file:

(defun config-visit ()
  (interactive)
  (find-file "~/website/posts/config.org"))
(global-set-key (kbd "C-c e") 'config-visit)

Reload init file when C-c r is pressed:

(defun config-reload ()
  "Reloads ~/website/posts/config.org at runtime."
  (interactive)
  (org-babel-load-file (expand-file-name "~/website/posts/config.org")))
(global-set-key (kbd "C-c r") 'config-reload)

Load up powerline, using default theme:

(use-package powerline
  :ensure t
  :init
    (powerline-default-theme))

Fix powerline colours for solarized theme:

(custom-set-faces
 '(mode-line ((t (:background "#073642" :foreground "#839496" :box nil))))
 '(mode-line-inactive ((t (:background "#002b36" :foreground "#586e75" :box nil)))))

Define custom layouts:

;; Media layout
(defun lmedia ()
(interactive)
 (eshell)
(split-window-below)
(mingus)
(mingus-cancel-timer) ;; stops mingus default behaviour of redisplaying every second, which caused xwindows to flicker and lose focus
(split-window-right)
(elfeed)
(other-window 2)
(split-window-right)
(dired "~/"))

;; Social layout
(defun lsocial ()
(interactive)
(mu4e)
(split-window-right)
(erc)
)
;; Web/blog authoring layout to be added

Interface keybindings

Open a new instance of eshell with Super + Enter:

(defun eshell-new()
  "Open a new instance of eshell."
  (interactive)
  (eshell 'N))

(exwm-input-set-key (kbd "<s-return>") #'eshell-new)
; the code below allowes setting urxvt as terminal
; (exwm-input-set-key (kbd "<s-return>")  
;                    (lambda ()  
;                     (interactive)  
;                    (start-process-shell-command "urxvt" nil "urxvt")))  

Lock screen with Super + l:

(exwm-input-set-key (kbd "s-l")  
                    (lambda ()  
                      (interactive)  
                      (start-process-shell-command "slock" nil "slock")))  

Screenshot with Super + shift + P:

(defun take-screenshot ()
  "Takes a fullscreen screenshot of the current workspace"
  (interactive)
  (when window-system
  (loop for i downfrom 3 to 1 do
        (progn
          (message (concat (number-to-string i) "..."))
          (sit-for 1)))
  (message "Cheese!")
  (sit-for 1)
  (start-process "screenshot" nil "import" "-window" "root" 
             (concat (getenv "HOME") "/" (subseq (number-to-string (float-time)) 0 10) ".png"))
  (message "Screenshot taken!")))
(exwm-input-set-key (kbd "s-P") 'take-screenshot)

Close all buffers with C-M-s-k:

(defun close-all-buffers ()
  "Kill all buffers without regard for their origin."
  (interactive)
  (mapc 'kill-buffer (buffer-list)))
(global-set-key (kbd "C-M-s-k") 'close-all-buffers)

Bind C-x 1 to toggle between a single window and previous window splits (useful for things like temporary image viewing, pdf-viewing, etc):

(defvar window-split-saved-config nil)

(defun window-split-toggle-one-window ()
  "Make the current window fill the frame.
If there is only one window try reverting to the most recently saved
window configuration."
  (interactive)
  (if (and window-split-saved-config (not (window-parent)))
      (set-window-configuration window-split-saved-config)
    (setq window-split-saved-config (current-window-configuration))
    (delete-other-windows)))

(global-set-key (kbd "C-x 1") 'window-split-toggle-one-window)

General packages

Capture

Allows screen capture recordings (commented out as it no longer works)

;(require 'capture)
;
;(setq capture-video-dest-dir "~/screencasts/SORT/")
;(global-set-key (kbd "s-c") 'capture-run-mode)
;
;(defun my-capture-presets ()
;  "Make my presets for capturing."
;  (interactive)
;  (capture-presets-clear)
;  (capture-add-preset 454 74 1280 720 15 "webm"
;                      ""
;                      "1280px (no audio)"))
;(my-capture-presets)

Chan

Browse 4chan within Emacs:

;; (use-package chan
;;   :load-path "lisp/chan")

Dired

Add icons:

(use-package all-the-icons
  :if (display-graphic-p)
  :ensure t
  :config
  ;; Run font installer only if fonts are missing
  (unless (find-font (font-spec :name "all-the-icons"))
    (all-the-icons-install-fonts t)))

(use-package all-the-icons-dired
  :if (display-graphic-p)
  :ensure t
  :hook (dired-mode . all-the-icons-dired-mode)
  :config
  (setq all-the-icons-dired-monochrome nil))

;; Improve font rendering performance
(setq inhibit-compacting-font-caches t)

List directories before files:

(defun mydired-sort ()
  "Sort dired listings with directories first."
  (save-excursion
    (let (buffer-read-only)
      (forward-line 2) ;; beyond dir. header 
      (sort-regexp-fields t "^.*$" "[ ]*." (point) (point-max)))
    (set-buffer-modified-p nil)))

(defadvice dired-readin
  (after dired-after-updating-hook first () activate)
  "Sort dired listings with directories first before adding marks."
  (mydired-sort))

Show file sizes in KB, MB, GB instead of just bytes:

(setq-default dired-listing-switches "-alh")

Set file asociations:

;; Enable external app handling via openwith.el
(use-package openwith
  :ensure t
  :config
  (setq openwith-associations
        '(("\\.\\(mp4\\|mkv\\|mp3\\|flac\\|webm\\|ogg\\|mov\\|wmv\\|avi\\|m4v\\|m4a\\|aac\\|3gp\\|mpg\\)$" "mpv" (file))
          ("\\.avif$" "viewnior" (file))
          ("\\.exe$" "wine" (file))))
  (openwith-mode t))

;; Large file warning is pointless for modern rigs
(setq large-file-warning-threshold nil)

;; .epub files go to nov.el
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))

;; .cbr files treated as archives
(add-to-list 'auto-mode-alist '("\\.\\(cbr\\)\\'" . archive-mode))

Use external converter for incompatible images types (e.g webp)

(setq image-use-external-converter t)

Loop animated images by default (e.g .gifs), and scale to fit buffer height by default:

(with-eval-after-load 'image
  (add-hook 'image-mode-hook 'image-transform-fit-to-height)
  (setq image-toggle-animation t)
  (setq image-animate-loop t))

Delete opened images fuction (WIP):

(defun image-delete (confirm) 
  (interactive
     (list (y-or-n-p (format "Delete %s?" buffer-file-name))))
  (when confirm (delete-file (buffer-file-name))))

;; (define-key image-mode-map (kbd "D") 'image-delete)

Hide dotfiles by default, and add super + h keybinding to toggle:

(add-hook 'dired-load-hook '(lambda () (require 'dired-x))) ; Load Dired X when Dired is loaded.
(setq dired-omit-mode t) ; Turn on Omit mode.

(require 'dired-x)
(setq-default dired-omit-files-p t) ; Buffer-local variable
(setq dired-omit-files (concat dired-omit-files "\\|^\\..+$"))

;; keybinding toggle
(define-key dired-mode-map (kbd "s-h") 'dired-omit-mode)

Dmenu

Allow external commands to be launched within emacs when keybinding super+d is pressed:

(use-package dmenu
 :ensure t
 :bind
 ("s-d" . 'dmenu))

EAF browser

Not something I really use just kept in case:

;; (add-to-list 'load-path "~/.emacs.d/site-lisp/emacs-application-framework/")
;; (require 'eaf)
;; (require 'eaf-browser)

Elfeed

Elfeed keyboard shortcut:

(global-set-key (kbd "C-x w") 'elfeed)

Load elfeed-org to allow rss feeds to be set up with an org file:

 ; Load elfeed-org
(use-package elfeed-org
  :ensure t
  :after elfeed
  :config
  (elfeed-org))

 ;; Optionally specify a number of files containing elfeed
 ;; configuration. If not set then the location below is used.
 (setq rmh-elfeed-org-files (list "~/website/posts/elfeed.org"))

Hide tags:

;; (defun anon/elfeed-search-print-entry (entry)
;;     "Print ENTRY to anon's buffer without tags"
;;     (let* ((date (elfeed-search-format-date (elfeed-entry-date entry)))
;; 	   (title (or (elfeed-meta entry :title) (elfeed-entry-title entry) ""))
;; 	   (title-faces (elfeed-search--faces (elfeed-entry-tags entry)))
;; 	   (feed (elfeed-entry-feed entry))
;; 	   (feed-title
;; 	(when feed
;; 	  (or (elfeed-meta feed :title) (elfeed-feed-title feed))))
;; 	   (title-width (- (window-width) 10 elfeed-search-trailing-width))
;; 	   (title-column (elfeed-format-column
;; 		  title (elfeed-clamp
;; 		     elfeed-search-title-min-width
;; 		     title-width
;; 		     elfeed-search-title-max-width)
;; 		  :left)))
;;       (insert (propertize date 'face 'elfeed-search-date-face) " ")
;;       (insert (propertize title-column 'face title-faces 'kbd-help title) " ")
;;       (when feed-title
;; 	(insert (propertize feed-title 'face 'elfeed-search-feed-face) " "))))

;; (setq elfeed-search-print-entry-function #'anon/elfeed-search-print-entry)

Set default search filter for video updates uploaded up to 5 days ago:

(setq-default elfeed-search-filter "@5-days-ago +videos")

Open youtube videos with mpv:

(defun elfeed-play-with-mpv ()
  "Play entry link with mpv."
  (interactive)
  (let ((entry (if (eq major-mode 'elfeed-show-mode) elfeed-show-entry (elfeed-search-selected :single)))
        (quality-arg "")
        )
    (message "Opening %s with mpv..." (elfeed-entry-link entry))
    (start-process "elfeed-mpv" nil "mpv" (elfeed-entry-link entry))))

(defvar elfeed-mpv-patterns
  '("youtu\\.?be")
  "List of regexp to match against elfeed entry link to know
whether to use mpv to visit the link.")

Open in mpv when o is pressed:

(eval-after-load 'elfeed-search
 '(define-key elfeed-search-mode-map (kbd "o") 'elfeed-play-with-mpv))

Download in ~/downloads/youtube/ when d is pressed.. in theory. I haven't actually managed to get this to work yet, I get the error Wrong type-argument: elfeed-entry nil when I press d currently. If you know why this is, please let me know.

;; Set executable path
 (setq youtube-dl-path "/usr/bin/youtube-dl")
 ;; Set video storage path
 (setq youtube-dl-output-dir "~/Downloads/youtube-dl/")

(defun elfeed-download-video (entry)
  "Download a video using youtube-dl."
  (interactive (list (elfeed-search-selected :ignore-region)))
  (async-shell-command (format "%s -o \"%s%s\" -f bestvideo+bestaudio %s"
                               youtube-dl-path
                               youtube-dl-output-dir
                               "%(title)s.%(ext)s"
                               (elfeed-entry-link entry))))

 ;; set keybinding
 (eval-after-load 'elfeed-search
  '(define-key elfeed-search-mode-map (kbd "d") 'elfeed-download-video))

Appearance settings:

(setq-default elfeed-initial-tags nil)
(setq-default elfeed-search-date-format (quote ("%a, %R" 10 :left)))
(setq-default elfeed-curl-max-connections 100)
(setq-default elfeed-search-trailing-width 30)

Engine-mode

Enable engine-mode, which allows searching across websites directly within Emacs:

  (use-package engine-mode
    :ensure t
    :config
    (engine-mode t)

    (defengine amazon
  "https://www.amazon.co.uk/s?k=%s"
  :keybinding "a")

(defengine bitchute
  "https://www.bitchute.com/search/?query=%s"
  :keybinding "b")

(defengine duckduckgo
  "https://duckduckgo.com/?q=%s"
  :keybinding "d")

(defengine ebay
  "https://www.ebay.co.uk/sch/i.html?_nkw=%s"
  :keybinding "e")

(defengine github
  "https://github.com/search?ref=simplesearch&q=%s")

(defengine google
  "http://www.google.com/search?ie=utf-8&oe=utf-8&q=%s"
  :keybinding "g")

(defengine google-images
  "https://www.google.co.uk/search?tbm=isch&q=%s"
  :keybinding "i")

(defengine google-maps
  "http://maps.google.com/maps?q=%s"
  :docstring "Mappin' it up.")

(defengine odysee
  "https://odysee.com/$/search?q=%s"
  :keybinding "o")

(defengine peertube
  "https://search.joinpeertube.org/search?search=%s"
  :keybinding "p")

(defengine project-gutenberg
  "http://www.gutenberg.org/ebooks/search/?query=%s")

(defengine qwant
  "https://www.qwant.com/?q=%s")

(defengine rfcs
  "http://pretty-rfc.herokuapp.com/search?q=%s")

(defengine stack-overflow
  "https://stackoverflow.com/search?q=%s")

(defengine startpage
  "https://searx.info/search?q=%s"
  :keybinding "s")

(defengine twitter
  "https://twitter.com/search?q=%s")

(defengine wikipedia
  "http://www.wikipedia.org/search-redirect.php?language=en&go=Go&search=%s"
  :keybinding "w"
  :docstring "Searchin' the wikis.")

(defengine wiktionary
  "https://www.wikipedia.org/search-redirect.php?family=wiktionary&language=en&go=Go&search=%s")

(defengine wolfram-alpha
  "http://www.wolframalpha.com/input/?i=%s")

(defengine youtube
  "http://www.youtube.com/results?aq=f&oq=&search_query=%s"
  :keybinding "y")
  )


Ivy

Load up Ivy:

(use-package ivy
  :ensure t
  :config
  (ivy-mode 1))

Mentor

Keep torrents from previous session:

;  (setq-default mentor-rtorrent-keep-session t)

Mu4e

Email for Emacs:

(use-package mu4e
  :ensure nil
  ;; :load-path "/usr/share/emacs/site-lisp/mu4e/"
  :defer 20 ; Wait until 20 seconds after startup
  :config

  ;; This is set to 't' to avoid mail syncing issues when using mbsync
  (setq mu4e-change-filenames-when-moving t)

  ;; Refresh mail using isync every 10 minutes
  (setq mu4e-update-interval (* 10 60))
  (setq mu4e-get-mail-command "mbsync -a")
  (setq mu4e-maildir "~/Mail")

  ;; Configure the function to use for sending mail
  (setq message-send-mail-function 'smtpmail-send-it)

  (setq mu4e-contexts
         (list
          ;; Old account
          (make-mu4e-context
           :name "Old"
           :match-func
           (lambda (msg)
             (when msg
               (string-prefix-p "/Gmail" (mu4e-message-field msg :maildir))))
           :vars '((user-mail-address . "hunt.jonr@gmail.com")
                   (user-full-name    . "Jon Hunts Gmail")
                   (smtpmail-smtp-server . "smtp.gmail.com")
                   (smtpmail-smtp-service . 465)
                   (smtpmail-stream-type . ssl)
                   (mu4e-drafts-folder . "/Gmail/[Gmail]/Drafts")
                   (mu4e-sent-folder . "/Gmail/[Gmail]/Sent Mail")
                   (mu4e-refile-folder . "/Gmail/[Gmail]/All Mail")
                   (mu4e-trash-folder . "/Gmail/[Gmail]/Trash")))

          ;; Personal account
          (make-mu4e-context
           :name "Personal"
           :match-func
           (lambda (msg)
             (when msg
               (string-prefix-p "/Personal" (mu4e-message-field msg :maildir))))
           :vars '((user-mail-address . "jon@jonhunt.uk")
                   (user-full-name    . "Jon Hunt")
                   (smtpmail-smtp-server . "mail.jonhunt.uk")
                   (smtpmail-smtp-service . 587)
                   (smtpmail-stream-type . tls)
                   (mu4e-drafts-folder . "/Personal/Drafts")
                   (mu4e-sent-folder . "/Personal/Sent")
                   (mu4e-refile-folder . "/Personal/Archive")
                   (mu4e-trash-folder . "/Personal/Trash")))))


  (setq mu4e-maildir-shortcuts
         '(("/Gmail/Inbox"               . ?i)
           ("/Gmail/[Gmail]/Sent Mail"   . ?s)
           ("/Gmail/[Gmail]/Trash"       . ?t)
           ("/Gmail/[Gmail]/Drafts"      . ?d)
           ("/Gmail/[Gmail]/All Mail"    . ?a)))

  ;; open with first context by default
  (setq mu4e-context-policy 'pick-first)

  ;; Run mu4e in the background to sync mail periodically
  (mu4e t))

MPD and Mingus

Keyboard controls:

(exwm-input-set-key (kbd "<s-tab>") #'mpd-pause)
(exwm-input-set-key (kbd "s-,") #'mpd-prev)
(exwm-input-set-key (kbd "s-.") #'mpd-next)
(exwm-input-set-key (kbd "s-9") #'mingus-vol-down)
(exwm-input-set-key (kbd "s-0") #'mingus-vol-up)
(exwm-input-set-key (kbd "s-n") #'mingus-seek)
(exwm-input-set-key (kbd "s-p") #'mingus-seek-backward)

Mingus settings:

(setq-default mingus-mode-always-modeline t)
(setq-default mingus-mode-line-separator " > ")
(setq-default mingus-mode-line-string-max 60)
(setq-default mingus-use-mouse-p nil)

Enable headers for mingus:

(use-package mingus
  :ensure t
  :after powerline
  :hook (mingus-playlist-mode . mingus-header-mode)
  :config
  (require 'powerline)
  (powerline-default-theme) ;; ensures faces are set
  (setq powerline-default-separator 'utf-8)

  (defgroup mingus-header nil
    "Customizations for `mingus-header-mode`."
    :group 'mingus)

  (defcustom mingus-header-use-powerline t
    "Use `powerline` to create the header."
    :group 'mingus-header)

  (defun mingus-header-line-format ()
    "Create a header line for `mingus-playlist-mode`."
    (if (and (boundp 'mpd-inter-conn)
             (fboundp 'mpd-get-status)
             (mpd-connected-p mpd-inter-conn))
        (let* ((status (mpd-get-status mpd-inter-conn))
               (show-song (member (plist-get status 'state) '(play pause)))
               (active (powerline-selected-window-active))
               (face1 (if active 'mode-line 'mode-line-inactive))
               (face2 (if active 'powerline-active1 'powerline-inactive1))
               (face3 (if active 'powerline-active2 'powerline-inactive2))
               (face4 (if active 'mode-line-buffer-id 'mode-line-buffer-id-inactive))
               (sep-left (intern (format "powerline-%s-%s"
                                         powerline-default-separator
                                         (car powerline-default-separator-dir))))
               (sep-right (intern (format "powerline-%s-%s"
                                          powerline-default-separator
                                          (cdr powerline-default-separator-dir))))
               (state (pcase (plist-get status 'state)
                        ('play "playing ")
                        ('pause "paused ")
                        ('stop "stopped ")
                        (_ "error ")))
               (volume (let ((vol (plist-get status 'volume)))
                         (if (equal vol 100)
                             "Vol: 100%% "
                           (format "Vol:  %d%%%% " vol))))
               (single (plist-get status 'single))
               (consume (plist-get status 'consume))
               (playback (format "%s%s%s%s- "
                                 (if (eq (plist-get status 'repeat) 1) "r" "-")
                                 (if (eq (plist-get status 'random) 1) "z" "-")
                                 (if (and single (string= single "1")) "s" "-")
                                 (if (and consume (string= consume "1")) "c" "-")))
               (time (if show-song
                         (format "%s/%s "
                                 (mingus-sec->min:sec (plist-get status 'time-elapsed))
                                 (mingus-sec->min:sec (plist-get status 'time-total)))
                       ""))
               (lhs (list (powerline-raw (propertize state 'face face4) face1 'l)
                          (when show-song
                            (funcall sep-left face1 face2))
                          (when show-song
                            (powerline-raw time face2 'l))
                          (funcall sep-left face2 face3)))
               (rhs (list (funcall sep-right face3 face2)
                          (powerline-raw volume face2 'l)
                          (funcall sep-right face2 face1)
                          (powerline-raw playback face1 'l))))
          (concat (powerline-render lhs)
                  (powerline-fill face3 (powerline-width rhs))
                  (powerline-render rhs)))
      ;; Not connected
      "MPD not connected."))

  (define-minor-mode mingus-header-mode
    "Minor mode to show MPD status in the header line."
    :lighter ""
    :group 'mingus-header
    (if (not (eq major-mode 'mingus-playlist-mode))
        (progn
          (message "[mingus-header-mode] Not in mingus-playlist-mode.")
          (setq header-line-format nil))
      (setq header-line-format
            (when mingus-header-mode
              '("%e" (:eval (mingus-header-line-format))))))))

Org mode

Make orgmode TODO list counters count beyond direct child:

(setq org-hierarchical-todo-statistics nil)

Define languages for org-babel

(org-babel-do-load-languages
 'org-babel-load-languages
 '((emacs-lisp . t)
 (python . t)))

(push '("conf-unix" . conf-unix) org-src-lang-modes)

;; stop it asking for confirmation upon evaluating code blocks
(setq org-confirm-babel-evaluate nil)

Structure templates. Allow you to type <el followed by tab to start an elisp codeblock for example:

(require 'org-tempo)

(add-to-list 'org-structure-template-alist '("sh" . "src shell"))
(add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
(add-to-list 'org-structure-template-alist '("py" . "src python"))
(add-to-list 'org-structure-template-alist '("conf" . "src conf"))

PDF tools

Initialise pdf tools:

;;(pdf-tools-install)

Set zooming to be more fine-grained (10% instead of the default 25%):

(setq pdf-view-resize-factor 1.1)

Image conversion:

(defun convert-to-png ()
  "Convert all .jpg, .jpeg, .tiff, and .webp files in the current directory to .png."
  (interactive)
  (dolist (f (directory-files default-directory t "\\(\\.jpg\\|\\.jpeg\\|\\.tiff\\|\\.webp\\)$"))
    (let ((output (concat (file-name-sans-extension f) ".png")))
      ;; Run the ffmpeg command and wait for it to finish
      (if (zerop (call-process "ffmpeg" nil nil nil "-i" f output))
          (progn
            (message "Successfully converted: %s" f)
            (delete-file f))  ;; Delete original if conversion is successful
        (message "Failed to convert: %s" f)))))  ;; If conversion fails, print an error message

(defun convert-to-png-recursive ()
  "Convert all .jpg, .jpeg, .tiff, and .webp files in the current directory and subdirectories to .png."
  (interactive)
  (dolist (f (directory-files-recursively default-directory "\\(\\.jpg\\|\\.jpeg\\|\\.tiff\\|\\.webp\\)$"))
    (let ((output (concat (file-name-sans-extension f) ".png")))
      ;; Run the ffmpeg command and wait for it to finish
      (if (zerop (call-process "ffmpeg" nil nil nil "-i" f output))
          (progn
            (message "Successfully converted: %s" f)
            (delete-file f))  ;; Delete original if conversion is successful
        (message "Failed to convert: %s" f)))))  ;; If conversion fails, print an error message

(defun convert-webp-tiff-to-png ()
  "Convert all .webp and .tiff files in the current directory to .png."
  (interactive)
  (dolist (f (directory-files default-directory t "\\(\\.webp\\|\\.tiff\\)$"))
    (let ((output (concat (file-name-sans-extension f) ".png")))
      ;; Run the ffmpeg command and wait for it to finish
      (if (zerop (call-process "ffmpeg" nil nil nil "-i" f output))
          (progn
            (message "Successfully converted: %s" f)
            (delete-file f))  ;; Delete original if conversion is successful
        (message "Failed to convert: %s" f)))))  ;; If conversion fails, print an error message

(defun convert-webp-tiff-to-png-recursive ()
  "Convert all .webp and .tiff files in the current directory and subdirectories to .png."
  (interactive)
  (dolist (f (directory-files-recursively default-directory "\\(\\.webp\\|\\.tiff\\)$"))
    (let ((output (concat (file-name-sans-extension f) ".png")))
      ;; Run the ffmpeg command and wait for it to finish
      (if (zerop (call-process "ffmpeg" nil nil nil "-i" f output))
          (progn
            (message "Successfully converted: %s" f)
            (delete-file f))  ;; Delete original if conversion is successful
        (message "Failed to convert: %s" f)))))  ;; If conversion fails, print an error message

Country visit:

;; Define custom TODO keywords and their faces
(setq org-todo-keywords
      '((sequence "TOGO(t)" "SOON(s)" "THRU(r)" "TODO(o)" "|" "GONE(g)" "HOME(h)" "DONE(d)")))

(setq org-todo-keyword-faces
      '(("TOGO" . (:foreground "orange" :weight bold))
        ("SOON" . (:foreground "yellow" :weight bold))
        ("THRU" . (:foreground "grey" :weight bold))
        ("TODO" . (:foreground "red" :weight bold))
        ("GONE" . (:foreground "green" :weight bold))
        ("HOME" . (:foreground "light blue" :weight bold))
        ("DONE" . (:foreground "green" :weight bold))))

    ;; Set font for emojis using Noto Color Emoji
    (set-fontset-font t 'unicode "Noto Color Emoji" nil 'prepend)

    ;; Mapping of country names to their flag emojis (with multiple names)
    (defvar country-flag-map
     '(("๐Ÿ‡ฆ๐Ÿ‡ซ" . ("Afghanistan"))
       ("๐Ÿ‡ฆ๐Ÿ‡ฝ" . ("ร…land" "Aland"))
       ("๐Ÿ‡ฆ๐Ÿ‡ฑ" . ("Albania"))
       ("๐Ÿ‡ฉ๐Ÿ‡ฟ" . ("Algeria"))
       ("๐Ÿ‡ฆ๐Ÿ‡ธ" . ("American Samoa"))
       ("๐Ÿ‡ฆ๐Ÿ‡ฉ" . ("Andorra"))
       ("๐Ÿ‡ฆ๐Ÿ‡ด" . ("Angola"))
       ("๐Ÿ‡ฆ๐Ÿ‡ฎ" . ("Anguilla"))
       ("๐Ÿ‡ฆ๐Ÿ‡ถ" . ("Antarctica"))
       ("๐Ÿ‡ฆ๐Ÿ‡ฌ" . ("Antigua and Barbuda" "Antigua"))
       ("๐Ÿ‡ฆ๐Ÿ‡ท" . ("Argentina"))
       ("๐Ÿ‡ฆ๐Ÿ‡ฒ" . ("Armenia"))
       ("๐Ÿ‡ฆ๐Ÿ‡ผ" . ("Aruba"))
       ("๐Ÿ‡ฆ๐Ÿ‡จ" . ("Ascension Island"))
       ("๐Ÿ‡ฆ๐Ÿ‡บ" . ("Australia"))
       ("๐Ÿ‡ฆ๐Ÿ‡น" . ("Austria"))
       ("๐Ÿ‡ฆ๐Ÿ‡ฟ" . ("Azerbaijan"))
       ("๐Ÿ‡ง๐Ÿ‡ธ" . ("Bahamas"))
       ("๐Ÿ‡ง๐Ÿ‡ญ" . ("Bahrain"))
       ("๐Ÿ‡ง๐Ÿ‡ฉ" . ("Bangladesh"))
       ("๐Ÿ‡ง๐Ÿ‡ง" . ("Barbados"))
       ("๐Ÿ‡ง๐Ÿ‡พ" . ("Belarus"))
       ("๐Ÿ‡ง๐Ÿ‡ช" . ("Belgium"))
       ("๐Ÿ‡ง๐Ÿ‡ฟ" . ("Belize"))
       ("๐Ÿ‡ง๐Ÿ‡ฏ" . ("Benin"))
       ("๐Ÿ‡ง๐Ÿ‡ฒ" . ("Bermuda"))
       ("๐Ÿ‡ง๐Ÿ‡น" . ("Bhutan"))
       ("๐Ÿ‡ง๐Ÿ‡ด" . ("Bolivia"))
       ("๐Ÿ‡ง๐Ÿ‡ฆ" . ("Bosnia and Herzegovina" "Bosnia"))
       ("๐Ÿ‡ง๐Ÿ‡ผ" . ("Botswana"))
       ("๐Ÿ‡ง๐Ÿ‡ท" . ("Brazil"))
       ("๐Ÿ‡ฎ๐Ÿ‡ด" . ("British Indian Ocean Territory"))
       ("๐Ÿ‡ป๐Ÿ‡ฌ" . ("British Virgin Islands"))
       ("๐Ÿ‡ง๐Ÿ‡ณ" . ("Brunei"))
       ("๐Ÿ‡ง๐Ÿ‡ฌ" . ("Bulgaria"))
       ("๐Ÿ‡ง๐Ÿ‡ซ" . ("Burkina Faso"))
       ("๐Ÿ‡ง๐Ÿ‡ฎ" . ("Burundi"))
       ("๐Ÿ‡จ๐Ÿ‡ป" . ("Cabo Verde" "Cape Verde"))
       ("๐Ÿ‡ฐ๐Ÿ‡ญ" . ("Cambodia"))
       ("๐Ÿ‡จ๐Ÿ‡ฒ" . ("Cameroon"))
       ("๐Ÿ‡จ๐Ÿ‡ฆ" . ("Canada"))
       ("๐Ÿ‡ง๐Ÿ‡ถ" . ("Caribbean Netherlands" "Bonaire" "Sint Eustatius" "Saba"))
       ("๐Ÿ‡ฐ๐Ÿ‡พ" . ("Cayman Islands"))
       ("๐Ÿ‡จ๐Ÿ‡ซ" . ("Central African Republic" "Central Africa"))
       ("๐Ÿ‡น๐Ÿ‡ฉ" . ("Chad"))
       ("๐Ÿ‡จ๐Ÿ‡ฑ" . ("Chile"))
       ("๐Ÿ‡จ๐Ÿ‡ณ" . ("China"))
       ("๐Ÿ‡จ๐Ÿ‡ฝ" . ("Christmas Island"))
       ("๐Ÿ‡จ๐Ÿ‡จ" . ("Cocos Islands"))
       ("๐Ÿ‡จ๐Ÿ‡ด" . ("Colombia"))
       ("๐Ÿ‡ฐ๐Ÿ‡ฒ" . ("Comoros"))
       ("๐Ÿ‡จ๐Ÿ‡ฌ" . ("Congo-Brazzaville"))
       ("๐Ÿ‡จ๐Ÿ‡ฉ" . ("Congo-Kinshasa" "DR Congo" "Democratic Republic of the Congo" "Congo"))
       ("๐Ÿ‡จ๐Ÿ‡ฐ" . ("Cook Islands"))
       ("๐Ÿ‡จ๐Ÿ‡ท" . ("Costa Rica"))
       ("๐Ÿ‡ญ๐Ÿ‡ท" . ("Croatia"))
       ("๐Ÿ‡จ๐Ÿ‡บ" . ("Cuba"))
       ("๐Ÿ‡จ๐Ÿ‡ผ" . ("Curaรงao" "Curacao"))
       ("๐Ÿ‡จ๐Ÿ‡พ" . ("Cyprus"))
       ("๐Ÿ‡จ๐Ÿ‡ฟ" . ("Czechia"))
       ("๐Ÿ‡ฉ๐Ÿ‡ฐ" . ("Denmark"))
       ("๐Ÿ‡ฉ๐Ÿ‡ฏ" . ("Djibouti"))
       ("๐Ÿ‡ฉ๐Ÿ‡ฒ" . ("Dominica"))
       ("๐Ÿ‡ฉ๐Ÿ‡ด" . ("Dominican Republic"))
       ("๐Ÿ‡ช๐Ÿ‡จ" . ("Ecuador"))
       ("๐Ÿ‡ช๐Ÿ‡ฌ" . ("Egypt"))
       ("๐Ÿ‡ธ๐Ÿ‡ป" . ("El Salvador"))
       ("๐Ÿ‡ฌ๐Ÿ‡ถ" . ("Equatorial Guinea"))
       ("๐Ÿ‡ช๐Ÿ‡ท" . ("Eritrea"))
       ("๐Ÿ‡ช๐Ÿ‡ช" . ("Estonia"))
       ("๐Ÿ‡ธ๐Ÿ‡ฟ" . ("Eswatini" "Swaziland"))
       ("๐Ÿ‡ช๐Ÿ‡น" . ("Ethiopia"))
       ("๐Ÿ‡ซ๐Ÿ‡ฐ" . ("Falkland Islands" "Falklands"))
       ("๐Ÿ‡ซ๐Ÿ‡ด" . ("Faroe Islands"))
       ("๐Ÿ‡ซ๐Ÿ‡ฏ" . ("Fiji"))
       ("๐Ÿ‡ซ๐Ÿ‡ฎ" . ("Finland"))
       ("๐Ÿ‡ซ๐Ÿ‡ท" . ("France"))
       ("๐Ÿ‡ฌ๐Ÿ‡ซ" . ("French Guiana"))
       ("๐Ÿ‡ต๐Ÿ‡ซ" . ("French Polynesia"))
       ("๐Ÿ‡น๐Ÿ‡ซ" . ("French Southern Territories"))
       ("๐Ÿ‡ฌ๐Ÿ‡ฆ" . ("Gabon"))
       ("๐Ÿ‡ฌ๐Ÿ‡ฒ" . ("Gambia"))
       ("๐Ÿ‡ฌ๐Ÿ‡ช" . ("Georgia"))
       ("๐Ÿ‡ฐ๐Ÿ‡พ" . ("Cayman Islands"))
       ("๐Ÿ‡จ๐Ÿ‡ซ" . ("Central African Republic"))
       ("๐Ÿ‡น๐Ÿ‡ฉ" . ("Chad"))
       ("๐Ÿ‡จ๐Ÿ‡ฑ" . ("Chile"))
       ("๐Ÿ‡จ๐Ÿ‡ณ" . ("China"))
       ("๐Ÿ‡จ๐Ÿ‡ฝ" . ("Christmas Island"))
       ("๐Ÿ‡จ๐Ÿ‡จ" . ("Cocos Islands"))
       ("๐Ÿ‡จ๐Ÿ‡ด" . ("Colombia"))
       ("๐Ÿ‡ฐ๐Ÿ‡ฒ" . ("Comoros"))
       ("๐Ÿ‡จ๐Ÿ‡ฌ" . ("Congo-Brazzaville"))
       ("๐Ÿ‡จ๐Ÿ‡ฉ" . ("Congo-Kinshasa" "DR Congo"))
       ("๐Ÿ‡จ๐Ÿ‡ฐ" . ("Cook Islands"))
       ("๐Ÿ‡จ๐Ÿ‡ท" . ("Costa Rica"))
       ("๐Ÿ‡ญ๐Ÿ‡ท" . ("Croatia"))
       ("๐Ÿ‡จ๐Ÿ‡บ" . ("Cuba"))
       ("๐Ÿ‡จ๐Ÿ‡ผ" . ("Curaรงao" "Curacao"))
       ("๐Ÿ‡จ๐Ÿ‡พ" . ("Cyprus"))
       ("๐Ÿ‡จ๐Ÿ‡ฟ" . ("Czechia"))
       ("๐Ÿ‡ฉ๐Ÿ‡ฐ" . ("Denmark"))
       ("๐Ÿ‡ฉ๐Ÿ‡ฏ" . ("Djibouti"))
       ("๐Ÿ‡ฉ๐Ÿ‡ฒ" . ("Dominica"))
       ("๐Ÿ‡ฉ๐Ÿ‡ด" . ("Dominican Republic"))
       ("๐Ÿ‡ช๐Ÿ‡จ" . ("Ecuador"))
       ("๐Ÿ‡ช๐Ÿ‡ฌ" . ("Egypt"))
       ("๐Ÿ‡ธ๐Ÿ‡ป" . ("El Salvador"))
       ("๐Ÿ‡ฌ๐Ÿ‡ถ" . ("Equatorial Guinea"))
       ("๐Ÿ‡ช๐Ÿ‡ท" . ("Eritrea"))
       ("๐Ÿ‡ช๐Ÿ‡ช" . ("Estonia"))
       ("๐Ÿ‡ธ๐Ÿ‡ฟ" . ("Eswatini" "Swaziland"))
       ("๐Ÿ‡ช๐Ÿ‡น" . ("Ethiopia"))
       ("๐Ÿ‡ซ๐Ÿ‡ฐ" . ("Falkland Islands" "Falklands"))
       ("๐Ÿ‡ซ๐Ÿ‡ด" . ("Faroe Islands"))
       ("๐Ÿ‡ซ๐Ÿ‡ฏ" . ("Fiji"))
       ("๐Ÿ‡ซ๐Ÿ‡ฎ" . ("Finland"))
       ("๐Ÿ‡ซ๐Ÿ‡ท" . ("France"))
       ("๐Ÿ‡ฌ๐Ÿ‡ซ" . ("French Guiana"))
       ("๐Ÿ‡ต๐Ÿ‡ซ" . ("French Polynesia"))
       ("๐Ÿ‡น๐Ÿ‡ซ" . ("French Southern Territories"))
       ("๐Ÿ‡ฌ๐Ÿ‡ฆ" . ("Gabon"))
       ("๐Ÿ‡ฌ๐Ÿ‡ฒ" . ("Gambia"))
       ("๐Ÿ‡ฌ๐Ÿ‡ช" . ("Georgia"))
       ("๐Ÿ‡ฉ๐Ÿ‡ช" . ("Germany"))
       ("๐Ÿ‡ฌ๐Ÿ‡ญ" . ("Ghana"))
       ("๐Ÿ‡ฌ๐Ÿ‡ฎ" . ("Gibraltar"))
       ("๐Ÿ‡ฌ๐Ÿ‡ท" . ("Greece"))
       ("๐Ÿ‡ฌ๐Ÿ‡ฑ" . ("Greenland"))
       ("๐Ÿ‡ฌ๐Ÿ‡ฉ" . ("Grenada"))
       ("๐Ÿ‡ฌ๐Ÿ‡ต" . ("Guadeloupe"))
       ("๐Ÿ‡ฌ๐Ÿ‡บ" . ("Guam"))
       ("๐Ÿ‡ฌ๐Ÿ‡น" . ("Guatemala"))
       ("๐Ÿ‡ฌ๐Ÿ‡ฌ" . ("Guernsey"))
       ("๐Ÿ‡ฌ๐Ÿ‡ณ" . ("Guinea"))
       ("๐Ÿ‡ฌ๐Ÿ‡ผ" . ("Guinea-Bissau"))
       ("๐Ÿ‡ฌ๐Ÿ‡พ" . ("Guyana"))
       ("๐Ÿ‡ญ๐Ÿ‡น" . ("Haiti"))
       ("๐Ÿ‡ญ๐Ÿ‡ณ" . ("Honduras"))
       ("๐Ÿ‡ญ๐Ÿ‡ฐ" . ("Hong Kong"))
       ("๐Ÿ‡ญ๐Ÿ‡บ" . ("Hungary"))
       ("๐Ÿ‡ฎ๐Ÿ‡ธ" . ("Iceland"))
       ("๐Ÿ‡ฎ๐Ÿ‡ณ" . ("India"))
       ("๐Ÿ‡ฎ๐Ÿ‡ฉ" . ("Indonesia"))
       ("๐Ÿ‡ฎ๐Ÿ‡ท" . ("Iran"))
       ("๐Ÿ‡ฎ๐Ÿ‡ถ" . ("Iraq"))
       ("๐Ÿ‡ฎ๐Ÿ‡ช" . ("Ireland" "Republic of Ireland"))
       ("๐Ÿ‡ฎ๐Ÿ‡ฒ" . ("Isle of Man"))
       ("๐Ÿ‡ฎ๐Ÿ‡ฑ" . ("Israel"))
       ("๐Ÿ‡ฎ๐Ÿ‡น" . ("Italy"))
       ("๐Ÿ‡จ๐Ÿ‡ฎ" . ("Ivory Coast" "Cรดte dโ€™Ivoire"))
       ("๐Ÿ‡ฏ๐Ÿ‡ฒ" . ("Jamaica"))
       ("๐Ÿ‡ฏ๐Ÿ‡ต" . ("Japan"))
       ("๐Ÿ‡ฏ๐Ÿ‡ช" . ("Jersey"))
       ("๐Ÿ‡ฏ๐Ÿ‡ด" . ("Jordan"))
       ("๐Ÿ‡ฐ๐Ÿ‡ฟ" . ("Kazakhstan"))
       ("๐Ÿ‡ฐ๐Ÿ‡ช" . ("Kenya"))
       ("๐Ÿ‡ฐ๐Ÿ‡ฎ" . ("Kiribati"))
       ("๐Ÿ‡ฐ๐Ÿ‡ต" . ("North Korea"))
       ("๐Ÿ‡ฐ๐Ÿ‡ท" . ("South Korea"))
       ("๐Ÿ‡ฐ๐Ÿ‡ผ" . ("Kuwait"))
       ("๐Ÿ‡ฐ๐Ÿ‡ฌ" . ("Kyrgyzstan"))
       ("๐Ÿ‡ฑ๐Ÿ‡ฆ" . ("Laos"))
       ("๐Ÿ‡ฑ๐Ÿ‡ป" . ("Latvia"))
       ("๐Ÿ‡ฑ๐Ÿ‡ง" . ("Lebanon"))
       ("๐Ÿ‡ฑ๐Ÿ‡ธ" . ("Lesotho"))
       ("๐Ÿ‡ฑ๐Ÿ‡ท" . ("Liberia"))
       ("๐Ÿ‡ฑ๐Ÿ‡พ" . ("Libya"))
       ("๐Ÿ‡ฑ๐Ÿ‡ฎ" . ("Liechtenstein"))
       ("๐Ÿ‡ฑ๐Ÿ‡น" . ("Lithuania"))
       ("๐Ÿ‡ฑ๐Ÿ‡บ" . ("Luxembourg"))
       ("๐Ÿ‡ฒ๐Ÿ‡ด" . ("Macau"))
       ("๐Ÿ‡ฒ๐Ÿ‡ฌ" . ("Madagascar"))
       ("๐Ÿ‡ฒ๐Ÿ‡ผ" . ("Malawi"))
       ("๐Ÿ‡ฒ๐Ÿ‡พ" . ("Malaysia"))
       ("๐Ÿ‡ฒ๐Ÿ‡ป" . ("Maldives"))
       ("๐Ÿ‡ฒ๐Ÿ‡ฑ" . ("Mali"))
       ("๐Ÿ‡ฒ๐Ÿ‡น" . ("Malta"))
       ("๐Ÿ‡ฒ๐Ÿ‡ญ" . ("Marshall Islands"))
       ("๐Ÿ‡ฒ๐Ÿ‡ถ" . ("Martinique"))
       ("๐Ÿ‡ฒ๐Ÿ‡ท" . ("Mauritania"))
       ("๐Ÿ‡ฒ๐Ÿ‡บ" . ("Mauritius"))
       ("๐Ÿ‡พ๐Ÿ‡น" . ("Mayotte"))
       ("๐Ÿ‡ฒ๐Ÿ‡ฝ" . ("Mexico"))
       ("๐Ÿ‡ซ๐Ÿ‡ฒ" . ("Micronesia"))
       ("๐Ÿ‡ฒ๐Ÿ‡ฉ" . ("Moldova"))
       ("๐Ÿ‡ฒ๐Ÿ‡จ" . ("Monaco"))
       ("๐Ÿ‡ฒ๐Ÿ‡ณ" . ("Mongolia"))
       ("๐Ÿ‡ฒ๐Ÿ‡ช" . ("Montenegro"))
       ("๐Ÿ‡ฒ๐Ÿ‡ธ" . ("Montserrat"))
       ("๐Ÿ‡ฒ๐Ÿ‡ฆ" . ("Morocco"))
       ("๐Ÿ‡ฒ๐Ÿ‡ฟ" . ("Mozambique"))
       ("๐Ÿ‡ณ๐Ÿ‡ฆ" . ("Namibia"))
       ("๐Ÿ‡ณ๐Ÿ‡ท" . ("Nauru"))
       ("๐Ÿ‡ณ๐Ÿ‡ต" . ("Nepal"))
       ("๐Ÿ‡ณ๐Ÿ‡ฑ" . ("Netherlands"))
       ("๐Ÿ‡ณ๐Ÿ‡จ" . ("New Caledonia"))
       ("๐Ÿ‡ณ๐Ÿ‡ฟ" . ("New Zealand"))
       ("๐Ÿ‡ณ๐Ÿ‡ฎ" . ("Nicaragua"))
       ("๐Ÿ‡ณ๐Ÿ‡ช" . ("Niger"))
       ("๐Ÿ‡ณ๐Ÿ‡ฌ" . ("Nigeria"))
       ("๐Ÿ‡ณ๐Ÿ‡บ" . ("Niue"))
       ("๐Ÿ‡ณ๐Ÿ‡ซ" . ("Norfolk Island"))
       ("๐Ÿ‡ฒ๐Ÿ‡ฐ" . ("North Macedonia"))
       ("๐Ÿ‡ฒ๐Ÿ‡ต" . ("Northern Mariana Islands"))
       ("๐Ÿ‡ณ๐Ÿ‡ด" . ("Norway"))
       ("๐Ÿ‡ด๐Ÿ‡ฒ" . ("Oman"))
       ("๐Ÿ‡ต๐Ÿ‡ฐ" . ("Pakistan"))
       ("๐Ÿ‡ต๐Ÿ‡ผ" . ("Palau"))
       ("๐Ÿ‡ต๐Ÿ‡ธ" . ("Palestine"))
       ("๐Ÿ‡ต๐Ÿ‡ฆ" . ("Panama"))
       ("๐Ÿ‡ต๐Ÿ‡ฌ" . ("Papua New Guinea"))
       ("๐Ÿ‡ต๐Ÿ‡พ" . ("Paraguay"))
       ("๐Ÿ‡ต๐Ÿ‡ช" . ("Peru"))
       ("๐Ÿ‡ต๐Ÿ‡ญ" . ("Philippines"))
       ("๐Ÿ‡ต๐Ÿ‡ณ" . ("Pitcairn Islands"))
       ("๐Ÿ‡ต๐Ÿ‡ฑ" . ("Poland"))
       ("๐Ÿ‡ต๐Ÿ‡น" . ("Portugal"))
       ("๐Ÿ‡ต๐Ÿ‡ท" . ("Puerto Rico"))
       ("๐Ÿ‡ถ๐Ÿ‡ฆ" . ("Qatar"))
       ("๐Ÿ‡ท๐Ÿ‡ด" . ("Romania"))
       ("๐Ÿ‡ท๐Ÿ‡บ" . ("Russia"))
       ("๐Ÿ‡ท๐Ÿ‡ผ" . ("Rwanda"))
       ("๐Ÿ‡ท๐Ÿ‡ช" . ("Rรฉunion" "Reunion"))
       ("๐Ÿ‡ผ๐Ÿ‡ธ" . ("Samoa"))
       ("๐Ÿ‡ธ๐Ÿ‡ฒ" . ("San Marino"))
       ("๐Ÿ‡ธ๐Ÿ‡น" . ("Sรฃo Tomรฉ and Prรญncipe"))
       ("๐Ÿ‡ธ๐Ÿ‡ฆ" . ("Saudi Arabia"))
       ("๐Ÿ‡ธ๐Ÿ‡ณ" . ("Senegal"))
       ("๐Ÿ‡ท๐Ÿ‡ธ" . ("Serbia"))
       ("๐Ÿ‡ธ๐Ÿ‡จ" . ("Seychelles"))
       ("๐Ÿ‡ธ๐Ÿ‡ฑ" . ("Sierra Leone"))
       ("๐Ÿ‡ธ๐Ÿ‡ฌ" . ("Singapore"))
       ("๐Ÿ‡ธ๐Ÿ‡ฝ" . ("Sint Maarten"))
       ("๐Ÿ‡ธ๐Ÿ‡ฐ" . ("Slovakia"))
       ("๐Ÿ‡ธ๐Ÿ‡ฎ" . ("Slovenia"))
       ("๐Ÿ‡ธ๐Ÿ‡ง" . ("Solomon Islands"))
       ("๐Ÿ‡ธ๐Ÿ‡ด" . ("Somalia"))
       ("๐Ÿ‡ฟ๐Ÿ‡ฆ" . ("South Africa"))
       ("๐Ÿ‡ฌ๐Ÿ‡ธ" . ("South Georgia and the South Sandwich Islands"))
       ("๐Ÿ‡ธ๐Ÿ‡ธ" . ("South Sudan"))
       ("๐Ÿ‡ช๐Ÿ‡ธ" . ("Spain"))
       ("๐Ÿ‡ฑ๐Ÿ‡ฐ" . ("Sri Lanka"))
       ("๐Ÿ‡ง๐Ÿ‡ฑ" . ("Saint Barthรฉlemy"))
       ("๐Ÿ‡ธ๐Ÿ‡ญ" . ("Saint Helena"))
       ("๐Ÿ‡ฐ๐Ÿ‡ณ" . ("Saint Kitts and Nevis"))
       ("๐Ÿ‡ฑ๐Ÿ‡จ" . ("Saint Lucia"))
       ("๐Ÿ‡ฒ๐Ÿ‡ซ" . ("Saint Martin"))
       ("๐Ÿ‡ต๐Ÿ‡ฒ" . ("Saint Pierre and Miquelon"))
       ("๐Ÿ‡ป๐Ÿ‡จ" . ("Saint Vincent and the Grenadines"))
       ("๐Ÿ‡ธ๐Ÿ‡ฉ" . ("Sudan"))
       ("๐Ÿ‡ธ๐Ÿ‡ท" . ("Suriname"))
       ("๐Ÿ‡ธ๐Ÿ‡ฏ" . ("Svalbard and Jan Mayen"))
       ("๐Ÿ‡ธ๐Ÿ‡ช" . ("Sweden"))
       ("๐Ÿ‡จ๐Ÿ‡ญ" . ("Switzerland"))
       ("๐Ÿ‡ธ๐Ÿ‡พ" . ("Syria"))
       ("๐Ÿ‡น๐Ÿ‡ผ" . ("Taiwan"))
       ("๐Ÿ‡น๐Ÿ‡ฏ" . ("Tajikistan"))
       ("๐Ÿ‡น๐Ÿ‡ฟ" . ("Tanzania"))
       ("๐Ÿ‡น๐Ÿ‡ญ" . ("Thailand"))
       ("๐Ÿ‡น๐Ÿ‡ฑ" . ("Timor-Leste"))
       ("๐Ÿ‡น๐Ÿ‡ฌ" . ("Togo"))
       ("๐Ÿ‡น๐Ÿ‡ฐ" . ("Tokelau"))
       ("๐Ÿ‡น๐Ÿ‡ด" . ("Tonga"))
       ("๐Ÿ‡น๐Ÿ‡น" . ("Trinidad and Tobago"))
       ("๐Ÿ‡น๐Ÿ‡ณ" . ("Tunisia"))
       ("๐Ÿ‡น๐Ÿ‡ท" . ("Turkey"))
       ("๐Ÿ‡น๐Ÿ‡ฒ" . ("Turkmenistan"))
       ("๐Ÿ‡น๐Ÿ‡จ" . ("Turks and Caicos Islands"))
       ("๐Ÿ‡น๐Ÿ‡ป" . ("Tuvalu"))
       ("๐Ÿ‡บ๐Ÿ‡ฌ" . ("Uganda"))
       ("๐Ÿ‡บ๐Ÿ‡ฆ" . ("Ukraine"))
       ("๐Ÿ‡ฆ๐Ÿ‡ช" . ("United Arab Emirates"))
       ("๐Ÿ‡ฌ๐Ÿ‡ง" . ("United Kingdom" "UK"))
       ("๐Ÿ‡บ๐Ÿ‡ธ" . ("United States" "USA"))
       ("๐Ÿ‡ป๐Ÿ‡ฎ" . ("United States Virgin Islands" "US Virgin Islands"))
       ("๐Ÿ‡บ๐Ÿ‡พ" . ("Uruguay"))
       ("๐Ÿ‡บ๐Ÿ‡ฟ" . ("Uzbekistan"))
       ("๐Ÿ‡ป๐Ÿ‡บ" . ("Vanuatu"))
       ("๐Ÿ‡ป๐Ÿ‡ฆ" . ("Vatican City" "Holy See"))
       ("๐Ÿ‡ป๐Ÿ‡ช" . ("Venezuela"))
       ("๐Ÿ‡ป๐Ÿ‡ณ" . ("Vietnam"))
       ("๐Ÿ‡ผ๐Ÿ‡ซ" . ("Wallis and Futuna"))
       ("๐Ÿ‡ช๐Ÿ‡ญ" . ("Western Sahara"))
       ("๐Ÿ‡พ๐Ÿ‡ช" . ("Yemen"))
       ("๐Ÿ‡ฟ๐Ÿ‡ฒ" . ("Zambia"))
       ("๐Ÿ‡ฟ๐Ÿ‡ผ" . ("Zimbabwe")))
      "Mapping of flags to country names.")

;; Function to extract the country name from a heading
(defun my/extract-country-name (heading)
  "Extract the country name from a heading, ignoring counters and extra text."
  (let ((case-fold-search t))
    (if (string-match "^\\([^\\[]+\\)" heading)
        (string-trim (match-string 1 heading))
      heading)))

;; Function to get the flag for a country based on its extracted name
(defun my/get-flag-for-country (name)
  "Return the flag for a given country name."
  (let ((country (my/extract-country-name name)))
    (cl-loop for (flag . names) in country-flag-map
             when (member (downcase country) (mapcar #'downcase names))
             return flag)))

;; Function to get the nearest parent heading's flag
(defun my/get-parent-flag ()
  "Return the flag for the nearest parent country."
  (save-excursion
    (while (org-up-heading-safe))
    (my/get-flag-for-country (org-get-heading t t t t))))

;; Function to update flags for all entries recursively
(defun my/org-update-children-status ()
  "Update flags for all entries."
  (interactive)
  (when (org-at-heading-p)
    (save-excursion
      (org-map-entries
       (lambda ()
         (let* ((state (org-get-todo-state))
                (heading (org-get-heading t t t t))
                ;; Apply flag from the current entry or its nearest parent
                (flag (or (my/get-flag-for-country heading) (my/get-parent-flag))))
           (when flag
             (cond
              ;; Add checkbox for TOGO if not present
              ((string= state "TOGO")
               (unless (string-match-p "^โ˜ " heading)
                 (org-edit-headline (concat "โ˜ " heading))))
              ;; Apply flag to SOON, TODO, GONE, THRU, HOME, and DONE
              ((member state '("SOON" "TODO" "GONE" "THRU" "HOME" "DONE"))
               (unless (string-match-p (regexp-quote flag) heading)
                 (org-edit-headline (concat flag " " heading))))))))
       nil 'tree))))

;; Adjust Org counters to treat TODO, TOGO, and SOON as not done, and DONE, GONE, HOME, and THRU as done
(defun my/org-count-todo-done ()
  "Count TODO/TOGO/SOON as not done, and DONE/GONE/HOME/THRU as done for Org counters."
  (let ((org-done-keywords '("DONE" "GONE" "HOME" "THRU"))
        (org-not-done-keywords '("TODO" "TOGO" "SOON")))
    (org-update-statistics-cookies t)))

(add-hook 'org-after-todo-state-change-hook #'my/org-count-todo-done)

;; Function to run `my/org-update-children-status` when `C-c C-c` is pressed
(defun my/org-update-on-confirm (&optional arg)
  "Run `my/org-update-children-status` when `C-c C-c` is pressed."
  (when (org-at-heading-p)
    (my/org-update-children-status)
    nil))

;; Add advice to `C-c C-c` to trigger the function
(advice-add 'org-ctrl-c-ctrl-c :before-until #'my/org-update-on-confirm)

Website

I have made this website you are on with the org-static-blog extension for orgmode. The code below defines the structure and layout of the blog pages:

(setq org-static-blog-publish-title "Jon's website")
(setq org-static-blog-publish-url "https://jonhunt.uk/")
(setq org-static-blog-publish-directory "~/website/")
(setq org-static-blog-posts-directory "~/website/posts/")
(setq org-static-blog-drafts-directory "~/website/drafts/")
(setq org-static-blog-use-preview t)
(setq org-static-blog-preview-convert-titles t)
(setq org-static-blog-preview-ellipsis "...")
(setq org-static-blog-enable-tags t)
(setq org-export-with-toc t)
(setq org-export-with-section-numbers nil)
(setq org-src-fontify-natively t
      org-src-tab-acts-natively t
      org-confirm-babel-evaluate nil
      org-edit-src-content-indentation 0)

This header is inserted into the <head> section of every page: (The stylesheet is located at ~/website/blog/static/style.css and favicon at ~/website/blog/static/favicon.ico):

(setq org-static-blog-page-header
"<meta name=\"author\" content=\"Jon Hunt\">
<meta name=\"referrer\" content=\"no-referrer\">
<meta content=\"width=device-width\" initial-scale=\"1\" name=\"viewport\" />
<link href=\"/static/style.css\" rel=\"stylesheet\" type=\"text/css\" />
<link rel=\"icon\" href=\"/static/favicon.ico\">")

Below is the config for HTML to be inserted at the begining of the <body> of every page. In my case I'm using it to insert the navigation header:

(setq org-static-blog-page-preamble
"<header>
    <h1>Jonathan Hunt</a></h1>
    - <a href=\"index\">jonhunt.uk</a> -
  <nav>
    <ul>
  <li><a href=\"index\">Home</a></li>
  <li><a href=\"https://jonhunt.uk/about-me\">About me</a></li>
  <li><a href=\"https://jonhunt.uk/resources\">Resources</a></li>
  <li><a href=\"https://jonhunt.uk/rss.xml\">RSS</a></li>
  <li><a href=\"https://jonhunt.uk/contact-and-support\">Contact and support</a></li>
</ul>
  </nav>
</header>")

The code below defines what to be inserted at the end of the <body> of every page. I've left mine blank but it could be used for a footer for example:

(setq org-static-blog-page-postamble
"")

By default this will only export orgmode files directly located in /website/posts/ but the code below allows it to export recursively, e.g a file located in /website/posts/technology/

(with-eval-after-load 'org-static-blog
  (defun org-static-blog--collect-posts ()
    "Collect all .org files recursively, ignoring '_archive' or 'drafts'."
    (seq-remove
     (lambda (file)
       (string-match-p "/\\(_archive\\|drafts\\)/" file))
     (directory-files-recursively org-static-blog-posts-directory "\\.org$"))))

Non-emacs settings

It's also possible to edit configurations for non-emacs files within orgmode, using org-babel. Below are areas I do this for:

MPD

MPD is a server-side application for playing music. I personally use it with the Mingus interface and I'm planning on setting up Polybar to interact with it also. The settings below are automatically exported to ~/.config/mpd/mpd.conf:

# Required files
db_file            "~/.config/mpd/database"
log_file           "~/.config/mpd/log"

# Optional
music_directory    "~/music/collection/"
playlist_directory "~/.config/mpd/playlists"
pid_file           "~/.config/mpd/pid"
state_file         "~/.config/mpd/state"
sticker_file       "~/.config/mpd/sticker.sql"

 audio_output {  

 type  "pulse"  

 name  "mpd pulse-audio-output"  

 }

audio_output {
        type                    "fifo"
        name                    "my_fifo"
        path                    "/tmp/mpd.fifo"
        format                  "44100:16:2"
}

bind_to_address "127.0.0.1"
port "6600"

MPV

I use MPV to watch videos. When combined with youtube-dl it's capable of streaming videos from websites also, and I regularly use it for that.

MPV keybindings, automatically exported to ~/.config/mpv/input.conf:

n seek 5
p seek -5
l vf toggle hflip
Alt+r no-osd cycle video-rotate 90
WHEEL_UP seek 50
WHEEL_DOWN seek -50

General MPV settings, automatically exported to ~/.config/mpv/mpv.conf:

# Set audio to English
alang=en            
# Prefers English subtitles    
slang=en      
# Turn subtitles off         
sid=no       
# Hopefully uses the "forced" subtitle track           
sub-forced-only
# Use hardware decoding if possible        
hwdec=auto    
# Fancy filter good for reducing resolution (e.g. laptop)          
dscale=mitchell     
# Filter for increasing resolution    
scale=ewa_lanczos
 # Get rid of banding       
deband=yes             
volume=50
loop-file=inf
profile=gpu-hq
force-window=yes

Nyxt

Nyxt is a highly customisable web browser, similar to Qutebrowser (which is my primary browser), but has the advantage (in my opinion) of being Emacs-like, including being written in lisp. The only reason it's not my main web browser is that I'm still getting to grips with it.

My config rules are below, and exported to ~/.config/nyxt/init.lisp

Disable session restore on startup (currently commented out because I want session restore):

;(define-configuration browser
;  ((session-restore-prompt :never-restore)))

Fuction to open videos in MPV:

(define-command play-video-in-current-page (&optional (buffer (current-buffer))) "Play video in the currently open buffer."
(uiop:run-program (list "mpv" (object-string (url buffer)))))

Custom keybindings:


Styling configuration:

(define-configuration window
  ((message-buffer-style
    (str:concat
     (cl-css:css
      '((body
         :background-color "#1d2021"
         :color "#fbf1c7"
         :font-size "9pt"
         :margin "0px")))))))


(define-configuration internal-buffer
  ((style
    (str:concat
     (cl-css:css
      '((title
         :color "#CD5C5C")
        (body
         :background-color "#1d2021"
         :color "#fbf1c7")
        (hr
         :color "darkgray")
        (a
         :color "#83a598")
        (.button
         :color "#FFFFFF"
         :background-color "#504945")))))))

Search engine setup. Allows me to type "od test" to search the word test in Odysee, "w example" to search for the word example in Wikipedia, etc. Note that whatever is listed as the last search engine will be the default engine (in this case "searx"). For anyone wanting to use Nyxt it's also worth noting that more advanced search functionality (such as filtering results by date, region, language etc) is available through nx-search-engines but I don't personally use those features so I've not included it in my config:

(define-configuration buffer
  ((search-engines (list
                    (make-instance 'search-engine
                                   :shortcut "4"
                                   :search-url "https://boards.4channel.org/~a/catalog"
                                   :fallback-url "https://boards.4channel.org/")
                    (make-instance 'search-engine
                                   :shortcut "8"
                                   :search-url "https://8kun.top/~a/catalog.html"
                                   :fallback-url "https://8kun.top/")
                    (make-instance 'search-engine
                                   :shortcut "a"
                                   :search-url "https://www.amazon.co.uk/s?=~a"
                                   :fallback-url "https://www.amazon.co.uk/")
                    (make-instance 'search-engine
                                   :shortcut "bc"
                                   :search-url "https://www.bitchute.com/search/?query=~a"
                                   :fallback-url "https://www.bitchute.com/")
                    (make-instance 'search-engine
                                   :shortcut "e"
                                   :search-url "https://www.ebay.co.uk/sch/~a"
                                   :fallback-url "https://www.ebay.co.uk/")
                    (make-instance 'search-engine
                                   :shortcut "g"
                                   :search-url "https://www.google.co.uk/search?q=~a"
                                   :fallback-url "https://www.google.co.uk/")
                    (make-instance 'search-engine
                                   :shortcut "i"
                                   :search-url "https://www.google.co.uk/search?tbm=isch&q=~a"
                                   :fallback-url "https://www.images.google.co.uk/")
                    (make-instance 'search-engine
                                   :shortcut "imdb"
                                   :search-url "https://www.imdb.com/find?q=~a"
                                   :fallback-url "https://www.imdb.com/")
                    (make-instance 'search-engine
                                   :shortcut "in"
                                   :search-url "https://yewtu.be/search?q=~a"
                                   :fallback-url "https://www.yewtu.be/")
                    (make-instance 'search-engine
                                   :shortcut "ist"
                                   :search-url "https://www.instocktrades.com/search?term=~a"
                                   :fallback-url "https://www.instocktrades.com/")
                    (make-instance 'search-engine
                                   :shortcut "jh"
                                   :search-url "https://jonhunt.uk/~a"
                                   :fallback-url "https://jonhunt.uk/")
                    (make-instance 'search-engine
                                   :shortcut "oc"
                                   :search-url "http://classify.oclc.org/classify2/ClassifyDemo?search-title-txt=~a"
                                   :fallback-url "https://www.classify.oclc.org/")
                    (make-instance 'search-engine
                                   :shortcut "od"
                                   :search-url "https://odysee.com/$/search?q=~a"
                                   :fallback-url "https://www.odysee.com/")
                   (make-instance 'search-engine
                                   :shortcut "pb"
                                   :search-url "https://tpb.wtf/search.php?q=~a&cat=0"
                                   :fallback-url "https://tpb.wtf/")
                    (make-instance 'search-engine
                                   :shortcut "pt"
                                   :search-url "https://search.joinpeertube.org/search?search=~a"
                                   :fallback-url "https://search.joinpeertube.org/")
                    (make-instance 'search-engine
                                   :shortcut "r"
                                   :search-url "https://www.reddit.com/r/~a"
                                   :fallback-url "https://www.reddit.com/")
                    (make-instance 'search-engine
                                   :shortcut "rbm"
                                   :search-url "https://rarbgto.org/torrents.php?search=~a&category%5B%5D=14&category%5B%5D=48&category%5B%5D=17&category%5B%5D=44&category%5B%5D=45&category%5B%5D=47&category%5B%5D=50&category%5B%5D=51&category%5B%5D=52&category%5B%5D=42&category%5B%5D=46&category%5B%5D=54"
                                   :fallback-url "https://www.rarbgto.org/")
                    (make-instance 'search-engine
                                   :shortcut "rbt"
                                   :search-url "https://rarbgto.org/torrents.php?search=~a&category%5B%5D=18&category%5B%5D=41&category%5B%5D=49"
                                   :fallback-url "https://www.rarbgto.org/")
                    (make-instance 'search-engine
                                   :shortcut "rc"
                                   :search-url "https://www.reedcomics.com/catalogsearch/result/?q=~a"
                                   :fallback-url "https://www.reedcomics.com/")
                    (make-instance 'search-engine
                                   :shortcut "rt"
                                   :search-url "https://www.rottentomatoes.com/search?search=~a"
                                   :fallback-url "https://www.rottentomatoes.com/")
                    (make-instance 'search-engine
                                   :shortcut "sh"
                                   :search-url "https://www.speedyhen.com/Search/Keyword?keyword=~a"
                                   :fallback-url "https://www.speedyhen.com/")
                    (make-instance 'search-engine
                                   :shortcut "w"
                                   :search-url "https://en.wikipedia.org/wiki/Special:Search?search=~a"
                                   :fallback-url "https://www.en.wikipedia.org/")
                    (make-instance 'search-engine
                                   :shortcut "wh"
                                   :search-url "https://www.wowhead.com/search?q=~a"
                                   :fallback-url "https://www.wowhead.com/")
                    (make-instance 'search-engine
                                   :shortcut "wp"
                                   :search-url "https://wow.gamepedia.com/~a"
                                   :fallback-url "https://www.wow.gamepedia.com/")
                    (make-instance 'search-engine
                                   :shortcut "yt"
                                   :search-url "https://www.youtube.com/results?search_query=~a"
                                   :fallback-url "https://www.youtube.com/")
                    (make-instance 'search-engine
                                   :shortcut "sp"
                                   :search-url "https://searx.info/search?q=~a"
                                   :fallback-url "https://www.searx.info/")))))

Hide statusbar:

(define-configuration status-buffer
  ((height 0)))

Nx-freestance-handler: Redirect mainstream websites to their privacy-supporting mirrors:

(defvar *my-request-resource-handlers* '())

(load-after-system :nx-freestance-handler
                   (nyxt-init-file "freestance.lisp"))

(define-configuration web-buffer
  ((request-resource-hook
    (reduce #'hooks:add-hook
            (mapcar #'make-handler-resource
                    *my-request-resource-handlers*)
            :initial-value %slot-default%))))
;; to add all handlers/redirectors (youtube to invidious, reddit to teddit,
;; instagram to bibliogram, medium to scribe, twitter to nitter)
(setq *my-request-resource-handlers*
      (nconc *my-request-resource-handlers*
             nx-freestance-handler:*freestance-handlers*))

;; alternatively, you may add each separately
;; (push #'nx-freestance-handler:invidious-handler *my-request-resource-handlers*)
;; (push #'nx-freestance-handler:nitter-handler *my-request-resource-handlers*)
;; (push #'nx-freestance-handler:bibliogram-handler *my-request-resource-handlers*)
;; (push #'nx-freestance-handler:teddit-handler *my-request-resource-handlers*)
;; (push #'nx-freestance-handler:scribe-handler *my-request-resource-handlers*)

;; to set your preferred instance, either invoke SET-PREFERRED-[name of website]-INSTANCE
;; command in Nyxt (the effect lasts until you close Nyxt), or write something like this:
;; (setf nx-freestance-handler:*preferred-invidious-instance* "https://invidious.snopyta.org")

Polybar

Note: this polybar configuration is still a work in progress, but once it's set up it will allow me to have things like time and date, volume, email count, CPU/GPU/RAM stats, music playing and much more to appear on a statusbar on the top of the screen.

These settings are automatically exported to ~/.config/polybar/config:

[settings]
screenchange-reload = true

[global/wm]
margin-top = 0
margin-bottom = 0

[colors]
background = #002b36
background-alt = #073642
foreground = #839496
foreground-alt = #586e75
primary = #268bd2
secondary = #2aa198
alert = #dc322f
underline-1 = #6c71c4

[bar/panel]
width = 100%
height = 23
offset-x = 0
offset-y = 0
fixed-center = true
enable-ipc = true

background = ${colors.background}
foreground = ${colors.foreground}

line-size = 1
line-color = #f00


border-size = 0
border-color = ${colors.foreground}

padding-left = 0
padding-right = 0

module-margin = 0


font-0 = "DejaVu Sans Mono:pixelsize=11;2"
font-1 = "Font Awesome 6 Free:style=Solid:pixelsize=10"
font-2 = "Material Icons:size=11;5"
font-3 = "FiraCode Nerd Font Mono:size=21"

modules-left = mpd arrow3
modules-center = arrow1 exwm arrow3
modules-right = arrow1 home-drive arrow2 external-drive arrow1 cpu arrow2  date

tray-position = right
tray-padding = 2

cursor-click = pointer
cursor-scroll = ns-resize

[module/arrow1]
type = custom/text
format = <label>
label-foreground = ${colors.background-alt}
label = "๎‚ฒ"
format-font = 4

[module/arrow2]
type = custom/text
format = <label>
label-foreground = ${colors.background}
label-background = ${colors.background-alt}
label = "๎‚ฒ"
format-font = 4

[module/arrow3]
type = custom/text
format = <label>
label-foreground = ${colors.background-alt}
label = "๎‚ฐ"
format-font = 4

[module/arrow4]
type = custom/text
format = <label>
label-foreground = ${colors.background}
label-background = ${colors.background-alt}
label = "๎‚ฐ"
format-font = 4

[module/exwm]
type = custom/ipc
hook-0 = emacsclient -n -e '(dw/polybar-current-workspace)' | sed -e 's/^"//' -e 's/"$//'
initial = 1
format-background = ${colors.background-alt}
format-padding = 5

[module/exwm-path]
type = custom/ipc
hook-0 = emacsclient -e "(dw/polybar-exwm-workspace-path)" | sed -e 's/^"//' -e 's/"$//'
format-foreground = #f78c6c
initial = 1

[module/mpd]
type = custom/script
exec = ~/.config/polybar/mpd-ipc.sh
click-left = ~/.config/polybar/mpd-ipc.sh click
format = <label>
format-background = ${colors.background-alt}
interval = 1
label = %output%

[module/mu4e]
type = custom/ipc
hook-0 = emacsclient -e '(dw/polybar-mail-count 500)' | sed -e 's/^"//' -e 's/"$//'
initial = 1
format-underline = ${colors.underline-1}
click-left = emacsclient -e '(dw/go-to-inbox)'

[module/telegram]
type = custom/ipc
hook-0 = emacsclient -e '(dw/polybar-telegram-chats)' | sed -e 's/^"//' -e 's/"$//'
format-padding = 3
initial = 1

[module/xkeyboard]
type = internal/xkeyboard
blacklist-0 = num lock

format-prefix-font = 1
format-prefix-foreground = ${colors.foreground-alt}
format-prefix-underline = ${colors.underline-1}

label-layout = %layout%
label-layout-underline = ${colors.underline-1}

label-indicator-padding = 2
label-indicator-margin = 1
label-indicator-underline = ${colors.underline-1}

[module/cpu]
type = internal/cpu
interval = 2

format = ๎Œข <label>
label = %percentage:2%%
click-left = emacsclient -e "(proced)"

bar-load-width = 3
bar-load-gradient = false
bar-load-indicator = |
bar-load-fill = โ–‰
bar-load-empty = โ–‘
bar-load-empty-foreground = ${colors.foreground-alt}
format-background = ${colors.background-alt}
format-padding = 2

[module/home-drive]
type = custom/script
interval = 10
exec = echo " %{T1}๏€•%{T-} $(df -h ~ | awk 'NR==2 {print $4}') "
format-background = ${colors.background-alt}
format-padding = 2

[module/external-drive]
type = custom/script
interval = 10
exec = echo "%{T1}๏‚ %{T-} $(df -h /mnt/storage/ | awk 'NR==2 {print $4}')"
format-prefix-foreground = ${colors.foreground-alt}
format-padding = 2

[module/memory]
type = internal/memory
interval = 2

format-prefix = "     %{T1}๏‹›%{T-}      "
format-prefix-foreground = ${colors.foreground-alt}
format-underline = ${colors.underline-1}
format-background = ${colors.background-alt}

label = %percentage_used%%

[module/date]
type = internal/date
interval = 1

date = %Y-%m-%d
time = %H:%M:%S

label = "%date% %time%"

format-padding = 2

[module/battery]
type = internal/battery
battery = BAT0
adapter = ADP1
full-at = 98
time-format = %-l:%M

label-charging = %percentage%% / %time%
format-charging = <animation-charging> <label-charging>
format-charging-underline = ${colors.underline-1}

label-discharging = %percentage%% / %time%
format-discharging = <ramp-capacity> <label-discharging>
format-discharging-underline = ${self.format-charging-underline}

format-full = <ramp-capacity> <label-full>
format-full-underline = ${self.format-charging-underline}

ramp-capacity-0 = ๏‰„
ramp-capacity-1 = ๏‰ƒ
ramp-capacity-2 = ๏‰‚
ramp-capacity-3 = ๏‰
ramp-capacity-4 = ๏‰€

animation-charging-0 = ๏‰„
animation-charging-1 = ๏‰ƒ
animation-charging-2 = ๏‰‚
animation-charging-3 = ๏‰
animation-charging-4 = ๏‰€
animation-charging-framerate = 750

[module/temperature]
type = internal/temperature
thermal-zone = 0
warn-temperature = 60

format = <label>
format-underline = ${colors.underline-1}
format-warn = <label-warn>
format-warn-underline = ${self.format-underline}

label = %temperature-c%
label-warn = %temperature-c%!
label-warn-foreground = ${colors.secondary}

Polybar music player script

Add IPC listener for MPD music display:

#!/bin/bash

# MPD Polybar script: Shows current song with playback time and handles play/pause toggle

case "$1" in
  click)
    # Toggle play/pause on left click
    mpc toggle
    ;;
  *)
    # Output current track info
    status=$(mpc status)
    current=$(echo "$status" | sed -n '1p')
    second_line=$(echo "$status" | sed -n '2p')
    state=$(echo "$second_line" | grep -o '\[.*\]')
    time=$(echo "$second_line" | grep -o '[0-9]*:[0-9][0-9]/[0-9]*:[0-9][0-9]' | sed 's|/| / |')

    if [[ -z "$current" || "$state" == "[paused]" ]]; then
      echo "   ๏Œ    $current    $time    "
    elif [[ "$state" == "[playing]" ]]; then
      if [[ -n "$time" ]]; then
        echo "   ๏€    $current    $time    "
      else
        echo "   ๏€    $current"
      fi
    else
      echo "โ–   Stopped"
    fi
    ;;
esac

Qutebrowser

Qutebrowser is one of the web browsers I use, and all the below settings are automatically exported to ~/.config/qutebrowser/config.py

Style default background colour of websites, and use an external stylesheet for anything else:

config.load_autoconfig(False)
# config.set('colors.webpage.prefers_color_scheme_dark', True) - This setting no longer works and there doesn't seem to be a working replacement currently
config.set('colors.webpage.bg', '#1d2021')

c.content.user_stylesheets = str(config.configdir) + '/stylesheets/gruvbox-all-sites.css'

Interface colours, taken from the Gruvbox-dark-hard config of Base16 Qutebrowser:

base00 = "#1d2021"
base01 = "#3c3836"
base02 = "#504945"
base03 = "#665c54"
base04 = "#bdae93"
base05 = "#d5c4a1"
base06 = "#ebdbb2"
base07 = "#fbf1c7"
base08 = "#fb4934"
base09 = "#fe8019"
base0A = "#fabd2f"
base0B = "#b8bb26"
base0C = "#8ec07c"
base0D = "#83a598"
base0E = "#d3869b"
base0F = "#d65d0e"

# set qutebrowser colors
c.colors.completion.fg = base05
c.colors.completion.odd.bg = base01
c.colors.completion.even.bg = base00
c.colors.completion.category.fg = base0A
c.colors.completion.category.bg = base00
c.colors.completion.category.border.top = base00
c.colors.completion.category.border.bottom = base00
c.colors.completion.item.selected.fg = base05
c.colors.completion.item.selected.bg = base02
c.colors.completion.item.selected.border.top = base02
c.colors.completion.item.selected.border.bottom = base02
c.colors.completion.item.selected.match.fg = base0B
c.colors.completion.match.fg = base0B
c.colors.completion.scrollbar.fg = base05
c.colors.completion.scrollbar.bg = base00
c.colors.contextmenu.disabled.bg = base01
c.colors.contextmenu.disabled.fg = base04
c.colors.contextmenu.menu.bg = base00
c.colors.contextmenu.menu.fg =  base05
c.colors.contextmenu.selected.bg = base02
c.colors.contextmenu.selected.fg = base05
c.colors.downloads.bar.bg = base00
c.colors.downloads.start.fg = base00
c.colors.downloads.start.bg = base0D
c.colors.downloads.stop.fg = base00
c.colors.downloads.stop.bg = base0C
c.colors.downloads.error.fg = base08
c.colors.hints.fg = base00
c.colors.hints.bg = base0A
c.colors.hints.match.fg = base05
c.colors.keyhint.fg = base05
c.colors.keyhint.suffix.fg = base05
c.colors.keyhint.bg = base00
c.colors.messages.error.fg = base00
c.colors.messages.error.bg = base08
c.colors.messages.error.border = base08
c.colors.messages.warning.fg = base00
c.colors.messages.warning.bg = base0E
c.colors.messages.warning.border = base0E
c.colors.messages.info.fg = base05
c.colors.messages.info.bg = base00
c.colors.messages.info.border = base00
c.colors.prompts.fg = base05
c.colors.prompts.border = base00
c.colors.prompts.bg = base00
c.colors.prompts.selected.bg = base02
c.colors.statusbar.normal.fg = base0B
c.colors.statusbar.normal.bg = base00
c.colors.statusbar.insert.fg = base00
c.colors.statusbar.insert.bg = base0D
c.colors.statusbar.passthrough.fg = base00
c.colors.statusbar.passthrough.bg = base0C
c.colors.statusbar.private.fg = base00
c.colors.statusbar.private.bg = base01
c.colors.statusbar.command.fg = base05
c.colors.statusbar.command.bg = base00
c.colors.statusbar.command.private.fg = base05
c.colors.statusbar.command.private.bg = base00
c.colors.statusbar.caret.fg = base00
c.colors.statusbar.caret.bg = base0E
c.colors.statusbar.caret.selection.fg = base00
c.colors.statusbar.caret.selection.bg = base0D
c.colors.statusbar.progress.bg = base0D
c.colors.statusbar.url.fg = base05
c.colors.statusbar.url.error.fg = base08
c.colors.statusbar.url.hover.fg = base05
c.colors.statusbar.url.success.http.fg = base0C
c.colors.statusbar.url.success.https.fg = base0B
c.colors.statusbar.url.warn.fg = base0E
c.colors.tabs.bar.bg = base00
c.colors.tabs.indicator.start = base0D
c.colors.tabs.indicator.stop = base0C
c.colors.tabs.indicator.error = base08
c.colors.tabs.odd.fg = base05
c.colors.tabs.odd.bg = base01
c.colors.tabs.even.fg = base05
c.colors.tabs.even.bg = base00
c.colors.tabs.pinned.even.bg = base0C
c.colors.tabs.pinned.even.fg = base07
c.colors.tabs.pinned.odd.bg = base0B
c.colors.tabs.pinned.odd.fg = base07
c.colors.tabs.pinned.selected.even.bg = base02
c.colors.tabs.pinned.selected.even.fg = base05
c.colors.tabs.pinned.selected.odd.bg = base02
c.colors.tabs.pinned.selected.odd.fg = base05
c.colors.tabs.selected.odd.fg = base05
c.colors.tabs.selected.odd.bg = base02
c.colors.tabs.selected.even.fg = base05
c.colors.tabs.selected.even.bg = base02

Font settings:

c.fonts.default_family = []
c.fonts.default_size = '10pt'
c.fonts.completion.entry = '8pt default_family'
c.fonts.completion.category = 'bold 8pt default_family'
c.fonts.debug_console = '8pt default_family'
c.fonts.downloads = '8pt default_family'
c.fonts.hints = 'bold 8pt default_family'
c.fonts.keyhint = '8pt default_family'
c.fonts.messages.error = '8pt default_family'
c.fonts.messages.info = '8pt default_family'
c.fonts.messages.warning = '8pt default_family'
c.fonts.prompts = '8pt sans-serif'
c.fonts.statusbar = '8pt default_family'
c.fonts.tabs.selected = '8pt default_family'
c.fonts.tabs.unselected = '8pt default_family'
c.fonts.web.family.cursive = None

Tab appearance:

c.tabs.favicons.scale = 1.0
c.tabs.favicons.show = 'never'
c.tabs.indicator.width = 0
c.tabs.min_width = -1
c.tabs.max_width = -1
c.tabs.padding = {'bottom': 2, 'left': 5, 'right': 5, 'top': 2}
c.tabs.pinned.shrink = True
c.tabs.position = 'top'
c.tabs.show = 'multiple'
c.tabs.show_switching_delay = 800
c.tabs.title.alignment = 'left'
c.tabs.title.format = '{audio}{current_title}'
c.tabs.title.format_pinned = '{index}'
c.tabs.width = '20%'

c.hints.border = '0px'
c.window.hide_decoration = False
c.window.title_format = '{perc}{current_title}{title_sep}qutebrowser'

Miscellaneous appearance settings:

c.completion.height = '30%'
c.completion.shrink = True
c.completion.scrollbar.width = 0
c.completion.timestamp_format = '%d/%m/%Y'

Aliases for commands:

c.aliases = {'q': 'close', 'qa': 'quit', 'w': 'session-save', 'wq': 'quit --save', 'wqa': 'quit --save'}

Keybindings. I'm intending to move to Nyxt at some point in the future so I've tried to make the keybindings close to Nyxt's default bindings:

# Unbind default keysbindings
config.unbind('[[')
config.unbind('d')
config.unbind('f')
config.unbind('F')
config.unbind('J')
config.unbind('K')
config.unbind('M')
config.unbind('n')
config.unbind('o')
config.unbind('O')
config.unbind('pp')
config.unbind('pP')
config.unbind('R')
config.unbind('r')
config.unbind('u')
config.unbind('v')
config.unbind(':')
config.unbind('<F11>')
# Bind new keybindings
config.bind('b', 'set-cmd-text -s :buffer')
config.bind('H', 'home')
config.bind('<Ctrl+0>', 'zoom')
config.bind('<Ctrl+[>', 'tab-prev')
config.bind('<Ctrl+]>', 'tab-next')
config.bind('<Ctrl+b>', 'set-cmd-text -s :bookmark-load')
config.bind('<Ctrl+g>', 'hint')
config.bind('<Ctrl+l>', 'set-cmd-text -s :open')
config.bind('<Ctrl+n>', 'scroll down')
config.bind('<Ctrl+p>', 'scroll up')
config.bind('<Ctrl+r>', 'reload')
config.bind('<Ctrl+s>', 'set-cmd-text /')
config.bind('<Ctrl+w>', 'tab-close')
config.bind('<Ctrl+Shift+w>', 'close')
config.bind('<Ctrl+/>', 'undo')
config.bind('<Ctrl+space>', 'set-cmd-text :')
config.bind('<Ctrl+Alt+c>', 'devtools')
config.bind('<Ctrl+Shift+Tab>', 'tab-prev')
config.bind('<Ctrl+Up>', 'scroll-to-perc 0')
config.bind('<Ctrl+Down>', 'scroll-to-perc 100')
config.bind('<Alt+g>', 'hint all tab')
config.bind('<Alt+l>', 'set-cmd-text -s :open -t')
config.bind('<Alt+left>', 'back')
config.bind('<Alt+right>', 'forward')
config.bind('<Alt+[', 'back')
config.bind('<Alt+]', 'forward')
config.bind('v', 'view-source')
config.bind ('<', 'set content.user_stylesheets ""')
config.bind ('>', 'set content.user_stylesheets /home/Jon/.config/qutebrowser/stylesheets/gruvbox-all-sites.css')
config.bind('c-y', 'rl-yank')
config.bind('<Alt+v>', 'scroll-page -1 -1')
config.bind('<Ctrl+v>', 'scroll-page 1 1')

Keybindings to download images and videos from a webpage to specific folders:

config.bind(',4a', 'spawn wget -P downloads/wget/anime-and-manga -nd -r -l 1 -H -D i.4cdn.org -A png,gif,jpg,jpeg,webm -R "*s.*" {url:pretty}')
config.bind(',4c', 'spawn wget -P downloads/wget/comics-and-cartoons -nd -r -l 1 -H -D i.4cdn.org -A png,gif,jpg,jpeg,webm -R "*s.*" {url:pretty}')
config.bind(',4g', 'spawn wget -P downloads/wget/technology -nd -r -l 1 -H -D i.4cdn.org -A png,gif,jpg,jpeg,webm -R "*s.*" {url:pretty}')
config.bind(',4i', 'spawn wget -P downloads/wget/international -nd -r -l 1 -H -D i.4cdn.org -A png,gif,jpg,jpeg,webm -R "*s.*" {url:pretty}')
config.bind(',4p', 'spawn wget -P downloads/wget/politics -nd -r -l 1 -H -D i.4cdn.org -A png,gif,jpg,jpeg,webm -R "*s.*" {url:pretty}')
config.bind(',4t', 'spawn wget -P downloads/wget/television-and-film -nd -r -l 1 -H -D i.4cdn.org -A png,gif,jpg,jpeg,webm -R "*s.*" {url:pretty}')
config.bind(',4v', 'spawn wget -P downloads/wget/videogames -nd -r -l 1 -H -D i.4cdn.org -A png,gif,jpg,jpeg,webm -R "*s.*" {url:pretty}')
config.bind(',4w', 'spawn wget -P downloads/wget/wallpapers -nd -r -l 1 -H -D i.4cdn.org -A png,gif,jpg,jpeg,webm -R "*s.*" {url:pretty}')
config.bind(',w', 'spawn wget -P Downloads/wget -nd -r -l 1 -H -D i.4cdn.org -A png,gif,jpg,jpeg,webm -R "*s.*" {url:pretty}')
config.bind(',W', 'hint links spawn wget -P Downloads/wget -nd -r -l 1 -H -D i.4cdn.org -A png,gif,jpg,jpeg,webm -R "*s.*" {hint-url}')
config.bind(',y', 'spawn youtube-dl --o "downloads/youtube-dl/%(title)s.%(ext)s" --add-metadata {url:pretty}')
config.bind(',Y', 'hint links spawn youtube-dl -o "downloads/youtube-dl/%(title)s.%(ext)s" --add-metadata {hint-url}')

Keybindings to open images and videos from a webpage in external programs:

config.bind(',i', 'spawn feh -F {url:pretty}')
config.bind(',I', 'hint links spawn feh -F {hint-url}')
config.bind(',m', 'spawn mpv {url}')
config.bind(',M', 'hint links spawn mpv {hint-url}')

Custom search engines shortcuts. Allows od test to search for the word 'test' within Odysee, or rt test to search for the word 'test' within Rotten Tomatoes for example:

c.url.searchengines = {
    '4': 'https://boards.4channel.org/{}/catalog',
    '8': 'https://8kun.top/{}/catalog.html',
    'a': 'https://www.amazon.co.uk/s?k={}',
    'bc': 'https://www.bitchute.com/search/?query={}',
    'DEFAULT': 'https://searx.info/?q={}',
    'e': 'https://www.ebay.co.uk/sch/{}',
    'g': 'https://www.google.co.uk/search?q={}',
    'i': 'https://www.google.co.uk/search?tbm=isch&q={}',
    'imdb': 'https://www.imdb.com/find?q={}',
    'in': 'https://yewtu.be/search?q={}',
    'ist': 'https://www.instocktrades.com/search?term={}',
    'jh': 'https://jonhunt.uk/{}',
    'oc': 'http://classify.oclc.org/classify2/ClassifyDemo?search-title-txt={}',
    'od': 'https://odysee.com/$/search?q={}',
    'pt': 'https://search.joinpeertube.org/search?search={}',
    'r': 'https://www.reddit.com/r/{}',
    'rc': 'https://www.reedcomics.com/catalogsearch/result/?q={}',
    'rt': 'https://www.rottentomatoes.com/search?search={}',
    'sh': 'https://www.speedyhen.com/Search/Keyword?keyword={}',
    's': 'https://searx.info/search?q={}',
    'w': 'https://en.wikipedia.org/wiki/Special:Search?search={}',
    'wh': 'https://www.wowhead.com/search?q={}',
    'wp': 'https://wow.gamepedia.com/{}',
    'yt': 'https://www.youtube.com/results?search_query={}'}

Miscellaneous settings:

c.auto_save.session = False
c.backend = 'webengine'
c.confirm_quit = ['always']
c.content.autoplay = False
c.content.cookies.store = False
c.content.geolocation = False
c.content.headers.accept_language = 'en-GB,en'
c.content.headers.custom = {}
c.content.blocking.enabled = True
c.content.javascript.can_access_clipboard = False
c.completion.web_history.exclude = []
c.completion.web_history.max_items = 0
c.hints.auto_follow_timeout = 0
c.qt.args = ['ppapi-widevine-path=/usr/lib/qt/plugins/ppapi/libwidevinecdmadapter.so']
c.tabs.background = True
c.tabs.close_mouse_button = 'middle'
c.tabs.close_mouse_button_on_bar = 'new-tab'
c.tabs.last_close = 'blank'
c.tabs.mousewheel_switching = True
c.tabs.new_position.related = 'next'
c.tabs.new_position.stacking = True
c.tabs.mode_on_change = 'normal'
c.tabs.undo_stack_size = 100
c.tabs.wrap = True
c.url.default_page = 'about:blank'
c.url.incdec_segments = ['path', 'query']
c.url.open_base_url = False
c.url.start_pages = 'https://jonhunt.uk/'
c.url.yank_ignored_parameters = ['ref', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']
config.set('content.images', True, 'chrome-devtools://*')
config.set('content.images', True, 'devtools://*')
config.set('content.javascript.enabled', True, 'chrome-devtools://*')
config.set('content.javascript.enabled', True, 'devtools://*')
config.set('content.javascript.enabled', True, 'chrome://*/*')
config.set('content.javascript.enabled', True, 'qute://*/*')

Startup

Now everything is set up, run emacs with elfeed, mingus, mentor and a terminal:

(when (display-graphic-p)
  (add-hook 'exwm-init-hook
            (lambda ()
              (run-at-time "1 sec" nil
                           (lambda ()
                             (start-process-shell-command
                              "lemonbar" nil "~/.local/bin/run-lemonbar.sh")
                             ;; Workspace layout after lemonbar and X settle
                             (ignore-errors (exwm-workspace-switch-create 2))
                             (ignore-errors (lmedia))
                             (ignore-errors (exwm-workspace-switch-create 1))
                             (ignore-errors (start-process-shell-command
                                             "chromium" nil "chromium")))))))

End of config

Credits

As free software, most of what you see above has been gathered and modified for my own needs from various talented programmers and tinkerers, so I feel I owe it to them to give them credit. It would be near-impossible to list every single contributor here, but here are some of the more significant ones to my own PC use:

Emacs as a whole

Emacs packages I use or intend to use in the future

Non-emacs

People I learned from

If there's others you think I should add, or you find I've miscredited someone, please let me know.

Tags: Tech Emacs