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 |

My config
The original source is an orgmode file, which is then automatically converted into 2 additional files:
- An elisp file so Emacs can run it. This is done via babel.
- 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
- John McCarthy: Developed the Lisp programming language family, from which Emacs is built.
- David A. Moon: Co-created Emacs with Guy Steele
- Richard Stallman: Created GNU Emacs (which is the main Emacs used today)
- Guy L. Steele Jr: Co-created Emacs with David Moon
Emacs packages I use or intend to use in the future
- Bastian Bechtold: Created
org-static-blog
- Alexander Belikoff: Created
erc
alongside Sergey Berezin - Sergey Berezin: Created
erc
alongside Alexander Belikoff - Dirk-Jan Binnema: Created
mu
andmu4e
- Carsten Dominik: Created
orgmode
- Chris Feng: Created
exwm
- Niels Giesen: Created
mingus
- Remy Honig: Created
elfeed-org
- Aaron Jacobs: Created
mingus-header-mode
- jtbm37: Created
all-the-icons-dired
- Stefan Kangas: Created
mentor
- Ohel Krehel: Created
ivy
,counsel
, andswiper
- Sebastian Kremer: Created
dired
anddired-x
- lujun9972: Created
dmenu
- Eric Schulte: Created
org-babel
- Markus Triska: Created
openwith
- Politza: Created
pdf-tools
- Marius Vollmer: Created
magit
- Christopher Wellons: Created
elfeed
Non-emacs
- Florian Bruhin: Created
qutebrowser
- Daniel Friesel: Created
feh
alongside Tom Gilbert - Ricardo Garcia: Created
youtube-dl
- รrpรกd Gereรถffy: Created
mplayer
, whichmpv
is a fork of - Tom Gilbert: Created
feh
alongside Daniel Friesel - Max Kellermann: Created
mpd
- Levente1 Polyak: Is the current project lead for
archlinux
and helped to create it - Hong Jen Yee: Also known as "PCMan", he created
pcmanfm
People I learned from
- Musa Al-hassy: Introduced me to
org-static-blog
from reading his blogpost on the subject. - David Eckert: Better known as 'Uncle Dave', his video tutorials and Emacs config taught me how to use Emacs when I first started.
- Derek Taylor: Better known as 'DistroTube', his videos helped spark my interest in Emacs.
- Thoughtbot: Their presentations helped to spark my interest in Emacs, particularly the talks from Jay Dixit, Perry Metzger, and Harry Schwartz.
- David Wilson: Better known as 'System Crafters', I learned how to set up
mu4e
andorg-babel-tangle
from his video tutorials. - Mike Zamansky: I was introduced to
elfeed
andelfeed-org
from his video channel.
If there's others you think I should add, or you find I've miscredited someone, please let me know.