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

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)

Load up use-package:

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

(require 'use-package)

Set default web browser to Nyxt:

(setq browse-url-browser-function 'browse-url-generic
      browse-url-generic-program "nyxt")

Use asynchronous processes wherever possible:

(use-package async
  :ensure t
  :init (dired-async-mode 1))

Interface settings

EXWM install and config:

(use-package exwm
  :ensure t
  :config

    ;; necessary to configure exwm manually
    (require 'exwm-config)

    ;; fringe size, most people prefer 1 
    (fringe-mode 1)

    ;; emacs as a daemon, use "emacsclient <filename>" to seamlessly edit files from the terminal directly in the exwm instance
    (server-start)

    ;; a number between 1 and 9, exwm creates workspaces dynamically so I like starting out with 1
    (setq exwm-workspace-number 1)

    ;; this is a way to declare truly global/always working keybindings
    ;; this is a nifty way to go back from char mode to line mode without using the mouse
    (exwm-input-set-key (kbd "s-r") #'exwm-reset)
    (exwm-input-set-key (kbd "s-k") #'exwm-workspace-delete)
    (exwm-input-set-key (kbd "s-w") #'exwm-workspace-swap)

    ;; the simplest launcher, I keep it in only if dmenu eventually stopped working or something
    (exwm-input-set-key (kbd "s-&")
                        (lambda (command)
                          (interactive (list (read-shell-command "$ ")))
                          (start-process-shell-command command nil command)))

    ;; this just enables exwm, it started automatically once everything is ready
    (exwm-enable))

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 Gruvbox theme:

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

;; set default font
(set-face-attribute 'default nil :font "DejaVu Sans Mono-8")

Add clock in modeline:

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

Set colors for packages:

(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(elfeed-search-date-face ((t (:inherit font-lock-builtin-face :foreground "#d3869b" :underline nil))))
 '(elfeed-search-feed-face ((t (:foreground "#fabd2f"))))
 '(elfeed-search-filter-face ((t (:inherit mode-line-buffer-id :weight normal))))
 '(elfeed-search-last-update-face ((t (:inherit font-lock-comment-face :foreground "#fdf4c1"))))
 '(elfeed-search-tag-face ((t (:foreground "#83a598"))))
 '(elfeed-search-title-face ((t (:foreground "#fdf4c1"))))
 '(elfeed-search-unread-count-face ((t (:inherit font-lock-comment-face :foreground "#fdf4c1"))))
 '(elfeed-search-unread-title-face ((t nil)))
 '(mentor-download-message ((t (:foreground "#e27878"))))
 '(mentor-download-name ((t (:foreground "#83a598"))))
 '(mentor-download-size ((t (:foreground "#fabd2f"))))
 '(mentor-tracker-name ((t (:foreground "#b8bb26"))))
 '(mingus-album-face ((t (:foreground "#fb4933" :underline t))))
 '(mingus-album-stale-face ((t (:foreground "#fb4933"))))
 '(mingus-artist-face ((t (:foreground "#d3869b"))))
 '(mingus-mark-face ((t (:foreground "#e2a478" :weight bold))))
 '(mingus-song-file-face ((t (:foreground "#8ec07c"))))
 '(mingus-stopped-face ((t (:foreground "#e27878")))))

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 ~/.emacs.d/config.org at runtime"
  (interactive)
  (org-babel-load-file (expand-file-name "~/.emacs.d/init.el")))
(global-set-key (kbd "C-c r") 'config-reload)

Start EXWM:

(require 'exwm)
(require 'exwm-config)
(exwm-config-default)

Load up powerline, using default theme:

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

Define custom layouts:

;; Media layout
(defun lmedia ()
(interactive)
 (dired "~/")
(split-window-below)
(mingus)
(mingus-cancel-timer) ;; stops mingus default behaviour of redisplaying every second, which caused xwindows to flicker and lose focus
(mingus-header-mode)
(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:

(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:

;;load dired icons
(add-hook 'dired-mode-hook 'all-the-icons-dired-mode)
;; reduce lag from dired-icons
(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:

;; load the packaged named xyz.
(load "openwith") ;; best not to include the ending “.el” or “.elc”

(setq large-file-warning-threshold nil)
(require 'openwith)
(openwith-mode t)
(setq openwith-associations '(
                              ("\\.3gp\\|.aac\\|.avi\\||.flac\\|.flv\\|.m4a\\|.m4b\\|.m4v\\|.mkv\\|.mp4\\|.mov\\|.mpg\\|.ogg\\|.ogm\\|.wav\\|.webm\\|.wmv\\'" "mpv" (file))
                              ;;("\\.png\\|.jp?g\\|.gif\\|.webp\\'" "viewnior" (file))
                              ("\\.exe\\'" "wine" (file))
                              ))
;; set epub files to open in nov.el
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))

(add-to-list 'auto-mode-alist '("\\.\\(cbr\\)\\'" . archive-mode))

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

(use-package image
  :defer t
  :init
  (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))

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
(require 'elfeed-org)

;; Initialize elfeed-org
(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:

(require 'engine-mode)
(engine-mode t)

Below are defined search engines and I can add as many as I'd like. To use them I just press C-x / followed by whatever key I've defined below. For example eBay search is C-x / e:

(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 odyseee
  "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:

(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:

(require 'mingus)
(require 'powerline)

;;;; Customization

(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)

;;;; Header Line Creation

(defun mingus-header-line-format ()
  "Create a header line for `mingus-playlist-mode'."
  (if mingus-header-use-powerline
      (mingus-header-powerline-format)
    "not yet implemented"))

;;;;; Powerline version

(defun mingus-header-powerline-format ()
  "Create a header line for `mingus-playlist-mode' using the
`powerline' package."
  (let* ((status (mpd-get-status mpd-inter-conn))
         ;; Whether to show the current song's information.
         (show-song (member (plist-get status 'state) '(play pause)))
         ;; Powerline information.
         (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-current-separator)
                                   (car powerline-default-separator-dir))))
         (sep-right (intern (format "powerline-%s-%s"
                                    (powerline-current-separator)
                                    (cdr powerline-default-separator-dir))))
         ;; MPD state string.
         (state  (case (plist-get status 'state)
                   (play  "playing ")
                   (pause "paused ")
                   (stop  "stopped ")
                   (t     "error ")))
         ;; Volume string.
         (volume (if (= (plist-get status 'volume) 100) "Vol: 100%% "
                   (format "Vol:  %d%%%% " (plist-get status 'volume))))
         ;; Playback string.
         (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" "-")))
         ;; Elapsed time string (only when playing/paused).
         (time (if (not 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))))

;;;; Minor Mode Definition

(define-minor-mode mingus-header-mode
  "A minor mode for displaying MPD information in the header line
of `mingus-playlist-mode' buffers.
Note that toggling this mode will fail in all other modes."
  :group 'mingus-header
  (if (not (eq major-mode 'mingus-playlist-mode))
      (progn
        ;; Toggling this minor mode only works in `mingus-playlist-mode'.
        (setq mingus-header-mode (not mingus-header-mode))
        (user-error "`mingus-header-mode' can only be enabled in `mingus-playlist-mode' buffers."))
    (if (not mingus-header-mode)
        (setq header-line-format nil)
      (setq header-line-format
            '("%e" (:eval (mingus-header-line-format)))))))

(provide 'mingus-header-mode)

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)

Open PDFs scaled to fit page:

(setq-default pdf-view-display-size 'fit-page)

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 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>
    <span class=\"subtitle\">―― <a href=\"index\">jonhunt.uk</a> ――</span>
  <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
"")

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"
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

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 (Youtube to Invidious, Reddit to Teddit, Instagram to Bibliogram, Medium to Scribe, Twitter to Nitter):

(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
(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 = #f0232635
background-alt = #576075
foreground = #A6Accd
foreground-alt = #555
primary = #ffb52a
secondary = #e60053
alert = #bd2c40
underline-1 = #c792ea

[bar/panel]
width = 100%
height = <<get-setting(name="polybar/height")>>
offset-x = 0
offset-y = 0
fixed-center = true
enable-ipc = true

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

line-size = 2
line-color = #f00

border-size = 0
border-color = #00000000

padding-top = 5
padding-left = 1
padding-right = 1

module-margin = 1

font-0 = "Cantarell:size=<<get-setting(name="polybar/font-0-size")>>:weight=bold;2"
font-1 = "Font Awesome:size=<<get-setting(name="polybar/font-1-size")>>;2"
font-2 = "Material Icons:size=<<get-setting(name="polybar/font-2-size")>>;5"
font-3 = "Fira Mono:size=<<get-setting(name="polybar/font-3-size")>>;-3"

modules-left = exwm exwm-path
modules-center = spotify
modules-right = mu4e cpu temperature date

tray-position = right
tray-padding = 2
tray-maxsize = 28

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

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

[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/spotify]
type = custom/script
exec = ~/.config/polybar/player-status.sh
interval = 3

[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> <ramp-coreload>
format-underline = ${colors.underline-1}
click-left = emacsclient -e "(proced)"
label = %percentage:2%%
ramp-coreload-spacing = 0
ramp-coreload-0 = ▁
ramp-coreload-0-foreground = ${colors.foreground-alt}
ramp-coreload-1 = ▂
ramp-coreload-2 = ▃
ramp-coreload-3 = ▄
ramp-coreload-4 = ▅
ramp-coreload-5 = ▆
ramp-coreload-6 = ▇

[module/memory]
type = internal/memory
interval = 2
format-prefix = "M:"
format-prefix-foreground = ${colors.foreground-alt}
format-underline = ${colors.underline-1}
label = %percentage_used%%

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

date = "W%U: %a %b %e"
date-alt = "%A %B %d %Y"

time = %l:%M %p
time-alt = %H:%M:%S

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

label = %date% %time%

[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}

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:

(setq inhibit-startup-screen t)
(delete-other-windows)
(exwm-workspace-switch-create 2)
(lmedia)
(exwm-workspace-switch-create 1)
(start-process-shell-command "nyxt" nil "nyxt")

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