CONFIG

My Orgconfig

Published — Updated

My Org config

My personal orgconfig

This is my live config, written as an Org file and integrated with my site with ox-hugo. Pretty much everything is subject to drastic change. I can’t even promise the same blocks will be in the same file next time you visit.

Oh! Use :noexport: tags on subtrees and :exports none on source blocks for sensitive or redundant material.

Diego Zamboni’s book Literate Configuration inspired me to get on an orgconfig project. I copied and pasted my dotfiles into source blocks — still need to finish that. After that, I gradually refactor logically connected chunks into their own blocks.

Well. Logical to me, at least. It’s fun to see how other people’s brains work, yes?

Emacs

Emacs

I frequently experiment with GNU Emacs configuration. Different starters like Doom or Centaur. Sometimes even one of my own experiments.

Chemacs for the Emacs profiles

Chemacs helps me juggle all those configurations. Since I am now up to four configs — five, since I started collecting these notes — this may or may not be a good thing.

Grabbing installation instructions from the Chemacs README:

[ -f ~/.emacs ] && mv ~/.emacs ~/.emacs.bak
[ -d ~/.emacs.d ] && mv ~/.emacs.d ~/.emacs.default.d
git clone https://github.com/plexus/chemacs2.git ~/.emacs.d

I add the configuration of the week to ~/.emacs-profiles.el.

(("better" . ((user-emacs-directory . "~/.emacs.better.d")))
 ("bmw" . ((user-emacs-directory . "~/.emacs.bmw.d")))
 ("centaur" . ((user-emacs-directory . "~/.emacs.centaur.d")))
 ("doom" . ((user-emacs-directory . "~/.emacs.doom.d")))
 ("prelude" . ((user-emacs-directory . "~/.emacs.prelude.d"))))

Now I can launch a specific profile by name:

emacs --with-profile better

If I fire up “Emacs” from the desktop, it’ll use the profile named in ~/.emacs-profile.

prelude
“Better Defaults” for guests or a safe fallback

I need a stable profile for when pairing. Also handy when my experiments break something. That’s where Phil Hagelberg’s better-defaults comes in.

Clone the repo and load it, and you’re basically done. It’s Emacs, but just a little better.

(add-to-list 'load-path "~/.emacs.better.d/elisp")
(require 'better-defaults)

Prelude — for a solid Emacs foundation

The Prelude experience is somewhere in between better-defaults and Doom. Thinks more about features than Better, but not as intensely customized as Doom. You can still get useful information from the default Emacs documentation. Oh, and Prelude has very nice online documentation.

This config is pretty bare right now. That’s because it’s my latest “Emacs bankruptcy” config. I only wanted needed enough to tangle and publish these pages.

Not to worry. It’ll soon be twisted beyond recognition.

Specify prelude modules to load

This is one of those big files where you comment out the lines you don’t need. I won’t drop the whole file here. Mostly enabled stuff for my favorite Web and server development languages.

Set up additional personalizations

Prelude looks in ~/.emacs.prelude.d/personal/ on my machine — probably ~/.emac.d/personal/ on yours — for personalization changes. You can throw as many files as you want in there. For now, I’ll just throw all these blocks into one file. If that doesn’t work? Well okay. I’ll fix it after I put out the fires.

<<prelude+lua>>
<<prelude+ox-hugo>>
Lua

I mainly need lua-mode to configure AwesomeWM. But someday I’ll dig deeper into Lua.

(prelude-require-package 'lua-mode)

(autoload 'lua-mode "lua-mode" "Lua editing mode." t)
(add-to-list 'auto-mode-alist '("\\.lua$" . lua-mode))
(add-to-list 'interpreter-mode-alist '("lua" . lua-mode))
Ox-Hugo

I need ox-hugo to publish these settings through Hugo as anything but one huge HTML file.

(prelude-require-package 'ox-hugo)

(with-eval-after-load 'ox
  (require 'ox-hugo))

Doom — for a highly tweaked Vim-ish experience

Introduction

This is the config of a mostly-Vim user trying to use Emacs.

I might be getting the hang of Doom Emacs and its not-quite-Emacs-not-quite-Vim quirks.

I have no idea how well it will work.

config

See the Doom config example for extremely helpful inline comments, which I have impatiently stripped from my own config.

However, I know I’ll need this bit:

Here are some additional functions/macros that could help you configure Doom:

  • load! for loading external *.el files relative to this one
  • use-package! for configuring packages
  • after! for running code after a package has loaded
  • add-load-path! for adding directories to the load-path, relative to this file. Emacs searches the load-path when you load packages with require or use-package.
  • map! for binding new keys

To get information about any of these functions/macros, move the cursor over the highlighted symbol at press K (non-evil users must press C-c c k). This will open documentation for it, including demos of how they are used.

You can also try gd (or C-c c d) to jump to their definition and see how they are implemented.

For themes I’m just going through emacs-doom-themes until I find a few that stick.

;;; -*- lexical-binding: t; -*-

<<doom/define-global-variables>>
(setq doom-theme 'doom-vibrant)
(setq display-line-numbers-type t)

<<doom/configure-projectile>>
<<doom/configure-org-mode>>
<<doom/configure-doom-dashboard>>
  • Personal Variables

    Some are preferences, some are handy ways to define my environment.

    (setq user-full-name "Brian Wisti"
          user-mail-address "brianwisti@pobox.com")
    
    <<define-private-variables>>
    

    And some are for work or personal details that we don’t need to be showing the public.

  • Org mode

    Honestly, Org mode is mostly what I use Emacs for.

    (setq
     org-directory "~/org/"
     org-startup-indented nil)
    
    <<doom/configure-org-roam>>
    
    • Showing the Org Roam Graph

      Allows org-roam to show its graph in the browser on macOS. Needs more configuration, but that’s a lower priority right now.

      (use-package! org-roam
        :init
        (if IS-MAC
            (setq org-roam-graph-viewer "/usr/bin/open")))
      
  • Projectile

    Projectile provides one approach to project management in Emacs.

    (after! projectile
      (dolist (project bmw/projects)
        (projectile-add-known-project project)))
    
  • Doom Dashboard

    (setq +doom-dashboard-menu-sections
      '(("Reload last session"
        :icon (all-the-icons-octicon "history" :face 'doom-dashboard-menu-title)
        :when (cond ((require 'persp-mode nil t)
                      (file-exists-p (expand-file-name persp-auto-save-fname persp-save-dir)))
                    ((require 'desktop nil t)
                      (file-exists-p (desktop-full-file-name))))
        :face (:inherit (doom-dashboard-menu-title bold))
        :action doom/quickload-session)
        ("Open org-roam Daily"
         :icon (all-the-icons-octicon "squirrel" :face 'doom-dashboard-menu-title)
         :when (fboundp 'org-roam-dailies-find-today)
         :action org-roam-dailies-today)
        ("Open org-agenda"
        :icon (all-the-icons-octicon "calendar" :face 'doom-dashboard-menu-title)
        :when (fboundp 'org-agenda)
        :action org-agenda)
        ("Recently opened files"
        :icon (all-the-icons-octicon "file-text" :face 'doom-dashboard-menu-title)
        :action recentf-open-files)
        ("Open project"
        :icon (all-the-icons-octicon "briefcase" :face 'doom-dashboard-menu-title)
        :action projectile-switch-project)
        ("Jump to bookmark"
        :icon (all-the-icons-octicon "bookmark" :face 'doom-dashboard-menu-title)
        :action bookmark-jump)
        ("Open private configuration"
        :icon (all-the-icons-octicon "tools" :face 'doom-dashboard-menu-title)
        :when (file-directory-p doom-private-dir)
        :action doom/open-private-config)
        ("Open documentation"
        :icon (all-the-icons-octicon "book" :face 'doom-dashboard-menu-title)
        :action doom/help)))
    
init.el

The best use of init.el is just uncommenting entries from the extensive Doom init file for bundled packages you want enabled, and adding bundle options where relevant.

Again, grabbing a useful tip from the original

Move your cursor over a module’s name (or its flags) and press ‘K’ (or ‘C-c c k’ for non-vim users) to view its documentation. This works on flags as well (those symbols that start with a plus).

Alternatively, press ‘gd’ (or ‘C-c c d’) on a module to browse its directory (for easy access to its source code).

packages.el

Nothing here yet, but this is where I would install packages beyond the plethora of options bundled with Doom.

And when I do hit that point, I may want to look at the Doom packages example.

My early attempts

I don’t know Emacs. I don’t know Lisp. And yet I created a more or less functioning config. Well, last I checked it was functioning.

My version of “.emacs bankruptcy” is falling back on this collection of learned habits and accumulated preferences.

Leans heavily on use-package, perhaps to excess. Anywhere I can configure something via the use-package macro, I do.

Over in Windows, Emacs doesn’t assume I want UTF-8. So first things first I’m going to spell that out.

(prefer-coding-system 'utf-8)
(prefer-coding-system 'utf-8)
;;
;; package manager setup
;;
;; Package not installing?
;;  Try 'M-x package-refresh-contents'

(require 'package)

;; via https://mastodon.social/@genehack/103737652356761968
(defvar bmw/packages-refreshed nil)

(defadvice package-install (before refresh activate)
  "Call package-refresh-contents` before the first package-install."
  (unless (eq bmw/packages-refreshed t)
    (progn
      (package-refresh-contents)
      (setq bmw/packages-refreshed nil))))

(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/"))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

(setq package-enable-at-startup nil)
(package-initialize)

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

(eval-when-compile
  (require 'use-package))
;; Arbitrarily chosen, but assigned to a variable for consistency.
(setq bmw/line-length 100)

;; Remove the distractions I don't care for
(setq-default visible-bell t)
(setq-default fill-column bmw/line-length)
(setq-default left-margin-width 2)
(setq-default right-margin-width 2)
(setq-default indent-tabs-mode nil)  ; I prefer spaces
(setq-default tab-width 2)
(setq-default vc-follow-symlinks nil)
(setq x-underline-at-descent-line t)
(setq inhibit-startup-message t
      inhibit-startup-echo-area-message t)

(if (eq system-type 'windows-nt)
    (setq inhibit-compacting-font-caches t))
;; Hugo gets confused by Emacs lockfiles
(setq-default create-lockfiles nil)
(setq bmw/org-dir
      (expand-file-name "~/org/"))

(setq bmw/org-agenda-dir (file-name-as-directory (concat bmw/org-dir "agendas")))
(setq bmw/org-journal-dir (file-name-as-directory (concat bmw/org-dir "journals")))
(setq bmw/org-templates-dir (file-name-as-directory (concat bmw/org-dir "templates")))
(setq bmw/org-config-dir (file-name-as-directory (concat bmw/org-dir "config")))
(setq bmw/org-site-dir (file-name-as-directory "~/Sites/rgb-hugo/org"))

(setq
 bmw/org-main (concat bmw/org-agenda-dir "main.org")
 bmw/org-beorg-inbox (concat bmw/org-dir "inbox.org")
 bmw/org-journal (concat bmw/org-journal-dir "journal.org")
 bmw/org-money (concat bmw/org-dir "money.org")
 bmw/org-config-main (concat bmw/org-config-dir "emacs.org")
 bmw/org-oxn-site (concat bmw/org-site-dir "content.org"))

(setq bmw/org-agenda-files
      (list bmw/org-main
            bmw/org-journal
            bmw/org-beorg-inbox
            bmw/org-money
            bmw/org-config-main
            bmw/org-ox-site))
(global-set-key (kbd "<f5>")
                (lambda ()
                  (interactive)
                  (find-file bmw/org-config-main)
                  (message "Opened: %s" (buffer-name))))

(add-to-list 'load-path
             (file-name-as-directory
              (concat bmw/org-config-dir "local-lisp")))

;;
;; My own special mode hooks
;;

;; base mode for coding
(add-hook 'prog-mode-hook (lambda ()
                            (setq-local display-line-numbers t)))
(add-hook 'cperl-mode-hook (lambda ()
                            (setq-local display-line-numbers t)))
(use-package generic-x)

(use-package color)

(defun bmw/color-dim (steps)
  (apply 'color-rgb-to-hex
         (car (color-gradient
               (color-name-to-rgb (face-attribute 'default :background))
               (color-name-to-rgb (face-attribute 'default :foreground))
               steps))))
(set-face-attribute 'default nil
                    :family "IBM Plex Mono"
                    :height 120
                    :weight 'normal
                    :width 'normal)

(set-face-attribute 'fixed-pitch nil
                    :family "IBM Plex Mono"
                    :height 120
                    :weight 'normal
                    :width 'normal)

(set-face-attribute 'variable-pitch nil
                    :family "IBM Plex Sans"
                    :height 120
                    :weight 'normal
                    :width 'normal)

(use-package face-remap
  :custom-face
  (font-lock-keyword-face ((t (:inherit fixed-pitch :background nil))))
  (line-number ((t (:inherit fixed-pitch))))

  (whitespace-indentation ((t (:inherit error))))
  (whitespace-space ((t (:inherit shadow))))
  (whitespace-tab ((t (:inherit font-lock-comment-face)))))
;; locks and autosaves all over the place just annoy me
;; TODO: set up proper autosave
(use-package files
  :custom
  (auto-save-default nil)
  (auto-save-file-name-transforms `((".*" ,(concat user-emacs-directory "autosaves"))))
  (backup-directory-alist `(("." . ,(concat user-emacs-directory "backups")))))
(use-package goto-addr
  :ensure t
  :hook
  ((text-mode prog-mode) . goto-address-mode))

(use-package scroll-bar
  :init
  (scroll-bar-mode -1))

;; Fine-tune basic editing commands
(use-package simple
  :init
  ;; Invoke M-x without Alt
  (global-set-key "\C-x\C-m" 'execute-extended-command)
  (column-number-mode)
  :hook
  ((prog-mode) . turn-on-auto-fill))

(use-package tool-bar
  :init
  (tool-bar-mode -1))

;; ensure environment in Emacs is environment in shell
(if (not (eq system-type 'windows-nt))
    (use-package exec-path-from-shell
      :ensure t
      :config
      (setq exec-path-from-shell-check-startup-files nil
            exec-path-from-shell-variables
            '("PLENV_ROOT"
              "PATH" "MAN_PATH"
              "RAKUBREW_HOME"))
      (exec-path-from-shell-initialize)))
(defun bmw/theme-whitespace ()
  "Apply my own face-attribute changes after loading a custom theme"
  (set-face-attribute 'whitespace-space nil
                      :background (face-attribute 'font-lock-comment-face :background)
                      :foreground (bmw/color-dim 3)))

(use-package whitespace
  :ensure t
  :preface
  (defun bmw/whitespace-mode ()
    (unless (eq major-mode 'org-mode)
      (progn
        (whitespace-mode)
        (bmw/theme-whitespace))))
  :custom
  (whitespace-action '(auto-cleanup))
  (whitespace-line-column bmw/line-length)
  (whitespace-style
   '(face lines trailing empty tabs spaces indentation space-mark tab-mark))
  :hook
  ((prog-mode text-mode) . bmw/whitespace-mode))
(use-package adoc-mode
  :ensure t
  :mode "\\.adoc")
(use-package deft
  :ensure t
  :init
  (setq deft-extensions '("md" "org" "txt"))
  :config
  (setq deft-markdown-mode-title-level 1)
  (setq deft-use-filename-as-title t)
  (setq deft-file-naming-rules
        '((noslash . "-")
          (nospace . "-")
          (case-fn . downcase)))
  (setq deft-directory "~/Dropbox/org/notes"))
;; elscreen for sessions
(use-package elscreen
  :ensure t
  :init
  (elscreen-start))
(use-package gnuplot
  :ensure t)
(use-package lua-mode
  :ensure t
  :mode ("\\.lua\\'" "\\.luac\\'")
  :config (setq lua-indent-level 2))
(use-package org-bullets
  :ensure t
  :custom
  (org-bullets-bullet-list '("◉" "○"))
  :hook (org-mode . org-bullets-mode))
(use-package olivetti
  :ensure t
  :custom
  (olivetti-body-width bmw/line-length)
  :hook (text-mode . olivetti-mode))

(use-package org
  :ensure org-plus-contrib
  :defer t
  :custom
  (org-agenda-files bmw/org-agenda-files)
  (org-babel-python-command "python3")
  (org-catch-invisible-edits 'show-and-error)
  (org-clock-persist t)
  (org-confirm-babel-evaluate nil)
  (org-display-inline-images t)
  (org-edit-src-content-indentation 2)
  (org-directory bmw/org-dir)
  (org-ellipsis "⬎")
  (org-enforce-todo-dependencies t)
  (org-fontify-quote-and-verse-blocks t)
  (org-log-into-drawer t)
  (org-log-done 'time)
  (org-log-redeadline 'time)
  (org-outline-path-complete-in-steps nil)
  (org-redisplay-inline-images t)
  (org-refile-targets '((org-agenda-files :maxlevel . 6)))
  (org-refile-use-outline-path 'file)
  (org-log-reschedule 'time)
  (org-src-fontify-natively t)
  (org-src-tab-acts-natively t)
  (org-startup-indented t)
  (org-startup-with-inline-images "inlineimages")

  (org-capture-templates
   (quote (("t" "Task")
           ("ts" "Site" entry
            (file+olp bmw/org-main "Tasks" "Site")
            (file "templates/generic-task.txt") :clock-in t :clock-resume t)
           ("tg" "General" entry
            (file+olp bmw/org-main "Tasks" "General")
            (file "templates/generic-task.txt") :clock-in t :clock-resume t)
           ("tn" "Now" entry
            (file+olp bmw/org-main "Tasks" "General")
            "* NOW %? \nDEADLINE: %t")
           ("ti" "Idea" entry
            (file+olp bmw/org-main "Tasks" "Ideas")
            (file "templates/idea.txt"))

           ("j" "Journal Entry" entry
            (file+datetree bmw/org-journal "Journal")
            (file "templates/journal.txt"))

           ("m" "Money")
           ("me" "Money Expense" entry
            (file+datetree bmw/org-money "Transactions")
            (file "templates/money-expense.txt"))
           ("mi" "Money Income" entry
            (file+datetree bmw/org-money "Transactions")
            (file "templates/money-income.txt")))))

  ;; just in case it's not set in the org file
  (org-todo-keywords
   '((type "TASK(t)" "NOW(n!)" "|" "DONE(d!)")      ; the main flow
     (type "MAYBE(m) WAIT(w)" "|" "CANCEL(c@)")))  ; common branches

  :init
  (global-set-key (kbd "C-c l") 'org-store-link)
  (global-set-key (kbd "C-c c") 'org-capture)
  (global-set-key (kbd "C-c a") 'org-agenda)
  (advice-add 'org-archive-default-command :after #'org-save-all-org-buffers)

  :config
  (turn-off-auto-fill)
  (setq-local display-line-numbers nil)
  (setq-local org-fontify-whole-heading-line t)
  (setq-local org-fontify-done-headline t)

  (org-babel-do-load-languages
   'org-babel-load-languages
   '((dot , t)
     (gnuplot . t)
     (shell . t)
     (ruby . t)
     (R . t)
     (python . t)
     (sql . t)
     (sqlite . t)
     (perl . t)))
  (org-clock-persistence-insinuate))
;; markdown-mode
(use-package markdown-mode
  :ensure t
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :init (setq markdown-command "mmark"))
;; writegood-mode
(use-package writegood-mode
  :ensure t
  :config
  (global-set-key (kbd "C-c g") 'writegood-mode)
  (add-hook 'text-mode-hook 'writegood-mode))
(use-package yaml-mode
  :ensure t)
;; Development with the Crystal language
(use-package crystal-mode
  :ensure t
  :mode ("\\.cr$" . crystal-mode)
  :interpreter ("crystal" . crystal-mode)
  :config
  (autoload 'crystal-mode "crystal-mode" "Major mode for Crystal files" t))

(use-package ob-crystal
  :ensure t)
(require 'tramp)
(require 'perltidy)

(if (eq system-type 'windows-nt)
    (setq-default perltidy-program "perltidy.bat"))

(add-hook 'cperl-mode-hook (lambda ()
                             (local-set-key (kbd "C-c t") 'perltidy-dwim)))
(use-package cperl-mode
  :ensure t
  :defer t
  :custom
  ;; automatic indent after semicolon insertion
  (cperl-autoindent-on-semi nil)

  ;; automatically newline before braces, and after braces, colons, and semi-colons
  (cperl-auto-newline nil)

  ;; "not overwrite C-h f"?
  (cperl-clobber-lisp-bindings t)

  ;; extra indent for substatements that start with close parens
  (cperl-close-paren-offset -2)

  ;; extra indent for lines not starting new statements
  (cperl-continued-statement-offset 2)

  ;; keywords are electric in cperl-mode
  (cperl-electric-keywords nil)

  ;; { after $ should be preceded by a space(?)
  (cperl-electric-lbrace-space nil)

  ;; linefeed should be hairy in cperl (otherwise `C-c <return>')
  (cperl-electric-linefeed t)

  ;; parens should be electric in cperl-mode
  (cperl-electric-parens nil)

  ;; use font-lock-mode in cperl buffers
  (cperl-font-lock t)

  ;; perform additional highlighting on variables (currently just scalars)
  (cperl-highlight-variables-indiscriminately t)

  ;; indentation of cperl statements relative to containing block (default 2)
  (cperl-indent-level 2)

  ;; non-block (), {}, and [] (without trailing `,') are indented as blocks
  (cperl-indent-parens-as-block t)

  ;; amount of space to insert between `}' and `else' or `elsif'
  (cperl-indent-region-fix-constructs 1)

  ;; skip prompts on C-h f
  (cperl-info-on-command-no-prompt t)

  ;; "result of evaluation of this expression highlights trailing whitespace"
  (cperl-invalid-face nil)

  ;; show lazy help after given idle time
  (cperl-lazy-help-time 5)

  ;; always reindent the current line on <tab>
  (cperl-tab-always-indent t)
  :interpreter "perl"
  :mode "\\.\\(cgi\\|psgi\\|t\\)\\'")
(defalias 'perl-mode 'cperl-mode)


(require 'digraphs)
(defadvice load-theme (before clear-previous-themes activate)
  "Clear existing theme settings instead of layering them"
  (mapc #'disable-theme custom-enabled-themes))

(use-package color-theme-modern
  :ensure t
  :config
  (load-theme 'wombat t t)
  (enable-theme 'wombat))
(setq custom-file "~/.bmw.emacs.d/.emacs-custom.el")
(load custom-file)

Set up package repositories and make sure use-package is in force.

Preface

;;
;; package manager setup
;;
;; Package not installing?
;;  Try 'M-x package-refresh-contents'

(require 'package)

;; via https://mastodon.social/@genehack/103737652356761968
(defvar bmw/packages-refreshed nil)

(defadvice package-install (before refresh activate)
  "Call package-refresh-contents` before the first package-install."
  (unless (eq bmw/packages-refreshed t)
    (progn
      (package-refresh-contents)
      (setq bmw/packages-refreshed nil))))

(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/"))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

(setq package-enable-at-startup nil)
(package-initialize)

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

(eval-when-compile
  (require 'use-package))
Variables

Personal custom variables and settings I haven’t assigned to a package yet.

;; Arbitrarily chosen, but assigned to a variable for consistency.
(setq bmw/line-length 100)

;; Remove the distractions I don't care for
(setq-default visible-bell t)
(setq-default fill-column bmw/line-length)
(setq-default left-margin-width 2)
(setq-default right-margin-width 2)
(setq-default indent-tabs-mode nil)  ; I prefer spaces
(setq-default tab-width 2)
(setq-default vc-follow-symlinks nil)
(setq x-underline-at-descent-line t)
(setq inhibit-startup-message t
      inhibit-startup-echo-area-message t)

(if (eq system-type 'windows-nt)
    (setq inhibit-compacting-font-caches t))
;; Hugo gets confused by Emacs lockfiles
(setq-default create-lockfiles nil)
(setq bmw/org-dir
      (expand-file-name "~/org/"))

(setq bmw/org-agenda-dir (file-name-as-directory (concat bmw/org-dir "agendas")))
(setq bmw/org-journal-dir (file-name-as-directory (concat bmw/org-dir "journals")))
(setq bmw/org-templates-dir (file-name-as-directory (concat bmw/org-dir "templates")))
(setq bmw/org-config-dir (file-name-as-directory (concat bmw/org-dir "config")))
(setq bmw/org-site-dir (file-name-as-directory "~/Sites/rgb-hugo/org"))

(setq
 bmw/org-main (concat bmw/org-agenda-dir "main.org")
 bmw/org-beorg-inbox (concat bmw/org-dir "inbox.org")
 bmw/org-journal (concat bmw/org-journal-dir "journal.org")
 bmw/org-money (concat bmw/org-dir "money.org")
 bmw/org-config-main (concat bmw/org-config-dir "emacs.org")
 bmw/org-oxn-site (concat bmw/org-site-dir "content.org"))

(setq bmw/org-agenda-files
      (list bmw/org-main
            bmw/org-journal
            bmw/org-beorg-inbox
            bmw/org-money
            bmw/org-config-main
            bmw/org-ox-site))
Hugo file lock workaround

Keep Hugo from complaining about the presence of Emacs file locks.

;; Hugo gets confused by Emacs lockfiles
(setq-default create-lockfiles nil)

This isn’t ideal — file locks are a good thing. OTOH I’m pretty much the only person on my system, so I have nobody but myself to blame if a missing file lock breaks something.

But without it?

$ hugo
Building sites … Built in 48 ms
Error: Error building site: process: readAndProcessContent: walk: Readdir: decorate: lstat /home/random/Sites/rgb-hugo/content/post/2020/04/from-dotfiles-to-org-file/random@geekery.3137437:1588601341: no such file or directory

Still an open issue as of 2020-05-09, using Hugo version:

Hugo Static Site Generator v0.69.2/extended linux/amd64 BuildDate: unknown
Inhibit compacting font caches on windows

Had a bad problem with performance on a Windows laptop — but one that didn’t show up on my Windows desktop PC, oddly. Eventually narrowed it down to org-bullets, and then further to a known font cache issue that comes up when using org-bullets on Windows.

(if (eq system-type 'windows-nt)
    (setq inhibit-compacting-font-caches t))
Fonts

Using VcXsrv as an X server on Windows, and it seems to be working okay. I’m still working on the finer details of variable-pitch-mode and Emacs fonts in general.

Current font choices:

Sitting in front of Pop!_OS at this split second, so here’s the apt instructions.

sudo apt install fonts-ibm-plex

fc-list is the font-listing command I keep forgetting, not xls-fonts

Maybe someday automate stuff like font installation with Ansible or even just a shell script? Maybe, but that kinda depends on me knowing what distro or operating system I’m sitting in front of from one week to the next.

(set-face-attribute 'default nil
                    :family "IBM Plex Mono"
                    :height 120
                    :weight 'normal
                    :width 'normal)

(set-face-attribute 'fixed-pitch nil
                    :family "IBM Plex Mono"
                    :height 120
                    :weight 'normal
                    :width 'normal)

(set-face-attribute 'variable-pitch nil
                    :family "IBM Plex Sans"
                    :height 120
                    :weight 'normal
                    :width 'normal)

(use-package face-remap
  :custom-face
  (font-lock-keyword-face ((t (:inherit fixed-pitch :background nil))))
  (line-number ((t (:inherit fixed-pitch))))

  (whitespace-indentation ((t (:inherit error))))
  (whitespace-space ((t (:inherit shadow))))
  (whitespace-tab ((t (:inherit font-lock-comment-face)))))
Resources
Org mode

Org mode is why I use Emacs. So yeah this section’s gonna be a little big.

Org-specific packages
  • Org bullets

    (use-package org-bullets
      :ensure t
      :custom
      (org-bullets-bullet-list '("◉" "○"))
      :hook (org-mode . org-bullets-mode))
    
Org variables for file organization

Because I move stuff around a lot, it’s less work in the long run to define my org files by building up from this week’s folder layout. In the end I explicitly specify which individual files belong in the agenda. This keeps me from “organizing” my tasks into a thousand different subtopic files. Only six.

So far.

Filename expansion works without fuss on Linux and Windows, so no need to be clever.

Well, you might want to define HOME for yourself in Windows, or Emacs guesses based on what Windows version you’re running.

(setq bmw/org-dir
      (expand-file-name "~/org/"))

(setq bmw/org-agenda-dir (file-name-as-directory (concat bmw/org-dir "agendas")))
(setq bmw/org-journal-dir (file-name-as-directory (concat bmw/org-dir "journals")))
(setq bmw/org-templates-dir (file-name-as-directory (concat bmw/org-dir "templates")))
(setq bmw/org-config-dir (file-name-as-directory (concat bmw/org-dir "config")))
(setq bmw/org-site-dir (file-name-as-directory "~/Sites/rgb-hugo/org"))

(setq
 bmw/org-main (concat bmw/org-agenda-dir "main.org")
 bmw/org-beorg-inbox (concat bmw/org-dir "inbox.org")
 bmw/org-journal (concat bmw/org-journal-dir "journal.org")
 bmw/org-money (concat bmw/org-dir "money.org")
 bmw/org-config-main (concat bmw/org-config-dir "emacs.org")
 bmw/org-oxn-site (concat bmw/org-site-dir "content.org"))

(setq bmw/org-agenda-files
      (list bmw/org-main
            bmw/org-journal
            bmw/org-beorg-inbox
            bmw/org-money
            bmw/org-config-main
            bmw/org-ox-site))
Capture templates

(org-capture-templates
 (quote (("t" "Task")
         ("ts" "Site" entry
          (file+olp bmw/org-main "Tasks" "Site")
          (file "templates/generic-task.txt") :clock-in t :clock-resume t)
         ("tg" "General" entry
          (file+olp bmw/org-main "Tasks" "General")
          (file "templates/generic-task.txt") :clock-in t :clock-resume t)
         ("tn" "Now" entry
          (file+olp bmw/org-main "Tasks" "General")
          "* NOW %? \nDEADLINE: %t")
         ("ti" "Idea" entry
          (file+olp bmw/org-main "Tasks" "Ideas")
          (file "templates/idea.txt"))

         ("j" "Journal Entry" entry
          (file+datetree bmw/org-journal "Journal")
          (file "templates/journal.txt"))

         ("m" "Money")
         ("me" "Money Expense" entry
          (file+datetree bmw/org-money "Transactions")
          (file "templates/money-expense.txt"))
         ("mi" "Money Income" entry
          (file+datetree bmw/org-money "Transactions")
          (file "templates/money-income.txt")))))

I use org for config, note-taking, and to a lesser extent site management. That currently means I have org files scattered throughout my system. Some go to the cloud, some to version control. I need org-agenda to be grabby.

apply solution via Reddit. I tried putting it in :custom instead — without setq — but Emacs complained about org-agenda-file-regexp being undefined.

Main org section

(use-package olivetti
  :ensure t
  :custom
  (olivetti-body-width bmw/line-length)
  :hook (text-mode . olivetti-mode))

(use-package org
  :ensure org-plus-contrib
  :defer t
  :custom
  (org-agenda-files bmw/org-agenda-files)
  (org-babel-python-command "python3")
  (org-catch-invisible-edits 'show-and-error)
  (org-clock-persist t)
  (org-confirm-babel-evaluate nil)
  (org-display-inline-images t)
  (org-edit-src-content-indentation 2)
  (org-directory bmw/org-dir)
  (org-ellipsis "⬎")
  (org-enforce-todo-dependencies t)
  (org-fontify-quote-and-verse-blocks t)
  (org-log-into-drawer t)
  (org-log-done 'time)
  (org-log-redeadline 'time)
  (org-outline-path-complete-in-steps nil)
  (org-redisplay-inline-images t)
  (org-refile-targets '((org-agenda-files :maxlevel . 6)))
  (org-refile-use-outline-path 'file)
  (org-log-reschedule 'time)
  (org-src-fontify-natively t)
  (org-src-tab-acts-natively t)
  (org-startup-indented t)
  (org-startup-with-inline-images "inlineimages")

  (org-capture-templates
   (quote (("t" "Task")
           ("ts" "Site" entry
            (file+olp bmw/org-main "Tasks" "Site")
            (file "templates/generic-task.txt") :clock-in t :clock-resume t)
           ("tg" "General" entry
            (file+olp bmw/org-main "Tasks" "General")
            (file "templates/generic-task.txt") :clock-in t :clock-resume t)
           ("tn" "Now" entry
            (file+olp bmw/org-main "Tasks" "General")
            "* NOW %? \nDEADLINE: %t")
           ("ti" "Idea" entry
            (file+olp bmw/org-main "Tasks" "Ideas")
            (file "templates/idea.txt"))

           ("j" "Journal Entry" entry
            (file+datetree bmw/org-journal "Journal")
            (file "templates/journal.txt"))

           ("m" "Money")
           ("me" "Money Expense" entry
            (file+datetree bmw/org-money "Transactions")
            (file "templates/money-expense.txt"))
           ("mi" "Money Income" entry
            (file+datetree bmw/org-money "Transactions")
            (file "templates/money-income.txt")))))

  ;; just in case it's not set in the org file
  (org-todo-keywords
   '((type "TASK(t)" "NOW(n!)" "|" "DONE(d!)")      ; the main flow
     (type "MAYBE(m) WAIT(w)" "|" "CANCEL(c@)")))  ; common branches

  :init
  (global-set-key (kbd "C-c l") 'org-store-link)
  (global-set-key (kbd "C-c c") 'org-capture)
  (global-set-key (kbd "C-c a") 'org-agenda)
  (advice-add 'org-archive-default-command :after #'org-save-all-org-buffers)

  :config
  (turn-off-auto-fill)
  (setq-local display-line-numbers nil)
  (setq-local org-fontify-whole-heading-line t)
  (setq-local org-fontify-done-headline t)

  (org-babel-do-load-languages
   'org-babel-load-languages
   '((dot , t)
     (gnuplot . t)
     (shell . t)
     (ruby . t)
     (R . t)
     (python . t)
     (sql . t)
     (sqlite . t)
     (perl . t)))
  (org-clock-persistence-insinuate))
Babel language support

(org-babel-do-load-languages
 'org-babel-load-languages
 '((dot , t)
   (gnuplot . t)
   (shell . t)
   (ruby . t)
   (R . t)
   (python . t)
   (sql . t)
   (sqlite . t)
   (perl . t)))
Perl

Initially just extracting interesting bits from genehack’s perl.el.

(require 'tramp)
(require 'perltidy)

(if (eq system-type 'windows-nt)
    (setq-default perltidy-program "perltidy.bat"))

(add-hook 'cperl-mode-hook (lambda ()
                             (local-set-key (kbd "C-c t") 'perltidy-dwim)))
(use-package cperl-mode
  :ensure t
  :defer t
  :custom
  ;; automatic indent after semicolon insertion
  (cperl-autoindent-on-semi nil)

  ;; automatically newline before braces, and after braces, colons, and semi-colons
  (cperl-auto-newline nil)

  ;; "not overwrite C-h f"?
  (cperl-clobber-lisp-bindings t)

  ;; extra indent for substatements that start with close parens
  (cperl-close-paren-offset -2)

  ;; extra indent for lines not starting new statements
  (cperl-continued-statement-offset 2)

  ;; keywords are electric in cperl-mode
  (cperl-electric-keywords nil)

  ;; { after $ should be preceded by a space(?)
  (cperl-electric-lbrace-space nil)

  ;; linefeed should be hairy in cperl (otherwise `C-c <return>')
  (cperl-electric-linefeed t)

  ;; parens should be electric in cperl-mode
  (cperl-electric-parens nil)

  ;; use font-lock-mode in cperl buffers
  (cperl-font-lock t)

  ;; perform additional highlighting on variables (currently just scalars)
  (cperl-highlight-variables-indiscriminately t)

  ;; indentation of cperl statements relative to containing block (default 2)
  (cperl-indent-level 2)

  ;; non-block (), {}, and [] (without trailing `,') are indented as blocks
  (cperl-indent-parens-as-block t)

  ;; amount of space to insert between `}' and `else' or `elsif'
  (cperl-indent-region-fix-constructs 1)

  ;; skip prompts on C-h f
  (cperl-info-on-command-no-prompt t)

  ;; "result of evaluation of this expression highlights trailing whitespace"
  (cperl-invalid-face nil)

  ;; show lazy help after given idle time
  (cperl-lazy-help-time 5)

  ;; always reindent the current line on <tab>
  (cperl-tab-always-indent t)
  :interpreter "perl"
  :mode "\\.\\(cgi\\|psgi\\|t\\)\\'")
(defalias 'perl-mode 'cperl-mode)

(use-package cperl-mode
  :ensure t
  :defer t
  :custom
  ;; automatic indent after semicolon insertion
  (cperl-autoindent-on-semi nil)

  ;; automatically newline before braces, and after braces, colons, and semi-colons
  (cperl-auto-newline nil)

  ;; "not overwrite C-h f"?
  (cperl-clobber-lisp-bindings t)

  ;; extra indent for substatements that start with close parens
  (cperl-close-paren-offset -2)

  ;; extra indent for lines not starting new statements
  (cperl-continued-statement-offset 2)

  ;; keywords are electric in cperl-mode
  (cperl-electric-keywords nil)

  ;; { after $ should be preceded by a space(?)
  (cperl-electric-lbrace-space nil)

  ;; linefeed should be hairy in cperl (otherwise `C-c <return>')
  (cperl-electric-linefeed t)

  ;; parens should be electric in cperl-mode
  (cperl-electric-parens nil)

  ;; use font-lock-mode in cperl buffers
  (cperl-font-lock t)

  ;; perform additional highlighting on variables (currently just scalars)
  (cperl-highlight-variables-indiscriminately t)

  ;; indentation of cperl statements relative to containing block (default 2)
  (cperl-indent-level 2)

  ;; non-block (), {}, and [] (without trailing `,') are indented as blocks
  (cperl-indent-parens-as-block t)

  ;; amount of space to insert between `}' and `else' or `elsif'
  (cperl-indent-region-fix-constructs 1)

  ;; skip prompts on C-h f
  (cperl-info-on-command-no-prompt t)

  ;; "result of evaluation of this expression highlights trailing whitespace"
  (cperl-invalid-face nil)

  ;; show lazy help after given idle time
  (cperl-lazy-help-time 5)

  ;; always reindent the current line on <tab>
  (cperl-tab-always-indent t)
  :interpreter "perl"
  :mode "\\.\\(cgi\\|psgi\\|t\\)\\'")
(defalias 'perl-mode 'cperl-mode)
Customized variables

Using :custom for anything that I could have set via Emacs Customization.

;; automatic indent after semicolon insertion
(cperl-autoindent-on-semi nil)

;; automatically newline before braces, and after braces, colons, and semi-colons
(cperl-auto-newline nil)

;; "not overwrite C-h f"?
(cperl-clobber-lisp-bindings t)

;; extra indent for substatements that start with close parens
(cperl-close-paren-offset -2)

;; extra indent for lines not starting new statements
(cperl-continued-statement-offset 2)

;; keywords are electric in cperl-mode
(cperl-electric-keywords nil)

;; { after $ should be preceded by a space(?)
(cperl-electric-lbrace-space nil)

;; linefeed should be hairy in cperl (otherwise `C-c <return>')
(cperl-electric-linefeed t)

;; parens should be electric in cperl-mode
(cperl-electric-parens nil)

;; use font-lock-mode in cperl buffers
(cperl-font-lock t)

;; perform additional highlighting on variables (currently just scalars)
(cperl-highlight-variables-indiscriminately t)

;; indentation of cperl statements relative to containing block (default 2)
(cperl-indent-level 2)

;; non-block (), {}, and [] (without trailing `,') are indented as blocks
(cperl-indent-parens-as-block t)

;; amount of space to insert between `}' and `else' or `elsif'
(cperl-indent-region-fix-constructs 1)

;; skip prompts on C-h f
(cperl-info-on-command-no-prompt t)

;; "result of evaluation of this expression highlights trailing whitespace"
(cperl-invalid-face nil)

;; show lazy help after given idle time
(cperl-lazy-help-time 5)

;; always reindent the current line on <tab>
(cperl-tab-always-indent t)
Formatting

Couldn’t find a “perltidy” package. I downloaded my own copy of perltidy.el. Might make more sense to check it out from the wiki git repo.

(require 'tramp)
(require 'perltidy)

(if (eq system-type 'windows-nt)
    (setq-default perltidy-program "perltidy.bat"))

(add-hook 'cperl-mode-hook (lambda ()
                             (local-set-key (kbd "C-c t") 'perltidy-dwim)))
Packages
Color handling

(use-package color)

(defun bmw/color-dim (steps)
  (apply 'color-rgb-to-hex
         (car (color-gradient
               (color-name-to-rgb (face-attribute 'default :background))
               (color-name-to-rgb (face-attribute 'default :foreground))
               steps))))
Exec path

Need to get Emacs starting happy under windows-nt before I get back to borrowing from the system environment.

;; ensure environment in Emacs is environment in shell
(if (not (eq system-type 'windows-nt))
    (use-package exec-path-from-shell
      :ensure t
      :config
      (setq exec-path-from-shell-check-startup-files nil
            exec-path-from-shell-variables
            '("PLENV_ROOT"
              "PATH" "MAN_PATH"
              "RAKUBREW_HOME"))
      (exec-path-from-shell-initialize)))
Asciidoctor

(use-package adoc-mode
  :ensure t
  :mode "\\.adoc")
Crystal programming

;; Development with the Crystal language
(use-package crystal-mode
  :ensure t
  :mode ("\\.cr$" . crystal-mode)
  :interpreter ("crystal" . crystal-mode)
  :config
  (autoload 'crystal-mode "crystal-mode" "Major mode for Crystal files" t))

(use-package ob-crystal
  :ensure t)
Deft for notes

https://jblevins.org/projects/deft/

(use-package deft
  :ensure t
  :init
  (setq deft-extensions '("md" "org" "txt"))
  :config
  (setq deft-markdown-mode-title-level 1)
  (setq deft-use-filename-as-title t)
  (setq deft-file-naming-rules
        '((noslash . "-")
          (nospace . "-")
          (case-fn . downcase)))
  (setq deft-directory "~/Dropbox/org/notes"))
Elscreen for multiplexing (tabs)

;; elscreen for sessions
(use-package elscreen
  :ensure t
  :init
  (elscreen-start))
Files

Keeping backups somewhere besides the current folder to reduce Hugo’s confusion. It helps, but see also Hugo file lock workaround.

;; locks and autosaves all over the place just annoy me
;; TODO: set up proper autosave
(use-package files
  :custom
  (auto-save-default nil)
  (auto-save-file-name-transforms `((".*" ,(concat user-emacs-directory "autosaves"))))
  (backup-directory-alist `(("." . ,(concat user-emacs-directory "backups")))))
gnuplot

(use-package gnuplot
  :ensure t)
Goto address

(use-package goto-addr
  :ensure t
  :hook
  ((text-mode prog-mode) . goto-address-mode))
Lua

(use-package lua-mode
  :ensure t
  :mode ("\\.lua\\'" "\\.luac\\'")
  :config (setq lua-indent-level 2))
Markdown

;; markdown-mode
(use-package markdown-mode
  :ensure t
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :init (setq markdown-command "mmark"))
Olivetti

for bigger margins

(use-package olivetti
  :ensure t
  :custom
  (olivetti-body-width bmw/line-length)
  :hook (text-mode . olivetti-mode))
Pyenv

Why oh why did I pick a version manager for every language I use? Seemed like such a great idea when everything was tmux. Oh well. Most of this pyenv stuff I grabbed from Setup Emacs for Python development using Pyenv by Rakan Al-Hneiti.

(defun pyenv-activate-current-project ()
  "Automatically activates pyenv version if .python-version file exists."
  (interactive)
  (let ((python-version-directory (locate-dominating-file (buffer-file-name) ".python-version")))
    (if python-version-directory
        (let* ((pyenv-version-path (f-expand ".python-version" python-version-directory))
               (pyenv-current-version (s-trim (f-read-text pyenv-version-path 'utf-8))))
          (pyenv-mode-set pyenv-current-version)
          (message (concat "Setting virtualenv to " pyenv-current-version))))))

(defvar pyenv-current-version nil nil)

(defun pyenv-init()
  "Initialize pyenv's current version to the global one."
  (let ((global-pyenv (replace-regexp-in-string "\n" "" (shell-command-to-string "pyenv global"))))
    (message (concat "Setting pyenv version to " global-pyenv))
    (pyenv-mode-set global-pyenv)
    (setq pyenv-current-version global-pyenv)))

(add-hook 'after-init-hook 'pyenv-init)

(use-package pyenv-mode
  :ensure t
  :init
  (add-to-list 'exec-path "~/.pyenv/shims")
  (setenv "WORKON_HOME" "~/.pyenv/versions")
  :config
  (pyenv-mode)
  :bind
  ("C-x p e" . pyenv-activate-current-project))
  • MAYBE Pyenv and windows?

    I’m getting a bit uncertain about pyenv in the first place, and with me trying to using Emacs in windows-nt I’m really getting uncertain. Maybe just explore Elpy for a bit — see if there’s anything in there about venv.

Markup faces (for adoc)

I fiddled with these so adoc-mode would be more aesthetically appealing to me.

(use-package markup-faces
  :config
  (set-face-attribute 'markup-title-0-face nil :height 1.0)
  (set-face-attribute 'markup-title-1-face nil :height 1.0)
  (set-face-attribute 'markup-title-2-face nil :height 1.0)
  (set-face-attribute 'markup-title-3-face nil :height 1.0)
  (set-face-attribute 'markup-title-4-face nil :height 1.0)
  (set-face-attribute 'markup-title-5-face nil :height 1.0)
  (set-face-attribute 'markup-secondary-text-face nil :height 1.0)
  (set-face-attribute 'markup-meta-face nil
                      :height 1.0
                      :foreground (face-attribute 'font-lock-comment-face :foreground))
  (set-face-attribute 'markup-meta-hide-face nil
                      :height 1.0
                      :foreground (face-attribute 'font-lock-comment-face :foreground))
  (set-face-attribute 'markup-list-face nil
                      :background (face-attribute 'font-lock-builtin-face :background)
                      :foreground (face-attribute 'font-lock-builtin-face :foreground))
  (set-face-attribute 'markup-table-face nil
                      :background (face-attribute 'font-lock-builtin-face :background)
                      :foreground (face-attribute 'font-lock-builtin-face :foreground))
  (set-face-attribute 'markup-verbatim-face nil
                      :background (face-attribute 'font-lock-string-face :background)
                      :foreground (face-attribute 'font-lock-string-face :foreground)))

I’ve experimented with my own settings, but so far poet is better than what I came up with.

Whitespace

(defun bmw/theme-whitespace ()
  "Apply my own face-attribute changes after loading a custom theme"
  (set-face-attribute 'whitespace-space nil
                      :background (face-attribute 'font-lock-comment-face :background)
                      :foreground (bmw/color-dim 3)))

(defun bmw/theme-whitespace ()
  "Apply my own face-attribute changes after loading a custom theme"
  (set-face-attribute 'whitespace-space nil
                      :background (face-attribute 'font-lock-comment-face :background)
                      :foreground (bmw/color-dim 3)))

(use-package whitespace
  :ensure t
  :preface
  (defun bmw/whitespace-mode ()
    (unless (eq major-mode 'org-mode)
      (progn
        (whitespace-mode)
        (bmw/theme-whitespace))))
  :custom
  (whitespace-action '(auto-cleanup))
  (whitespace-line-column bmw/line-length)
  (whitespace-style
   '(face lines trailing empty tabs spaces indentation space-mark tab-mark))
  :hook
  ((prog-mode text-mode) . bmw/whitespace-mode))
Writegood mode

;; writegood-mode
(use-package writegood-mode
  :ensure t
  :config
  (global-set-key (kbd "C-c g") 'writegood-mode)
  (add-hook 'text-mode-hook 'writegood-mode))
Yaml Mode

(use-package yaml-mode
  :ensure t)
Web Mode

Need more than stock HTML for Hugo templates.

(use-package web-mode
  :ensure t
  :mode (("\\.html?\\'" . web-mode)))
Themes

I bounce between themes a lot.

Startup theme selector

Make some non-standard themes available, then load whatever today’s favorite theme is.

(defadvice load-theme (before clear-previous-themes activate)
  "Clear existing theme settings instead of layering them"
  (mapc #'disable-theme custom-enabled-themes))

(use-package color-theme-modern
  :ensure t
  :config
  (load-theme 'wombat t t)
  (enable-theme 'wombat))
More color themes

With mixed-pitch-mode and friends taking care of font sizing, most theme handling is done with replace-colorthemes, which is in MELPA as “color-theme-modern.”

(use-package color-theme-modern
  :ensure t
  :config
  (load-theme 'wombat t t)
  (enable-theme 'wombat))
Random bits of functionality
Quick-edit config

(global-set-key (kbd "<f5>")
                (lambda ()
                  (interactive)
                  (find-file bmw/org-config-main)
                  (message "Opened: %s" (buffer-name))))
Digraphs

Like Vim digraphs, but for Emacs. I like those. C-c d calls digraph-map, which gets Emacs listening for the next sequence of characters. Via Github user ibarland.

That’s a big source block. Just go to the gist.

I know for sure I’ll be revisiting this until I get it just so for my own tastes. With that in mind, here are some resources for future me.

Timestamps

Generate ISO-style timestamps with UTC offset, mainly for usage in front matter for site content.

(defun bmw/timestamp ()
  (concat
   (format-time-string "%Y-%m-%d %T")
   ((lambda (x) (concat (substring x 0 3) ":" (substring x 3 5)))
    (format-time-string "%z"))))

Awesome WM

Introduction

Every once in a while I get bored of those smooth-running, everything basically works desktops, and I pull out awesomewm. It basically works too, but not after I’m through with it.

My starting rc.lua wasn’t much different from the stock default.rc.lua. I started with the themes that ship with the Ubuntu setup one gets with apt-get install awesomewm. You can find those in /usr/share/awesome/themes.

That chafed pretty quick, though. Awesome WM Copycats is a large theme collection for Awesome.

git clone --recursive https://github.com/lcpz/awesome-copycats.git \
    mv -bv awesome-copycats/* ~/.config/awesome && rm -rf awesome-copycats

Course, it also needs a different rc.lua to drive it. So I scrapped my old config and used rc.lua.template from Awesome WM Copycats as my new starter.

Main rc.lua file

<<awesome-required-libraries>>
<<awesome-error-handling>>
<<awesome-autostart-windowless-processes>>
<<awesome-variable-definitions>>
<<awesome-menu>>
<<awesome-screen>>
<<awesome-mouse-bindings>>
<<awesome-key-bindings>>
<<awesome-rules>>
<<awesome-signals>>

Tangle fragments

Required libraries
awesome API index
a reference for all the core awesome libraries
  • gears
  • awful
  • beautiful
  • naughty
  • menubar
Awesome-Freedesktop
Freedesktop menu and desktop icon support for Awesome WM 4.x
Lain
layouts, widgets, and utilities for Awesome WM 4.x

local awesome, client, mouse, screen, tag = awesome, client, mouse, screen, tag
local ipairs, string, os, table, tostring, tonumber, type = ipairs, string, os, table, tostring, tonumber, type

local gears         = require("gears")
local awful         = require("awful")
require("awful.autofocus")
local wibox         = require("wibox")
local beautiful     = require("beautiful")
local naughty       = require("naughty")
local lain          = require("lain")
--local menubar       = require("menubar")
local freedesktop   = require("freedesktop")
local hotkeys_popup = require("awful.hotkeys_popup").widget
require("awful.hotkeys_popup.keys")
local my_table      = awful.util.table or gears.table -- 4.{0,1} compatibility
local dpi           = require("beautiful.xresources").apply_dpi
Error handling

Check if awesome encountered an error during startup and fell back to another config (This code will only ever execute for the fallback config)

if awesome.startup_errors then
  naughty.notify({ preset = naughty.config.presets.critical,
                   title = "Oops, there were errors during startup!",
                   text = awesome.startup_errors })
end

-- Handle runtime errors after startup
do
  local in_error = false
  awesome.connect_signal(
    "debug::error",
    function (err)
      if in_error then return end

      in_error = true
      naughty.notify({ preset = naughty.config.presets.critical,
                       title = "Oops, an error happened!",
                       text = tostring(err) })
      in_error = false
  end)
end
Autostart windowless processes

In order to make these work I installed rxvt-unicode, which provides the urxvtd daemon.

sudo apt install rxvt-unicode

-- This function will run once every time Awesome is started
local function run_once(cmd_arr)
  for _, cmd in ipairs(cmd_arr) do
    awful.spawn.with_shell(string.format("pgrep -u $USER -fx '%s' > /dev/null || (%s)", cmd, cmd))
  end
end

run_once({ "urxvtd", "unclutter -root" }) -- entries must be separated by commas

-- This function implements the XDG autostart specification
--[[
awful.spawn.with_shell(
  'if (xrdb -query | grep -q "^awesome\\.started:\\s*true$"); then exit; fi;' ..
  'xrdb -merge <<< "awesome.started:true";' ..
  -- list each of your autostart commands, followed by ; inside single quotes, followed by ..
  'dex --environment Awesome --autostart --search-paths --"$XDG_CONFIG_DIRS/autostart:$XDG_CONFIG_HOME/autostart"'
-- see https://github.com/jceb/dex
)
--]]
Variable definitions

I found slock in suckless-tools.

local themes = {
  "blackburn",       -- 1
  "copland",         -- 2
  "dremora",         -- 3
  "holo",            -- 4
  "multicolor",      -- 5
  "powerarrow",      -- 6
  "powerarrow-dark", -- 7
  "rainbow",         -- 8
  "steamburn",       -- 9
  "vertex",          -- 10
}

local chosen_theme = themes[5]
local modkey       = "Mod4"
local altkey       = "Mod1"
local shiftkey     = "Shift"
local controlkey   = "Control"
local terminal     = "gnome-terminal"
-- vi-like client focus
-- https://github.com/lcpz/awesome-copycats/issues/275
local vi_focus     = false
-- cycle through all previous client or just the first
-- https://github.com/lcpz/awesome-copycats/issues/274
local cycle_prev   = true
local editor       = os.getenv("EDITOR") or "vim"
local gui_editor   = os.getenv("GUI_EDITOR") or "gvim"
local browser      = os.getenv("BROWSER") or "firefox"
local scrlocker    = "slock"

awful.util.terminal = terminal
awful.util.tagnames = { "1", "2", "3", "4", "5" }
awful.layout.layouts = {
  awful.layout.suit.floating,
  awful.layout.suit.tile,
  awful.layout.suit.tile.left,
  awful.layout.suit.tile.bottom,
  awful.layout.suit.tile.top,
  awful.layout.suit.fair,
  awful.layout.suit.magnifier,
  awful.layout.suit.corner.sw,
  awful.layout.suit.corner.se,
  lain.layout.cascade,
  lain.layout.cascade.tile,
  lain.layout.centerwork,
  lain.layout.termfair,
  lain.layout.termfair.center,
}

awful.util.taglist_buttons = my_table.join(
  awful.button({ }, 1, function(t) t:view_only() end),
  awful.button({ modkey }, 1, function(t)
      if client.focus then
        client.focus:move_to_tag(t)
      end
  end),
  awful.button({ }, 3, awful.tag.viewtoggle),
  awful.button({ modkey }, 3, function(t)
      if client.focus then
        client.focus:toggle_tag(t)
      end
  end),
  awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end),
  awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end)
)

awful.util.tasklist_buttons = my_table.join(
  awful.button({ }, 1, function (c)
      if c == client.focus then
        c.minimized = true
      else
        --c:emit_signal("request::activate", "tasklist", {raise = true})<Paste>

        -- Without this, the following
        -- :isvisible() makes no sense
        c.minimized = false
        if not c:isvisible() and c.first_tag then
          c.first_tag:view_only()
        end
        -- This will also un-minimize
        -- the client, if needed
        client.focus = c
        c:raise()
      end
  end),
  awful.button({ }, 2, function (c) c:kill() end),
  awful.button({ }, 3, function ()
      local instance = nil

      return function ()
        if instance and instance.wibox.visible then
          instance:hide()
          instance = nil
        else
          instance = awful.menu.clients({theme = {width = dpi(250)}})
        end
      end
  end),
  awful.button({ }, 4, function () awful.client.focus.byidx(1) end),
  awful.button({ }, 5, function () awful.client.focus.byidx(-1) end)
)

lain.layout.termfair.nmaster           = 3
lain.layout.termfair.ncol              = 1
lain.layout.termfair.center.nmaster    = 3
lain.layout.termfair.center.ncol       = 1
lain.layout.cascade.tile.offset_x      = dpi(2)
lain.layout.cascade.tile.offset_y      = dpi(32)
lain.layout.cascade.tile.extra_padding = dpi(5)
lain.layout.cascade.tile.nmaster       = 5
lain.layout.cascade.tile.ncol          = 2

beautiful.init(
  string.format("%s/.config/awesome/themes/%s/theme.lua", os.getenv("HOME"), chosen_theme)
)

Better change that “edit config” menu option so it’s launching Emacs with this file. Sure, at some point I should be launching emacsclient, but one step at a time.

local myawesomemenu = {
  { "hotkeys", function() return false, hotkeys_popup.show_help end },
  { "manual", terminal .. " -e man awesome" },
  { "edit config", "emacs ~/org/config/awesomewm.org" },
  { "restart", awesome.restart },
  { "quit", function() awesome.quit() end }
}
awful.util.mymainmenu = freedesktop.menu.build({
    icon_size = beautiful.menu_height or dpi(16),
    before = {
      { "Awesome", myawesomemenu, beautiful.awesome_icon },
      -- other triads can be put here
    },
    after = {
      { "Open terminal", terminal },
      -- other triads can be put here
    }
})
Screen

-- Re-set wallpaper when a screen's geometry changes (e.g. different resolution)
screen.connect_signal(
  "property::geometry",
  function(s)
    -- Wallpaper
    if beautiful.wallpaper then
      local wallpaper = beautiful.wallpaper
      -- If wallpaper is a function, call it with the screen
      if type(wallpaper) == "function" then
        wallpaper = wallpaper(s)
      end
      gears.wallpaper.maximized(wallpaper, s, true)
    end
end)

-- No borders when rearranging only 1 non-floating or maximized client
screen.connect_signal(
  "arrange",
  function (s)
    local only_one = #s.tiled_clients == 1
    for _, c in pairs(s.clients) do
      if only_one and not c.floating or c.maximized then
        c.border_width = 0
      else
        c.border_width = beautiful.border_width
      end
    end
end)

-- Create a wibox for each screen and add it
awful.screen.connect_for_each_screen(function(s) beautiful.at_screen_connect(s) end)
Mouse bindings

root.buttons(
  my_table.join(
    awful.button({ }, 3, function () awful.util.mymainmenu:toggle() end),
    awful.button({ }, 4, awful.tag.viewnext),
    awful.button({ }, 5, awful.tag.viewprev)
))
Key bindings

This chunk is big. It’s really really big. I broke it down into smaller chunks and subchunks. That doesn’t make it smaller, but it does make things easier to chew.

Global keys

Key bindings in effect at all times. I pick and choose from the available awesome-keys-global-* chunks.

globalkeys = my_table.join(
  <<awesome-keys-global-screenshot>>
  <<awesome-keys-global-lock>>
  <<awesome-keys-global-hotkeys>>
  <<awesome-keys-global-tags-browse-all>>
  <<awesome-keys-global-tags-browse-nonempty>>
  <<awesome-keys-global-focus-byindex>>
  <<awesome-keys-global-layout>>
  <<awesome-keys-global-wibox>>
  <<awesome-keys-global-tagging>>
  <<awesome-keys-global-standard>>
  <<awesome-keys-global-dropdown>>
  <<awesome-keys-global-widgets>>
  <<awesome-keys-global-volume-alsa>>
  <<awesome-keys-global-media-mpd>>
  <<awesome-keys-global-clipboard>>
  <<awesome-keys-global-user>>
  <<awesome-keys-global-prompt>>
)
  • Take a screenshot

    Building on the example screenshot script at https://github.com/lcpz/dots/blob/master/bin/screenshot.

    The screenshot script asks scrot to take a screenshot of the current root window, saving it to a timestamped file. Guaranteed to be more screenshot than I need 98% of the time, but it will do until I get the hang of things.

    timestamp="$(date +%Y-%m-%dT%H-%M-%S)"
    screenshot_file="screenshot-$timestamp.png"
    screenshot_dir="$HOME/Pictures"
    
    [ -d $screenshot_dir ] || exit 1
    
    scrot $screenshot_dir/$screenshot_file
    

    awful.key({ modkey }, "Print", function() os.execute("screenshot") end,
      {description = "take a screenshot", group = "hotkeys"}),
    
  • X screen locker

    awful.key({ modkey, controlkey }, "l", function () os.execute(scrlocker) end,
      {description = "lock screen", group = "hotkeys"}),
    
  • Hotkeys popup

    awful.key({ modkey,           }, "s",      hotkeys_popup.show_help,
      {description = "show help", group="awesome"}),
    
  • Tag browsing

    awful.key({ modkey,           }, "Left",   awful.tag.viewprev,
      {description = "view previous", group = "tag"}),
    awful.key({ modkey,           }, "Right",  awful.tag.viewnext,
      {description = "view next", group = "tag"}),
    awful.key({ modkey,           }, "Escape", awful.tag.history.restore,
      {description = "go back", group = "tag"}),
    
  • Non-empty tag browsing

    awful.key({ modkey, shiftkey }, "Left", function () lain.util.tag_view_nonempty(-1) end,
      {description = "view  previous nonempty", group = "tag"}),
    awful.key({ modkey, shiftkey }, "Right", function () lain.util.tag_view_nonempty(1) end,
      {description = "view  previous nonempty", group = "tag"}),
    
  • Default client focus

    awful.key({ modkey, }, "j",
      function ()
        awful.client.focus.byidx( 1)
      end,
      {description = "focus next by index", group = "client"}
    ),
    awful.key({ modkey, }, "k",
      function ()
        awful.client.focus.byidx(-1)
      end,
      {description = "focus previous by index", group = "client"}
    ),
    
  • By direction client focus

    awful.key({ modkey }, "j",
      function()
        awful.client.focus.global_bydirection("down")
        if client.focus then client.focus:raise() end
      end,
      {description = "focus down", group = "client"}),
    awful.key({ modkey }, "k",
      function()
        awful.client.focus.global_bydirection("up")
        if client.focus then client.focus:raise() end
      end,
      {description = "focus up", group = "client"}),
    awful.key({ modkey }, "h",
      function()
        awful.client.focus.global_bydirection("left")
        if client.focus then client.focus:raise() end
      end,
      {description = "focus left", group = "client"}),
    awful.key({ modkey }, "l",
      function()
        awful.client.focus.global_bydirection("right")
        if client.focus then client.focus:raise() end
      end,
      {description = "focus right", group = "client"}),
    
  • Layout manipulation

    awful.key({ modkey, "Shift"   }, "j", function () awful.client.swap.byidx(  1)    end,
      {description = "swap with next client by index", group = "client"}),
    awful.key({ modkey, "Shift"   }, "k", function () awful.client.swap.byidx( -1)    end,
      {description = "swap with previous client by index", group = "client"}),
    awful.key({ modkey, "Control" }, "j", function () awful.screen.focus_relative( 1) end,
      {description = "focus the next screen", group = "screen"}),
    awful.key({ modkey, "Control" }, "k", function () awful.screen.focus_relative(-1) end,
      {description = "focus the previous screen", group = "screen"}),
    awful.key({ modkey,           }, "u", awful.client.urgent.jumpto,
      {description = "jump to urgent client", group = "client"}),
    awful.key({ modkey,           }, "Tab",
      function ()
        if cycle_prev then
          awful.client.focus.history.previous()
        else
          awful.client.focus.byidx(-1)
        end
        if client.focus then
          client.focus:raise()
        end
      end,
      {description = "cycle with previous/go back", group = "client"}),
    awful.key({ modkey, "Shift"   }, "Tab",
      function ()
        if cycle_prev then
          awful.client.focus.byidx(1)
          if client.focus then
            client.focus:raise()
          end
        end
      end,
      {description = "go forth", group = "client"}),
    
  • Show / Hide Wibox

    awful.key({ modkey }, "b", function ()
        for s in screen do
          s.mywibox.visible = not s.mywibox.visible
          if s.mybottomwibox then
            s.mybottomwibox.visible = not s.mybottomwibox.visible
          end
        end
      end,
      {description = "toggle wibox", group = "awesome"}),
    
  • Dynamic tagging

    awful.key({ modkey, "Shift" }, "n", function () lain.util.add_tag() end,
      {description = "add new tag", group = "tag"}),
    awful.key({ modkey, "Shift" }, "r", function () lain.util.rename_tag() end,
      {description = "rename tag", group = "tag"}),
    awful.key({ modkey, "Shift" }, "Left", function () lain.util.move_tag(-1) end,
      {description = "move tag to the left", group = "tag"}),
    awful.key({ modkey, "Shift" }, "Right", function () lain.util.move_tag(1) end,
      {description = "move tag to the right", group = "tag"}),
    awful.key({ modkey, "Shift" }, "d", function () lain.util.delete_tag() end,
      {description = "delete tag", group = "tag"}),
    
  • Standard program

    awful.key({ modkey,           }, "Return", function () awful.spawn(terminal) end,
      {description = "open a terminal", group = "launcher"}),
    awful.key({ modkey, "Control" }, "r", awesome.restart,
      {description = "reload awesome", group = "awesome"}),
    awful.key({ modkey, "Shift"   }, "q", awesome.quit,
      {description = "quit awesome", group = "awesome"}),
    
    awful.key({ altkey, "Shift"   }, "l",     function () awful.tag.incmwfact( 0.05)          end,
      {description = "increase master width factor", group = "layout"}),
    awful.key({ altkey, "Shift"   }, "h",     function () awful.tag.incmwfact(-0.05)          end,
      {description = "decrease master width factor", group = "layout"}),
    awful.key({ modkey, "Shift"   }, "h",     function () awful.tag.incnmaster( 1, nil, true) end,
      {description = "increase the number of master clients", group = "layout"}),
    awful.key({ modkey, "Shift"   }, "l",     function () awful.tag.incnmaster(-1, nil, true) end,
      {description = "decrease the number of master clients", group = "layout"}),
    awful.key({ modkey, "Control" }, "h",     function () awful.tag.incncol( 1, nil, true)    end,
      {description = "increase the number of columns", group = "layout"}),
    awful.key({ modkey, "Control" }, "l",     function () awful.tag.incncol(-1, nil, true)    end,
      {description = "decrease the number of columns", group = "layout"}),
    awful.key({ modkey,           }, "space", function () awful.layout.inc( 1)                end,
      {description = "select next", group = "layout"}),
    awful.key({ modkey, "Shift"   }, "space", function () awful.layout.inc(-1)                end,
      {description = "select previous", group = "layout"}),
    
    awful.key({ modkey, "Control" }, "n",
      function ()
        local c = awful.client.restore()
        -- Focus restored client
        if c then
          client.focus = c
          c:raise()
        end
      end,
      {description = "restore minimized", group = "client"}),
    
    awful.key({ modkey,           }, "w", function () awful.util.mymainmenu:show() end,
      {description = "show main menu", group = "awesome"}),
    
  • Dropdown application

    awful.key({ modkey, }, "z", function () awful.screen.focused().quake:toggle() end,
      {description = "dropdown application", group = "launcher"}),
    
  • Widgets popups

    awful.key({ altkey, }, "c", function () if beautiful.cal then beautiful.cal.show(7) end end,
      {description = "show calendar", group = "widgets"}),
    awful.key({ altkey, }, "h", function () if beautiful.fs then beautiful.fs.show(7) end end,
      {description = "show filesystem", group = "widgets"}),
    awful.key({ altkey, }, "w",
      function () if beautiful.weather then beautiful.weather.show(7) end end,
      {description = "show weather", group = "widgets"}),
    
  • Brightness

    I might find this more useful if any of my monitors had a backlight property.

    awful.key({ }, "XF86MonBrightnessUp", function () os.execute("xbacklight -inc 10") end,
      {description = "+10%", group = "hotkeys"}),
    awful.key({ }, "XF86MonBrightnessDown", function () os.execute("xbacklight -dec 10") end,
      {description = "-10%", group = "hotkeys"}),
    
  • ALSA volume control

    awful.key({ altkey }, "Up",
      function ()
        os.execute(string.format("amixer -q set %s 1%%+", beautiful.volume.channel))
        beautiful.volume.update()
      end,
      {description = "volume up", group = "hotkeys"}),
    awful.key({ altkey }, "Down",
      function ()
        os.execute(string.format("amixer -q set %s 1%%-", beautiful.volume.channel))
        beautiful.volume.update()
      end,
      {description = "volume down", group = "hotkeys"}),
    awful.key({ altkey }, "m",
      function ()
        os.execute(
          string.format(
            "amixer -q set %s toggle",
            beautiful.volume.togglechannel or beautiful.volume.channel
        ))
        beautiful.volume.update()
      end,
      {description = "toggle mute", group = "hotkeys"}),
    awful.key({ altkey, "Control" }, "m",
      function ()
        os.execute(string.format("amixer -q set %s 100%%", beautiful.volume.channel))
        beautiful.volume.update()
      end,
      {description = "volume 100%", group = "hotkeys"}),
    awful.key({ altkey, "Control" }, "0",
      function ()
        os.execute(string.format("amixer -q set %s 0%%", beautiful.volume.channel))
        beautiful.volume.update()
      end,
      {description = "volume 0%", group = "hotkeys"}),
    
  • MPD control

    awful.key({ altkey, "Control" }, "Up",
      function ()
        os.execute("mpc toggle")
        beautiful.mpd.update()
      end,
      {description = "mpc toggle", group = "widgets"}),
    awful.key({ altkey, "Control" }, "Down",
      function ()
        os.execute("mpc stop")
        beautiful.mpd.update()
      end,
      {description = "mpc stop", group = "widgets"}),
    awful.key({ altkey, "Control" }, "Left",
      function ()
        os.execute("mpc prev")
        beautiful.mpd.update()
      end,
      {description = "mpc prev", group = "widgets"}),
    awful.key({ altkey, "Control" }, "Right",
      function ()
        os.execute("mpc next")
        beautiful.mpd.update()
      end,
      {description = "mpc next", group = "widgets"}),
    awful.key({ altkey }, "0",
      function ()
        local common = { text = "MPD widget ", position = "top_middle", timeout = 2 }
        if beautiful.mpd.timer.started then
          beautiful.mpd.timer:stop()
          common.text = common.text .. lain.util.markup.bold("OFF")
        else
          beautiful.mpd.timer:start()
          common.text = common.text .. lain.util.markup.bold("ON")
        end
        naughty.notify(common)
      end,
      {description = "mpc on/off", group = "widgets"}),
    
  • Clipboard

    Transferring terminal clipboard to and from the GTK clipboard.

    awful.key({ modkey }, "c", function () awful.spawn.with_shell("xsel | xsel -i -b") end,
      {description = "copy terminal to gtk", group = "hotkeys"}),
    awful.key({ modkey }, "v", function () awful.spawn.with_shell("xsel -b | xsel") end,
        {description = "copy gtk to terminal", group = "hotkeys"}),
    
  • User programs

    awful.key({ modkey }, "q", function () awful.spawn(browser) end,
      {description = "run browser", group = "launcher"}),
    awful.key({ modkey }, "a", function () awful.spawn(gui_editor) end,
      {description = "run gui editor", group = "launcher"}),
    
  • Prompt

    awful.key({ modkey }, "r", function () awful.screen.focused().mypromptbox:run() end,
      {description = "run prompt", group = "launcher"}),
    
    awful.key({ modkey }, "x",
      function ()
        awful.prompt.run {
          prompt       = "Run Lua code: ",
          textbox      = awful.screen.focused().mypromptbox.widget,
          exe_callback = awful.util.eval,
          history_path = awful.util.get_cache_dir() .. "/history_eval"
        }
      end,
      {description = "lua execute prompt", group = "awesome"})
    
  • Default

    Disabled in rc.lua.template. I uncommented the code bits but haven’t included it in my menu definition.

    -- Menubar
    awful.key({ modkey }, "p", function() menubar.show() end,
      {description = "show the menubar", group = "launcher"})
    
    -- dmenu
    awful.key({ modkey }, "x", function ()
        os.execute(
          string.format(
            "dmenu_run -i -fn 'Monospace' -nb '%s' -nf '%s' -sb '%s' -sf '%s'",
            beautiful.bg_normal, beautiful.fg_normal, beautiful.bg_focus, beautiful.fg_focus
          )
        )
      end,
      {description = "show dmenu", group = "launcher"})
    
    -- alternatively use rofi, a dmenu-like application with more features
    -- check https://github.com/DaveDavenport/rofi for more details
    -- rofi
    awful.key({ modkey }, "x", function ()
        os.execute(
          string.format("rofi -show %s -theme %s", 'run', 'dmenu')
        )
      end,
      {description = "show rofi", group = "launcher"}),
    
Client keys

Keys for managing the client — whatever has focus right now.

clientkeys = my_table.join(
  awful.key({ altkey, "Shift"   }, "m",      lain.util.magnify_client,
    {description = "magnify client", group = "client"}),
  awful.key({ modkey,           }, "f",
    function (c)
      c.fullscreen = not c.fullscreen
      c:raise()
    end,
    {description = "toggle fullscreen", group = "client"}),
  awful.key({ modkey, "Shift"   }, "c",      function (c) c:kill()                         end,
    {description = "close", group = "client"}),
  awful.key({ modkey, "Control" }, "space",  awful.client.floating.toggle                     ,
    {description = "toggle floating", group = "client"}),
  awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end,
    {description = "move to master", group = "client"}),
  awful.key({ modkey,           }, "o",      function (c) c:move_to_screen()               end,
    {description = "move to screen", group = "client"}),
  awful.key({ modkey,           }, "t",      function (c) c.ontop = not c.ontop            end,
    {description = "toggle keep on top", group = "client"}),
  awful.key({ modkey,           }, "n",
    function (c)
      -- The client currently has the input focus, so it cannot be
      -- minimized, since minimized clients can't have the focus.
      c.minimized = true
    end ,
    {description = "minimize", group = "client"}),
  awful.key({ modkey,           }, "m",
    function (c)
      c.maximized = not c.maximized
      c:raise()
    end ,
    {description = "maximize", group = "client"})
)
Tag keys

Key bindings to manage your tags. That’s what AwesomeWM calls its workspaces.

Bind all key numbers to tags. Be careful: we use keycodes to make it works on any keyboard layout. This should map on the top row of your keyboard, usually 1 to 9.

for i = 1, 9 do
  -- Hack to only show tags 1 and 9 in the shortcut window (mod+s)
  local descr_view, descr_toggle, descr_move, descr_toggle_focus
  if i == 1 or i == 9 then
    descr_view = {description = "view tag #", group = "tag"}
    descr_toggle = {description = "toggle tag #", group = "tag"}
    descr_move = {description = "move focused client to tag #", group = "tag"}
    descr_toggle_focus = {description = "toggle focused client on tag #", group = "tag"}
  end
  globalkeys = my_table.join(
    globalkeys,
    -- View tag only.
    awful.key({ modkey }, "#" .. i + 9,
      function ()
        local screen = awful.screen.focused()
        local tag = screen.tags[i]
        if tag then
          tag:view_only()
        end
      end,
      descr_view),
    -- Toggle tag display.
    awful.key({ modkey, "Control" }, "#" .. i + 9,
      function ()
        local screen = awful.screen.focused()
        local tag = screen.tags[i]
        if tag then
          awful.tag.viewtoggle(tag)
        end
      end,
      descr_toggle),
    -- Move client to tag.
    awful.key({ modkey, "Shift" }, "#" .. i + 9,
      function ()
        if client.focus then
          local tag = client.focus.screen.tags[i]
          if tag then
            client.focus:move_to_tag(tag)
          end
        end
      end,
      descr_move),
    -- Toggle tag on focused client.
    awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9,
      function ()
        if client.focus then
          local tag = client.focus.screen.tags[i]
          if tag then
            client.focus:toggle_tag(tag)
          end
        end
      end,
      descr_toggle_focus)
  )
end
Client buttons

Use the keyboard to send mouse clicks.

clientbuttons = gears.table.join(
  awful.button({ }, 1, function (c)
      c:emit_signal("request::activate", "mouse_click", {raise = true})
  end),
  awful.button({ modkey }, 1, function (c)
      c:emit_signal("request::activate", "mouse_click", {raise = true})
      awful.mouse.client.move(c)
  end),
  awful.button({ modkey }, 3, function (c)
      c:emit_signal("request::activate", "mouse_click", {raise = true})
      awful.mouse.client.resize(c)
  end)
)
Putting it all together

<<awesome-keys-global>>
<<awesome-keys-client>>
<<awesome-keys-tags>>
<<awesome-keys-clientbuttons>>

-- Set keys
root.keys(globalkeys)
Rules

-- Rules to apply to new clients (through the "manage" signal).
awful.rules.rules = {
  -- All clients will match this rule.
  { rule = { },
    properties = {
      border_width = beautiful.border_width,
      border_color = beautiful.border_normal,
      focus = awful.client.focus.filter,
      raise = true,
      keys = clientkeys,
      buttons = clientbuttons,
      screen = awful.screen.preferred,
      placement = awful.placement.no_overlap+awful.placement.no_offscreen,
      size_hints_honor = false
    }
  },

  -- Titlebars
  { rule_any = { type = { "dialog", "normal" } },
    properties = { titlebars_enabled = true } },

  { rule = { class = "Emacs" },
    properties = { screen = 2, tag = awful.util.tagnames[1] } },

  -- Set Firefox to always map on the first tag on screen 1.
  { rule = { class = "Firefox" },
    properties = { screen = 1, tag = awful.util.tagnames[1] } },

  { rule = { class = "Gimp", role = "gimp-image-window" },
    properties = { maximized = true } },
}
Signals

-- Signal function to execute when a new client appears.
client.connect_signal(
  "manage",
  function (c)
    -- Set the windows at the slave,
    -- i.e. put it at the end of others instead of setting it master.
    -- if not awesome.startup then awful.client.setslave(c) end

    if awesome.startup and
      not c.size_hints.user_position
      and not c.size_hints.program_position then
      -- Prevent clients from being unreachable after screen count changes.
      awful.placement.no_offscreen(c)
    end
end)

-- Add a titlebar if titlebars_enabled is set to true in the rules.
client.connect_signal(
  "request::titlebars",
  function(c)
    -- Custom
    if beautiful.titlebar_fun then
      beautiful.titlebar_fun(c)
      return
    end

    -- Default
    -- buttons for the titlebar
    local buttons = my_table.join(
      awful.button({ }, 1, function()
          c:emit_signal("request::activate", "titlebar", {raise = true})
          awful.mouse.client.move(c)
      end),
      awful.button({ }, 2, function() c:kill() end),
      awful.button({ }, 3, function()
          c:emit_signal("request::activate", "titlebar", {raise = true})
          awful.mouse.client.resize(c)
      end)
    )

    awful.titlebar(c, {size = dpi(16)}) : setup {
      { -- Left
        awful.titlebar.widget.iconwidget(c),
        buttons = buttons,
        layout  = wibox.layout.fixed.horizontal
      },
      { -- Middle
        { -- Title
          align  = "center",
          widget = awful.titlebar.widget.titlewidget(c)
        },
        buttons = buttons,
        layout  = wibox.layout.flex.horizontal
      },
      { -- Right
        awful.titlebar.widget.floatingbutton (c),
        awful.titlebar.widget.maximizedbutton(c),
        awful.titlebar.widget.stickybutton   (c),
        awful.titlebar.widget.ontopbutton    (c),
        awful.titlebar.widget.closebutton    (c),
        layout = wibox.layout.fixed.horizontal()
      },
      layout = wibox.layout.align.horizontal
                                                }
end)

-- Enable sloppy focus, so that focus follows mouse.
client.connect_signal(
  "mouse::enter",
  function(c)
    c:emit_signal("request::activate", "mouse_enter", {raise = vi_focus})
  end
)

client.connect_signal("focus", function(c) c.border_color = beautiful.border_focus end)
client.connect_signal("unfocus", function(c) c.border_color = beautiful.border_normal end)

-- possible workaround for tag preservation when switching back to default screen:
-- https://github.com/lcpz/awesome-copycats/issues/251

autorun

#!/usr/bin/env bash

# stricter bash
#  see http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail
IFS=$'\n\t'

function run {
  if ! pgrep -f $1 ;
  then
    $@&
  fi
}

run dropbox
#run setxkbmap -option compose:ralt
run xrandr --output DisplayPort-1 --mode 2560x1440 --rate 59.95
if [ -f "~/.Xmodmap" ]; then
    xmodmap ~/.Xmodmap
fi

#run xcape -e 'Control_L=Escape'

Resources