summaryrefslogtreecommitdiff
path: root/kolwynia
diff options
context:
space:
mode:
authorbdunahu <bdunahu@operationnull.com>2026-01-04 13:13:39 -0700
committerbdunahu <bdunahu@operationnull.com>2026-01-04 13:13:39 -0700
commitc2b706ff2f3aa42d58a03febad1d1b8f8d5a1142 (patch)
treed6786f531f02717472abdc992cc6c6ef81e660b3 /kolwynia
parentc4ca05231236c7e9bdf5304275eadde954acf588 (diff)
remove unnecessary nested guix dir
Diffstat (limited to 'kolwynia')
-rw-r--r--kolwynia/home/bdunahu.scm175
-rw-r--r--kolwynia/home/bdunahu/app-preferences.scm219
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/early-init.el38
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/gnus.el65
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/images/raven.pngbin0 -> 134324 bytes
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/images/wolf.pngbin0 -> 117636 bytes
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/init.el127
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/libraries/copyright.el35
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/libraries/exwm-outer-gaps.el77
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/libraries/fill-column.el39
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/libraries/powerthesaurus.el940
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/libraries/selector.el653
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--browse.el146
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--buffer.el32
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--chat.el118
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--devel.el237
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--dictionary.el20
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--emms.el68
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--exwm.el115
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--files.el92
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--gpg.el59
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--image.el22
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--minibuffer.el171
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--modeline.el99
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--notes.el77
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--org.el255
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--project.el18
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--shells.el135
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--tabs.el43
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--themes.el115
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--utility.el160
-rw-r--r--kolwynia/home/bdunahu/files/.config/emacs/modules/bd--window.el59
-rw-r--r--kolwynia/home/bdunahu/files/.config/eww/eww.scss118
-rw-r--r--kolwynia/home/bdunahu/files/.config/eww/eww.yuck18
-rw-r--r--kolwynia/home/bdunahu/files/.config/eww/pollers.yuck14
-rwxr-xr-xkolwynia/home/bdunahu/files/.config/eww/scripts/getvol9
-rwxr-xr-xkolwynia/home/bdunahu/files/.config/eww/scripts/update_workspaces34
-rwxr-xr-xkolwynia/home/bdunahu/files/.config/eww/scripts/update_x_names33
-rw-r--r--kolwynia/home/bdunahu/files/.config/eww/widgets.yuck92
-rw-r--r--kolwynia/home/bdunahu/files/.config/guix/channels.scm9
-rw-r--r--kolwynia/home/bdunahu/files/.config/zathura/zathurarc195
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/bkup-home6
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/fetch-album38
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/guix-gc7
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/mail-string4
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/sort-music101
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/string-weather3
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/t1-string17
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/tag-media44
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/touchpad-defaults6
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/unfreeze8
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/upload43
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/xkeyboard-auto12
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/xkeyboard-layout12
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/xkeyboard-toggle5
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/xrandr-refresh10
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/xrandr-toggle15
-rwxr-xr-xkolwynia/home/bdunahu/files/.local/bin/xxinit6
-rw-r--r--kolwynia/home/bdunahu/files/.xinitrc25
-rw-r--r--kolwynia/home/bdunahu/files/.xkb/keymap/rocket7
-rw-r--r--kolwynia/home/bdunahu/files/.xkb/keymap/standard7
-rw-r--r--kolwynia/home/bdunahu/files/.xkb/symbols/rocket9
-rw-r--r--kolwynia/home/bdunahu/packages.scm221
-rw-r--r--kolwynia/home/bdunahu/ssh.scm43
-rw-r--r--kolwynia/os/garmr.scm61
-rw-r--r--kolwynia/os/garmr/file-systems.scm39
-rw-r--r--kolwynia/os/hel.scm45
-rw-r--r--kolwynia/os/hel/file-systems.scm39
-rw-r--r--kolwynia/os/ymir.scm117
-rw-r--r--kolwynia/os/ymir/packages.scm106
-rw-r--r--kolwynia/os/ymir/users.scm28
-rw-r--r--kolwynia/utils.scm14
72 files changed, 6029 insertions, 0 deletions
diff --git a/kolwynia/home/bdunahu.scm b/kolwynia/home/bdunahu.scm
new file mode 100644
index 0000000..4c60772
--- /dev/null
+++ b/kolwynia/home/bdunahu.scm
@@ -0,0 +1,175 @@
+;;; Copyright © 2025-2026 bdunahu <bdunahu@operationnull.com>
+(define-module (kolwynia home bdunahu)
+ #:use-module (gnu)
+ #:use-module (gnu home services)
+ #:use-module (gnu home services desktop)
+ #:use-module (gnu home services dict)
+ #:use-module (gnu home services dotfiles)
+ #:use-module (gnu home services gnupg)
+ #:use-module (gnu home services mail)
+ #:use-module (gnu home services mcron)
+ #:use-module (gnu home services shells)
+ #:use-module (gnu home services shepherd)
+ #:use-module (gnu home services ssh)
+ #:use-module (gnu home services xdg)
+ #:use-module (gnu packages gnupg) ;pinentry-emacs
+ #:use-module ((kolwynia home bdunahu packages)
+ #:prefix pkgs:)
+ #:use-module ((kolwynia home bdunahu app-preferences)
+ #:prefix app:)
+ #:use-module ((kolwynia home bdunahu ssh)
+ #:prefix ssh-host:)
+ #:use-module ((kolwynia home bdunahu mail) ;gitignored
+ #:prefix mail:)
+ #:use-module (gnu home)
+ #:export (bdunahu))
+
+;;; Commentary:
+;;; Code:
+
+(define bdunahu
+ (home-environment
+ (packages
+ `(,@pkgs:emacs
+ ,@pkgs:browser
+ ,@pkgs:pass
+ ,@pkgs:media
+ ,@pkgs:mail
+ ,@pkgs:tex
+ ,@pkgs:desktop
+ ,@pkgs:pdf
+ ,@pkgs:fonts
+ ,@pkgs:development
+ ,@pkgs:reverse-engineering
+ ,@pkgs:university
+ ,@pkgs:emulators
+ ,@pkgs:games))
+ (services
+ (list (service home-shepherd-service-type)
+ (service home-dicod-service-type)
+ ;; TODO: causes EXWM XELB: Authorization required,
+ ;; but no authorization protocol specified.
+ ;; ,(service home-startx-command-service-type)
+ (service home-dotfiles-service-type
+ (home-dotfiles-configuration
+ (directories '("./bdunahu/files"))))
+ (service home-gpg-agent-service-type
+ (home-gpg-agent-configuration
+ (pinentry-program
+ (file-append pinentry-emacs "/bin/pinentry-emacs"))
+ (ssh-support? #t)
+ (default-cache-ttl 28800)
+ (max-cache-ttl 28800)
+ (default-cache-ttl-ssh 28800)
+ (max-cache-ttl-ssh 28800)))
+ (service home-xdg-user-directories-service-type
+ (home-xdg-user-directories-configuration
+ (desktop "$HOME/dl")
+ (documents "$HOME/dc")
+ (download "$HOME/dl")
+ (music "$HOME/ik")
+ (pictures "$HOME/md/ig")
+ (publicshare "$HOME")
+ (templates "$HOME")
+ (videos "$HOME/md/vi")))
+ (service home-bash-service-type
+ (home-bash-configuration
+ (aliases
+ '(("aardwolf" . "telnet aardmud.org 23")
+ ("auth" . "echo -e \"$PWD\n\" >> $HOME/.config/guix/shell-authorized-directories")
+ ("cp" . "cp -i")
+ ("grep" . "grep --color=auto")
+ ("guix-rcfg" . "sudo guix system -L ${HOME}/.dotfiles/ reconfigure ${HOME}/.dotfiles/kolwynia/os/${HOSTNAME}.scm")
+ ("ll" . "ls -l")
+ ("ls" . "ls -p --color=auto")
+ ("mkd" . "mkdir -pv")
+ ("mv" . "mv -i")
+ ("rm" . "rm -i")
+ ("suspend" . "sudo loginctl suspend")
+ ("usb0-shell" . "screen /dev/ttyUSB0 115200")
+ ("vi" . "vim")
+ ("xeb" . "emacsclient -nce '(switch-to-buffer nil)'")
+ ("xet" . "emacsclient -nce '(shell)'")
+ ("ytta" . "yt-dlp --extract-audio --format bestaudio/best")
+ ("yttv" . "yt-dlp -f b")))
+
+ (bashrc (list (plain-file "bashrc" "
+# converts above aliases into eshell aliases automatically
+alias | sed -E \"s/^alias ([^=]+)='(.*)'$/alias \\1 \\2 \\$*/g; s/'\\\\\\''/'/g;\" >~/.config/emacs/eshell/alias
+")))
+ (environment-variables
+ `(("PATH" . "$HOME/.local/bin:$PATH")
+ ("BROWSER" . ,(car app:default-browser))
+ ("VISUAL" . ,(car app:default-editor))
+ ("EDITOR" . ,(car app:default-editor))
+ ("NETHACKOPTIONS" . "@$XDG_CONFIG_HOME/nethack/config")
+ ("ZATHURA_PLUGINS_PATH" . "$HOME/.guix-home/profile/lib/zathura")))))
+ (service home-openssh-service-type
+ (home-openssh-configuration
+ (hosts
+ (list ssh-host:garmr
+ ssh-host:heimdallr
+ ssh-host:nott
+ ssh-host:surt
+ ssh-host:codeberg))))
+ (service home-mcron-service-type
+ (home-mcron-configuration
+ (jobs
+ (list
+ #~(job
+ ;; twenty minutes
+ (lambda (current-time) (+ current-time (* 20 60)))
+ "offlineimap")
+ #~(job
+ (lambda (current-time) (+ current-time (* 5 60)))
+ "~/.local/bin/set-t1")))))
+ (service home-xdg-configuration-files-service-type
+ `(("gdbinit"
+ ,(plain-file
+ (plain-file-name %default-gdbinit)
+ (string-append (plain-file-content %default-gdbinit)
+ "set disassembly-flavor intel
+set confirm no
+set history filename ~/.cache/gdb/history
+set history save on
+set history size unlimited")))))
+ (service home-msmtp-service-type
+ (home-msmtp-configuration
+ (defaults
+ (msmtp-configuration
+ (port 587)
+ (tls? #t)
+ (tls-starttls? #t)
+ (log-file "/home/bdunahu/.local/state/msmtp.log")
+ (auth? #t)))
+ (accounts
+ (list
+ mail:operationnull
+ mail:umass))
+ (default-account "operationnull")))
+ (service home-xdg-mime-applications-service-type
+ (home-xdg-mime-applications-configuration
+ (default app:mime-app-alist)
+ (desktop-entries
+ (list
+ (xdg-desktop-entry
+ (file "torrent")
+ (name "Torrent")
+ (type 'application)
+ (config '((exec . "transmission-remote -a %u"))))))))
+ (simple-service 'xorg-dot
+ home-files-service-type
+ `((".Xresources"
+ ,(plain-file "Xresources" "
+Nsxiv.window.background: #000000
+Nsxiv.window.foreground: #FFFFFF
+Nsxiv.bar.font: MedievalSharp:size=9
+"))
+ (".config/gtk-2.0/settings.ini"
+ ,(plain-file "settings.ini" "[Settings]
+gtk-application-prefer-dark-theme=1\n"))
+ (".config/gtk-3.0/settings.ini"
+ ,(plain-file "settings.ini" "[Settings]
+gtk-application-prefer-dark-theme=1\n"))))))))
+
+;;; bdunahu.scm ends here
diff --git a/kolwynia/home/bdunahu/app-preferences.scm b/kolwynia/home/bdunahu/app-preferences.scm
new file mode 100644
index 0000000..b794f2d
--- /dev/null
+++ b/kolwynia/home/bdunahu/app-preferences.scm
@@ -0,0 +1,219 @@
+;;; Copyright © 2026 bdunahu <bdunahu@operationnull.com>
+(define-module (kolwynia home bdunahu app-preferences)
+ #:export (default-browser
+ default-editor
+ default-media
+ default-image
+ default-pdf
+ default-torrent
+ mime-app-alist))
+
+;;; Commentary:
+;;; the 'torrent' xdg app is defined in bdunahu.scm
+;;; Code:
+
+(define default-browser '("librewolf" . "librewolf.desktop"))
+(define default-editor '("emacsclient" . "emacsclient.desktop"))
+(define default-media '("mpv" . "mpv.desktop"))
+(define default-image '("nsxiv" . "nsxiv.desktop"))
+(define default-pdf '("zathura" . "zathura.desktop"))
+(define default-torrent '("torrent" . "torrent.desktop"))
+
+(define browser-mime
+ '("application/x-extension-htm"
+ "application/x-extension-xht"
+ "application/x-extension-xhtml"
+ "application/xhtml+xml"
+ "text/html"
+ "x-scheme-handler/http"
+ "x-scheme-handler/https"))
+
+(define editor-mime
+ '("application/json"
+ "application/x-sh"
+ "text/javascript"
+ "text/markdown"
+ "text/plain"
+ "text/troff"
+ "text/x-c"
+ "text/x-c++"
+ "text/x-diff"
+ "text/x-lisp"
+ "text/xml"
+ "text/x-scheme"
+ "text/x-shellscript"
+ "text/x-tex"))
+
+(define file-manager-mime
+ '("inode/directory"))
+
+(define media-mime
+ '("application/mxf"
+ "application/ogg"
+ "application/sdp"
+ "application/smil"
+ "application/streamingmedia"
+ "application/vnd.apple.mpegurl"
+ "application/vnd.ms-asf"
+ "application/vnd.rn-realmedia"
+ "application/vnd.rn-realmedia-vbr"
+ "application/x-cue"
+ "application/x-extension-m4a"
+ "application/x-extension-mp4"
+ "application/x-matroska"
+ "application/x-mpegurl"
+ "application/x-ogg"
+ "application/x-ogm"
+ "application/x-ogm-audio"
+ "application/x-ogm-video"
+ "application/x-shorten"
+ "application/x-smil"
+ "application/x-streamingmedia"
+ "audio/3gpp"
+ "audio/3gpp2"
+ "audio/aac"
+ "audio/ac3"
+ "audio/aiff"
+ "audio/AMR"
+ "audio/amr-wb"
+ "audio/dv"
+ "audio/eac3"
+ "audio/flac"
+ "audio/m3u"
+ "audio/m4a"
+ "audio/mp1"
+ "audio/mp2"
+ "audio/mp3"
+ "audio/mp4"
+ "audio/mpeg"
+ "audio/mpeg2"
+ "audio/mpeg3"
+ "audio/mpegurl"
+ "audio/mpg"
+ "audio/musepack"
+ "audio/ogg"
+ "audio/opus"
+ "audio/rn-mpeg"
+ "audio/scpls"
+ "audio/vnd.dolby.heaac.1"
+ "audio/vnd.dolby.heaac.2"
+ "audio/vnd.dts"
+ "audio/vnd.dts.hd"
+ "audio/vnd.rn-realaudio"
+ "audio/vorbis"
+ "audio/wav"
+ "audio/webm"
+ "audio/x-aac"
+ "audio/x-adpcm"
+ "audio/x-aiff"
+ "audio/x-ape"
+ "audio/x-m4a"
+ "audio/x-matroska"
+ "audio/x-mp1"
+ "audio/x-mp2"
+ "audio/x-mp3"
+ "audio/x-mpegurl"
+ "audio/x-mpg"
+ "audio/x-ms-asf"
+ "audio/x-ms-wma"
+ "audio/x-musepack"
+ "audio/x-pls"
+ "audio/x-pn-au"
+ "audio/x-pn-realaudio"
+ "audio/x-pn-wav"
+ "audio/x-pn-windows-pcm"
+ "audio/x-realaudio"
+ "audio/x-scpls"
+ "audio/x-shorten"
+ "audio/x-tta"
+ "audio/x-vorbis"
+ "audio/x-vorbis+ogg"
+ "audio/x-wav"
+ "audio/x-wavpack"
+ "video/3gp"
+ "video/3gpp"
+ "video/3gpp2"
+ "video/avi"
+ "video/divx"
+ "video/dv"
+ "video/fli"
+ "video/flv"
+ "video/mkv"
+ "video/mp2t"
+ "video/mp4"
+ "video/mp4v-es"
+ "video/mpeg"
+ "video/msvideo"
+ "video/ogg"
+ "video/quicktime"
+ "video/vnd.divx"
+ "video/vnd.mpegurl"
+ "video/vnd.rn-realvideo"
+ "video/webm"
+ "video/x-avi"
+ "video/x-flc"
+ "video/x-flic"
+ "video/x-flv"
+ "video/x-m4v"
+ "video/x-matroska"
+ "video/x-mpeg2"
+ "video/x-mpeg3"
+ "video/x-ms-afs"
+ "video/x-ms-asf"
+ "video/x-msvideo"
+ "video/x-ms-wmv"
+ "video/x-ms-wmx"
+ "video/x-ms-wvxvideo"
+ "video/x-ogm"
+ "video/x-ogm+ogg"
+ "video/x-theora"
+ "video/x-theora+ogg"))
+
+(define image-mime
+ '("image/avif"
+ "image/bmp"
+ "image/gif"
+ "image/heif"
+ "image/jpeg"
+ "image/jpg"
+ "image/pjpeg"
+ "image/png"
+ "image/tiff"
+ "image/x-bmp"
+ "image/x-pcx"
+ "image/x-png"
+ "image/x-portable-anymap"
+ "image/x-portable-bitmap"
+ "image/x-portable-graymap"
+ "image/x-portable-pixmap"
+ "image/x-tga"
+ "image/x-xbitmap"))
+
+(define pdf-mime
+ '("application/pdf"
+ "application/epub+zip"))
+
+(define torrent-mime
+ '("application/x-bittorrent"
+ "x-scheme-handler/magnet"))
+
+(define mime-app-alist
+ (apply append
+ (map (lambda (app types)
+ (map (lambda (e) (cons e (cdr app))) types))
+ `(,default-browser
+ ,default-editor
+ ,default-editor
+ ,default-media
+ ,default-image
+ ,default-pdf
+ ,default-torrent)
+ `(,browser-mime
+ ,editor-mime
+ ,file-manager-mime
+ ,media-mime
+ ,image-mime
+ ,pdf-mime
+ ,torrent-mime))))
+
+;;; app-preferences.scm ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/early-init.el b/kolwynia/home/bdunahu/files/.config/emacs/early-init.el
new file mode 100644
index 0000000..037dc17
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/early-init.el
@@ -0,0 +1,38 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+;;;; display load information + speedups
+(defun bd/display-startup-time ()
+ (message "Emacs loaded in %s with %d garbage collections."
+ (format "%.2f seconds"
+ (float-time
+ (time-subtract after-init-time before-init-time)))
+ gcs-done))
+
+(add-hook 'emacs-startup-hook #'bd/display-startup-time)
+
+(setq frame-inhibit-implied-resize t)
+
+;; reduce the frequency of garbage collection during startup
+;; by making it happen as little as possible
+;; TODO: `most-positive-fixnum' will pressure system memory if used
+;; as a permanent value
+(setopt gc-cons-threshold most-positive-fixnum
+ gc-cons-percentage 0.5)
+
+(add-hook 'emacs-startup-hook
+ (lambda ()
+ (setopt gc-cons-threshold (* 1024 1024 20)
+ gc-cons-percentage 0.2)))
+
+
+;;;; avoid-blinding startup
+(push '(menu-bar-lines . 0) default-frame-alist)
+(push '(tool-bar-lines . 0) default-frame-alist)
+(push '(background-color . "#000000") default-frame-alist)
+(push '(foreground-color . "#ffffff") default-frame-alist)
+
+
+;;; early-init.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/gnus.el b/kolwynia/home/bdunahu/files/.config/emacs/gnus.el
new file mode 100644
index 0000000..8482ec9
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/gnus.el
@@ -0,0 +1,65 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(require 'pgg)
+(require 'gnus)
+(require 'gnus-util)
+
+(keymap-set gnus-article-mode-map "r" #'gnus-summary-very-wide-reply)
+(keymap-set gnus-summary-mode-map "r" #'gnus-summary-very-wide-reply)
+(keymap-set gnus-article-mode-map "R" #'gnus-summary-very-wide-reply-with-original)
+(keymap-set gnus-summary-mode-map "R" #'gnus-summary-very-wide-reply-with-original)
+
+(setopt gnus-dbus-close-on-sleep t
+ gnus-interactive-exit nil
+
+ gnus-check-new-newsgroups nil
+ gnus-large-newsgroup 200
+
+ message-kill-buffer-on-exit t
+ message-interactive t
+
+ message-directory "/home/bdunahu/.ml/"
+
+ message-send-mail-function 'message-send-mail-with-sendmail
+ message-sendmail-f-is-evil 't
+ sendmail-program "/home/bdunahu/.guix-home/profile/bin/msmtp"
+ message-sendmail-extra-arguments '("--read-envelope-from"))
+
+(setopt gnus-select-method '(nnnil ""))
+(setopt gnus-secondary-select-methods
+ '((nntp "news.gwene.org")
+ (nnmaildir "admin"
+ (directory "~/.ml/admin"))
+ (nnmaildir "personal"
+ (directory "~/.ml/personal"))
+ (nnmaildir "rss"
+ (directory "~/.ml/rss"))
+ (nnmaildir "umass"
+ (directory "~/.ml/umass"))))
+
+
+(setopt gnus-face-3 'calendar-today
+ gnus-face-4 'calendar-weekend-header
+ gnus-face-5 'calendar-weekday-header)
+
+(setopt gnus-summary-line-format
+ (concat
+ "%0{%U%R%z%}"
+ "%4{%-11,11&user-date;%}"
+ " "
+ "%2{%-20,20n%}" ;; name
+ " "
+ " "
+ "%5{%B%}"
+ "%s\n"))
+
+(setopt message-from-style 'angles
+ mml-secure-openpgp-encrypt-to-self t
+ mml-secure-openpgp-sign-with-sender t)
+(add-hook 'gnus-message-setup-hook 'mml-secure-message-sign-pgpmime)
+
+
+;;; gnus.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/images/raven.png b/kolwynia/home/bdunahu/files/.config/emacs/images/raven.png
new file mode 100644
index 0000000..6f27c08
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/images/raven.png
Binary files differ
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/images/wolf.png b/kolwynia/home/bdunahu/files/.config/emacs/images/wolf.png
new file mode 100644
index 0000000..fabad74
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/images/wolf.png
Binary files differ
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/init.el b/kolwynia/home/bdunahu/files/.config/emacs/init.el
new file mode 100644
index 0000000..7243419
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/init.el
@@ -0,0 +1,127 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+;;;; auto-generated files
+;; disable the custom file by making it temporary
+(setopt custom-file (make-temp-file "emacs-custom-")
+ tramp-auto-save-directory (expand-file-name "tmp/auto_saves/sessions/" user-emacs-directory)
+ ;; disable lockfiles
+ create-lockfiles nil
+ ;; move auto-saves to tmp folder
+ auto-save-list-file-prefix (expand-file-name "tmp/auto_saves/sessions/" user-emacs-directory)
+ auto-save-file-transforms `((".*" ,(expand-file-name "tmp/auto_saves/" user-emacs-directory) t))
+ ;; move backups to tmp folder
+ backup-directory-alist `(("." . ,(expand-file-name "tmp/backups/" user-emacs-directory))))
+(make-directory (expand-file-name "tmp/auto_saves/" user-emacs-directory) t)
+
+(setenv "PATH" (format "%s:%s" (expand-file-name "~/.local/bin/") (getenv "PATH")))
+
+(keymap-global-unset "C-z")
+(keymap-global-unset "C-x C-z")
+(keymap-global-set "C-x C-b" #'ibuffer)
+(keymap-global-set "C-c SPC" #'pop-to-mark-command)
+
+(mapc
+ (lambda (command)
+ (put command 'disabled nil))
+ '(upcase-region downcase-region))
+
+(setopt warning-minimum-level :error
+
+ inhibit-startup-message t
+ initial-major-mode 'eshell-mode
+ initial-scratch-message nil
+
+ text-scale-mode-step 1.1
+
+ set-mark-command-repeat-pop t
+
+ kill-whole-line t
+ kill-ring-max 1000
+ undo-limit 320000
+
+ sentence-end-double-space nil
+ next-line-add-newlines t
+ scroll-up-aggressively '0.50
+ scroll-down-aggressively '0.50
+ scroll-step 1
+
+ use-short-answers t
+ vc-follow-symlinks t
+ completion-ignore-case t
+ save-place-mode t
+ indent-tabs-mode nil
+ read-buffer-completion-ignore-case t)
+(setq-default tab-width 8)
+(add-to-list 'default-frame-alist '(alpha . (92 . 92)))
+(blink-cursor-mode -1)
+(fringe-mode '(0 . 0))
+(global-visual-line-mode t)
+(menu-bar-mode -1)
+(minibuffer-depth-indicate-mode 1)
+(scroll-bar-mode -1)
+(tool-bar-mode -1)
+(tooltip-mode -1)
+(add-hook #'before-save-hook #'delete-trailing-whitespace)
+
+(keymap-global-set "C-c m" #'gnus)
+(setopt readmail-command 'gnus
+ mail-user-agent 'gnus-user-agent
+
+ gnus-init-file (expand-file-name "gnus" user-emacs-directory)
+ gnus-home-directory (expand-file-name "gnus-home" user-emacs-directory)
+ gnus-directory (expand-file-name "News" gnus-home-directory)
+ mail-source-directory (expand-file-name "Mail" gnus-home-directory)
+
+ gnus-save-newsrc-file nil
+ gnus-read-newsrc-file nil)
+
+
+(if (file-directory-p "~/.guix-profile")
+ ;; packages installed via guix
+ (progn (setopt package-archives nil
+ package-enable-at-startup t)
+ (add-to-list 'load-path (expand-file-name "~/.guix-home/profile/share/emacs/site-lisp"))
+ (require 'guix-emacs)
+ (guix-emacs-autoload-packages)
+ (guix-prettify-global-mode 1)
+ (setopt guix-directory "/home/bdunahu/pt/guix"))
+ (require 'package)
+ (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
+ (package-initialize))
+
+
+;;;; add my modules to load path
+(mapc
+ (lambda (path)
+ (add-to-list 'load-path (locate-user-emacs-file path)))
+ '("modules" "libraries"))
+
+
+;;;; load modules
+(require 'bd--utility)
+(require 'bd--secret) ;; contains bookmarks/email vars---git ignored
+(require 'bd--gpg)
+(require 'bd--tabs)
+(require 'bd--browse)
+(require 'bd--project)
+(require 'bd--files)
+(require 'bd--dictionary)
+(require 'bd--chat)
+(require 'bd--shells)
+(require 'bd--minibuffer)
+(require 'bd--buffer)
+(require 'bd--window)
+(require 'bd--devel)
+(require 'bd--org)
+(require 'bd--notes)
+(require 'bd--emms)
+(require 'bd--modeline)
+(if (not (string= (system-name) "surt"))
+ (require 'bd--exwm))
+(require 'bd--themes)
+
+
+;;; init.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/libraries/copyright.el b/kolwynia/home/bdunahu/files/.config/emacs/libraries/copyright.el
new file mode 100644
index 0000000..112512d
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/libraries/copyright.el
@@ -0,0 +1,35 @@
+;;; copyright.el --- Insert a Guix copyright. -*- lexical-binding: t; -*-
+
+;; Copyright © 2020 Oleg Pykhalov <go.wigust@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Guix is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package provides skeleton to insert a copyright with `guix-copyright'.
+
+;;; Code:
+
+(define-skeleton guix-copyright
+ "Insert a copyright by $USER notice at cursor."
+ "FULL_NAME <MAIL_ADDRESS>: "
+ comment-start
+ ";; Copyright © " `(format-time-string "%Y") " "
+ (or (format "%s <%s>" user-full-name user-mail-address) str)
+ comment-end)
+
+(provide 'copyright)
+;;; copyright.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/libraries/exwm-outer-gaps.el b/kolwynia/home/bdunahu/files/.config/emacs/libraries/exwm-outer-gaps.el
new file mode 100644
index 0000000..c315e8b
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/libraries/exwm-outer-gaps.el
@@ -0,0 +1,77 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+
+;; modified version of https://github.com/lucasgruss/exwm-outer-gaps
+
+;;; Code:
+
+
+(require 'exwm-workspace)
+(require 'exwm-core)
+(require 'exwm)
+(require 'xelb)
+(require 'xcb)
+
+(defgroup exwm-outer-gaps nil
+ "Outer gaps for exwm."
+ :group 'appearance
+ :prefix "exwm-outer-gaps")
+
+(defcustom exwm-outer-gaps-width 15
+ "Width between the edge of the monitor and emacs frame for all sides.")
+
+(defcustom exwm-outer-gaps-increment-step 5
+ "Default increment/decrement value for gaps.")
+
+(defcustom exwm-outer-gaps-max-width
+ (* exwm-outer-gaps-increment-step 20)
+ "The maximum size of the gaps.")
+
+(defun exwm-outer-gaps-compute-gaps ()
+ "Hook to be ran after exwm-workspace--update-workareas-hook"
+ (let (workareas frames)
+ (dolist (w exwm-workspace--workareas)
+ (setf (aref w 3) (+ (aref w 3) exwm-outer-gaps-width)
+ (aref w 4) (+ (aref w 4) exwm-outer-gaps-width)
+ (aref w 5) (- (aref w 5) (* 2 exwm-outer-gaps-width))
+ (aref w 6) (- (aref w 6) (* 2 exwm-outer-gaps-width))))))
+
+(defun exwm-outer-gaps-apply ()
+ "Function used to apply gaps to the emacs frames."
+ (exwm-workspace--update-workareas)
+ (dolist (f exwm-workspace--list)
+ (exwm-workspace--set-fullscreen f)))
+
+(defun exwm-outer-gaps-set (width)
+ "Sets the gap width to WIDTH. Automatically clamps the size of the gaps
+from 0 to `exwm-outer-max-gaps-width'"
+ (setq exwm-outer-gaps-width
+ (max 0 (min width exwm-outer-gaps-max-width))))
+
+(defun exwm-outer-gaps-increment ()
+ "Increment the outer gaps by exwm-outer-gaps-increment-step"
+ (interactive)
+ (when exwm-outer-gaps-mode
+ (exwm-outer-gaps-set (+ exwm-outer-gaps-width exwm-outer-gaps-increment-step))
+ (exwm-outer-gaps-apply)))
+
+(defun exwm-outer-gaps-decrement ()
+ "Decrement the outer gaps by exwm-outer-gaps-increment-step"
+ (interactive)
+ (when exwm-outer-gaps-mode
+ (exwm-outer-gaps-set (- exwm-outer-gaps-width exwm-outer-gaps-increment-step))
+ (exwm-outer-gaps-apply)))
+
+;;;###autoload
+(define-minor-mode exwm-outer-gaps-mode
+ "Add useless outer gaps to exwm."
+ :global t
+ (if exwm-outer-gaps-mode
+ (add-hook 'exwm-workspace--update-workareas-hook
+ #'exwm-outer-gaps-compute-gaps)
+ (remove-hook 'exwm-workspace--update-workareas-hook
+ #'exwm-outer-gaps-compute-gaps))
+ (exwm-outer-gaps-apply))
+
+
+(provide 'exwm-outer-gaps)
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/libraries/fill-column.el b/kolwynia/home/bdunahu/files/.config/emacs/libraries/fill-column.el
new file mode 100644
index 0000000..2a70cd6
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/libraries/fill-column.el
@@ -0,0 +1,39 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+
+;; yanked from https://codeberg.org/daviwil/dotfiles/src/branch/master/emacs
+
+;;; Code:
+
+
+(defvar fill-column-desired-width 120
+ "The desired width of a document centered in the window.")
+
+(defun fill-column--adjust-margins ()
+ "Resets window margins, then calculates the appropriate
+margin given the window width and `fill-column-desired-width'
+if fill-column-mode is t."
+ (set-window-parameter nil 'min-margins nil)
+ (set-window-margins nil nil)
+ (when fill-column-mode
+ (let ((margin-width (max 0
+ (truncate
+ (/ (- (window-width)
+ fill-column-desired-width)
+ 2.0)))))
+ (when (> margin-width 0)
+ (set-window-parameter nil 'min-margins '(0 . 0))
+ (set-window-margins nil margin-width margin-width)))))
+
+(define-minor-mode fill-column-mode
+ "Toggle centered text layout in the current buffer."
+ :lighter " Centered"
+ :group 'editing
+ (if fill-column-mode
+ (add-hook 'window-configuration-change-hook #'fill-column--adjust-margins 'append 'local)
+ (remove-hook 'window-configuration-change-hook #'fill-column--adjust-margins 'local))
+ (fill-column--adjust-margins))
+
+
+(provide 'fill-column)
+;;; fill-column ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/libraries/powerthesaurus.el b/kolwynia/home/bdunahu/files/.config/emacs/libraries/powerthesaurus.el
new file mode 100644
index 0000000..2c76df0
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/libraries/powerthesaurus.el
@@ -0,0 +1,940 @@
+;;; powerthesaurus.el --- Powerthesaurus integration -*- lexical-binding: t; -*-
+
+;; Copyright (c) 2018-2023 Valeriy Savchenko (GNU/GPL Licence)
+
+;; Authors: Valeriy Savchenko <sinmipt@gmail.com>
+;; URL: http://github.com/SavchenkoValeriy/emacs-powerthesaurus
+;; Version: 0.4.1
+;; Package-Requires: ((emacs "26.1") (jeison "1.0.0") (s "1.13.0"))
+;; Keywords: convenience, writing
+
+;; This file is NOT part of GNU Emacs.
+
+;; powerthesaurus.el is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; powerthesaurus.el is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with powerthesaurus.el.
+;; If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; This package is an integration with powerthesaurus.org.
+;;; It helps to look up a word in powerthesaurus and either replace or
+;;; insert selected option in the buffer (depending on the current selection).
+
+;;; Code:
+(require 'eieio)
+(require 'cl-seq)
+(require 'url)
+(require 's)
+(require 'subr-x)
+(require 'jeison)
+
+(defvar powerthesaurus-request-headers
+ '(("Content-Type" . "application/json"))
+ "List of headers included in the request sent to 'powerthesaurus.org'.")
+
+(defvar powerthesaurus-user-agent "Chrome/74.0.3729.169")
+
+(defvar powerthesaurus-synchronous-requests nil
+ "If non-nil, requests send to 'powerthesaurus.org' are synchronous.")
+
+(defvar powerthesaurus-show-tags t
+ "Whether to show word tags during completion.")
+
+(defvar powerthesaurus-show-part-of-speech t
+ "Whether to show word's part of speech during completion.")
+
+(defvar powerthesaurus-show-rating t
+ "Whether to show word's rating during completion.")
+
+(defconst powerthesaurus-supported-query-types
+ (list :synonyms :antonyms :related :definitions :sentences))
+
+(defconst powerthesaurus-api-url "https://api.powerthesaurus.org")
+
+(defface powerthesaurus-definition-part-of-speech
+ '((t :inherit font-lock-keyword-face))
+ "Face of the definition's part of speech."
+ :group 'powerthesaurus)
+
+(defface powerthesaurus-definition-definition
+ '((t :weight bold))
+ "Face of the actual definition part of the definition."
+ :group 'powerthesaurus)
+
+(defface powerthesaurus-definition-usages
+ '((t :inherit font-lock-comment-face))
+ "Face of the usage example for the definition."
+ :group 'powerthesaurus)
+
+(defface powerthesaurus-definition-synonyms
+ '((t :inherit font-lock-type-face))
+ "Face of the definition's synonyms."
+ :group 'powerthesaurus)
+
+(defface powerthesaurus-definition-author
+ '((t :inherit default))
+ "Face of the definition's author."
+ :group 'powerthesaurus)
+
+(defface powerthesaurus-sentence-sentence
+ '((t :weight bold))
+ "Face of the example sentence."
+ :group 'powerthesaurus)
+
+(defface powerthesaurus-sentence-author
+ '((t :inherit default))
+ "Face of the example sentence author."
+ :group 'powerthesaurus)
+
+;;;###autoload
+(defun powerthesaurus-lookup-dwim (&optional action-type query-type)
+ "Wrapper function for general lookup commands.
+
+When called interactively, optional argument ACTION-TYPE corresponds to
+the prefix argument passed to this command, which is translated to an action
+using `powerthesaurus-prefix-to-action'. When called programmatically,
+its value can either be nil or a symbol that can be possibly returned by
+`powerthesaurus-prefix-to-action' (e.g., `action-insert' or `action-display').
+
+The argument passed to QUERY-TYPE should be the same as in
+`powerthesaurus-lookup' or nil; in the latter case,
+the user will be prompt for a valid value."
+ (interactive "P")
+ (pcase-let ((`(,query-term ,beg ,end)
+ ;; selection is active -> look up whatever is selected
+ (if (use-region-p)
+ (powerthesaurus--extract-query-region)
+ ;; point is is at a word -> look it up
+ (if (thing-at-point 'word)
+ (powerthesaurus--extract-original-word)
+ ;; nothing appropriate nearby -> ask the user
+ (list nil nil nil)))))
+ (setq query-term (read-string "Term: " query-term)
+ query-type (or query-type
+ (intern (completing-read "Query type: "
+ powerthesaurus-supported-query-types
+ nil t)))
+ action-type (powerthesaurus-prefix-to-action action-type query-type))
+ (cond
+ ((eq action-type 'action-insert)
+ (when (null beg)
+ (setq beg (point) end (point))))
+ ((eq action-type 'action-display)
+ (when (or beg end)
+ (setq beg nil end nil))))
+ (funcall 'powerthesaurus-lookup query-term query-type beg end)))
+
+;;;###autoload
+(defun powerthesaurus-lookup-synonyms-dwim (&optional action-type)
+ "Wrapper function for synonym lookup.
+ACTION-TYPE accepts the same arguments as in `powerthesaurus-lookup-dwim'."
+ (interactive "P")
+ (powerthesaurus-lookup-dwim action-type :synonyms))
+
+;;;###autoload
+(defun powerthesaurus-lookup-antonyms-dwim (&optional action-type)
+ "Wrapper function for antonym lookup.
+ACTION-TYPE accepts the same arguments as in `powerthesaurus-lookup-dwim'."
+ (interactive "P")
+ (powerthesaurus-lookup-dwim action-type :antonyms))
+
+;;;###autoload
+(defun powerthesaurus-lookup-related-dwim (&optional action-type)
+ "Wrapper function for related lookup.
+ACTION-TYPE accepts the same arguments as in `powerthesaurus-lookup-dwim'."
+ (interactive "P")
+ (powerthesaurus-lookup-dwim action-type :related))
+
+;;;###autoload
+(defun powerthesaurus-lookup-definitions-dwim (&optional action-type)
+ "Wrapper function for definition lookup.
+ACTION-TYPE accepts the same arguments as in `powerthesaurus-lookup-dwim'."
+ (interactive "P")
+ (powerthesaurus-lookup-dwim action-type :definitions))
+
+;;;###autoload
+(defun powerthesaurus-lookup-sentences-dwim (&optional action-type)
+ "Wrapper function for sentence lookup.
+ACTION-TYPE accepts the same arguments as in `powerthesaurus-lookup-dwim'."
+ (interactive "P")
+ (powerthesaurus-lookup-dwim action-type :sentences))
+
+;;;###autoload
+(defun powerthesaurus-lookup (query-term query-type &optional beg end)
+ "Retrieve the given QUERY-TERM's synonyms, antonyms, etc... online.
+
+Argument QUERY-TYPE specifies the type of query and must be an element of
+`powerthesaurus-supported-query-types'.
+QUERY-TERM corresponds to the word/term/sentence to look up.
+
+If specified, BEG and END specify the beginning and end positions of
+the text in the buffer to be replaced by the selected result.
+Particularly, if both BEG and END are both nil, then the results of the queries
+will be displayed on a distinct buffer. If only BEG is specified or
+both BEG and END are the same, then the user will be prompted to select one of
+the results to be inserted at BEG. Finally, if both BEG and END are specified
+and are different, then the user will be prompted to select a result
+which will replace the text between these bounds."
+ (powerthesaurus--query
+ query-term
+ query-type
+ (powerthesaurus--make-callback query-term query-type beg end)))
+
+;; ===============================================================
+;; UX functions implementation
+;; ===============================================================
+
+(defun powerthesaurus-prefix-to-action (uarg qtype)
+ "Map given prefix argument UARG to corresponding action type.
+
+A single universal argument (\\[universal-argument]) indicates that
+the result of the query should be inserted at point,
+potentially replacing the word under it or the selected phrase.
+This corresponds to returning the symbol `action-insert'.
+
+A double universal argument (\\[universal-argument] \\[universal-argument])
+indicates that the results of the query should be displayed on
+a separate buffer without modifying the current one.
+This corresponds to returning the symbol `action-display'.
+
+If no prefix argument is given,
+then the type of the query specified via QTYPE is used for
+determining the action that should be preferred.
+Particularly, if the type is one of 'synonyms', 'antonyms' or 'related',
+then the result defaults to `action-insert'.
+In any other case, it defaults to `action-display'."
+ (cond
+ ((null uarg)
+ (if (member qtype '(:synonyms :antonyms :related))
+ 'action-insert
+ 'action-display))
+ ((memq uarg '(action-insert action-display)) uarg)
+ ((equal uarg '(4)) 'action-insert)
+ ((equal uarg '(16)) 'action-display)
+ (t (error "Unexpected prefix argument"))))
+
+(defun powerthesaurus--extract-original-word (&optional pnt)
+ "Parse the word under point to look up.
+
+If optional argument PNT is not specified,
+default to cursor's current location."
+ (setq pnt (or pnt (point)))
+ (save-mark-and-excursion
+ (goto-char pnt)
+ (unless (looking-at-p "\\<")
+ (backward-word))
+ (let (beg end)
+ (setq beg (point))
+ (forward-word)
+ (setq end (point))
+ (powerthesaurus--extract-query-region beg end))))
+
+(defun powerthesaurus--extract-query-region (&optional beg end)
+ "Parse the phrase in region.
+
+If optional arguments BEG and END are not specified,
+the contents of the current active region are used."
+ (cl-flet ((substring-and-bounds
+ (lambda (beg end)
+ (list (buffer-substring-no-properties beg end)
+ beg end))))
+ ;; If *either* BEG or END have been specified,
+ ;; then try to get the specified substring.
+ ;; Notice that in case only one of them has been passed,
+ ;; then `buffer-substring-no-properties' will take care of throwing an error.
+ (if (or beg end)
+ (substring-and-bounds beg end)
+ (if (use-region-p)
+ (substring-and-bounds (region-beginning) (region-end))
+ (error "Failed parsing query term from active region")))))
+
+(defun powerthesaurus--read-query-term (&optional prompt)
+ "Ask the user for which word to look up.
+If PROMPT is not specified, a default one will be used."
+ (setq prompt (or prompt "Term: "))
+ (list (substring-no-properties (read-string prompt)) nil nil))
+
+(defun powerthesaurus--make-callback (query-term query-type
+ &optional beg end)
+ "Generate a callback to be executed upon successful completion of request.
+
+If BEG and/or END are non-nil, then `powerthesaurus--make-insert-callback'
+will be used as the underlying callback generator, otherwise it defaults to
+`powerthesaurus--make-display-callback'.
+
+QUERY-TYPE and QUERY-TERM will be passed to
+the underlying callback generator, possibly altering its behavior to
+better accommodate the corresponding type of query."
+ (cond
+ ((or beg end)
+ (powerthesaurus--make-insert-callback query-term query-type
+ (current-buffer)
+ beg end))
+ (t
+ (powerthesaurus--make-display-callback query-term query-type))))
+
+(defun powerthesaurus--make-insert-callback (query-term
+ query-type
+ buffer beg end)
+ "Generate a callback that will insert the query's result to buffer.
+
+The callback generated by this function accepts the data belonging to
+the response to a previously made request as its sole argument.
+
+If END is nil or BEG and END are equal,
+the generated callback will prompt the user to select a returned result and
+insert it at point.
+Otherwise, if BEG and END differ,
+then the region between these points will be replaced by the selected result.
+BEG must be non-nil.
+
+BUFFER is the buffer object where term will be replaced and
+should be explicitly specified since, in case of asynchronous execution,
+the callback may be executed with cursor under a different buffer.
+
+QUERY-TERM corresponds to the text to be replaced by
+the generated callback, and BEG and END correspond to the substituted text's
+beginning and ending positions within the buffer.
+
+QUERY-TYPE must be an element of `powerthesaurus-supported-query-types' and
+is used for determining how to parse the aforementioned data.
+In general, its argument should be the same as the type specified when
+creating the corresponding request."
+ (let ((backend (if (or (null end)
+ (equal beg end))
+ (lambda (new original)
+ (with-current-buffer buffer
+ (powerthesaurus--insert-text new original)))
+ (lambda (new original)
+ (with-current-buffer buffer
+ (powerthesaurus--replace-text new beg end original))))))
+ (lambda (results)
+ (funcall backend
+ (powerthesaurus--select-candidate results)
+ query-term))))
+
+(defun powerthesaurus--make-display-callback (query-term query-type)
+ "Generate a callback that will display the query's results to another buffer.
+
+The callback generated by this function accepts the data belonging to
+the response to a previously made request as its sole argument.
+The results of the query will then be extracted and displayed on
+a different buffer.
+
+QUERY-TERM corresponds to the original text that was queried online.
+
+QUERY-TYPE must be an element of `powerthesaurus-supported-query-types' and
+is used for determining how to parse the aforementioned data.
+In general, its argument should be the same as the type specified when
+creating the corresponding request.
+Additionally, it affects aspects of the generated callback's behavior,
+such as the default string used for separating the results displayed
+in the buffer."
+ (lambda (results)
+ (powerthesaurus--display-results
+ results
+ query-term
+ query-type)))
+
+(defun powerthesaurus--replace-text (replacement beg end original)
+ "Pick an alternative from response and replace the selected text.
+
+REPLACEMENT corresponds to the new text to be inserted in place of ORIGINAL.
+BEG and END correspond to the bounds of the selected text to be replaced."
+ (delete-region beg end)
+ (powerthesaurus--insert-text replacement original (min beg end)))
+
+(defun powerthesaurus--preprocess-text (text reference)
+ "Adjust cases of TEXT according to REFERENCE.
+
+For now, it supports upcasing and capitalization."
+ (cond ((s-uppercase-p reference) (upcase text))
+ ((s-capitalized-p reference) (capitalize text))
+ (t text)))
+
+(defun powerthesaurus--insert-text (text reference &optional pnt)
+ "Insert TEXT at the point after preprocessing it according to REFERENCE.
+
+REFERENCE corresponds to the term whose query yielded TEXT.
+
+If optional argument PNT is given, the insert text there. Otherwise,
+insert text under cursor."
+ (when pnt (goto-char pnt))
+ (insert (powerthesaurus--preprocess-text text reference)))
+
+(defun powerthesaurus--insert-definition-as-text (definition)
+ "Insert given lookup DEFINITION as text into the current buffer."
+ (let ((pos (string-join (mapcar
+ (lambda (index)
+ (propertize
+ (oref (powerthesaurus--part-of-speech-of-index index) singular)
+ 'face 'powerthesaurus-definition-part-of-speech))
+ (oref definition pos))
+ ", "))
+ (usages (string-join (mapcar (lambda (usage)
+ (propertize
+ (format "%S" usage)
+ 'face 'powerthesaurus-definition-usages))
+ (oref definition usages))
+ "\n"))
+ (definition (propertize (oref definition text)
+ 'face 'powerthesaurus-definition-definition))
+ (author (propertize (oref definition author)
+ 'face 'powerthesaurus-definition-author))
+ (synonyms (string-join (mapcar (lambda (synonym)
+ (propertize
+ synonym
+ 'face 'powerthesaurus-definition-synonyms))
+ (oref definition synonyms))
+ ", ")))
+ (when (> (length pos) 0)
+ (insert pos "\n\n"))
+ (insert definition "\n\n")
+ (when (> (length usages) 0)
+ (insert usages "\n\n"))
+ (when (> (length synonyms) 0)
+ (insert "synonyms: " synonyms "\n\n"))
+ (insert author)))
+
+(defun powerthesaurus--insert-sentence-as-text (sentence)
+ "Insert given lookup SENTENCE as text into the current buffer."
+ (let ((text (propertize (oref sentence text)
+ 'face 'powerthesaurus-sentence-sentence))
+ (author (propertize (oref sentence author)
+ 'face 'powerthesaurus-sentence-author)))
+ (insert text)
+ (when (and (> (length author) 0) (not (string= author "unknown")))
+ (insert "\n\n" author))))
+
+(defun powerthesaurus--insert-as-text (result)
+ "Insert given lookup RESULT as text into the current buffer."
+ (cond
+ ((powerthesaurus-definition-p result)
+ (powerthesaurus--insert-definition-as-text result))
+ ((powerthesaurus-sentence-p result)
+ (powerthesaurus--insert-sentence-as-text result))
+ (t (insert (oref result text)))))
+
+(defun powerthesaurus--display-results (results query-term query-type
+ &optional sep)
+ "Display results on a dedicated buffer.
+
+RESULTS must be a list of `powerthesaurus-result' instances.
+QUERY-TERM and QUERY-TYPE must be the text that was queried online
+and the corresponding query type that yielded the results to be displayed.
+
+Optional argument SEP is the string that will be used to separate
+the displayed results in the buffer. If not specified,
+its default value varies depending on value of QUERY-TYPE."
+ (unless sep
+ (cond
+ ((member query-type '(:definitions :sentences))
+ (setq sep "\n────────────────\n"))
+ (t (setq sep "\n"))))
+ (let* ((buf-name (format "*Powerthesaurus - \"%s\" - %s*"
+ query-term query-type))
+ (buf-exists (get-buffer buf-name))
+ (buf (or buf-exists (get-buffer-create buf-name))))
+ (with-current-buffer buf
+ (when buf-exists
+ (fundamental-mode)
+ (read-only-mode -1)
+ (erase-buffer))
+ (dolist (elt results)
+ (powerthesaurus--insert-as-text elt)
+ (insert "\n"
+ sep
+ (propertize "\014" 'display "")
+ "\n"))
+ (help-mode)
+ (goto-char (point-min)))
+ (pop-to-buffer buf)))
+
+(defun powerthesaurus--compose-completion-candidate (result)
+ "Compose completion candidate out of the given RESULT.
+
+RESULT should be an instance of `powerthesaurus-result'."
+ (let* ((text (oref result text))
+ (rating (format "%s★ " (oref result rating))))
+ (propertize text 'line-prefix rating)))
+
+(defun powerthesaurus--compose-completion-candidates (results)
+ "Compose completion candidates out of the given RESULTS.
+
+RESULT should be a list of `powerthesaurus-result'."
+ (if (not powerthesaurus-show-rating)
+ (mapcar (lambda (result) (oref result text)) results)
+ (let* ((ratings (mapcar
+ (lambda (result) (number-to-string (oref result rating)))
+ results))
+ (maxlen (cl-reduce #'max (mapcar #'length ratings))))
+ (cl-mapcar (lambda (result rating)
+ (let* ((len (length rating))
+ (padding-len (1+ (- maxlen len)))
+ (padding (make-string padding-len ? ))
+ (rating-pretty (format "%s★%s" rating padding)))
+ (propertize (oref result text) 'line-prefix rating-pretty)))
+ results ratings))))
+
+(defun powerthesaurus--annotate-candidate (candidate max-candidate-length)
+ "Annotate given completion CANDIDATE.
+
+MAX-CANDIDATE-LENGTH is the maximum length among all candidates, it is required
+for proper annotation alignment."
+ (let* ((tags (string-join (oref candidate tags) ", "))
+ (padding-len (- max-candidate-length (length (oref candidate text))))
+ (padding (make-string padding-len ? ))
+ (pos (string-join (mapcar
+ (lambda (index)
+ (oref (powerthesaurus--part-of-speech-of-index index) shorter))
+ (oref candidate pos))
+ ", "))
+ (annotation ""))
+ (when (and powerthesaurus-show-part-of-speech (> (length pos) 0))
+ (setq annotation pos))
+ (when (and powerthesaurus-show-tags (> (length tags) 0))
+ (setq annotation (concat annotation "\t#" tags)))
+ (if (> (length annotation) 0)
+ (concat padding "\t\t\t" annotation)
+ "")))
+
+(defun powerthesaurus--select-candidate (candidates)
+ "Prompt the user to select one of the CANDIDATES returned from a query."
+ (let* ((candidates-processed (powerthesaurus--compose-completion-candidates candidates))
+ (candidates-by-text (mapcar
+ (lambda (candidate) `(,(oref candidate text) . ,candidate))
+ candidates))
+ (maxlen (cl-reduce #'max (mapcar
+ (lambda (candidate) (length (oref candidate text)))
+ candidates)))
+ ;; this is the only way we can keep the order while using
+ ;; the default implementation of completing-read function
+ ;; see: https://emacs.stackexchange.com/a/41808/23751
+ (completion-table
+ (lambda (string pred action)
+ (if (eq action 'metadata)
+ `(metadata (display-sort-function . identity)
+ (cycle-sort-function . identity)
+ (annotation-function . ,(lambda (text)
+ (powerthesaurus--annotate-candidate
+ (assoc-default text candidates-by-text)
+ maxlen))))
+ (complete-with-action
+ action candidates-processed string pred))))
+ ;; ivy still will try to sort it lexicographically: deny it
+ (ivy-sort-functions-alist '((t . (lambda (x y) 0))))
+ ;; ivy-rich can mess up our efforts of displaying rating
+ (ivy--display-transformers-alist nil))
+ ;; If we try to call completing-read with an active company popup,
+ ;; we inherit its key map. That leads to some funny bugs (see issue#33).
+ (when (fboundp 'company-uninstall-map)
+ (company-uninstall-map))
+ (substring-no-properties
+ (completing-read "Choose a candidate: " completion-table nil nil))))
+
+;; ===============================================================
+;; Requests and JSON parsing
+;; ===============================================================
+
+(defclass powerthesaurus--part-of-speech nil
+ ((singular :initarg :singular :type string)
+ (plural :initarg :plural :type string)
+ (shorter :initarg :shorter :type string)))
+
+(defconst powerthesaurus-parts-of-speech
+ (vector
+ (powerthesaurus--part-of-speech :singular "adjective"
+ :plural "adjectives"
+ :shorter "adj.")
+ (powerthesaurus--part-of-speech :singular "noun"
+ :plural "nouns"
+ :shorter "n.")
+ (powerthesaurus--part-of-speech :singular "pronoun"
+ :plural "pronouns"
+ :shorter "pr.")
+ (powerthesaurus--part-of-speech :singular "adverb"
+ :plural "adverbs"
+ :shorter "adv.")
+ (powerthesaurus--part-of-speech :singular "idiom"
+ :plural "idioms"
+ :shorter "idi.")
+ (powerthesaurus--part-of-speech :singular "verb"
+ :plural "verbs"
+ :shorter "v.")
+ (powerthesaurus--part-of-speech :singular "interjection"
+ :plural "interjections"
+ :shorter "int.")
+ (powerthesaurus--part-of-speech :singular "phrase"
+ :plural "phrases"
+ :shorter "phr.")
+ (powerthesaurus--part-of-speech :singular "conjunction"
+ :plural "conjunctions"
+ :shorter "conj.")
+ (powerthesaurus--part-of-speech :singular "preposition"
+ :plural "prepositions"
+ :shorter "prep.")
+ (powerthesaurus--part-of-speech :singular "phrasal verb"
+ :plural "phrasal verbs"
+ :shorter "phr. v."))
+ "All parts of speech supported by powerthesaurus.")
+
+(defun powerthesaurus--part-of-speech-of-index (index)
+ "Return the POS info for the given API INDEX."
+ (elt powerthesaurus-parts-of-speech (1- index)))
+
+(jeison-defclass powerthesaurus-result nil
+ ((text :initarg :text :type string :path (node targetTerm name)
+ :documentation "Actual text of the word from Powerthesaurus")
+ (rating :initarg :rating :type number :path (node rating)
+ :documentation "User rating of the word")
+ (tags :initarg :tags :type (list-of string) :path (node relations tags)
+ :documentation "Tags of the word")
+ (pos :initarg :pos :type (list-of number) :path (node relations parts_of_speech)
+ :documentation "Parts of speech indicies (1-based) of the word")))
+
+(defun powerthesaurus--get-synonym-names (json-array)
+ "For the given synonym JSON-ARRAY, return its name as string."
+ (mapcar (lambda (json) (jeison-read 'string json '(name))) json-array))
+
+(jeison-defclass powerthesaurus-definition nil
+ ((text :initarg :text :type string :path (node definition)
+ :documentation "Definition from Powerthesaurus")
+ (rating :initarg :rating :type number :path (node rating)
+ :documentation "User rating of the definition")
+ (synonyms :initarg :synonyms :type (list-of string)
+ :path (node (powerthesaurus--get-synonym-names synonyms))
+ :documentation "List of synonyms for the definition")
+ (usages :initarg :usages :type (list-of string) :path (node usages)
+ :documentation "List of usages for the definition")
+ (author :initarg :author :type string :path (node author title)
+ :documentation "Original author of the definition")
+ (pos :initarg :pos :type (list-of number) :path (node partsOfSpeech)
+ :documentation "Parts of speech indicies (1-based) of the definition")))
+
+(jeison-defclass powerthesaurus-sentence nil
+ ((text :initarg :text :type string :path (node sentence)
+ :documentation "Sentence example from Powerthesaurus")
+ (rating :initarg :rating :type number :path (node rating)
+ :documentation "User rating of the sentence")
+ (author :initarg :author :type string :path (node author title)
+ :documentation "Original author of the sentence")))
+
+(defun powerthesaurus--query (term type &optional callback sync)
+ "Make a query to Powerthesaurus.
+
+TERM is the main text of the query.
+TYPE should be a query type for thesaurus (e.g. ':synonyms' or ':related').
+CALLBACK gets called whenever the response is received and processed.
+SYNC is t for synchronous version of the request."
+ (let ((query (pcase type
+ ((pred powerthesaurus--is-thesaurus-query-type)
+ #'powerthesaurus--query-thesaurus)
+ (:definitions #'powerthesaurus--query-definition)
+ (:sentences #'powerthesaurus--query-sentence)
+ (_ (error "Unknown query type '%s'" type)))))
+ (funcall query term type callback sync)))
+
+(defun powerthesaurus--request-term-id (term callback &optional sync)
+ "Request id for the given TERM.
+
+CALLBACK gets called whenever the response is received and processed.
+SYNC is t for synchronous version of the request.
+
+Powerthesaurus APIs require explicit IDs assigned to every term.
+This request fetches it for the further use."
+ (powerthesaurus--query-impl
+ `(("query" . ,term))
+ powerthesaurus--search-query
+ callback
+ (lambda (data) (jeison-read t data '(data search terms 0 id)))
+ sync))
+
+(defmacro powerthesaurus--with-term-id (term name sync &rest body)
+ "Request id for the given TERM, bind it to NAME, and execute BODY.
+
+TERM is the term to get ID for.
+SYNC is t for synchronous version of the request."
+ (declare (indent 3) (debug t))
+ `(let ((on-success
+ (lambda (,name)
+ ,@body)))
+ (powerthesaurus--request-term-id ,term on-success ,sync)))
+
+(defun powerthesaurus--query-thesaurus (term type &optional callback sync)
+ "Request thesaurus information from Powerthesaurus.
+
+TERM is the text to get definition for.
+TYPE should be a query type for thesaurus (e.g. ':synonyms' or ':related').
+CALLBACK gets called whenever the response is received and processed.
+SYNC is t for synchronous version of the request."
+ (powerthesaurus--with-term-id term term-id sync
+ (powerthesaurus--query-impl
+ `(("type" . ,(powerthesaurus--type-of-thesaurus-query type))
+ ("termID" . ,term-id)
+ ("sort" .
+ (("field" . "RATING")
+ ("direction" . "DESC"))))
+ powerthesaurus--thesaurus-query
+ callback
+ (lambda (data) (jeison-read '(list-of powerthesaurus-result) data '(data thesauruses edges)))
+ sync)))
+
+(defun powerthesaurus--is-thesaurus-query-type (query-type)
+ "Return 't' if the given QUERY-TYPE is for thesaurus queries."
+ (member query-type '(:synonyms :antonyms :related)))
+
+(defun powerthesaurus--type-of-thesaurus-query (type)
+ "Return an API type corresponding to the given query TYPE."
+ (pcase type
+ (:synonyms "SYNONYM")
+ (:antonyms "ANTONYM")
+ (:related "RELATED")
+ (_ (error "Unknown thesaurus query type '%s'" type))))
+
+(defun powerthesaurus--query-definition (term type &optional callback sync)
+ "Request definitions from Powerthesaurus.
+
+TERM is the text to get definition for.
+TYPE should be nothing but ':definitions'.
+CALLBACK gets called whenever the response is received and processed.
+SYNC is t for synchronous version of the request."
+ (powerthesaurus--with-term-id term term-id sync
+ (powerthesaurus--query-impl
+ `(("termID" . ,term-id))
+ powerthesaurus--definition-query
+ callback
+ (lambda (data) (jeison-read '(list-of powerthesaurus-definition) data '(data definitions edges)))
+ sync)))
+
+(defun powerthesaurus--query-sentence (term type &optional callback sync)
+ "Request sentences from Powerthesaurus.
+
+TERM is the text for sentence examples.
+TYPE should be nothing but ':sentences'.
+CALLBACK gets called whenever the response is received and processed.
+SYNC is t for synchronous version of the request."
+ (powerthesaurus--with-term-id term term-id sync
+ (powerthesaurus--query-impl
+ `(("termID" . ,term-id))
+ powerthesaurus--sentence-query
+ callback
+ (lambda (data) (jeison-read '(list-of powerthesaurus-sentence) data '(data sentences edges)))
+ sync)))
+
+(defun powerthesaurus--query-impl (variables query &optional callback postprocess sync)
+ "Request data from Powerthesaurus GraphQL API.
+
+VARIABLES is an alist of query-specific parameters.
+QUERY is the actual GraphQL query.
+CALLBACK gets called whenever the response is received and processed.
+POSTPROCESS is the additional processing of the JSON response alist.
+SYNC is t for synchronous version of the request."
+ (make-local-variable 'url-show-status)
+ (let* ((post (or postprocess 'identity))
+ (sync (or (null callback)
+ sync
+ powerthesaurus-synchronous-requests))
+ (url-request-data (json-encode `(("variables" . ,variables)
+ ("query" . ,query))))
+ (url-request-extra-headers powerthesaurus-request-headers)
+ (url-request-method "POST")
+ ;; User agent has to be set separately like this, so that
+ ;; url-retrieve won't add anything else to it.
+ (url-user-agent powerthesaurus-user-agent)
+ ;; Prohibit it from writing "Contacting host:..." every
+ ;; time we send a request, it's not informative.
+ (url-show-status nil)
+ (callback (lambda (&rest _)
+ (with-local-quit
+ (let* ((raw (string-trim
+ (buffer-substring
+ url-http-end-of-headers (point-max))))
+ ;; TODO: parse JSON more efficiently
+ ;; if native method is available
+ (json (json-read-from-string raw))
+ (data (funcall post json)))
+ (funcall callback data))))))
+ (if (not sync)
+ (url-retrieve powerthesaurus-api-url callback)
+ (with-current-buffer
+ (url-retrieve-synchronously powerthesaurus-api-url)
+ (funcall callback)))))
+
+(defun powerthesaurus--wrap-as-callback (fun)
+ "Wrap the given FUN function as a `request' callback."
+ (cl-function
+ (lambda (&key data &allow-other-keys)
+ ;; in order to allow users to quit powerthesaurus
+ ;; prompt with C-g, we need to wrap callback with this
+ (with-local-quit (funcall fun data)))))
+
+;; ===============================================================
+;; Define old API's now deprecated functions.
+;; ===============================================================
+
+;;;###autoload
+(defun powerthesaurus-lookup-word-dwim ()
+ "Wrapper function for powerthesaurus-lookup-word commands.
+
+If a region is selected use powerthesaurus-lookup-word
+if a thing at point is not empty use powerthesaurus-lookup-word-at-point
+otherwise as for word using powerthesaurus-lookup-word"
+ (interactive)
+ (let (beg end)
+ ;; selection is active -> look up whatever is selected
+ (if (use-region-p)
+ (progn
+ (setq beg (region-beginning))
+ (setq end (region-end))
+ (powerthesaurus-lookup-word beg end))
+ ;; point is is at a word -> look it up
+ (if (thing-at-point 'word)
+ (powerthesaurus-lookup-word-at-point (point))
+ ;; nothing appropriate nearby -> ask the user
+ (powerthesaurus-lookup-word)))))
+
+;;;###autoload
+(defun powerthesaurus-lookup-word-at-point (word-point)
+ "Find word at `WORD-POINT', look it up in powerthesaurs, and replace it."
+ (interactive (list (point)))
+ (pcase-let ((`(,word ,beg ,end)
+ (powerthesaurus--extract-original-word word-point)))
+ (powerthesaurus-lookup word :synonyms beg end)))
+
+;;;###autoload
+(defun powerthesaurus-lookup-word (&optional beginning end)
+ "Find the given word's synonyms at powerthesaurus.org.
+
+`BEGINNING' and `END' correspond to the selected text with a word to replace.
+If there is no selection provided, additional input will be required.
+In this case, a selected synonym will be inserted at the point."
+ (interactive
+ ;; it is a simple interactive function instead of interactive "r"
+ ;; because it doesn't produce an error in a buffer without a mark
+ (if (use-region-p) (list (region-beginning) (region-end))
+ (list nil nil)))
+ (pcase-let ((`(,word _ _)
+ (if beginning
+ (powerthesaurus--extract-query-region beginning end)
+ (powerthesaurus--read-query-term "Word to fetch: "))))
+ (powerthesaurus-lookup word :synonyms (or beginning (point)) end)))
+
+(make-obsolete 'powerthesaurus-lookup-word
+ 'powerthesaurus-lookup "0.2.0")
+(make-obsolete 'powerthesaurus-lookup-word-at-point
+ 'powerthesaurus-lookup "0.2.0")
+(make-obsolete 'powerthesaurus-lookup-word-dwim
+ 'powerthesaurus-lookup "0.2.0")
+
+;; ===============================================================
+;; GraphQL queries
+;; ===============================================================
+
+(defconst powerthesaurus--search-query
+ "query SEARCH($query: String!) {
+ search(query: $query) {
+ terms {
+ id
+ name
+ }
+ }
+}")
+
+(defconst powerthesaurus--thesaurus-query
+ "query THESAURUS($termID: ID!, $type: List!, $sort: ThesaurusSorting!) {
+ thesauruses(termId: $termID, sort: $sort, list: $type) {
+ edges {
+ node {
+ targetTerm {
+ name
+ }
+ relations
+ rating
+ votes
+ }
+ }
+ }
+}")
+
+(defconst powerthesaurus--definition-query
+ "query DEFINITION($termID: ID!) {
+ definitions(termId: $termID) {
+ edges {
+ node {
+ definition
+ rating
+ votes
+ synonyms
+ usages
+ partsOfSpeech
+ author {
+ title
+ }
+ }
+ }
+ }
+}")
+
+(defconst powerthesaurus--sentence-query
+ "query SENTENCE($termID: ID!) {
+ sentences(termId: $termID) {
+ edges {
+ node {
+ sentence
+ rating
+ votes
+ author {
+ title
+ }
+ }
+ }
+ }
+}")
+
+;; ===============================================================
+;; UI shortcuts
+;; ===============================================================
+
+;;;###autoload
+(when (require 'hydra nil :noerror)
+ (eval '(defhydra powerthesaurus-hydra (:color blue :hint nil)
+ "
+ Power Thesaurus
+ ^Similarity^ ^Information^
+ ---------------------------------------
+ _s_: Synonyms _d_: Definitions
+ _a_: Antonyms _e_: Example Sentences
+ _r_: Related Words
+ _q_: Quit
+ "
+ ("s" powerthesaurus-lookup-synonyms-dwim)
+ ("a" powerthesaurus-lookup-antonyms-dwim)
+ ("r" powerthesaurus-lookup-related-dwim)
+ ("d" powerthesaurus-lookup-definitions-dwim)
+ ("e" powerthesaurus-lookup-sentences-dwim)
+ ("q" nil))))
+
+;;;###autoload
+(when (require 'transient nil :noerror)
+ (eval '(transient-define-prefix powerthesaurus-transient ()
+ "Transient for Power Thesaurus."
+ [["Similarity"
+ ("s" "Synonyms" powerthesaurus-lookup-synonyms-dwim)
+ ("a" "Antonyms" powerthesaurus-lookup-antonyms-dwim)
+ ("r" "Related Words" powerthesaurus-lookup-related-dwim)]
+ ["Information"
+ ("d" "Definitions" powerthesaurus-lookup-definitions-dwim)
+ ("e" "Example Sentences" powerthesaurus-lookup-sentences-dwim)]])))
+
+(provide 'powerthesaurus)
+;;; powerthesaurus.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/libraries/selector.el b/kolwynia/home/bdunahu/files/.config/emacs/libraries/selector.el
new file mode 100644
index 0000000..3b77190
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/libraries/selector.el
@@ -0,0 +1,653 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+
+;; modified version of lcolonq: https://github.com/lcolonq/emacs
+
+;;; Code:
+
+
+(require 'dash)
+(require 'recentf)
+
+(defgroup selector nil
+ "Efficient selection and navigation."
+ :group 'convenience)
+
+(defgroup selector-faces nil
+ "Faces for `selector'."
+ :group 'selector
+ :group 'faces)
+
+(defface selector-source-name
+ '((default :underline t :inherit bold)
+ (((class color) (background dark)) :foreground "white"))
+ "Face used to highlight source names.")
+
+(defface selector-highlight
+ '((t :inherit highlight))
+ "Face used to highlight the current selection.")
+
+(defvar selector-minibuffer-lines 20
+ "Number of lines to display in the minibuffer.")
+
+(defvar selector-exit-hook nil
+ "Hook run when exiting minibuffer selection.")
+
+(defvar selector--sources '())
+(defvar selector--last nil)
+(defvar selector--matching '())
+(defvar selector--source 0)
+(defvar selector--index 0)
+(defvar selector--action nil)
+(defvar selector--result nil)
+(defvar selector--drawn-this-frame 0)
+
+(defvar selector-minibuffer-map (make-sparse-keymap))
+(define-key selector-minibuffer-map (kbd "\\") (lambda () (interactive) nil))
+(define-key selector-minibuffer-map (kbd "C-g") 'selector-quit)
+(define-key selector-minibuffer-map (kbd "C-c") 'selector-quit)
+(define-key selector-minibuffer-map (kbd "<return>") 'selector-do)
+(define-key selector-minibuffer-map (kbd "C-m") 'selector-do)
+(define-key selector-minibuffer-map (kbd "<backtab>") 'selector-previous)
+(define-key selector-minibuffer-map (kbd "<tab>") 'selector-next)
+(define-key selector-minibuffer-map (kbd "<up>") 'selector-previous)
+(define-key selector-minibuffer-map (kbd "<down>") 'selector-next)
+(define-key selector-minibuffer-map (kbd "<left>") 'selector-previous-source)
+(define-key selector-minibuffer-map (kbd "<right>") 'selector-next-source)
+(define-key selector-minibuffer-map (kbd "C-p") 'selector-previous)
+(define-key selector-minibuffer-map (kbd "C-n") 'selector-next)
+(define-key selector-minibuffer-map (kbd "M-v") 'selector-previous-source)
+(define-key selector-minibuffer-map (kbd "C-v") 'selector-next-source)
+
+(defun selector-minibuffer-line-face (str face)
+ "Write STR to the minibuffer in FACE."
+ (cl-incf selector--drawn-this-frame) ;; sort of an ugly hack,
+ ;; but this fixes the annoying "cursor jumping" glitch when multiple monitors
+ ;; are active. the real fix probably should live in selector-nearby?
+ (when (< selector--drawn-this-frame selector-minibuffer-lines)
+ (let ((before (point)))
+ (goto-char (point-max))
+ (insert (concat "\n" str))
+ (goto-char before)
+ (forward-line)
+ (put-text-property (line-beginning-position) (point-max) 'face face))))
+
+(defun selector-minibuffer-clear ()
+ "Clear minibuffer."
+ (save-excursion
+ (goto-char (minibuffer-prompt-end))
+ (delete-region (line-end-position) (point-max))))
+
+(defun selector-minibuffer-input ()
+ "Get current minibuffer input."
+ (buffer-substring-no-properties
+ (minibuffer-prompt-end)
+ (line-end-position)))
+
+(cl-defstruct (selector-candidate (:constructor selector-candidate--create)
+ (:copier nil)
+ (:conc-name selector-candidate--))
+ type (display nil :type string) face value action)
+
+(cl-defun selector-candidate-create
+ (display &key
+ value
+ (type 'normal)
+ (face 'default)
+ (action '()))
+ "Create a candidate.
+DISPLAY is the string to display (using FACE) / match against.
+VALUE is the value to pass to actions when the candidate is selected.
+TYPE is either normal or dummy - dummy candidates always appear in the
+results list regardless of the input pattern.
+ACTION is an alist mapping keybindings to candidate-specific actions."
+ (selector-candidate--create
+ :type type
+ :display display
+ :face face
+ :value (if value value display)
+ :action action))
+
+(defun selector-candidate-display (candidate)
+ "Return the display string for CANDIDATE."
+ (cond ((selector-candidate-p candidate) (selector-candidate--display candidate))
+ (t candidate)))
+
+(defun selector-candidate-value (candidate)
+ "Return the value of CANDIDATE."
+ (cond ((selector-candidate-p candidate) (selector-candidate--value candidate))
+ (t candidate)))
+
+(defun selector-candidate-type (candidate)
+ "Return the candidate type of CANDIDATE."
+ (cond ((selector-candidate-p candidate) (selector-candidate--type candidate))
+ (t 'normal)))
+
+(defun selector-candidate-face (candidate)
+ "Return the display face for CANDIDATE."
+ (cond ((selector-candidate-p candidate) (selector-candidate--face candidate))
+ (t 'default)))
+
+(defun selector-candidate-action (candidate)
+ "Return the actions for CANDIDATE."
+ (cond ((selector-candidate-p candidate) (selector-candidate--action candidate))
+ (t '())))
+
+(defun selector-candidate-display-string (candidate)
+ "Return the display of CANDIDATE as a string."
+ (let ((display (selector-candidate-display candidate)))
+ (cond ((stringp display) display)
+ (t (error "Invalid candidate display %s for candidate %s (of type %s)"
+ display candidate (type-of display))))))
+
+(defun selector-highlight-candidate (candidate)
+ "Return a copy of CANDIDATE with the face set to selector-highlight."
+ (selector-candidate--create
+ :type (selector-candidate-type candidate)
+ :display (selector-candidate-display candidate)
+ :face 'selector-highlight
+ :value (selector-candidate-value candidate)
+ :action (selector-candidate-action candidate)))
+
+(defun selector-match (candidate regex)
+ "Determine whether CANDIDATE is a match for REGEX."
+ (let ((type (selector-candidate-type candidate)))
+ (cond ((eq 'dummy type) t)
+ (t (string-match-p regex (selector-candidate-display-string candidate))))))
+
+(cl-defstruct (selector-source (:constructor selector-source--create)
+ (:copier nil))
+ name candidates actions keymap)
+
+(defun selector-action-function (action)
+ "Return the function associated with ACTION."
+ (if (functionp action)
+ action
+ (cdr action)))
+
+(defun selector-actions-keymap (actions)
+ "Return a keymap for ACTIONS."
+ (let ((keymap (make-sparse-keymap)))
+ (set-keymap-parent keymap selector-minibuffer-map)
+ (mapc
+ (lambda (a)
+ (unless (functionp a)
+ (define-key keymap (car a)
+ (lambda () (interactive)
+ (selector-do (cdr a))))))
+ actions)
+ keymap))
+
+(cl-defun selector-source-create (name &key candidates (actions '()))
+ "Create a new source named NAME with the given CANDIDATES and ACTIONS.
+
+CANDIDATES is either:
+- A list of candidates
+- A function returning a list of candidates given a regex
+ACTIONS is a list of actions, which can be:
+- functions taking candidate values as arguments
+- pairs of key strings and such functions"
+ (selector-source--create
+ :name name
+ :candidates
+ (if (functionp candidates) candidates
+ (--map (if (selector-candidate-p it) it (selector-candidate-create it))
+ candidates))
+ :actions actions
+ :keymap (selector-actions-keymap actions)))
+
+(defun selector-matching-candidates (candidates pattern)
+ "Return the candidates in CANDIDATES matching PATTERN."
+ (cond ((functionp candidates) (funcall candidates pattern))
+ (t (let ((regex (selector-pattern-regex pattern)))
+ (--filter (selector-match it regex) candidates)))))
+
+(defun selector-filter-source (source pattern)
+ "Return a copy of SOURCE including only the candidates matching PATTERN."
+ (selector-source--create
+ :name (selector-source-name source)
+ :candidates (selector-matching-candidates (selector-source-candidates source) pattern)
+ :actions (selector-source-actions source)
+ :keymap (selector-source-keymap source)))
+
+(defun selector-pattern-regex (pattern)
+ "Convert PATTERN into a regular expression."
+ (apply #'string-join
+ (--map (concat "\\(" it "\\)") (split-string pattern))
+ '(".*")))
+
+(defun selector-matching-sources (sources pattern)
+ "Return the sources in SOURCES matching PATTERN."
+ (let* ((matches (--map (selector-filter-source it pattern) sources)))
+ (-filter #'selector-source-candidates matches)))
+
+(defun selector-display-source (source)
+ "Display SOURCE."
+ (when source
+ (selector-minibuffer-line-face (selector-source-name source) 'selector-source-name)
+ (--map (selector-minibuffer-line-face
+ (selector-candidate-display-string it)
+ (selector-candidate-face it))
+ (selector-source-candidates source))))
+
+(defun selector-nearby (sources)
+ "Filter SOURCES to only include candidates close to the selected candidate."
+ (let* ((adjacent
+ (--map
+ (cond ((and (< (cdr it) (+ selector--source selector-minibuffer-lines))
+ (> (cdr it) selector--source))
+ (cons (car it) 'g))
+ ((= (cdr it) selector--source)
+ (cons (car it) 'e))
+ (t nil))
+ (-zip-pair sources (number-sequence 0 (length sources))))))
+ (--map
+ (when it
+ (let* ((candidates (selector-source-candidates (car it))))
+ (selector-source--create
+ :name (selector-source-name (car it))
+ :candidates
+ (cond ((eq (cdr it) 'g)
+ (-take selector-minibuffer-lines candidates))
+ (t
+ (cl-loop for i from (max (- selector--index
+ (- (/ selector-minibuffer-lines 2) 1))
+ 0)
+ for j in (-take
+ selector-minibuffer-lines
+ (-drop
+ (- selector--index
+ (- (/ selector-minibuffer-lines 2) 1))
+ candidates))
+ collect (if (= i selector--index)
+ (selector-highlight-candidate j)
+ j))))
+ :actions (selector-source-actions (car it))
+ :keymap (selector-source-keymap (car it)))))
+ adjacent)))
+
+(defun selector-update-transient-map ()
+ "Update the transient keymap to match the current source."
+ (let ((source (car (nthcdr selector--source selector--matching))))
+ (when source
+ (set-transient-map (selector-source-keymap source)))))
+
+(defun selector-minibuffer-render ()
+ "Draw matching candidates to minibuffer."
+ (setq selector--drawn-this-frame 0)
+ (save-excursion
+ (let ((pattern (selector-minibuffer-input)))
+ (unless (string= pattern selector--last)
+ (setq selector--last pattern
+ selector--index 0
+ selector--source 0
+ selector--matching (selector-matching-sources selector--sources pattern))))
+ (-map #'selector-display-source (selector-nearby selector--matching))
+ (goto-char (minibuffer-prompt-end))
+ (put-text-property (line-end-position) (point-max) 'readonly t))
+ (selector-update-transient-map))
+
+(defun selector-minibuffer-setup (initial)
+ "Ready minibuffer for completion with INITIAL as initial input."
+ (add-hook 'pre-command-hook 'selector-minibuffer-clear nil t)
+ (add-hook 'post-command-hook 'selector-minibuffer-render nil t)
+ (setq-local max-mini-window-height selector-minibuffer-lines)
+ (when initial
+ (save-excursion
+ (minibuffer-prompt-end)
+ (insert initial)))
+ (end-of-line)
+ (selector-update-transient-map))
+
+(defun selector-previous-source ()
+ "Move to the previous source."
+ (interactive)
+ (setq selector--index 0)
+ (setq selector--source (if (= selector--source 0)
+ (- (length selector--matching) 1)
+ (- selector--source 1))))
+
+(defun selector-next-source ()
+ "Move to the next source."
+ (interactive)
+ (setq selector--index 0)
+ (setq selector--source (% (+ selector--source 1) (length selector--matching))))
+
+(defun selector-previous ()
+ "Move to the previous candidate."
+ (interactive)
+ (let* ((new-source-index (if (= selector--source 0)
+ (- (length selector--matching) 1)
+ (- selector--source 1)))
+ (source (car (nthcdr new-source-index selector--matching))))
+ (setq selector--index (- selector--index 1))
+ (when (< selector--index 0)
+ (setq selector--index (- (length (selector-source-candidates source)) 1)
+ selector--source new-source-index))))
+
+(defun selector-next ()
+ "Move to the next candidate."
+ (interactive)
+ (let* ((source (car (nthcdr selector--source selector--matching))))
+ (setq selector--index (+ selector--index 1))
+ (when (= selector--index (length (selector-source-candidates source)))
+ (setq selector--index 0
+ selector--source (% (+ selector--source 1) (length selector--matching))))))
+
+(defun selector-quit ()
+ "Quit the selection interface without running an action."
+ (interactive)
+ (run-hooks 'selector-exit-hook)
+ (keyboard-escape-quit))
+
+(defun selector-do (&optional action-function)
+ "Act upon selected candidate.
+If ACTION-FUNCTION is given use it, otherwise use the first action for the candidate."
+ (interactive)
+ (if (null selector--matching)
+ (progn
+ (setq selector--action (lambda (x) x)
+ selector--result nil))
+ (progn
+ (let* ((source (car (nthcdr selector--source selector--matching)))
+ (candidate (car (nthcdr selector--index (selector-source-candidates source)))))
+ (setq selector--action (cond (action-function
+ action-function)
+ ((selector-candidate-action candidate)
+ (selector-candidate-action candidate))
+ (t
+ (let ((actions (selector-source-actions source)))
+ (if actions
+ (selector-action-function (car actions))
+ (lambda (x) x)))))
+ selector--result (selector-candidate-value candidate)))))
+ (run-hooks 'selector-exit-hook)
+ (exit-minibuffer))
+
+;;;###autoload
+(cl-defun selector (sources &key prompt initial)
+ "Select a candidate and run an action using SOURCES.
+Display PROMPT as the prompt, or \"pattern: \" if not given.
+Use INITIAL as the initial input."
+ (setq selector--sources sources
+ selector--last nil
+ selector--matching (selector-matching-sources sources "")
+ selector--source 0
+ selector--index 0
+ selector--action nil
+ selector--result nil)
+ (let ((inhibit-message t))
+ (minibuffer-with-setup-hook
+ (apply-partially 'selector-minibuffer-setup initial)
+ (read-from-minibuffer (or prompt "pattern: ") nil selector-minibuffer-map)))
+ (funcall selector--action selector--result))
+
+(defvar selector-completing-read-candidate-transformer (lambda (x) x))
+
+;;;###autoload
+(defun selector-completing-read (prompt collection &optional predicate require-match
+ initial-input hist def inherit-input-method)
+ "Replacement for `completing-read'.
+PROMPT, COLLECTION, PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, HIST, DEF, and
+INHERIT-INPUT-METHOD have the same meaning as in `completing-read'."
+ (ignore predicate hist def inherit-input-method)
+ (let ((unspecified-source
+ (if require-match
+ '()
+ (list
+ (selector-source-create
+ "Other"
+ :candidates
+ (list (selector-candidate-create
+ "Specify"
+ :type 'dummy
+ :action (lambda (_) (selector-input)))))))))
+ (or
+ (cond ((functionp collection)
+ (selector
+ (cons
+ (selector-source-create
+ "Completions"
+ :candidates
+ (-non-nil
+ (--map
+ (when-let ((disp (funcall selector-completing-read-candidate-transformer it)))
+ (selector-candidate-create disp :value it))
+ (funcall collection "" nil t))))
+ unspecified-source)
+ :prompt prompt
+ :initial initial-input))
+ ((hash-table-p collection)
+ (selector
+ (cons
+ (selector-source-create
+ "Completions"
+ :candidates (hash-table-keys collection))
+ unspecified-source)
+ :prompt prompt
+ :initial initial-input))
+ ((obarrayp collection)
+ (let ((candidates (list)))
+ (mapatoms (lambda (x) (push (selector-candidate-create (symbol-name x)) candidates)) collection)
+ (selector
+ (cons (selector-source-create "Completions" :candidates candidates) unspecified-source)
+ :prompt prompt
+ :initial initial-input)))
+ (t (selector
+ (cons (selector-source-create
+ "Completions"
+ :candidates
+ (--map (if (consp it)
+ (selector-candidate-create (car it))
+ it)
+ collection))
+ unspecified-source)
+ :prompt prompt
+ :initial initial-input)))
+ (selector-input))))
+
+(defun selector-input () "Return last minibuffer input." selector--last)
+
+(defvar selector-extended-command-actions
+ (list (lambda (c)
+ (add-to-list 'extended-command-history c)
+ (command-execute (intern-soft c)))
+ (cons (kbd "C-h") (lambda (c) (describe-function (intern-soft c))))))
+
+;;;###autoload
+(defun selector-extended-commands-source ()
+ "Source for extended commands (`M-x')."
+ (selector-source-create
+ "Commands"
+ :candidates
+ (-map
+ #'selector-candidate-create
+ (all-completions "" obarray #'commandp))
+ :actions
+ selector-extended-command-actions))
+
+;;;###autoload
+(defun selector-extended-command-history-source ()
+ "Source for extended command history."
+ (selector-source-create
+ "Command History"
+ :candidates
+ (-map
+ #'selector-candidate-create
+ extended-command-history)
+ :actions
+ selector-extended-command-actions))
+
+;;;###autoload
+(defun selector-apropos-command-source ()
+ "Source for command lookup."
+ (selector-source-create
+ "Commands"
+ :candidates
+ (lambda (r) (-map #'selector-candidate-create (all-completions r obarray #'commandp)))
+ :actions
+ (list (lambda (c) (describe-function (intern-soft c))))))
+
+;;;###autoload
+(defun selector-apropos-function-source ()
+ "Source for function lookup."
+ (selector-source-create
+ "Functions"
+ :candidates
+ (lambda (r) (-map #'selector-candidate-create (all-completions r obarray #'fboundp)))
+ :actions
+ (list (lambda (c) (describe-function (intern-soft c))))))
+
+;;;###autoload
+(defun selector-apropos-variable-source ()
+ "Source for variable lookup."
+ (selector-source-create
+ "Variables"
+ :candidates
+ (lambda (r) (-map #'selector-candidate-create
+ (all-completions
+ r obarray
+ (lambda (x) (let ((sym (intern-soft x)))
+ (and (boundp sym) (not (keywordp sym))))))))
+ :actions
+ (list (lambda (c) (describe-variable (intern-soft c))))))
+
+(defvar selector-buffer-actions
+ (list 'switch-to-buffer
+ (cons (kbd "C-k") 'kill-buffer)))
+
+;;;###autoload
+(defun selector-buffers-source (&optional sort-pred)
+ "Source for open buffers.
+An optional SORT-PRED may be provided to sort the buffers (see `sort')."
+ (selector-source-create
+ "Buffers"
+ :candidates
+ (--map (selector-candidate-create (buffer-name it))
+ (if sort-pred (sort (buffer-list) sort-pred) (buffer-list)))
+ :actions
+ selector-buffer-actions))
+
+;;;###autoload
+(defun selector-create-buffer-source ()
+ "Dummy source to create a buffer."
+ (selector-source-create
+ "Other"
+ :candidates
+ (list (selector-candidate-create
+ "Create buffer"
+ :type 'dummy
+ :action (lambda (_) (switch-to-buffer (selector-input)))))))
+
+(defvar selector-file-actions
+ (list 'find-file
+ (cons (kbd "C-d") (lambda (f)
+ (when (y-or-n-p (concat "Delete file " f "? "))
+ (delete-file f))))))
+
+;;;###autoload
+(defun selector-files-source ()
+ "Source for files in current directory."
+ (selector-source-create
+ "Files"
+ :candidates
+ (-map #'selector-candidate-create (directory-files default-directory))
+ :actions
+ selector-file-actions))
+
+;;;###autoload
+(defun selector-create-file-source ()
+ "Dummy source to create a file."
+ (selector-source-create
+ "Other"
+ :candidates
+ (list (selector-candidate-create
+ "Create file"
+ :type 'dummy
+ :action (lambda (_) (find-file (selector-input)))))))
+
+;;;###autoload
+(defun selector-recentf-source ()
+ "Source for recentf."
+ (selector-source-create
+ "Recent Files"
+ :candidates
+ (-map #'selector-candidate-create recentf-list)
+ :actions
+ selector-file-actions))
+
+;;;###autoload
+(defun selector-M-x ()
+ "Preconfigured `selector' interface to replace `execute-external-command'."
+ (interactive)
+ (selector (list (selector-extended-command-history-source)
+ (selector-extended-commands-source))))
+
+;;;###autoload
+(defun selector-apropos (&optional initial)
+ "Preconfigured `selector' interface to replace `apropos'.
+INITIAL is the initial text to match."
+ (interactive)
+ (selector (list (selector-apropos-command-source)
+ (selector-apropos-function-source)
+ (selector-apropos-variable-source))
+ :initial (if initial initial (thing-at-point 'symbol t))))
+
+;;;###autoload
+(defun selector-for-buffers ()
+ "Preconfigured `selector' interface for open buffers and recentf."
+ (interactive)
+ (selector (list (selector-buffers-source)
+ (selector-recentf-source)
+ (selector-create-buffer-source))))
+
+;;;###autoload
+(defun selector-for-files ()
+ "Preconfigured `selector' interface for files in the current directory."
+ (interactive)
+ (selector (list (selector-files-source)
+ (selector-create-file-source))))
+
+;;;###autoload
+(defun selector-read-file-name (prompt &optional dir default-filename mustmatch initial predicate)
+ "Replacement for `read-file-name'.
+PROMPT, DIR, DEFAULT-FILENAME, MUSTMATCH, INITIAL and PREDICATE have the same
+meaning as in `read-file-name'."
+ (ignore default-filename mustmatch predicate)
+ (let ((d (if dir dir default-directory)))
+ (concat d (selector (list (selector-source-create
+ "Files"
+ :candidates (-map #'selector-candidate-create (directory-files d)))
+ (selector-source-create
+ "Other"
+ :candidates (list (selector-candidate-create
+ "New file"
+ :type 'dummy
+ :action (lambda (_) (selector-input))))))
+ :prompt prompt
+ :initial initial))))
+
+(defun selector-file-contents-actions (file)
+ "Actions for candidate values corresponding to lines in FILE."
+ (list
+ (lambda (index)
+ (find-file file)
+ (goto-char (point-min))
+ (forward-line index)
+ (pulse-momentary-highlight-one-line (point)))))
+
+(defun selector-file-contents-source (file)
+ "Source for lines in FILE."
+ (selector-source-create
+ file
+ :candidates
+ (-map-indexed
+ (lambda (index l)
+ (selector-candidate-create l :value index))
+ (split-string (f-read-text file) "\n"))
+ :actions
+ (selector-file-contents-actions file)))
+
+
+(provide 'selector)
+;;; selector.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--browse.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--browse.el
new file mode 100644
index 0000000..7ba6513
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--browse.el
@@ -0,0 +1,146 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+(require 'selector)
+(require 'dash)
+(require 'fill-column)
+
+(defun bd/browse (url &optional pref &rest _)
+ "Given PREF, launches URL in an external browser or eww."
+ (interactive)
+ (pcase pref
+ ('eww (eww url))
+ ('tor (start-process "torbrowser" nil "torbrowser" "--new-window" url))
+ ('chromium (start-process "chromium" nil "chromium" "--new-window" url))
+ (_ (start-process "browser" nil (getenv "BROWSER") "--new-window" url))))
+
+(defun bd/selector-bookmarks ()
+ "Selector source for all bookmarks."
+ (selector-source-create
+ "Bookmarks"
+ :candidates
+ (-map
+ (lambda (b) (selector-candidate-create (car b) :value (cdr b)))
+ bd/bookmarks)
+ :actions
+ (list (lambda (x) (apply #'bd/browse x)))))
+
+(defmacro bd/search-candidate (name url pref)
+ "Syntax for a search candidate given NAME, URL, and PREF."
+ `(selector-candidate-create
+ ,(concat "Search " name)
+ :type 'dummy
+ :action (lambda (_) (browse-url (concat ,url (selector-input)) ,pref))))
+
+(defun rip-video (url)
+ "Play URL (or search string) in mpv."
+ (message "Ludu %s" url)
+ (start-process "rip" nil
+ "mpv" "--force-window=yes"
+ (concat (if (string-match "https://.*" url)
+ "ytdl://"
+ "ytdl://ytsearch:") url)))
+
+(defun rip-html (url)
+ "Open an HTML document in an emacs org buffer."
+ (with-current-buffer-window url
+ '((display-buffer-same-window))
+ nil
+ (princ
+ (shell-command-to-string
+ (concat "curl --silent " url " | pandoc --from=html --to=org --standalone")))
+ (org-mode)))
+
+(defun bd/selector-rip-video ()
+ "Selector source for streaming a video off of youtube."
+ (selector-candidate-create
+ "Search Immediate"
+ :type 'dummy
+ :action (lambda (_) (rip-video (selector-input)))))
+
+(defun bd/selector-search ()
+ "Selector source for all search engines."
+ (selector-source-create
+ "Browser"
+ :candidates
+ (list (bd/search-candidate "DuckDuckGo" "https://duckduckgo.com/html/?q=" 'wolf)
+ (bd/search-candidate "SearXNG" "https://searx.operationnull.com/searxng/search?q=" 'wolf)
+ (bd/search-candidate "SearXNG-E" "https://searx.operationnull.com/searxng/search?q=" 'eww)
+ (bd/search-candidate "Wikipedia" "https://en.wikipedia.org/w/index.php?search=" 'eww)
+ (bd/search-candidate "Invidious" "https://inv.nadeko.net/search?q=" 'eww)
+ (bd/search-candidate "Urban Dictionary" "https://www.urbandictionary.com/define.php?term=" 'wolf)
+ (bd/search-candidate "Nethack Wiki" "https://nethackwiki.com/w/index.php?search=" 'eww)
+ (bd/search-candidate "Archive of Our Own" "https://archiveofourown.org/works/search?work_search%5Bquery%5D=" 'eww)
+ (bd/search-candidate "Archwiki" "https://wiki.archlinux.org/index.php?title=Special%3ASearch&search=" 'eww)
+
+ (bd/selector-rip-video)
+ (bd/search-candidate "Torbrowser" "" 'tor)
+ (bd/search-candidate "Librewolf" "" 'wolf))))
+
+(defun bd/browse-dispatcher ()
+ "Select and `browse-url' a bookmark or search feature."
+ (interactive)
+ (unwind-protect
+ (selector
+ (list (bd/selector-bookmarks)
+ (bd/selector-search)))))
+
+(setopt browse-url-handlers
+ `((,(regexp-opt '("youtube.com" "youtu.be" "vid.puffyan.us" "deezer.page" "deezer.com")) .
+ (lambda (url &rest _) (rip-video url))))
+ url-privacy-level '(email os emacs lastloc cookies))
+
+(use-package elpher
+ :bind
+ (:map elpher-mode-map
+ ("l" . #'elpher-back)
+ ("d" . #'elpher-download)
+ ("w" . #'elpher-copy-current-url)
+ ("A" . #'elpher-copy-link-url)
+ ("E" . #'elpher-bookmark-current)
+ ("TAB" . #'elpher-next-link)
+ ("g" . #'elpher-reload)
+ ("G" . #'elpher-go))
+ :config
+ (defun bd/elpher (original url &optional new-window)
+ "Handle gemini links."
+ (cond ((string-match-p "\\`\\(gemini\\|gopher\\)://" url)
+ (elpher-go url))
+ (t (funcall original url new-window))))
+ (advice-add 'eww :around 'bd/elpher)
+ (setopt elpher-default-url-type "gemini"
+ elpher-connection-timeout 120
+ elpher-gemini-max-fill-width 85
+ elpher-use-emacs-bookmark-menu t))
+
+(use-package eww
+ :defer 1
+ :bind
+ (:map eww-mode-map
+ ("i" . eww-toggle-images)
+ ("o" . (lambda () (interactive) (rip-html (eww-current-url)))))
+ :hook
+ ;; eww-mode by default sets this as local var to eww-browse-url
+ ((eww-mode .
+ (lambda ()
+ (setq-local browse-url-browser-function #'bd/browse)))
+ (eww-after-render . eww-readable))
+ :config
+ (setopt eww-search-prefix "https://searx.operationnull.com/searxng/search?q="
+ eww-auto-rename-buffer 'title
+ eww-browse-url-new-window-is-tab nil
+ browse-url-browser-function 'bd/browse
+ browse-url-secondary-browser-function #'browse-url-default-browser
+ eww-header-line-format "%t: %u"
+ eww-use-browse-url (regexp-opt '("mailto:"
+ "youtube.com"
+ "youtu.be"))
+ shr-use-fonts nil
+ shr-inhibit-images t
+ shr-cookie-policy nil
+ shr-max-width 90))
+
+
+(provide 'bd--browse)
+;;; bd--browse.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--buffer.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--buffer.el
new file mode 100644
index 0000000..4a09805
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--buffer.el
@@ -0,0 +1,32 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(use-package autorevert
+ :init
+ (global-auto-revert-mode)
+ :config
+ (setopt global-auto-revert-non-file-buffers t
+ auto-revert-interval 30))
+
+(use-package midnight
+ :config
+ ;; kill forgotten browser windows
+ (push
+ "\.\*\\(LibreWolf\\|Chromium\\|IceCat\\|Tor\sBrowser\\)"
+ clean-buffer-list-kill-regexps)
+ ;; never kill irc buffers
+ (push
+ ".*operationnull.com"
+ clean-buffer-list-kill-never-regexps)
+ (setopt clean-buffer-list-delay-special 1800
+ midnight-period (* 12 3600)))
+
+(use-package atomic-chrome
+ :config
+ (atomic-chrome-start-server))
+
+
+(provide 'bd--buffer)
+;;; bd--buffer.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--chat.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--chat.el
new file mode 100644
index 0000000..5fa0c2d
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--chat.el
@@ -0,0 +1,118 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(require 'fill-column)
+(use-package rcirc
+ :bind (:map rcirc-mode-map
+ ("C-c j" . #'bd/rcirc-jump-net)
+ ("C-c q" . #'bd/rcirc-detach-buffer))
+ :hook
+ ((rcirc-mode . (lambda ()
+ (setq-local fill-column-desired-width 80)
+ (fill-column-mode)
+ (rcirc-omit-mode))))
+ :config
+ (setopt bd/rcirc-networks '("libera" "furnet"))
+ (defun bd/rcirc-jump-net ()
+ "Prompts the user for a irc network in BD/RCIRC-NETWORKS, then issues
+ZNC to hop networks."
+ (interactive)
+ (let ((buffer (current-buffer)))
+ (when (and (buffer-local-value 'rcirc-server-buffer buffer)
+ (eq (process-status (rcirc-buffer-process)) 'open))
+ (let ((target (completing-read "Jump to: " bd/rcirc-networks)))
+ (if (stringp target)
+ (rcirc-send-string (rcirc-buffer-process)
+ "PRIVMSG" "*status" :
+ (concat "JUMPNETWORK " target)))))))
+ (defun bd/rcirc-detach-buffer ()
+ "If the current buffer is an rcirc channel, detaches through ZNC and
+deletes the buffer. This bypasses the default behavior of deleting an active
+channel, which is issuing the PART command."
+ (interactive)
+ (let ((buffer (current-buffer)))
+ (when (and (rcirc-buffer-process)
+ (eq (process-status (rcirc-buffer-process)) 'open))
+ (with-rcirc-server-buffer
+ (setq rcirc-buffer-alist
+ (rassq-delete-all buffer rcirc-buffer-alist)))
+ (rcirc-update-short-buffer-names)
+ (if (rcirc-channel-p rcirc-target)
+ (rcirc-send-string (rcirc-buffer-process)
+ "PRIVMSG" "*status" :
+ (concat "DETACH " rcirc-target))))
+ (setq rcirc-target nil)
+ (kill-buffer buffer)))
+ (setopt rcirc-fill-column 80
+ rcirc-omit-threshold 5
+ rcirc-reconnect-delay 60
+ rcirc-omit-responses '("JOIN" "PART" "QUIT" "NICK" "AWAY")
+ rcirc-track-minor-mode t
+ rcirc-track-ignore-server-buffer-flag t
+ rcirc-server-alist
+ '(("operationnull.com"
+ :nick "Gondul"
+ :user-name "Gondul"
+ :port 6697
+ :encryption tls))))
+
+(use-package gptel
+ :bind (("C-c g" . gptel-menu)
+ ("C-c k" . (lambda () (interactive) (gptel "*evka*") (switch-to-buffer "*evka*"))))
+ :config
+ (defvar bd/llama-cpp-buffer-name "*llama-cpp-proc*")
+ (defvar bd/llama-cpp-reasoning-buffer-name "*llama-cpp-reasoning*")
+ (defvar bd/llama-cpp-port "4568")
+ (defvar bd/llama-cpp-threads "8")
+ (defvar bd/llama-cpp-model-file "Qwen3-8B.Q4_K_M.gguf")
+ ;; most models seem to ignore this, or llama-cpp doesn't add /no_think like it's supposed to
+ (defvar bd/llama-cpp-reasoning-budget nil)
+ (defun bd/gptel-start-backend ()
+ (interactive)
+ (let ((process (get-buffer-process bd/llama-cpp-buffer-name)))
+ (if process
+ (message "llama-cpp process is already running!")
+ (progn
+ (start-process-shell-command
+ "llama-cpp" bd/llama-cpp-buffer-name
+ (concat "llama-server --reasoning-budget "
+ (if bd/llama-cpp-reasoning-budget "-1" "0")
+ " --port " bd/llama-cpp-port
+ " -t " bd/llama-cpp-threads
+ " -m " (expand-file-name bd/llama-cpp-model-file "~/.config/guix/assets/"))))
+ (unless (get-buffer bd/llama-cpp-reasoning-buffer-name)
+ (generate-new-buffer bd/llama-cpp-reasoning-buffer-name)))))
+ (defun bd/gptel-stop-backend ()
+ (interactive)
+ (let ((process (get-buffer-process bd/llama-cpp-buffer-name)))
+ (if process
+ (progn
+ (delete-process process)
+ (kill-buffer bd/llama-cpp-buffer-name)
+ (message "Killed %s." process))
+ (message "No llama-cpp process is running."))))
+ (defun bd/gptel-restart-backend ()
+ (interactive)
+ (bd/gptel-stop-backend)
+ (bd/gptel-start-backend))
+
+ (bd/gptel-start-backend)
+
+ (add-to-list 'gptel-directives
+ '(evka . "You are a wolf (furry) named Evka hired as a secretary to complete language-based tasks. First describe an action your character does, e.x.: *I tap my claws on the desk*. Finish by responding to the task tersely, in character./no_think"))
+
+ (setopt gptel-model 'qwen-4b
+ gptel-backend (gptel-make-openai "llama-cpp"
+ :stream t
+ :protocol "http"
+ :host (concat "localhost:" bd/llama-cpp-port)
+ :models '(qwen-4b))
+ gptel-max-tokens 500
+ gptel--system-message (alist-get 'evka gptel-directives)
+ gptel-include-reasoning bd/llama-cpp-reasoning-buffer-name))
+
+
+(provide 'bd--chat)
+;;; bd--chat.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--devel.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--devel.el
new file mode 100644
index 0000000..dfc9f15
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--devel.el
@@ -0,0 +1,237 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+(require 'selector)
+(require 'dash)
+
+(setopt display-line-numbers-type 'relative)
+(defvar bd/enable-line-numbers-in-hooks
+ '(prog-mode-hook)
+ "List of hook symbols to add `display-line-numbers-mode' to.")
+(mapc
+ (lambda (hook)
+ (add-hook hook #'display-line-numbers-mode))
+ bd/enable-line-numbers-in-hooks)
+
+;; skr (skribe) is a scheme extension
+(add-to-list 'auto-mode-alist '("\\.skr\\'" . scheme-mode))
+
+(defun bd/beginning-of-visual-line-dwim (&optional n)
+ (interactive "P")
+ (let ((pt (point)))
+ (back-to-indentation)
+ (when (or n (eq pt (point)))
+ (beginning-of-visual-line n))))
+
+(define-minor-mode dwim-cursor-mode
+ "Toggle dwim-cursor-mode."
+ :init-value nil
+ :group 'quality
+ :light " SC"
+ :keymap (list
+ (cons (kbd "C-a") #'bd/beginning-of-visual-line-dwim)))
+
+(use-package prog-mode
+ :hook
+ ((js-mode . (lambda ()
+ (add-to-list 'prettify-symbols-alist '("function" . ?ƒ))))
+ (prog-mode . (lambda ()
+ (font-lock-add-keywords
+ nil '(("\\<\\(FIX\\(ME\\)?\\|TODO\\)"
+ 1 font-lock-warning-face t)))))
+ (prog-mode . dwim-cursor-mode))
+ :config
+ (global-prettify-symbols-mode 1))
+
+(use-package tramp
+ :config
+ (defun request-sudo ()
+ "Uses TRAMP to edit current opened file as root."
+ (interactive)
+ (when buffer-file-name
+ (find-alternate-file
+ (concat "/sudo:root@localhost:"
+ buffer-file-name))))
+ (setopt tramp-remote-path
+ (append tramp-remote-path
+ '(tramp-own-remote-path
+ "~/.guix-profile/bin" "~/.guix-profile/sbin"))))
+
+(use-package vc
+ :bind (("C-x v B" . #'bd/vc-browse-remote))
+ :config
+ (defun bd/vc-browse-remote (&optional current-line)
+ "Open the repository's remote URL in the browser.
+If CURRENT-LINE is non-nil, point to the current branch, file, and line.
+Otherwise, open the repository's main page."
+ (interactive "P")
+ (let* ((remote-url (string-trim (vc-git--run-command-string nil "config" "--get" "remote.origin.url")))
+ (branch (string-trim (vc-git--run-command-string nil "rev-parse" "--abbrev-ref" "HEAD")))
+ (file (string-trim (file-relative-name (buffer-file-name) (vc-root-dir))))
+ (line (line-number-at-pos)))
+ (message "Opening remote on browser: %s" remote-url)
+ (if (and remote-url (string-match "\\(?:git@\\|https://\\)\\([^:/]+\\)[:/]\\(.+?\\)\\(?:\\.git\\)?$" remote-url))
+ (let ((host (match-string 1 remote-url))
+ (path (match-string 2 remote-url)))
+ ;; Convert SSH URLs to HTTPS (e.g., git@github.com:user/repo.git -> https://github.com/user/repo)
+ (when (string-prefix-p "git@" host)
+ (setq host (replace-regexp-in-string "^git@" "" host)))
+ ;; Construct the appropriate URL based on CURRENT-LINE
+ (browse-url
+ (if current-line
+ (format "https://%s/%s/blob/%s/%s#L%d" host path branch file line)
+ (format "https://%s/%s" host path))))
+ (message "Could not determine repository URL")))))
+
+(use-package hl-line
+ :hook
+ ((prog-mode . hl-line-mode)
+ (text-mode . hl-line-mode)))
+
+(use-package compile
+ :demand t
+ :hook ((compilation-filter . ansi-color-compilation-filter))
+ :config
+ (setopt compilation-always-kill t
+ compilation-scroll-output 'first-error
+ compilation-ask-about-save nil
+ ansi-color-for-compilation-mode t)
+ (defun bd/compile-dwim (f)
+ (let ((default-directory (bd/get-directory-dwim)))
+ (call-interactively f)))
+ (add-to-list 'display-buffer-alist
+ '((major-mode . compilation-mode)
+ (display-buffer-in-side-window)
+ (side . bottom)
+ (slot . -1)
+ (width . 0.15)
+ (post-command-select-window t)))
+
+ (keymap-set prog-mode-map
+ "C-," #'(lambda ()
+ (interactive)
+ (bd/compile-dwim #'recompile)))
+ (keymap-set prog-mode-map
+ "C-<" #'(lambda ()
+ (interactive)
+ (bd/compile-dwim #'compile))))
+
+(use-package man
+ :config
+ (setopt Man-notify-method 'pushy))
+
+(use-package eldoc
+ :init
+ (global-eldoc-mode))
+
+(use-package flymake
+ :bind (("C-c f f" . #'flymake-mode)
+ :map flymake-mode-map
+ ("C-c f s" . #'flymake-start)
+ ("C-c f n" . #'flymake-goto-next-error)
+ ("C-c f p" . #'flymake-goto-prev-error)
+ ("C-c f b" . #'flymake-show-buffer-diagnostics)
+ ("C-c f d" . #'flymake-show-project-diagnostics))
+ :config
+ (setopt flymake-no-changes-timeout nil
+ flymake-start-on-flymake-mode t
+ flymake-start-on-save-buffer t
+ flymake-proc-compilation-prevents-syntax-check t
+ flymake-wrap-around nil
+ flymake-show-diagnostics-at-end-of-line nil))
+
+(require 'geiser)
+(require 'geiser-mode)
+(require 'geiser-guile)
+
+(use-package cider)
+
+(use-package gdb-mi
+ :config
+ (setopt gdb-debuginfod-enable-setting nil))
+
+(use-package eglot
+ :defer t
+ :bind (:map eglot-mode-map
+ ("C-c C-f" . eglot-format)
+ ("C-c C-e" . eglot-rename))
+ :config
+ (setopt eglot-autoshutdown t
+ eglot-prefer-plaintext t
+ jsonrpc-event-hook nil)
+ (add-to-list 'eglot-server-programs
+ '(c-mode . ("ccls" "--init={\"clang\": {\"extraArgs\": [\"-std=c++20\"]}}"))))
+
+(use-package rainbow-mode
+ :hook css-mode)
+
+(use-package lua-mode
+ :config
+ (setopt lua-indent-level 4
+ lua-indent-nested-block-content-align nil)
+ (defun lua-at-most-one-indent (old-function &rest arguments)
+ (let ((old-res (apply old-function arguments)))
+ (if (> old-res lua-indent-level) lua-indent-level old-res)))
+ (advice-add #'lua-calculate-indentation-block-modifier
+ :around #'lua-at-most-one-indent))
+
+(use-package clojure-mode)
+
+(use-package slime
+ :defer t
+ :commands slime
+ :bind (:map slime-mode-map
+ ("C-c C-k" . slime-eval-buffer))
+ :config
+ ;; more memory for ml libraries
+ (setopt inferior-lisp-program "sbcl --dynamic-space-size 4096"))
+
+(use-package yasnippet
+ :hook (vc-git-log-edit-mode . yas-minor-mode)
+ :config
+ (add-to-list 'yas-snippet-dirs (expand-file-name "~/pt/guix/etc/snippets/yas")))
+
+(use-package paren
+ :config
+ (setopt show-paren-delay 0
+ show-paren-style 'mixed
+ show-paren-highlight-openparen t
+ show-paren-context-when-offscreen t
+ show-paren-when-point-in-periphery t
+ show-paren-when-point-inside-paren t))
+
+(use-package rainbow-delimiters
+ :hook prog-mode)
+
+(use-package paredit
+ :hook (ielm-mode
+ emacs-lisp-mode
+ eshell-mode
+ geiser-repl-mode
+ clojure-mode
+ cider-repl-mode
+
+ lisp-mode
+ scheme-mode
+ slime-repl-mode
+ lisp-interaction-mode)
+ :config
+ (defun bd/paredit-preserve-repl (f &rest args)
+ "Wrapper around F (paredit-RET), discarding ARGS."
+ (pcase (cons major-mode (eolp))
+ ('(inferior-emacs-lisp-mode . t) (ielm-return))
+ ('(eshell-mode . t) (eshell-send-input))
+ ('(geiser-repl-mode . t) (geiser-repl-maybe-send))
+ ('(cider-repl-mode . t) (eshell-send-input))
+ ('(slime-repl-mode . t) (slime-repl-return))
+ (_ (funcall f))))
+ (advice-add #'paredit-RET :around #'bd/paredit-preserve-repl)
+ (eldoc-add-command
+ 'paredit-backward-delete
+ 'paredit-close-round))
+
+(use-package copyright)
+
+(provide 'bd--devel)
+;;; bd--devel.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--dictionary.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--dictionary.el
new file mode 100644
index 0000000..e032837
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--dictionary.el
@@ -0,0 +1,20 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(use-package dictionary
+ :bind (("C-c i" . dictionary-lookup-definition))
+ :config
+ (setopt dictionary-server "localhost"
+ dictionary-use-single-buffer t))
+
+(use-package powerthesaurus
+ :bind (("C-c t" . powerthesaurus-transient))
+ :config
+ (setopt powerthesaurus-show-rating nil
+ powerthesaurus-user-agent "Chrome/138.0.0.0"))
+
+
+(provide 'bd--dictionary)
+;;; bd--dictionary.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--emms.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--emms.el
new file mode 100644
index 0000000..f4e5064
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--emms.el
@@ -0,0 +1,68 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(use-package emms
+ :bind (("C-z C-s" . #'emms-quickstart)
+ ("C-z C-m" . #'switch-to-emms)
+ :map emms-playlist-mode-map
+ ("q" . #'quit-window))
+ :config
+ (emms-all)
+
+ (defun emms-quickstart ()
+ "Queues a shuffled playlist and starts
+playback."
+ (interactive)
+ (emms-stop)
+ (when (bufferp emms-playlist-buffer-name)
+ (kill-buffer emms-playlist-buffer-name))
+ (emms-play-directory-tree (expand-file-name "~/ik/"))
+ (emms-shuffle))
+ (defun switch-to-emms ()
+ (interactive)
+
+ (if (get-buffer emms-playlist-buffer-name)
+ (emms-playlist-mode-go)
+ (message "The Ainur cannot hear you...")))
+
+ (add-to-list 'emms-player-list 'emms-player-mpv)
+ (emms-playing-time-disable-display)
+ (setq-default emms-playlist-default-major-mode 'emms-playlist-mode
+
+ emms-player-list '(emms-player-mpv)
+ emms-player-mpv-environment '("PULSE_PROP_media.role=music")
+ emms-player-mpv-parameters '("--quiet" "--really-quiet" "--no-audio-display" "--force-window=no" "--vo=null"))
+ (setopt
+ emms-track-description-function
+ '(lambda (track)
+ (let ((artist (emms-track-get track 'info-artist))
+ (title (emms-track-get track 'info-title)))
+ (cond
+ ((and artist title)
+ (concat artist " - " title))
+ (title
+ title)
+ (t
+ (emms-track-simple-description track))))))
+ (setopt emms-source-file-default-directory (expand-file-name "~/ik/playlists/")
+ emms-info-report-each-num-tracks 2000
+ emms-playlist-buffer-name "*Playlist*"
+ emms-mode-line-icon-enabled-p nil
+ emms-mode-line-length-limit 35
+ emms-mode-line-format " [%s] "
+ emms-repeat-playlist t
+ emms-info-functions '(emms-info-native
+ emms-info-exiftool))
+ (add-to-list 'display-buffer-alist
+ '((major-mode . emms-playlist-mode)
+ (display-buffer-in-side-window)
+ (side . left)
+ (slot . 0)
+ (width . 0.2)
+ (post-command-select-window t))))
+
+
+(provide 'bd--emms)
+;;; bd--emms.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--exwm.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--exwm.el
new file mode 100644
index 0000000..1738ebe
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--exwm.el
@@ -0,0 +1,115 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(use-package exwm
+ :demand t
+ :config
+
+ (require 'exwm-randr)
+ (setopt exwm-randr-workspace-monitor-plist '(0 "HDMI-1" 1 "eDP-1")
+ exwm-workspace-number 10)
+ (add-hook 'exwm-randr-screen-change-hook
+ (lambda ()
+ (start-process-shell-command
+ "xrandr" nil
+ "xrandr --output HDMI-1 --mode 2560x1440 --primary --auto --left-of eDP-1 --output eDP-1 --mode 1920x1080")
+ (bd/set-bg)))
+ (exwm-randr-mode)
+
+ (defun bd/exwm-update-title ()
+ "Changes the buffer name to reflect the class name for
+that buffer."
+ (exwm-workspace-rename-buffer exwm-title))
+ (add-hook 'exwm-update-title-hook #'bd/exwm-update-title)
+ (define-key exwm-mode-map [?\C-q] 'exwm-input-send-next-key)
+ (exwm-enable)
+ (setopt exwm-replace nil
+ exwm-manage-force-tiling nil
+ exwm-input-prefix-keys
+ `([?\C-x]
+ [?\C-u]
+ [?\C-g]
+ [?\C-h]
+ [?\C-z]
+ [?\C-`]
+ [?\M-x]
+ [?\M-`]
+ [?\M-&]
+ [?\M-:]
+ [?\M-!]
+ ,@(mapcar (lambda (i)
+ (kbd (concat "s-" (number-to-string i))))
+ (number-sequence 0 9)))
+
+ exwm-input-global-keys
+ `(([?\s-n] . other-window)
+ ([?\s-p] . (lambda ()
+ (interactive)
+ (other-window -1)))
+ ([?\s-L] . bd/lock)
+ ([f2] . bd/toggle-mute)
+ ([f5] . bd/decrement-volume)
+ ([f6] . bd/increment-volume)
+ ([f7] . bd/decrement-brightness)
+ ([f8] . bd/increment-brightness)
+ ([f9] . emms-previous)
+ ([f10] . emms-next)
+ ([print] . bd/shoot-part)
+ ([S-print] . bd/shoot-full)
+ ([?\s-O] . bd/browse-dispatcher)
+ ([?\s-P] . bd/password)
+ ([?\s-r] . exwm-reset)
+ ([?\s-d] . toggle-window-dedicated)
+ ([?\s-t] . bd/toggle-tab-bar)
+ ([?\s-q] . kill-current-buffer)
+ ([?\s-x] . (lambda (command)
+ (interactive (list (read-shell-command "s-x ")))
+ (start-process-shell-command command nil command)))
+ ,@(mapcar (lambda (i)
+ `(,(kbd (format "s-%s" (car i))) .
+ (lambda ()
+ (interactive
+ (exwm-workspace-switch-create ,(car (cdr i)))))))
+ '((! 0) (@ 1) (\# 2) ($ 3) (% 4) (^ 5) (& 6) (* 7) (\( 8) (\) 9))))
+
+ exwm-input-simulation-keys
+ '(([?\C-b] . [left])
+ ([?\C-f] . [right])
+ ([?\C-p] . [up])
+ ([?\C-n] . [down])
+ ([?\C-a] . [home])
+ ([?\C-e] . [end])
+ ([?\C-j] . [S-return])
+ ([?\C-m] . [return])
+ ([?\M-v] . [prior])
+ ([?\C-v] . [next])
+ ([?\C-d] . [delete])
+ ([?\C-k] . [S-end delete])
+ ([?\M-w] . [C-c])
+ ([?\C-y] . [C-v])
+ ([?\C-s] . [C-g])
+ ([?\C-r] . [C-S-g])
+ ([?\M-d] . [C-delete])
+ ([?\M-b] . [C-left])
+ ([?\M-f] . [C-right]))))
+
+(use-package exwm-outer-gaps
+ :defer 1
+ :config
+ (setopt exwm-outer-gaps-mode 1
+ exwm-outer-gaps-width 10)
+ (exwm-outer-gaps-apply))
+
+(use-package server
+ :defer 1
+ :config
+ (setopt server-client-instructions nil)
+ (unless (server-running-p)
+ (server-start)))
+
+(setopt tab-bar-select-tab-modifiers '(super))
+
+(provide 'bd--exwm)
+;;; bd--exwm.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--files.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--files.el
new file mode 100644
index 0000000..c2f3df8
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--files.el
@@ -0,0 +1,92 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(use-package files
+ :config
+ (setopt safe-local-variable-directories
+ '("/home/bdunahu/pt/guix")))
+
+(use-package recentf
+ :demand t
+ :bind
+ (("C-x g" . recentf-open))
+ :init
+ (recentf-mode 1)
+ :config
+ (setopt recentf-max-saved-items 3500
+ recentf-auto-cleanup 300))
+
+(defun bd/zathura (file)
+ "Open FILE with zathura."
+ (start-process "zathura" nil "zathura" (expand-file-name file)))
+(defun bd/mpv (file)
+ "Open FILE with mpv."
+ (start-process "mpv" nil "mpv" "--force-window=yes" (expand-file-name file)))
+(defun bd/info (file)
+ "Open FILE with info."
+ (info file))
+(defun bd/nsxiv (file)
+ "Open FILE with nsxiv."
+ (start-process "nsxiv" nil "nsxiv" (expand-file-name file)))
+(defun bd/qemu (file)
+ "Open FILE with QEMU."
+ (let ((options `("qemu-system" nil "qemu-system-x86_64" ,(expand-file-name file)
+ "-net" "nic" "-net""user,hostfwd=tcp:127.0.0.1:2222-:22"
+ "--accel" "kvm" "-m" "8G")))
+ (unless (string-match-p "graphic" file)
+ (setq options (append options (list "-nographic"))))
+ (apply 'start-process options)))
+(defun bd/rom (file)
+ "Open FILE with an emulator."
+ (let ((command
+ (cdr (assoc (file-name-extension file)
+ '(("gba" . "mgba")
+ ("z64" . "mupen64plus")
+ ("iso" . "dolphin-emu")
+ ("ciso" . "dolphin-emu")
+ ("n64" . "mupen64plus")
+ ("sfc" . "bsnes"))))))
+ (start-process command nil command (expand-file-name file))))
+
+(defun bd/external-find-file-wrapper (f &rest args)
+ "Wrapper around F (find-file), passing ARGS."
+ (defun bd/open-with-function (f)
+ (funcall f (car args))
+ (recentf-add-file (car args)))
+
+ (let ((ext (or (file-name-extension (car args)) "")))
+ (cond
+ ((string-match (regexp-opt '("epub" "pdf")) ext)
+ (bd/open-with-function #'bd/zathura))
+ ((string-match (regexp-opt '("mkv" "mov" "mp4" "webm" "m4v"
+ "wav" "mp3" "opus" "ogv" "flac"
+ "m4a" "ogg")) ext)
+ (bd/open-with-function #'bd/mpv))
+ ((string-match (regexp-opt '("info")) ext)
+ (bd/open-with-function #'bd/info))
+ ((string-match (regexp-opt '("jpg" "jpeg" "png" "webp"
+ "ico" "gif" "JPG" "PNG")) ext)
+ (bd/open-with-function #'bd/nsxiv))
+ ((string-match (regexp-opt '("qcow2")) ext)
+ (bd/open-with-function #'bd/qemu))
+ ((string-match (regexp-opt '("gba" "z64" "n64" "sfc" "iso" "ciso")) ext)
+ (bd/open-with-function #'bd/rom))
+ (t (apply f args)))))
+(advice-add #'find-file :around #'bd/external-find-file-wrapper)
+
+(use-package dired
+ :demand t
+ :config
+ (setopt dired-listing-switches "-alhLG --time-style=long-iso --group-directories-first"
+ dired-recursive-copies 'always
+ dired-recursive-deletes 'always
+ dired-auto-revert-buffer t
+ dired-dwim-target t
+ dired-guess-shell-alist-user
+ `((,(regexp-opt '(".pdf")) "pdftotext -nopgbrk -enc UTF-8 -eol unix -layout"))))
+
+
+(provide 'bd--files)
+;;; bd--files.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--gpg.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--gpg.el
new file mode 100644
index 0000000..88d9425
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--gpg.el
@@ -0,0 +1,59 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(defvar bd/password-store-kill-ring-pointer nil
+ "The tail of the kill ring whose car is the password.")
+(defvar bd/password-store-time-before-clear 10
+ "The time before a killed password is cleared.")
+
+(defun bd/password-store-list ()
+ "List password-store entries."
+ (mapcar (lambda (file)
+ (file-name-sans-extension (file-relative-name file "~/.password-store/")))
+ (directory-files-recursively "~/.password-store" ".*\\.gpg$")))
+
+(defun bd/password-store-clear (id)
+ "Clears the most recent password copied to the kill ring, printing ID."
+ (when bd/password-store-kill-ring-pointer
+ (setcar bd/password-store-kill-ring-pointer nil)
+ (kill-new "")
+ (setq bd/password-store-kill-ring-pointer nil)
+ (message "Cleared password for %s from the kill ring and system clipboard." id)))
+
+(defun bd/read-password (id)
+ "Read the password-store entry corresponding to ID."
+ (bd/password-store-clear "id")
+ (let ((find-file-hook (remq 'recentf-track-opened-file find-file-hook)))
+ (find-file (concat "~/.password-store/" id ".gpg"))
+ (goto-char 1)
+ (kill-new (buffer-substring-no-properties (line-beginning-position) (line-end-position)))
+ (setq bd/password-store-kill-ring-pointer kill-ring-yank-pointer)
+ (kill-buffer (current-buffer))
+ (run-at-time bd/password-store-time-before-clear nil
+ (lambda () (funcall #'bd/password-store-clear id)))
+ (message "Copied password for %s to the kill ring and system clipboard. Will clear in %s seconds."
+ id bd/password-store-time-before-clear)))
+
+(defun bd/password ()
+ "Interactively select a password-store password."
+ (interactive)
+ (bd/read-password (completing-read "Yank: " (bd/password-store-list))))
+
+(use-package pinentry
+ :defer 1
+ :config
+ (pinentry-start)
+ (defun pinentry-toggle ()
+ "Stops and starts Pinentry service. Workaround
+for a bug I've encountered."
+ (interactive)
+ (pinentry-stop)
+ (pinentry-start))
+ (setopt enable-recursive-minibuffers t
+ pinentry-popup-prompt-window nil))
+
+
+(provide 'bd--gpg)
+;;; bd--gpg.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--image.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--image.el
new file mode 100644
index 0000000..1d5876c
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--image.el
@@ -0,0 +1,22 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; not loaded bd 10/28/24
+;;; Code:
+
+
+(use-package image-mode
+ :config
+ (defun toggle-mode-line ()
+ "If the mode line is displayed, toggle it off.
+If the mode line is off, set it to the default value."
+ (interactive)
+ (setq mode-line-format
+ (if mode-line-format
+ nil
+ (default-value 'mode-line-format))))
+ (add-hook 'image-mode-hook 'toggle-mode-line)
+ (keymap-set image-mode-map "l" #'toggle-mode-line))
+
+
+(provide 'bd--image)
+;;; bd--image.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--minibuffer.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--minibuffer.el
new file mode 100644
index 0000000..cf5641b
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--minibuffer.el
@@ -0,0 +1,171 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(require 's)
+(use-package icomplete
+ :demand t
+ :bind
+ ((:map icomplete-minibuffer-map
+ ("RET" . icomplete-force-complete-and-exit)))
+ :config
+ (setopt completing-read-function #'completing-read-default
+ read-file-name-function #'read-file-name-default
+ completion-styles '(basic
+ substring
+ initials
+ flex)
+ icomplete-delay-completions-threshold 0
+ icomplete-compute-delay 0.10
+ icomplete-show-matches-on-no-input nil
+ icomplete-separator " | "
+ completions-max-height '30
+ completions-detailed t)
+ (icomplete-vertical-mode t))
+
+(use-package completion-preview
+ :hook
+ ((comint-mode . completion-preview-mode)
+ (c-mode . completion-preview-mode)
+ (c++-mode . completion-preview-mode)
+ (emacs-lisp-mode . completion-preview-mode)))
+
+(use-package selector
+ :demand t
+ :config
+ (setopt selector-minibuffer-lines 15)
+ (defvar bd/navigate-recent-display-number 6
+ "The number of recent buffers that show up in bd/navigate.")
+ (defvar bd/blacklisted-buffer-regexp-list
+ '(
+ "\\*Async Shell Command\\*"
+ "\\*http"
+ "\\magit-process"
+ "\\*Minibuf"
+ "\\*Echo Area"
+ "\\*newsticker"
+ "\\*Org Preview LaTeX Output\\*"
+ "\\*Shell Command Output\\*"
+ "\\*tramp"
+ "\\*eldoc"
+ "\\*server\\*"
+ )
+ "Buffers that should not show up in buffer-related
+selection commands.")
+
+ (defun bd/buffer-blacklisted-p (buf)
+ "Return non-nil if BUF is blacklisted."
+ (cl-reduce (lambda (x y) (or x y)) (mapcar (lambda (r) (string-match r buf))
+ bd/blacklisted-buffer-regexp-list)))
+
+ (defun bd/buffer-list ()
+ "Return a list of non-blacklisted buffers."
+ (cl-remove-if #'bd/buffer-blacklisted-p (mapcar 'buffer-name (buffer-list))))
+
+ (defun bd/selector-recent-buffers ()
+ (selector-source-create
+ "Recent"
+ :candidates
+ (take bd/navigate-recent-display-number
+ (cl-remove-if (lambda (b)
+ (get-buffer-window b 'visible))
+ (bd/buffer-list)))
+ :actions
+ selector-buffer-actions))
+
+ (defun bd/selector-project-files ()
+ (selector-source-create
+ "Project Files"
+ :candidates
+ (let ((proj (project-current)))
+ (when proj
+ (project-files proj)))
+ :actions
+ selector-file-actions))
+
+ (defmacro bd/selector-buffer-type (name c)
+ `(selector-source-create
+ ,name
+ :candidates
+ (cl-remove-if-not ,c (bd/buffer-list))
+ :actions
+ selector-buffer-actions))
+
+ (defun bd/navigate ()
+ (interactive)
+ (selector
+ (list
+ (bd/selector-recent-buffers)
+ (bd/selector-buffer-type "EWW" #'bd/buffer-eww-p)
+ (bd/selector-buffer-type "EXWM" #'bd/buffer-exwm-p)
+ (bd/selector-buffer-type "Shell" #'bd/buffer-shell-p)
+ (bd/selector-buffer-type "IRC" #'bd/buffer-irc-p)
+ (bd/selector-buffer-type "Text" #'bd/buffer-text-p)
+ (bd/selector-buffer-type "Source" #'bd/buffer-prog-p)
+ (bd/selector-buffer-type "Scratch" #'bd/buffer-scratch-p)
+ (bd/selector-buffer-type "Directories" #'bd/buffer-dired-p)
+ (bd/selector-buffer-type "Ordinary" #'bd/buffer-ordinary-p)
+ (bd/selector-project-files)
+ (selector-recentf-source))))
+ (keymap-global-set "C-x b" 'bd/navigate)
+
+ (defun bd/selector-rg ()
+ "Sources for lines found via grep (or a clone)."
+ (interactive)
+ (let ((query (read-string "rg: ")))
+ (defun conv (x)
+ (cons (car x) (cons (- (string-to-number (cadr x)) 1) (caddr x))))
+ (defun all-in-file (key list)
+ (--map (to-candidate (cdr it)) (--filter (s-equals? key (car it)) list)))
+ (defun to-candidate (x)
+ (selector-candidate-create (cdr x) :value (car x)))
+ (let* ((dir (expand-file-name (bd/get-directory-dwim)))
+ (result (with-temp-buffer
+ (call-process "rg" nil t nil "-n" "-." query dir)
+ (buffer-string)))
+ (lines (--map (conv (s-split-up-to ":" it 2)) (--filter (not (s-blank? it)) (s-split "\n" result))))
+ (files (-uniq (-map #'car lines)))
+ (sources (--map (selector-source-create
+ it
+ :candidates (all-in-file it lines)
+ :actions (selector-file-contents-actions it))
+ files)))
+ (when (not (null sources))
+ (selector sources)))))
+ (keymap-global-set "C-z s" 'bd/selector-rg)
+ (keymap-global-set "C-z d" 'bd/selector-occur))
+
+(defun bd/selector-occur ()
+ "Sources for lines in the current file."
+ (interactive)
+ (when-let ((file (buffer-file-name)))
+ (selector (list (selector-file-contents-source file)))))
+
+(defun bd/selector-rg ()
+ "Sources for lines found via grep (or a clone)."
+ (interactive)
+ (let ((query (read-string "rg: ")))
+ (defun conv (x)
+ (cons (car x) (cons (- (string-to-number (cadr x)) 1) (caddr x))))
+ (defun all-in-file (key list)
+ (--map (to-candidate (cdr it)) (--filter (s-equals? key (car it)) list)))
+ (defun to-candidate (x)
+ (selector-candidate-create (cdr x) :value (car x)))
+ (let* ((dir (expand-file-name (bd/get-directory-dwim)))
+ (result (with-temp-buffer
+ (call-process "rg" nil t nil "-n" "-." query dir)
+ (buffer-string)))
+ (lines (--map (conv (s-split-up-to ":" it 2)) (--filter (not (s-blank? it)) (s-split "\n" result))))
+ (files (-uniq (-map #'car lines)))
+ (sources (--map (selector-source-create
+ it
+ :candidates (all-in-file it lines)
+ :actions (selector-file-contents-actions it))
+ files)))
+ (when (not (null sources))
+ (selector sources)))))
+
+
+(provide 'bd--minibuffer)
+;;; bd--minibuffer.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--modeline.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--modeline.el
new file mode 100644
index 0000000..2ca7ccb
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--modeline.el
@@ -0,0 +1,99 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+;;;; remove all the stupid stupid stupid crap
+(define-minor-mode minor-mode-blackout-mode
+ "Hides minor modes from the mode line."
+ t)
+
+(catch 'done
+ (mapc (lambda (x)
+ (when (and (consp x)
+ (equal (cadr x) '("" minor-mode-alist)))
+ (let ((original (copy-sequence x)))
+ (setcar x 'minor-mode-blackout-mode)
+ (setcdr x (list "" original)))
+ (throw 'done t)))
+ mode-line-modes))
+(global-set-key (kbd "C-c ,") 'minor-mode-blackout-mode)
+
+(defvar-local bd/buffer-identification-mode-line
+ '(:eval (format "%s" (propertize (buffer-name) 'face
+ (if (mode-line-window-selected-p)
+ 'modus-themes-fg-cyan-intense
+ 'mode-line-inactive))))
+ "Formats the modeline-buffer-name.")
+
+(defvar-local bd/project-mode-line
+ '(:eval
+ (when-let ((project (project-current))
+ (file? (buffer-file-name)))
+ (let ((last-coding-system-used last-coding-system-used))
+ (format "%s "
+ (propertize
+ (project-name project)
+ 'face 'shadow
+ 'mouse-face 'mode-line-highlight
+ 'help-echo "mouse-1: Project menu"
+ 'local-map project-mode-line-map))))))
+
+(defvar-local bd/global-mode-string
+ '(:eval (when (mode-line-window-selected-p)
+ global-mode-string))
+ "Displays the global mode string only on the current window.")
+
+(column-number-mode 1)
+(setopt mode-line-position-column-line-format '("%l:%c")
+ mode-line-percent-position nil)
+(defvar-local bd/line-position
+ '(:eval
+ (when (mode-line-window-selected-p)
+ mode-line-position)))
+
+(defvar-local bd/vc-mode-line
+ '(:eval (when vc-mode
+ (format "%s "
+ (propertize (cadr (split-string vc-mode "[:-]")) 'face 'shadow))))
+ "Formats the checked out git repository.")
+
+(defvar-local bd/modeline-window-dedicated
+ '(:eval
+ (when (window-dedicated-p)
+ (format "%s -- "
+ (propertize "LK" 'face 'font-lock-comment-face))))
+ "Indicator for dedicated window.")
+
+(dolist (construct '(bd/buffer-identification-mode-line
+ bd/project-mode-line
+ bd/vc-mode-line
+ bd/global-mode-string
+ bd/line-position
+ bd/modeline-window-dedicated))
+ (put construct 'risky-local-variable t))
+
+
+(setopt mode-line-right-align-edge 'window)
+(setq-default mode-line-format
+ '("%e"
+ mode-line-front-space
+ bd/project-mode-line
+ bd/buffer-identification-mode-line
+ " -- "
+ mode-line-modes
+ "-- "
+ bd/line-position
+ mode-line-format-right-align
+ bd/modeline-window-dedicated
+ bd/vc-mode-line
+ mode-line-mule-info
+ mode-line-modified
+ mode-line-front-space
+ bd/global-mode-string
+ mode-line-front-space
+ ))
+
+
+(provide 'bd--modeline)
+;;; bd--modeline.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--notes.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--notes.el
new file mode 100644
index 0000000..13f80f4
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--notes.el
@@ -0,0 +1,77 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(defvar scratch-buffer nil
+ "Non-nil if the current buffer is a scratch buffer.")
+(make-variable-buffer-local 'scratch-buffer)
+(defun bd/send-to-scratch ()
+ "Creates/switches to the scratch for `major-mode',
+then pastes the active region."
+ (interactive)
+ (let* ((mode major-mode)
+ (name (format "*scratch for %s*" mode))
+ (contents (when (region-active-p)
+ (buffer-substring-no-properties
+ (region-beginning) (region-end))))
+ (buf (get-buffer name)))
+ (pop-to-buffer
+ (with-current-buffer (get-buffer-create name)
+ (funcall mode)
+ (setq-local scratch-buffer t)
+ (when contents
+ (insert (format "\n\n%s" contents)))
+ (current-buffer)))))
+(keymap-global-set "C-c s" #'scratch-buffer)
+(keymap-global-set "C-c C-s" #'bd/send-to-scratch)
+;; default *scratch* must have var set
+(add-hook 'emacs-startup-hook
+ (lambda ()
+ (with-current-buffer "*scratch*"
+ (setq-local scratch-buffer t))))
+(advice-add 'scratch-buffer :after (lambda () (setq-local scratch-buffer t)))
+
+(use-package denote
+ :defer 1
+ :hook
+ ((dired-mode . denote-dired-mode-in-directories))
+ :bind (("C-c d d" . 'denote)
+ ("C-c d f" . 'denote-open-or-create)
+ :map org-mode-map
+ ("C-c l" . 'denote-link))
+ :config
+
+ (defconst bd/denote-skribe-front-matter
+ "(post
+ :title \"%s\"
+ :date %s
+ :tags '(\"%s\")
+ ;; identifier: %s
+\n\n)")
+ (defun bd/denote-skribe-format-date (date)
+ "Format DATE as a scheme procedure."
+ (format-time-string "(make-date* %Y %m %d %H %M)" date))
+ (defun bd/denote-format-keywords-for-skribe-front-matter (keywords)
+ "Format front matter KEYWORDS for skribe file type.
+KEYWORDS is a list of strings."
+ (string-join keywords "\" \""))
+ :config
+ (setopt denote-file-type 'org
+ denote-known-keywords
+ '("ss" "writing" "reading" "art"
+ "csu" "umass" "cs" "guix"
+ "emacs" "programs" "mem")
+ denote-directory (expand-file-name "~/dc/")
+ denote-prompts '(title file-type keywords subdirectory)
+ denote-dired-directories (list denote-directory)))
+
+(use-package denote-journal
+ :bind (("C-c d j" . 'denote-journal-new-or-existing-entry))
+ :config
+ (setopt denote-journal-directory (expand-file-name "~/dc/log")
+ denote-journal-title-format 'day-date-month-year))
+
+
+(provide 'bd--notes)
+;;; bd--notes.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--org.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--org.el
new file mode 100644
index 0000000..b88a72b
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--org.el
@@ -0,0 +1,255 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(require 'fill-column)
+(use-package org
+ :demand t
+ :hook
+ ((org-mode . (lambda ()
+ (org-indent-mode)
+ (org-toggle-inline-images)
+ (fill-column-mode)
+ (abbrev-mode)
+ (org-latex-preview '(16)))))
+ :config
+ (setopt org-ellipsis " ▾"
+ org-babel-python-command "python3"
+ org-confirm-babel-evaluate nil
+ org-hide-emphasis-markers t
+ org-startup-folded 'showeverything
+ org-src-window-setup 'current-window)
+ (plist-put org-format-latex-options :scale 1.3)
+ (push '("\\.pdf\\'" . "zathura %s") org-file-apps)
+ (add-hook 'org-babel-after-execute-hook #'org-redisplay-inline-images)
+ (org-babel-do-load-languages
+ 'org-babel-load-languages
+ '((emacs-lisp . t)
+ (shell . t)
+ (lisp . t)
+ (scheme . t)
+ (dot . t)
+ (latex . t)
+ (python . t)))
+ (define-abbrev org-mode-abbrev-table
+ "lbm" "\\begin{equation*}\n\\begin{bmatrix}\n\\end{bmatrix}\n\\end{equation*}")
+ (define-abbrev org-mode-abbrev-table
+ "lca" "\\begin{equation*}\n\\begin{cases}\n\\end{cases}\n\\end{equation*}")
+ (define-abbrev org-mode-abbrev-table
+ "gvd" "#+begin_src dot :file images/1.png\ndigraph g {\nrankdir=LR;\n\tnode [shape = doublecircle]; A;\n\tnode [shape = point]; qi\n\tnode[shape=circle];\n}\n#+end_src")
+ (define-abbrev org-mode-abbrev-table
+ "les" "\\begin{equation*}\n\\end{equation*}")
+ (define-abbrev org-mode-abbrev-table
+ "lbm" "\\begin{equation*}\n\\begin{bmatrix}\n\\end{bmatrix}\n\\end{equation*}")
+ (define-abbrev org-mode-abbrev-table
+ "lds" "\\begin{drawstack}\n\\end{drawstack}"))
+
+(use-package ox
+ :config
+ (add-to-list 'org-latex-packages-alist '("" "listings"))
+ (setopt org-latex-toc-command "\\tableofcontents \\clearpage"
+ org-latex-src-block-backend 'listings
+ org-latex-image-default-width ".6\\linewidth"
+ org-latex-compiler "xelatex"
+ org-export-with-toc nil
+ org-export-preserve-breaks nil
+ org-latex-classes
+ '(("article"
+ "\\PassOptionsToPackage{svgnames}{xcolor}
+\\documentclass[11pt]{article}
+\\usepackage[margin=1in]{geometry}
+\\usepackage{xcolor,color,tikz,amsmath,amssymb,amsthm,amsfonts,graphicx,enumitem,listings,comment}
+\\lstset{frame=single,aboveskip=1em,
+ framesep=.5em,backgroundcolor=\\color{AliceBlue},
+ rulecolor=\\color{Black},framerule=1pt}
+\\usepackage{xcolor}
+\\newcommand\\basicdefault[1]{\\scriptsize\\color{Black}\\fontfamily{pcr}\\selectfont}
+\\lstset{basicstyle=\\basicdefault{\\spaceskip1em}}
+\\lstset{keywordstyle=\\color{DarkGreen}\\bfseries,
+ identifierstyle=\\color{DarkRed},
+ commentstyle=\\color{DimGray}\\upshape,
+ stringstyle=\\color{DarkBlue}\\upshape,
+ emphstyle=\\color{Chocolate}\\upshape,
+ showstringspaces=false,
+ columns=fullflexible,
+ keepspaces=true}
+\\lstset{columns=fullflexible,basicstyle=\\ttfamily}
+\\setlength{\\headsep}{0.75 in}
+\\setlength{\\parskip}{0.1 in}
+
+\\makeatletter
+\\renewcommand{\\maketitle}{%
+ \\begingroup\\parindent0pt
+ \\begin{center}
+ \\LARGE{\\bfseries\\@title}\\par\\bigskip
+ \\large{\\@author}\\par\\medskip
+ \\normalsize\\@date\\par\\bigskip
+ \\end{center}
+ \\endgroup\\@afterindentfalse\\@afterheading}
+\\makeatother
+[DEFAULT-PACKAGES]
+\\hypersetup{linkcolor=Blue,urlcolor=DarkBlue,
+ citecolor=DarkRed,colorlinks=true}
+[PACKAGES]
+[EXTRA]"
+ ("\\section{%s}" . "\\section*{%s}")
+ ("\\subsection{%s}" . "\\subsection*{%s}")
+ ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
+ ("\\paragraph{%s}" . "\\paragraph*{%s}")
+ ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
+ ("report" "\\documentclass[11pt]{report}"
+ ("\\part{%s}" . "\\part*{%s}") ("\\chapter{%s}" . "\\chapter*{%s}")
+ ("\\section{%s}" . "\\section*{%s}")
+ ("\\subsection{%s}" . "\\subsection*{%s}")
+ ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
+ ("acmart" "\\PassOptionsToPackage{svgnames}{xcolor}
+\\documentclass[sigconf,authorversion,nonacm]{acmart}
+\\usepackage{xcolor,color,tikz,amsmath,amssymb,amsthm,amsfonts,graphicx,enumitem,listings,comment}
+\\usepackage{xcolor}
+\\lstset{frame=single,aboveskip=1em,
+ framesep=.5em,backgroundcolor=\\color{AliceBlue},
+ rulecolor=\\color{Black},framerule=1pt}
+\\lstset{keywordstyle=\\color{DarkGreen}\\bfseries,
+ identifierstyle=\\color{DarkRed},
+ commentstyle=\\color{DimGray}\\upshape,
+ stringstyle=\\color{DarkBlue}\\upshape,
+ emphstyle=\\color{Chocolate}\\upshape,
+ showstringspaces=false,
+ columns=fullflexible,
+ keepspaces=true}"
+ ("\\section{%s}" . "\\section*{%s}")
+ ("\\subsection{%s}" . "\\subsection*{%s}")
+ ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
+ ("\\paragraph{%s}" . "\\paragraph*{%s}")
+ ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
+ ("book" "\\documentclass[11pt]{book}" ("\\part{%s}" . "\\part*{%s}")
+ ("\\chapter{%s}" . "\\chapter*{%s}")
+ ("\\section{%s}" . "\\section*{%s}")
+ ("\\subsection{%s}" . "\\subsection*{%s}")
+ ("\\subsubsection{%s}" . "\\subsubsection*{%s}")))))
+
+(use-package oc
+ :config
+ (setopt org-cite-global-bibliography
+ (directory-files-recursively
+ (concat (xdg-user-dir "DOCUMENTS") "/bib/")
+ ".*\\.bib$")
+ org-cite-export-processors
+ '((latex biblatex))))
+
+(use-package org-agenda
+ :bind
+ (("C-c n a" . org-agenda)
+ ("C-c n c" . org-capture)
+ ("C-c n s" . org-schedule)
+ ("C-c n d" . org-deadline)
+ ("C-c n r" . org-refile))
+ :config
+ (advice-add 'org-refile :after 'org-save-all-org-buffers)
+
+ (defvar-local bd/course-list
+ '(("598" . ?0))
+ "Courses for tagging, capturing, and various
+agenda views.")
+
+ (defvar-local bd/device-list
+ '(("garmr" . ?G)
+ ("heimdallr" . ?H)
+ ("hodr" . ?M)
+ ("surt" . ?I)
+ ("vali" . ?V))
+ "Devices for tagging, capturing, and various
+agenda views.")
+
+ (defun bd/combine-tags (tag-alist add? or-p)
+ "Given TAG-ALIST in the form of
+ORG-TAG-ALIST, returns a concatenated string
+representing all the tags ORd or ANDed together."
+ (apply #'concat
+ (mapcar (lambda (e)
+ (concat (when or-p "|") (if add? "+" "-") (car e)))
+ tag-alist)))
+
+ (setopt org-log-done 'time
+ org-deadline-warning-days 7
+ org-log-into-drawer "history"
+ org-agenda-restore-windows-after-quit t
+ org-agenda-show-future-repeats nil
+ org-agenda-block-separator nil
+ org-deadline-warning-days 0
+ org-todo-keywords
+ '((sequence "TODO(t)" "NEXT(n!)" "HOLD(h!)" "|" "DONE(d)" "CANC(c)"))
+ org-tag-alist
+ `(("noexport" . ?e)
+
+ ("chore" . ?C)
+ ,@bd/device-list
+
+ ("idea" . ?i)
+ ("programming" . ?p)
+ ("web" . ?s)
+ ("writing" . ?w)
+ ("reading" . ?r)
+
+ ,@bd/course-list)
+ org-directory "~/dc/agenda/"
+
+ org-agenda-files (list org-directory)
+ org-refile-use-outline-path 'file
+ org-refile-targets '((org-agenda-files :level . 0))
+ org-capture-templates
+ `(("t" "Task Entry" entry
+ (file ,(concat org-directory "inbox.org"))
+ "* %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n%i\nrecorded visiting: %a"
+ :empty-lines 1)))
+
+ (let ((orphan-view '(tags-todo "-{.*}"
+ ((org-agenda-overriding-header "\nUnfiltered Items\n")))))
+ (setopt org-agenda-custom-commands
+ `(("c" "Chores"
+ ((agenda "" ((org-agenda-entry-types '(:deadline))
+ (org-agenda-show-all-dates nil)
+ (org-agenda-span 21)
+ (org-agenda-skip-function
+ '(org-agenda-skip-entry-if 'notregexp
+ (regexp-opt (cons "chore" (mapcar #'car bd/device-list)))))
+ (org-agenda-overriding-header "Priority Deadlines (+21d)\n")))
+ (tags-todo (concat "chore" (bd/combine-tags bd/device-list nil nil))
+ ((org-agenda-overriding-header "\nMundane\n")))
+ ,@(mapcar (lambda (e)
+ `(tags-todo ,(car e)
+ ((org-agenda-overriding-header
+ (concat "\nDevice: " ,(capitalize (car e)) "\n")))))
+ bd/device-list)
+ ,orphan-view))
+ ("r" "Recreational"
+ ((tags-todo "+idea"
+ ((org-agenda-skip-function
+ '(org-agenda-skip-entry-if 'regexp "chore"))
+ (org-agenda-overriding-header "Ideas\n")))
+ (tags-todo "+reading|+writing"
+ ((org-agenda-skip-function
+ '(org-agenda-skip-entry-if 'regexp "chore"))
+ (org-agenda-overriding-header "\nReading/Writing\n")))
+ (tags-todo "+programming|+web"
+ ((org-agenda-skip-function
+ '(org-agenda-skip-entry-if 'regexp "chore"))
+ (org-agenda-overriding-header "\nProgramming\n")))
+ ,orphan-view))
+ ("s" "College"
+ ((tags-todo (bd/combine-tags bd/course-list t t)
+ ((org-agenda-overriding-header "\nAll Courses\n")))
+ (agenda "" ((org-agenda-entry-types '(:deadline :scheduled))
+ (org-agenda-show-all-dates nil)
+ (org-agenda-span 15)
+ (org-scheduled-past-days 7)
+ (org-agenda-skip-function
+ '(org-agenda-skip-entry-if 'notregexp
+ (regexp-opt (mapcar #'car bd/course-list))))
+ (org-agenda-overriding-header "\nUpcoming Deadlines (+15d)\n")))
+ ,orphan-view))))))
+
+
+(provide 'bd--org)
+;;; bd--org.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--project.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--project.el
new file mode 100644
index 0000000..243e3cd
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--project.el
@@ -0,0 +1,18 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(use-package project
+ :defer t
+ :config
+ (setopt project-switch-commands
+ '((project-find-file "Find file")
+ (project-find-regexp "Find regexp")
+ (project-find-dir "Find directory")
+ (magit-project-status "Magit")
+ (project-shell "Shell"))))
+
+
+(provide 'bd--project)
+;;; bd--project.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--shells.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--shells.el
new file mode 100644
index 0000000..1072694
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--shells.el
@@ -0,0 +1,135 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(add-to-list 'exec-path "/home/bdunahu/.local/bin")
+
+(use-package vterm
+ :config
+ (with-eval-after-load "term" (defalias 'term 'vterm))
+ ;; use vterm for visual visual commands
+ (defun bd/eshell-exec-visual (&rest args)
+ "Run the specified PROGRAM in a vterm emulation buffer.
+ARGS are passed to the program. At the moment, no piping of input is
+allowed."
+ (let* (eshell-interpreter-alist
+ (interp (eshell-find-interpreter (car args) (cdr args)))
+ (program (car interp))
+ (args (mapconcat #'shell-quote-argument
+ (flatten-tree
+ (eshell-stringify-list (append (cdr interp)
+ (cdr args)))) " "))
+ (term-buf
+ (generate-new-buffer
+ (concat "*" (file-name-nondirectory program) "*")))
+ (eshell-buf (current-buffer))
+ (vterm-shell (concat (shell-quote-argument
+ (file-local-name program))
+ " " args)))
+ (save-current-buffer
+ (switch-to-buffer term-buf)
+ (vterm-mode)
+ (setq-local eshell-parent-buffer eshell-buf)
+ (let ((proc (get-buffer-process term-buf)))
+ (if (and proc (eq 'run (process-status proc)))
+ (set-process-sentinel proc #'eshell-term-sentinel)
+ (error "Failed to invoke visual command")))))
+ nil)
+ (advice-add #'eshell-exec-visual :override #'bd/eshell-exec-visual))
+
+(use-package esh-module
+ :config
+ (add-to-list 'eshell-modules-list 'eshell-smart))
+
+(use-package esh-mode
+ :config
+ (setopt eshell-scroll-to-bottom-on-input 'this))
+
+(use-package em-banner
+ :config
+ (setopt eshell-banner-message (concat "\n" (propertize " " 'display (create-image (expand-file-name "images/wolf.png" user-emacs-directory) 'png nil :scale 0.8 :align-to "center")) "\n")))
+
+(use-package em-hist
+ :config
+ (setopt eshell-hist-ignoredups t
+ eshell-history-append t))
+
+(use-package em-term
+ :config
+ (mapc (lambda (x) (add-to-list 'eshell-visual-commands x))
+ '(
+ "angband"
+ "nethack"
+ "r2"
+ ))
+ (setopt eshell-destroy-buffer-when-process-dies t))
+
+(use-package em-unix
+ :config
+ (setopt eshell-cp-overwrite-files nil
+ eshell-mv-overwrite-files nil))
+
+(use-package em-prompt
+ :config
+ (defun bd/get-prompt-path ()
+ (abbreviate-file-name (eshell/pwd)))
+ (defun bd/eshell-prompt ()
+ "Return a prettified shell prompt."
+ (concat
+ (system-name)
+ (format " %s" (bd/get-prompt-path))
+ " >\n"))
+ (setopt eshell-prompt-function 'bd/eshell-prompt
+ eshell-prompt-regexp (rx bol (eval (system-name)) (one-or-more anything) " >\n")))
+
+(use-package eshell
+ :bind
+ (:map eshell-mode-map
+ ("<tab>" . #'completion-at-point))
+ :config
+ (defun eshell/clear (&optional scrollback)
+ "Clear the eshell buffer and output the banner message."
+ (interactive)
+ (let ((inhibit-read-only t))
+ (delete-all-overlays)
+ (set-text-properties (point-min) (point-max) nil)
+ (erase-buffer)
+ (eval eshell-banner-message)))
+ (defun eshell/c (&optional scrollback)
+ (eshell/clear scrollback))
+ (defun eshell/o (file)
+ (interactive)
+ (find-file file))
+ (setopt eshell-buffer-maximum-lines 7500))
+
+
+(use-package shell
+ :bind
+ (:map shell-mode-map
+ ("C-c C-k" . #'comint-clear-buffer))
+ :config
+ (setopt shell-command-prompt-show-cwd t
+ shell-highlight-undef-enable t
+ shell-kill-buffer-on-exit t
+ comint-prompt-read-only t))
+
+
+(use-package proced
+ :defer t
+ :hook (proced-mode .
+ (lambda ()
+ (visual-line-mode -1)
+ (toggle-truncate-lines 1)
+ (proced-toggle-auto-update 1)))
+ :config
+ (setopt proced-enable-color-flag t
+ proced-tree-flag t
+ proced-auto-update-flag 'visible
+ proced-auto-update-interval 3
+ proced-descend t
+ proced-filter 'user))
+
+
+(provide 'bd--shells)
+;;; bd--shells.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--tabs.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--tabs.el
new file mode 100644
index 0000000..6d4f12f
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--tabs.el
@@ -0,0 +1,43 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(use-package tab-bar
+ :demand t
+ :config
+ (defun bd/get-mode-line-modes (buffer)
+ (with-current-buffer buffer
+ (format-mode-line (remove '(t erc-modified-channels-object) mode-line-modes))))
+ (defun bd/tab-bar-name-function ()
+ (concat (tab-bar-tab-name-current)
+ " "
+ (bd/get-mode-line-modes (window-buffer (minibuffer-selected-window)))))
+ (defun bd/toggle-tab-bar ()
+ (interactive)
+ (setopt tab-bar-show (not tab-bar-show))
+ ;; required to wait for the frame to update
+ (sit-for 0)
+ ;; dumb outer-gaps bug
+ (exwm-outer-gaps-apply))
+
+ (tab-bar-select-tab 1)
+ (tab-bar-mode)
+ (tab-bar-history-mode)
+ (setopt tab-bar-tab-name-function #'bd/tab-bar-name-function
+ tab-bar-show t
+
+ tab-bar-select-restore-windows nil
+
+ ;; remove useless gui elements
+ tab-bar-format
+ '(tab-bar-format-tabs)
+ tab-bar-close-button-show nil
+ tab-bar-auto-width-max nil
+
+ ;; add useless text elements
+ tab-bar-tab-hints nil))
+
+
+(provide 'bd--tabs)
+;;; bd--tabs.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--themes.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--themes.el
new file mode 100644
index 0000000..cd24cab
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--themes.el
@@ -0,0 +1,115 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(use-package modus-themes
+ :load-path (lambda () (expand-file-name "themes/" data-directory))
+ :demand t
+ :init
+ (load-theme 'modus-vivendi-tinted t)
+ :hook
+ ((modus-themes-post-load . bd/modus-set-faces))
+ :config
+ (defun bd/modus-set-faces (&rest _)
+ "Blends the modeline with the echo area,
+and some other minor face changes."
+ (modus-themes-with-colors
+ (custom-set-faces
+ `(eshell-prompt ((,c :foreground ,fg-main :background ,bg-prose-block-contents :height 1.1 :extend t))))))
+ (setopt modus-themes-to-toggle '(modus-operandi-tinted modus-vivendi-tinted)
+ modus-themes-mixed-fonts t
+ modus-themes-italic-constructs t
+ modus-themes-bold-constructs t
+ modus-themes-variable-pitch-ui t
+ modus-themes-prompts '(bold)
+ modus-themes-headings
+ '((0 variable-pitch regular 1.4)
+ (1 variable-pitch regular 1.4)
+ (2 variable-pitch regular 1.3)
+ (3 variable-pitch regular 1.2)
+ (t variable-pitch regular 1.2))
+ modus-themes-common-palette-overrides
+ '((bg-main "#000B0E") ;; primary
+ (bg-active bg-main)
+ (fg-main "#c6b7ad")
+ (fg-active fg-main)
+ (fg-mode-line-active "#008EA2")
+ (bg-mode-line-active "#012C31") ;; primary
+ (fg-mode-line-inactive "#8D6D91")
+ (bg-mode-line-inactive "#442c50") ;; secondary
+ (border-mode-line-active nil)
+ (border-mode-line-inactive nil)
+ (bg-tab-bar bg-main)
+ (bg-tab-current "#042429") ;; primary
+ (bg-tab-other "#100014") ;; secondary
+
+ (fg-heading-0 "#b2ebf2")
+ (fg-heading-1 "#98fb98")
+ (fg-heading-2 "#fa80e6")
+ (fg-heading-3 "#ff7f50")
+ (fg-heading-4 "#ffd700")
+
+ (fg-prompt "#FF4E00") ;; tertiary
+ (bg-prompt unspecified)
+
+ (bg-region "#E65C19") ;; tertiary
+ (fg-region "#fffff0")
+
+ (bg-hl-line "#034852") ;; primary
+
+ (fg-line-number-active fg-main)
+ (fg-line-number-inactive "#a9a9a9")
+ (bg-line-number-active unspecified)
+ (bg-line-number-inactive "#0D5D62") ;; primary
+
+ (fringe bg-main)
+ (cursor "#FF5300") ;; tertiary
+
+ (fg-prose-verbatim "#af9fff")
+ (bg-prose-block-contents "#244449") ;; primary
+ (fg-prose-block-delimiter "#c6b7ad")
+ (bg-prose-block-delimiter bg-prose-block-contents)
+
+ (keyword "#4dd0e1")
+ (builtin "#a490ff")
+ (comment "#afa7b0")
+ (string "#50f2ca")
+ (fnname "#d8afd8")
+ (type "#89c6f9")
+ (variable "#98fb98")
+ (docstring "#f0e68c")
+ (constant "#fa80e6"))))
+(run-hooks 'modus-themes-post-load-hook)
+
+(defun bd/enable-variable-pitch-exempt ()
+ "Text modes to exempt from variable pitch fonts."
+ (unless (derived-mode-p 'latex-mode 'mhtml-mode 'nxml-mode 'yaml-mode)
+ (variable-pitch-mode 1)))
+
+(defvar bd/enable-variable-pitch-in-hooks
+ '(text-mode-hook)
+ "List of hook symbols to add `variable-pitch-mode'
+to.")
+
+(mapc
+ (lambda (hook)
+ (add-hook hook #'bd/enable-variable-pitch-exempt))
+ bd/enable-variable-pitch-in-hooks)
+
+(set-face-attribute 'variable-pitch nil
+ :family "Dejavu Serif"
+ :height 130)
+(set-face-attribute 'fixed-pitch nil
+ :family "Iosevka"
+ :height 100)
+(set-face-attribute 'default nil
+ :family "Iosevka"
+ :height 140)
+(set-face-attribute 'modus-themes-ui-variable-pitch nil
+ :family "Iosevka"
+ :height 90)
+
+
+(provide 'bd--themes)
+;;; bd--themes.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--utility.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--utility.el
new file mode 100644
index 0000000..ee74f58
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--utility.el
@@ -0,0 +1,160 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+(require 'project)
+(require 'xdg)
+
+
+(defun bd/set-frame-alpha (value)
+ "Set the transparency of ALL frame backgrounds to VALUE. 0=transparent/100=opaque."
+ (interactive "nTransparency Value (50 - 100 opaque): ")
+ (setq value (max 50 (min value 100)))
+ (mapc (lambda (f)
+ (set-frame-parameter f 'alpha `(,value . ,value)))
+ (frame-list))
+ (message "Alpha set to %d" value))
+
+(defun bd/set-bg (&optional arg)
+ "Set the current wallpaper using feh.
+ARG can be one of the following:
+
+- nil: set the most recent wallpaper
+- directory: set a random image from the directory
+- file: set the specified file"
+ (interactive
+ (list (read-file-name
+ "Select a wallpaper: "
+ (expand-file-name "~/wf/wall/") nil t)))
+ (let ((wall (expand-file-name "~/wf/wall/current")))
+ (when arg
+ (cond
+ ((file-regular-p arg) (copy-file arg wall t))
+ ((file-directory-p arg)
+ (let* ((images (directory-files arg (lambda (f) (string-match-p "\\.jpeg\\'" f))))
+ (rfile (nth (random (length images)) images)))
+ (and rfile (copy-file rfile wall t))))))
+ (start-process "set wallpaper" nil "feh" "--bg-fill" wall)
+ (message "Set wallpaper.")))
+
+(defun bd/lock ()
+ "Lock the screen."
+ (interactive)
+ (start-process "lock" nil "slock"))
+
+(defun bd/shoot-full ()
+ "Take a full-screen screenshot."
+ (interactive)
+ (let ((default-directory (xdg-user-dir "PICTURES")))
+ (start-process-shell-command "flameshot" nil "flameshot full")))
+
+(defun bd/shoot-part ()
+ "Take a selective screen screenshot."
+ (interactive)
+ (let ((default-directory (xdg-user-dir "PICTURES")))
+ (start-process-shell-command "flameshot" nil "flameshot launcher")))
+
+(defun bd/toggle-mute ()
+ "Toggle between muted and unmuted."
+ (interactive)
+ (start-process "sound toggle" nil "pactl" "set-sink-mute" "@DEFAULT_SINK@" "toggle"))
+
+(defun bd/set-volume (value)
+ "Set the volume to VALUE."
+ (start-process "set volume" nil "pactl" "set-sink-volume" "@DEFAULT_SINK@" value))
+
+(defun bd/decrement-volume ()
+ "Decrements the volume."
+ (interactive)
+ (bd/set-volume "-4%"))
+
+(defun bd/increment-volume ()
+ "Increments the volume."
+ (interactive)
+ (bd/set-volume "+4%"))
+
+(defun bd/set-brightness (value)
+ "Set the brightness to VALUE."
+ (start-process "set brightness" nil "brightnessctl" "set" value))
+
+(defun bd/decrement-brightness ()
+ "decrements the brightness."
+ (interactive)
+ (bd/set-brightness "5%-"))
+
+(defun bd/increment-brightness ()
+ "Increments the brightness."
+ (interactive)
+ (bd/set-brightness "5%+"))
+
+(defun bd/get-directory-dwim ()
+ "Return the directory you always wanted."
+ (or (when (project-current)
+ (project-root (project-current))) ;; git
+ (locate-dominating-file "." "Makefile") ;; make
+ (locate-dominating-file "." "manifest.scm") ;; guix
+ default-directory))
+
+(defun bd/buffer-eww-p (buf)
+ "Return non-nil if BUF is a `eww-mode' buffer."
+ (member
+ (buffer-local-value 'major-mode (get-buffer buf))
+ '(eww-mode)))
+
+(defun bd/buffer-exwm-p (buf)
+ "Return non-nil if BUF is an `exwm-mode' buffer and is in the current workspace."
+ (and (member
+ (buffer-local-value 'major-mode (get-buffer buf))
+ '(exwm-mode))
+ (eq (exwm-workspace--position exwm-workspace--current)
+ (alist-get 'exwm--desktop (buffer-local-variables (get-buffer buf))))))
+
+(defun bd/buffer-scratch-p (buf)
+ "Return non-nil if BUF is a scratch buffer."
+ (buffer-local-value 'scratch-buffer (get-buffer buf)))
+
+(defun bd/buffer-text-p (buf)
+ "Return non-nil if BUF derives from `text-mode'."
+ (provided-mode-derived-p (buffer-local-value 'major-mode (get-buffer buf)) 'text-mode))
+
+(defun bd/buffer-prog-p (buf)
+ "Return non-nil if BUF derives from `prog-mode'."
+ (provided-mode-derived-p (buffer-local-value 'major-mode (get-buffer buf)) 'prog-mode))
+
+(defun bd/buffer-shell-p (buf)
+ "Return non-nil if BUF derives from `text-mode'."
+ (member
+ (buffer-local-value 'major-mode (get-buffer buf))
+ '(shell-mode eshell-mode term-mode vterm-mode)))
+
+(defun bd/buffer-dired-p (buf)
+ "Return non-nil if BUF is a `dired-mode' buffer."
+ (member
+ (buffer-local-value 'major-mode (get-buffer buf))
+ '(dired-mode)))
+
+(defun bd/buffer-irc-p (buf)
+ "Return non-nil if BUF is an `irc-mode' buffer."
+ (member
+ (buffer-local-value 'major-mode (get-buffer buf))
+ '(rcirc-mode erc-mode)))
+
+(defun bd/buffer-ordinary-p (buf)
+ "Return non-nil if BUF does not fit into known categories."
+ (not (or (bd/buffer-eww-p buf)
+ (bd/buffer-exwm-p buf)
+ (bd/buffer-text-p buf)
+ (bd/buffer-prog-p buf)
+ (bd/buffer-shell-p buf)
+ (bd/buffer-scratch-p buf)
+ (bd/buffer-dired-p buf)
+ (bd/buffer-irc-p buf))))
+
+(defun bd/kill-quietly (proc)
+ "Kill PROC quietly."
+ (ignore-errors (cond ((bufferp proc) (kill-buffer proc))
+ ((stringp proc) (interrupt-process proc)))))
+
+
+(provide 'bd--utility)
+;;; bd--utility.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--window.el b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--window.el
new file mode 100644
index 0000000..bdc173b
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/emacs/modules/bd--window.el
@@ -0,0 +1,59 @@
+;;; -*- lexical-binding: t; -*-
+;;; Commentary:
+;;; Code:
+
+
+(require 'transient)
+
+(setopt switch-to-buffer-obey-display-actions t
+ switch-to-buffer-in-dedicated-window 'pop)
+;; C-u C-h a ^display-buffer-[^-]
+(add-to-list 'display-buffer-alist
+ '("^\\*Async Shell Command\\*.*$"
+ (display-buffer-no-window)))
+
+(defun bd/layout--do-with-haste (f)
+ (let* ((args (transient-args 'bd/layout-dispatcher))
+ (haste (if (member "haste" args) 3 1)))
+ (funcall f haste)))
+(transient-define-prefix bd/layout-dispatcher ()
+ ["Dispatcher > Layout\n"
+ ["Infixes"
+ ("s" "haste" "haste")]]
+ [["Commands"
+ ("-" "text decrease"
+ (lambda () (interactive) (bd/layout--do-with-haste #'text-scale-decrease))
+ :transient t)
+ ("=" "text increase"
+ (lambda () (interactive) (bd/layout--do-with-haste #'text-scale-increase))
+ :transient t)
+ ("b" "narrow"
+ (lambda () (interactive) (bd/layout--do-with-haste #'shrink-window-horizontally))
+ :transient t)
+ ("f" "widen"
+ (lambda () (interactive) (bd/layout--do-with-haste #'enlarge-window-horizontally))
+ :transient t)
+ ("p" "shrink"
+ (lambda () (interactive) (bd/layout--do-with-haste #'shrink-window))
+ :transient t)
+ ("n" "enlarge"
+ (lambda () (interactive) (bd/layout--do-with-haste #'enlarge-window))
+ :transient t)
+ ("|" "balance" balance-windows)]
+ [""
+ ("N" "shift down" windmove-swap-states-down :transient t)
+ ("P" "shift up" windmove-swap-states-up :transient t)
+ ("F" "shift right" windmove-swap-states-right :transient t)
+ ("B" "shift left" windmove-swap-states-left :transient t)]
+ [""
+ ("a" "alpha" bd/set-frame-alpha)
+ ("w" "wallpaper" bd/set-bg)
+ ("t" "theme" load-theme)]
+ [""
+ ("z" "widen gaps" exwm-outer-gaps-increment :transient t)
+ ("x" "shrink gaps" exwm-outer-gaps-decrement :transient t)]])
+(keymap-global-set "C-c w" #'bd/layout-dispatcher)
+
+
+(provide 'bd--window)
+;;; bd--window.el ends here
diff --git a/kolwynia/home/bdunahu/files/.config/eww/eww.scss b/kolwynia/home/bdunahu/files/.config/eww/eww.scss
new file mode 100644
index 0000000..198a00b
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/eww/eww.scss
@@ -0,0 +1,118 @@
+*
+{
+ all: unset;
+}
+
+/* separator */
+
+.sepbar
+{
+ color: #D9E0EE;
+ font-family: Iosevka;
+ font-size: 12px;
+ margin-right: 5px;
+ margin-left: 5px;
+}
+
+/* bar */
+.bar
+{
+ background-color: rgba(#000B0E, 0.72);
+}
+
+/* time*/
+.time-cpu-mem
+{
+ margin-right: 15px;
+}
+.time-label
+{
+ color: #91d7e3;
+ font-family: Iosevka;
+ font-size: 12px;
+}
+
+/* mem cpu */
+.vol
+{
+ color: #6434ff;
+ background-color: rgba(#232323, 1.0);
+ border-radius: 0px;
+ margin-right: 6px;
+}
+.mem
+{
+ color: #ed8dff;
+ background-color: rgba(#232323, 1.0);
+ border-radius: 0px;
+ margin-right: 6px;
+}
+.cpu
+{
+ color: #606060;
+ background-color: rgba(#232323, 1.0);
+ border-radius: 0px;
+ margin-right: 6px;
+}
+.cpu-and-mem
+{
+ border-radius: 0px;
+ margin-right: 0px;
+}
+
+/* workspaces */
+.active
+{
+ color: #ed8dff;
+}
+
+.workspaces
+{
+ padding-top: 5px;
+ font-size: 12px;
+}
+
+.mail
+{
+ color: #ed8dff;
+ font-family: Iosevka;
+ font-size: 12px;
+}
+
+.t1
+{
+ color: #c6a0f6;
+ font-family: Iosevka;
+ font-size: 12px;
+}
+
+.weather
+{
+ color: #ff5300;
+ font-family: Iosevka;
+ font-size: 12px;
+}
+
+.battery
+{
+ color: #d42ea2;
+ font-family: Iosevka;
+ font-size: 12px;
+}
+
+.classes
+{
+ margin-left: 10px;
+}
+
+.class
+{
+ margin-right: 10px;
+ color: #D9E0EE;
+ padding: 4px;
+ margin-top: 0px;
+ border-radius: 0px;
+ background: rgba(#042429, 0.48);
+ font-family: Iosevka;
+ font-size: 12px;
+}
diff --git a/kolwynia/home/bdunahu/files/.config/eww/eww.yuck b/kolwynia/home/bdunahu/files/.config/eww/eww.yuck
new file mode 100644
index 0000000..335a687
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/eww/eww.yuck
@@ -0,0 +1,18 @@
+(include "pollers.yuck")
+(include "widgets.yuck")
+
+(defwindow bar
+ :monitor 0
+ :windowtype "dock"
+ :geometry (geometry :x "0%"
+ :y "0%"
+ :width "100%"
+ :height "20px"
+ :anchor "top center")
+ :reserve (struts :side "top" :distance "2.3%")
+ (bar))
+
+;; Local Variables:
+;; mode: scheme
+;; compile-command: "eww daemon --restart; eww open bar"
+;; End:
diff --git a/kolwynia/home/bdunahu/files/.config/eww/pollers.yuck b/kolwynia/home/bdunahu/files/.config/eww/pollers.yuck
new file mode 100644
index 0000000..c3cdecf
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/eww/pollers.yuck
@@ -0,0 +1,14 @@
+(defpoll volume :interval "2s" "scripts/getvol")
+(defpoll time :interval "30s" "date '+%H:%M'")
+(defpoll date :interval "30s" "date '+%b %d'")
+(defpoll ws :interval "0.15s" "scripts/update_workspaces")
+(defpoll cs :interval "0.15s" "scripts/update_x_names")
+(defpoll mail :interval "60s" "$HOME/.local/bin/mail-string")
+(defpoll t1 :interval "60s" "$HOME/.local/bin/t1-string")
+(defpoll weather :interval "1800s" "$HOME/.local/bin/string-weather")
+(defpoll batperc :interval "60s" "echo \"$(cat /sys/class/power_supply/BAT0/capacity)%\"")
+
+;; Local Variables:
+;; mode: scheme
+;; compile-command: "eww daemon --restart; eww open bar"
+;; End:
diff --git a/kolwynia/home/bdunahu/files/.config/eww/scripts/getvol b/kolwynia/home/bdunahu/files/.config/eww/scripts/getvol
new file mode 100755
index 0000000..9fbe60f
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/eww/scripts/getvol
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+RUNNING_SINK=$(pactl list sinks | awk '
+ /^Sink #/ {sink=$2}
+ /State: RUNNING/ {print sink}
+' | head -n1)
+
+pactl list sinks | grep -A 20 "^Sink $RUNNING_SINK" | grep '^[[:space:]]Volume:' | \
+ head -n $(( $SINK + 1 )) | tail -n 1 | sed -e 's,.* \([0-9][0-9]*\)%.*,\1,'
diff --git a/kolwynia/home/bdunahu/files/.config/eww/scripts/update_workspaces b/kolwynia/home/bdunahu/files/.config/eww/scripts/update_workspaces
new file mode 100755
index 0000000..4fb1b1c
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/eww/scripts/update_workspaces
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+gib_workspace_names() {
+ wmctrl -d \
+ | awk '{ print $1 " " $2 " " $9 }' \
+ | grep -v NSP
+}
+
+gib_workspace_yuck() {
+ buffered=""
+ gib_workspace_names | while read -r id active name; do
+ name="${name#*_}"
+ if [ "$active" == '*' ]; then
+ active_class="active"
+ else
+ active_class="inactive"
+ fi
+
+ if wmctrl -l | grep --regexp '.*\s\+'"$id"'\s\+.*' >/dev/null; then
+ button_class="occupied"
+ button_name="●"
+ else
+ button_class="empty"
+ button_name="○"
+ fi
+ buffered+="(button :class \"$button_class $active_class\" :onclick \"wmctrl -s $id\" \"$button_name\")"
+ echo -n "$buffered"
+ buffered=""
+ done
+}
+
+box_attrs=':orientation "h" :class "workspaces" :space-evenly false :halign "center" :valign "center" :vexpand false :spacing 8 '
+
+echo "(box $box_attrs $(gib_workspace_yuck))"
diff --git a/kolwynia/home/bdunahu/files/.config/eww/scripts/update_x_names b/kolwynia/home/bdunahu/files/.config/eww/scripts/update_x_names
new file mode 100755
index 0000000..63117ef
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/eww/scripts/update_x_names
@@ -0,0 +1,33 @@
+#!/run/current-system/profile/bin/guile \
+-s
+!#
+
+(use-modules (ice-9 popen)
+ (ice-9 textual-ports)
+ (ice-9 rdelim))
+
+(define cmd "wmctrl -l | awk -v ws=\"$(wmctrl -d | awk '$2 == \"*\" {print $1}')\" '$2 == ws {print}' | cut -d ' ' -f 5-")
+
+(define box-attrs '(:class "classes"
+ :orientation "h"
+ :valign "center"
+ :halign "start"
+ :space-evenly "false"))
+
+(define titles
+ (let* ((process (open-pipe* OPEN_READ "sh" "-c" cmd))
+ (output (get-string-all process)))
+ (close-pipe process)
+ output))
+
+(define labels
+ (map (lambda (t) `(eventbox
+ :onclick ,(string-concatenate `("wmctrl -a \"" ,t "\""))
+ (label :class "class"
+ :text ,t
+ :truncate true
+ :valign "center")))
+ (filter (lambda (s) (not (string=? s "")))
+ (string-split titles #\newline))))
+
+(write `(box ,@box-attrs ,@labels))
diff --git a/kolwynia/home/bdunahu/files/.config/eww/widgets.yuck b/kolwynia/home/bdunahu/files/.config/eww/widgets.yuck
new file mode 100644
index 0000000..c7ea4d4
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/eww/widgets.yuck
@@ -0,0 +1,92 @@
+(defwidget bar []
+ (box :class "bar"
+ :orientation "h"
+ (classeswidget)
+ (workspaces)
+ (power-time-cpu-mem)))
+
+(defwidget power-time-cpu-mem []
+ (box :class "time-cpu-mem"
+ :halign "end"
+ :valign "center"
+ :space-evenly "false"
+ :orientation "h"
+ (cpu-and-mem)
+ (sep)
+ (weatherwidget)
+ (sep)
+ (mailwidget)
+ (sep)
+ (t1widget)
+ (sep)
+ (batterywidget)
+ (sep)
+ (label :text "${date}, "
+ :class "time-label"
+ :halign "end"
+ :valign "center"
+ :tooltip "time")
+ (label :text time
+ :class "time-label"
+ :halign "end"
+ :valign "center"
+ :tooltip "time")))
+
+(defwidget cpu-and-mem []
+ (box :class "cpu-and-mem"
+ :orientation "h"
+ :halign "end"
+ :valign "center"
+ :space-evenly "false"
+ (circular-progress :class "vol"
+ :value volume
+ :thickness 6)
+ (circular-progress :class "cpu"
+ :value {EWW_CPU.avg}
+ :thickness 6)
+ (circular-progress :class "mem"
+ :value {EWW_RAM.used_mem_perc}
+ :thickness 6)))
+
+(defwidget batterywidget []
+ (label
+ :class "battery"
+ :text batperc))
+
+(defwidget t1widget []
+ (label
+ :class "t1"
+ :text "${t1}"))
+
+(defwidget mailwidget []
+ (label
+ :class "mail"
+ :text "${mail}"))
+
+(defwidget weatherwidget []
+ (label
+ :class "weather"
+ :text "${weather}"))
+
+(defwidget classeswidget []
+ (literal :content cs))
+
+(defwidget iconwidget []
+ (image
+ :path "assets/raven.png"
+ :class "icon"
+ :image-height 20
+ :preserve-aspect-ratio true))
+
+(defwidget workspaces []
+ (literal :content ws))
+
+(defwidget sep []
+ (box :orientation "h"
+ :valign "center"
+ (label :class "sepbar" :text "|")))
+
+;; Local Variables:
+;; mode: scheme
+;; compile-command: "eww daemon --restart; eww open bar"
+;; End:
diff --git a/kolwynia/home/bdunahu/files/.config/guix/channels.scm b/kolwynia/home/bdunahu/files/.config/guix/channels.scm
new file mode 100644
index 0000000..f0ed659
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/guix/channels.scm
@@ -0,0 +1,9 @@
+(append (list (channel
+ (name 'tanelorn)
+ (url "https://git.operationnull.com/tanelorn.git")
+ (introduction
+ (make-channel-introduction
+ "feb72dbc897bcf2db3901ea5b25caba4add860f7"
+ (openpgp-fingerprint
+ "5550 5CA6 9DE5 D342 7F31 F9AE 5F86 6C65 2A34 C996")))))
+ %default-channels)
diff --git a/kolwynia/home/bdunahu/files/.config/zathura/zathurarc b/kolwynia/home/bdunahu/files/.config/zathura/zathurarc
new file mode 100644
index 0000000..ecdae54
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.config/zathura/zathurarc
@@ -0,0 +1,195 @@
+# start in dark mode
+set recolor true
+# mouse select copies to clipboard
+nset selection-clipboard clipboard
+set scroll-full-overlap 0.2
+# stops at page break
+set scroll-page-aware true
+set window-title-basename true
+# start at width fit
+set adjust-open width
+# short version of the file path
+set statusbar-home-tilde true
+# center screen vertical midpoint
+set vertical-center true
+# synctex support
+set synctex true
+set zoom-step 3
+
+# lots taken from
+# https://web.archive.org/web/20220815064614/https://gist.github.com/ne9z/8778d614b90b85dfe8f6b698ad758f36
+map [normal] <C-b> scroll left
+map [normal] <C-n> scroll down
+map [normal] <C-p> scroll up
+map [normal] <C-f> scroll right
+map [normal] <C-g> abort
+map [insert] <C-g> abort
+map [normal] <C-[> abort
+map [normal] <A-\<> goto top
+map [normal] <A-\>> goto bottom
+map [normal] a adjust_window best-fit
+map [normal] s adjust_window width
+map [normal] F display_link
+map [normal] <C-c> copy_link
+map [normal] f follow
+map [normal] m mark_add
+map [normal] \' mark_evaluate
+map [normal] \, navigate next
+map [normal] \. navigate previous
+map [normal] <A-Right> navigate next
+map [normal] <A-Left> navigate previous
+map [normal] <PageDown> scroll full-down
+map [normal] <PageUp> scroll full-up
+map [normal] <C-P> print
+map [normal] c recolor
+map [normal] R reload
+map [normal] v rotate rotate_cw
+map [normal] V rotate rotate_ccw
+map [normal] <Left> scroll left
+map [normal] <Up> scroll up
+map [normal] <Down> scroll down
+map [normal] <Right> scroll right
+map [normal] <A-a> scroll half-left
+map [normal] <C-V> scroll half-down
+map [normal] <A-V> scroll half-up
+map [normal] <A-e> scroll half-right
+map [normal] <C-a> scroll full-left
+map [normal] <C-v> scroll full-down
+map [normal] <Return> scroll full-down
+map [normal] <A-v> scroll full-up
+map [normal] <C-e> scroll full-right
+map [normal] <Space> scroll full-down
+map [normal] <C-h> scroll full-up
+map [normal] <BackSpace> scroll full-up
+map [normal] <S-Space> scroll full-up
+map [normal] l jumplist backward
+map [normal] r jumplist forward
+map [normal] <A-r> bisect forward
+map [normal] <A-l> bisect backward
+# still need to use '/' to trigger search
+map [normal] <C-s> search forward
+map [normal] <C-r> search backward
+map [normal] p snap_to_page
+map [normal] <C-i> toggle_index
+map [normal] i toggle_index
+map [normal] <Tab> toggle_index
+map [normal] <A-s> toggle_statusbar
+map [normal] <A-i> focus_inputbar
+map [normal] d toggle_page_mode
+map [normal] q quit
+map [normal] + zoom in
+map [normal] - zoom out
+map [normal] = zoom in
+map [normal] <A-P> toggle_presentation
+map [normal] <A-F> toggle_fullscreen
+map [normal] j toggle_fullscreen
+map [fullscreen] j toggle_fullscreen
+map [fullscreen] q toggle_fullscreen
+map [fullscreen] <C-b> scroll left
+map [fullscreen] <C-n> scroll down
+map [fullscreen] <C-p> scroll up
+map [fullscreen] <C-f> scroll right
+map [fullscreen] <C-g> abort
+map [fullscreen] <C-[> abort
+map [fullscreen] <A-\<> goto top
+map [fullscreen] <A-\>> goto bottom
+map [fullscreen] a adjust_window best-fit
+map [fullscreen] s adjust_window width
+map [fullscreen] F display_link
+map [fullscreen] <C-c> copy_link
+map [fullscreen] f follow
+map [fullscreen] m mark_add
+map [fullscreen] \' mark_evaluate
+map [fullscreen] \, navigate next
+map [fullscreen] \. navigate previous
+map [fullscreen] <A-Right> navigate next
+map [fullscreen] <A-Left> navigate previous
+map [fullscreen] <PageDown> scroll full-down
+map [fullscreen] <PageUp> scroll full-up
+map [fullscreen] <C-P> print
+map [fullscreen] c recolor
+map [fullscreen] R reload
+map [fullscreen] v rotate rotate_cw
+map [fullscreen] V rotate rotate_ccw
+map [fullscreen] <Left> scroll left
+map [fullscreen] <Up> scroll up
+map [fullscreen] <Down> scroll down
+map [fullscreen] <Right> scroll right
+map [fullscreen] <A-a> scroll half-left
+map [fullscreen] <C-V> scroll half-down
+map [fullscreen] <A-V> scroll half-up
+map [fullscreen] <A-e> scroll half-right
+map [fullscreen] <C-a> scroll full-left
+map [fullscreen] <C-v> scroll full-down
+map [fullscreen] <Return> scroll full-down
+map [fullscreen] <A-v> scroll full-up
+map [fullscreen] <C-e> scroll full-right
+map [fullscreen] <Space> scroll full-down
+map [fullscreen] <C-h> scroll full-up
+map [fullscreen] <BackSpace> scroll full-up
+map [fullscreen] <S-Space> scroll full-up
+map [fullscreen] l jumplist backward
+map [fullscreen] r jumplist forward
+map [fullscreen] <A-r> bisect forward
+map [fullscreen] <A-l> bisect backward
+map [fullscreen] <C-s> search forward
+map [fullscreen] <C-r> search backward
+map [fullscreen] z snap_to_page
+map [fullscreen] i toggle_index
+map [fullscreen] <C-i> toggle_index
+map [fullscreen] <Tab> toggle_index
+map [fullscreen] <A-s> toggle_statusbar
+map [fullscreen] <A-i> focus_inputbar
+map [fullscreen] d toggle_page_mode
+map [fullscreen] + zoom in
+map [fullscreen] - zoom out
+map [fullscreen] = zoom in
+# status bar will obscure last item in index mode
+map [index] <A-s> toggle_statusbar
+map [index] q toggle_index
+map [index] i toggle_index
+map [index] <C-p> navigate_index up
+map [index] <C-h> navigate_index up
+map [index] <BackSpace> navigate_index up
+map [index] <C-n> navigate_index down
+map [index] <A-v> navigate_index up
+map [index] <C-v> navigate_index down
+map [index] \< navigate_index top
+map [index] \> navigate_index bottom
+map [index] <A-\<> navigate_index top
+map [index] <A-\>> navigate_index bottom
+map [index] <C-b> navigate_index collapse
+map [index] <C-f> navigate_index expand
+map [index] <C-i> navigate_index expand-all
+map [index] <A-i> navigate_index collapse-all
+map [index] <Up> navigate_index up
+map [index] <Down> navigate_index down
+map [index] <Left> navigate_index collapse
+map [index] <Right> navigate_index expand
+map [index] <C-m> navigate_index select
+map [index] <Space> navigate_index select
+map [index] <Return> navigate_index select
+map [index] <C-j> navigate_index select
+map [index] <Esc> toggle_index
+map [index] <C-[> toggle_index
+map [index] <C-g> toggle_index
+map [index] <C-c> toggle_index
+map [presentation] i toggle_index
+map [presentation] r navigate next
+map [presentation] <Down> navigate next
+map [presentation] <Right> navigate next
+map [presentation] <PageDown> navigate next
+map [presentation] <Space> navigate next
+map [presentation] l navigate previous
+map [presentation] <Left> navigate previous
+map [presentation] <Up> navigate previous
+map [presentation] <PageUp> navigate previous
+map [presentation] <S-Space> navigate previous
+map [presentation] <BackSpace> navigate previous
+map [presentation] <F5> toggle_presentation
+map [presentation] q toggle_presentation
+map [presentation] <C-h> navigate previous
+map [presentation] <M-v> navigate previous
+map [presentation] <C-v> navigate next
+map [presentation] <A-\<> goto top
+map [presentation] <A-\>> goto bottom \ No newline at end of file
diff --git a/kolwynia/home/bdunahu/files/.local/bin/bkup-home b/kolwynia/home/bdunahu/files/.local/bin/bkup-home
new file mode 100755
index 0000000..b2d31b6
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/bkup-home
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+cryptsetup open /dev/sdb1 bkup
+mount /dev/mapper/bkup /mnt
+
+rsync -aPHAXSu --inplace --delete /home/ /mnt/
diff --git a/kolwynia/home/bdunahu/files/.local/bin/fetch-album b/kolwynia/home/bdunahu/files/.local/bin/fetch-album
new file mode 100755
index 0000000..3885f88
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/fetch-album
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+err() { echo "Usage:
+ rename [OPTIONS]
+Options:
+ -d: delimiter
+ -a: artist/author
+ -A: album/book title
+ -y: year of publication
+You will be prompted for these fields if not given." && exit 1 ;}
+
+[ "$PWD" = "${HOME}/ik/tmp" ] || { echo "Unsafe directory!" && exit 1 ;}
+
+while getopts "d:a:A:y:" o; do case "${o}" in
+ d) delimiter="${OPTARG}" ;;
+ a) artist="${OPTARG}" ;;
+ A) album="${OPTARG}" ;;
+ y) year="${OPTARG}" ;;
+ *) printf "Invalid option: -%s\\n" "$OPTARG" && err ;;
+ esac done
+files=(*)
+echo "Reference: ${files[0]}"
+[ -z "$delimiter" ] && echo "What are the delimiters for these files?" && read -r delimiter
+echo "Which index contains the track title? (Starting from 1)" && read -r index
+
+[ -z "$artist" ] && echo 'Enter an artist.' && read -r artist
+[ -z "$album" ] && echo 'Enter an album.' && read -r album
+[ -z "$year" ] && echo 'Enter a year.' && read -r year
+
+for file in *;
+do
+ extension=$(echo $file | awk -F "." '{print $NF}')
+ title=$(echo $file | awk -F "${delimiter}" "{print \$${index}}" |
+ sed -re 's/^[[:blank:]]+|[[:blank:]]+$//g' -e 's/[[:blank:]]+/ /g')
+ new_file="${title}_${artist}_${album}_${year}.${extension}"
+ mv "./$file" "./$new_file"
+ tag-media -a "${artist}" -t "${title}" -A "${album}" -d "${year}" "${new_file}"
+done
diff --git a/kolwynia/home/bdunahu/files/.local/bin/guix-gc b/kolwynia/home/bdunahu/files/.local/bin/guix-gc
new file mode 100755
index 0000000..23a423b
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/guix-gc
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+sudo guix system delete-generations 2w
+guix home delete-generations 2w
+guix package --delete-generations=2w
+guix pull --delete-generations=2w
+guix gc
diff --git a/kolwynia/home/bdunahu/files/.local/bin/mail-string b/kolwynia/home/bdunahu/files/.local/bin/mail-string
new file mode 100755
index 0000000..e4f1d20
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/mail-string
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+mail="$(find /home/bdunahu/.ml/*/[Ii][Nn][Bb][Oo][Xx]/new/* -type f 2>/dev/null | wc -l)"
+echo -n "${mail}M"
diff --git a/kolwynia/home/bdunahu/files/.local/bin/sort-music b/kolwynia/home/bdunahu/files/.local/bin/sort-music
new file mode 100755
index 0000000..013f194
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/sort-music
@@ -0,0 +1,101 @@
+#!/usr/bin/env -S guix shell python -- python3
+
+''' Imports '''
+import argparse
+import logging
+import sys
+import glob
+import os
+
+''' Global variables '''
+logger = logging.getLogger('sort_music.py')
+
+'''-----------------------------------------'''
+
+def parse_arguments():
+ ''' Parse command line arguments '''
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-a', '--album', help='EXACT album name shared across designated files.',
+ required=True)
+ parser.add_argument('-f', '--format', help='ncmpcpp format of files. Default is %t_%a_%b_%y. Read the documentation for that program for more information. It MUST include an artist name (%a) and year (%y).',
+ default="%t_%a_%b_%y")
+ parser.add_argument('-v', '--verbose', help='Enable full debug output.',
+ action='store_true')
+ return parser.parse_args()
+
+
+
+def configure_logs(verbose):
+ ''' Configure program logging level '''
+ level = logging.DEBUG if verbose else logging.INFO
+
+ ''' Configure stream handler '''
+ stream_handler = logging.StreamHandler(sys.stdout)
+ stream_handler.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s'))
+
+ ''' For main logger '''
+ logger.addHandler(stream_handler)
+ logger.setLevel(level)
+
+'''-----------------------------------------'''
+
+class SortMusic():
+ def __init__(self, logger, args):
+ self.logger = logger
+ self.album = args.album
+ self.delimiter = args.format[2]
+ self.artist_index = args.format.split(self.delimiter).index('%a')
+ self.year_index = args.format.split(self.delimiter).index('%y')
+
+ logger.info(f'Starting rename with file extension {self.album} and artist_index {self.artist_index}')
+
+ self.files = None # list of relevant files in specified directory
+ self.artist = None
+ self.year = None
+
+
+ def run(self):
+ self._find_files()
+ self._prompt()
+ self._organize()
+
+
+ def _find_files(self):
+ self.files = list(glob.glob(f"*{self.album}*"))
+ if len(self.files) == 0:
+ self.logger.error(f'Files in this album NOT found!')
+ exit(1)
+
+ self.artist = self.files[0].split(self.delimiter).pop(self.artist_index).rsplit(".",1)[0] # remove file extension (sometimes occurs)
+ self.year = self.files[0].split(self.delimiter).pop(self.year_index).rsplit(".",1)[0]
+
+ def _prompt(self):
+ self.logger.info(f'Found {len(self.files)} files written by {self.artist} in {self.year}.')
+ quit = input(f'Files will now be organized (y/n)\t').lower()
+
+ if not quit.startswith('y'):
+ exit(1)
+
+ def _organize(self):
+ path = f'./{self.artist}/({self.year}) {self.album}'
+ if not os.path.exists(path):
+ logger.info(f'Creating previously missing path {path}')
+ os.makedirs(path)
+ for file in self.files:
+ os.rename(f'./{file}', f'{path}/{file}')
+
+'''-----------------------------------------'''
+
+def main():
+ ''' This is the main function '''
+ args = parse_arguments()
+ configure_logs(args.verbose)
+
+ ''' Initialize and run '''
+ sorter = SortMusic(logger, args)
+ sorter.run()
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/kolwynia/home/bdunahu/files/.local/bin/string-weather b/kolwynia/home/bdunahu/files/.local/bin/string-weather
new file mode 100755
index 0000000..01ece24
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/string-weather
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+curl wttr.in/Amherst?format="%x+%t+%m\n"
diff --git a/kolwynia/home/bdunahu/files/.local/bin/t1-string b/kolwynia/home/bdunahu/files/.local/bin/t1-string
new file mode 100755
index 0000000..ffdd61a
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/t1-string
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+loop=$(cat /tmp/t1 | jq .stat | tr -d '"')
+[ -n "$loop" ] && echo -n "${loop} "
+bg=$(cat /tmp/t1 | jq .bg)
+[ -n "$bg" ] && echo -n "${bg}B "
+iob=$(cat /tmp/t1 | jq .iob)
+[ -n "$iob" ] && echo -n " ${iob}U"
+rbat=$(cat /tmp/t1 | jq .rbat)
+[ -n "$rbat" ] && [ "$rbat" -lt 25 ] && echo -n " RB"
+res=$(echo "$(cat /tmp/t1 | jq .res) * 100 / 1" | bc)
+[ -n "$res" ] && [ "$res" -lt 2500 ] && echo -n " PR"
+pbat=$(echo "$(cat /tmp/t1 | jq .pbat) * 100 / 1" | bc)
+[ -n "$pbat" ] && [ "$pbat" -lt 136 ] && echo -n " PB"
+cage=$(cat /tmp/t1 | jq .cage)
+[ -n "$cage" ] && [ 14 -lt "$cage" ] && echo -n " CA"
+echo -n
diff --git a/kolwynia/home/bdunahu/files/.local/bin/tag-media b/kolwynia/home/bdunahu/files/.local/bin/tag-media
new file mode 100755
index 0000000..64b4586
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/tag-media
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+err() { echo "Usage:
+ tag [OPTIONS] file
+Options:
+ -a: artist/author
+ -t: song/chapter title
+ -A: album/book title
+ -d: year of publication
+ -g: genre
+ -c: comment
+You will be prompted for title, artist, album and date if not given." && exit 1 ;}
+
+while getopts "a:t:A:d:g:c:" o; do case "${o}" in
+ a) artist="${OPTARG}" ;;
+ t) title="${OPTARG}" ;;
+ A) album="${OPTARG}" ;;
+ d) date="${OPTARG}" ;;
+ g) genre="${OPTARG}" ;;
+ c) comment="${OPTARG}" ;;
+ *) printf "Invalid option: -%s\\n" "$OPTARG" && err ;;
+ esac done
+
+shift $((OPTIND - 1))
+
+file="$1"
+
+temp="$(mktemp -p "$(dirname "$file")")"
+trap 'rm -f $temp' HUP INT QUIT TERM PWR EXIT
+
+[ ! -f "$file" ] && echo 'Provide file to tag.' && err
+
+[ -z "$title" ] && echo 'Enter a title.' && read -r title
+[ -z "$artist" ] && echo 'Enter an artist.' && read -r artist
+[ -z "$album" ] && echo 'Enter an album.' && read -r album
+[ -z "$date" ] && echo 'Enter a date.' && read -r date
+
+cp -f "$file" "$temp" && ffmpeg -i "$temp" -map 0 -y -codec copy \
+ -metadata title="$title" \
+ -metadata album="$album" \
+ -metadata artist="$artist" \
+ -metadata date="$date" \
+ ${genre:+-metadata genre="$genre"} \
+ ${comment:+-metadata comment="$comment"} "$file"
diff --git a/kolwynia/home/bdunahu/files/.local/bin/touchpad-defaults b/kolwynia/home/bdunahu/files/.local/bin/touchpad-defaults
new file mode 100755
index 0000000..6c07797
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/touchpad-defaults
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# see:
+# xinput --list
+# xinput --list-props 'ELAN0412:00 04F3:311D Touchpad'
+[[ $HOSTNAME == "garmr" ]] && xinput --set-prop "ELAN0412:00 04F3:311D Touchpad" "libinput Tapping Enabled" 1
diff --git a/kolwynia/home/bdunahu/files/.local/bin/unfreeze b/kolwynia/home/bdunahu/files/.local/bin/unfreeze
new file mode 100755
index 0000000..072473d
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/unfreeze
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+epid=$(pidof emacs)
+
+kill -USR2 "$epid"
+kill -USR1 "$epid"
+
+echo "Done."
diff --git a/kolwynia/home/bdunahu/files/.local/bin/upload b/kolwynia/home/bdunahu/files/.local/bin/upload
new file mode 100755
index 0000000..a2db9b5
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/upload
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+
+err() { echo "Usage:
+ upload [OPTIONS]
+Options:
+ -t: time from now to expire in hours (default: 48)
+ file: a path to a file. If this is not given, receives input from stdin." && exit 1 ;}
+
+hours=48
+
+while getopts ":t:" opt; do
+ case $opt in
+ t)
+ hours=$OPTARG
+ if [[ $OPTARG =~ ^-?[0-9]+$ ]]; then
+ hours=$OPTARG
+ else
+ err
+ fi
+ ;;
+ *)
+ err
+ ;;
+ esac
+done
+
+shift $((OPTIND -1))
+
+if [ -n "$1" ] && [ -f "$1" ]; then
+ source="$1"
+ ext="${1##*.}"
+else
+ source=$(mktemp)
+ ext="txt"
+ cat > "$source"
+ chmod a+r "$source"
+fi
+
+dest="$(cat /dev/random | tr -dc 'a-zA-Z0-9' | fold -w 4 | head -n 1)-$(date -d "+$hours hours" +%s).$ext"
+
+scp "$source" "root@operationnull.com:/var/www/operationnull/paste/$dest" &&
+ echo "https://operationnull.com/paste/$dest"
diff --git a/kolwynia/home/bdunahu/files/.local/bin/xkeyboard-auto b/kolwynia/home/bdunahu/files/.local/bin/xkeyboard-auto
new file mode 100755
index 0000000..b81ced4
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/xkeyboard-auto
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+/home/bdunahu/.local/bin/xkeyboard-toggle
+while true;
+do
+ if [ -f /tmp/keyboard_plugged ]; then
+ rm /tmp/keyboard_plugged
+ sleep 2
+ /home/bdunahu/.local/bin/xkeyboard-toggle
+ fi
+ sleep 1
+done
diff --git a/kolwynia/home/bdunahu/files/.local/bin/xkeyboard-layout b/kolwynia/home/bdunahu/files/.local/bin/xkeyboard-layout
new file mode 100755
index 0000000..de6f7a2
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/xkeyboard-layout
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+WORKMAN=$(setxkbmap -print | grep "workman")
+echo $WORKMAN
+
+if [ -z WORKMAN ]; then
+ xkbcomp -I$HOME/.xkb ~/.xkb/keymap/standard $DISPLAY &&
+ echo 'Switched to standard layout.'
+else
+ test -f ~/.xkb/symbols/rocket && xkbcomp -I$HOME/.xkb ~/.xkb/keymap/rocket $DISPLAY &&
+ echo 'Switched to workman layout.'
+fi
diff --git a/kolwynia/home/bdunahu/files/.local/bin/xkeyboard-toggle b/kolwynia/home/bdunahu/files/.local/bin/xkeyboard-toggle
new file mode 100755
index 0000000..70f9516
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/xkeyboard-toggle
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+xset r rate 250 70
+xset s 600
+xkbcomp -I$HOME/.xkb ~/.xkb/keymap/standard $DISPLAY
diff --git a/kolwynia/home/bdunahu/files/.local/bin/xrandr-refresh b/kolwynia/home/bdunahu/files/.local/bin/xrandr-refresh
new file mode 100755
index 0000000..c9d55c5
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/xrandr-refresh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+declare -i count=2
+declare -i seconds=1
+
+while ((count)); do
+ xrandr >/dev/null
+ sleep $seconds
+ ((count--))
+done \ No newline at end of file
diff --git a/kolwynia/home/bdunahu/files/.local/bin/xrandr-toggle b/kolwynia/home/bdunahu/files/.local/bin/xrandr-toggle
new file mode 100755
index 0000000..6acff6d
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/xrandr-toggle
@@ -0,0 +1,15 @@
+#!/bin/sh
+intern=eDP-1
+extern=DP-1
+extern2=HDMI-1
+
+if xrandr | grep "$extern disconnected" >/dev/null; then
+ xrandr --output "$extern" --off --output "$intern" --mode 1920x1080 --auto
+# usually, HDMI-1 is a 2560x1440 screen, so cannot be duplicated well
+elif xrandr | grep "$extern2 connected" >/dev/null; then
+ xrandr --output "$extern2" --primary --mode 1920x1080 --auto --same-as "$intern"
+else
+ xrandr --output "$extern" --primary --mode 1920x1080 --auto --same-as "$intern"
+fi
+
+echo "xrandr done!"
diff --git a/kolwynia/home/bdunahu/files/.local/bin/xxinit b/kolwynia/home/bdunahu/files/.local/bin/xxinit
new file mode 100755
index 0000000..1ec9a82
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.local/bin/xxinit
@@ -0,0 +1,6 @@
+ #!/bin/sh
+
+DIR=/home/bdunahu/.guix-home/profile/
+$DIR/bin/xinit -- $DIR/bin/Xorg :0 vt1 -keeptty \
+ -configdir $DIR/share/X11/xorg.conf.d \
+ -modulepath $DIR/lib/xorg/modules
diff --git a/kolwynia/home/bdunahu/files/.xinitrc b/kolwynia/home/bdunahu/files/.xinitrc
new file mode 100644
index 0000000..0e51e5a
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.xinitrc
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+userresources=$HOME/.Xresources
+usermodmap=$HOME/.Xmodmap
+
+# merge in defaults and keymaps
+
+if [ -f "$userresources" ]; then
+ xrdb -merge "$userresources"
+fi
+
+if [ -f "$usermodmap" ]; then
+ xmodmap "$usermodmap"
+fi
+
+picom --backend=glx &
+xss-lock -- slock &
+xset r rate 250 70
+xset s 600
+touchpad-defaults
+xrandr-toggle
+xkeyboard-auto &
+eww daemon
+eww open bar
+exec emacs -mm
diff --git a/kolwynia/home/bdunahu/files/.xkb/keymap/rocket b/kolwynia/home/bdunahu/files/.xkb/keymap/rocket
new file mode 100644
index 0000000..4fdd8c1
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.xkb/keymap/rocket
@@ -0,0 +1,7 @@
+xkb_keymap {
+ xkb_keycodes { include "evdev+aliases(qwerty)" };
+ xkb_types { include "complete" };
+ xkb_compat { include "complete" };
+ xkb_symbols { include "pc+us(workman)+inet(evdev)+rocket(rocket)" };
+ xkb_geometry { include "pc(pc105)" };
+};
diff --git a/kolwynia/home/bdunahu/files/.xkb/keymap/standard b/kolwynia/home/bdunahu/files/.xkb/keymap/standard
new file mode 100644
index 0000000..528dd72
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.xkb/keymap/standard
@@ -0,0 +1,7 @@
+xkb_keymap {
+ xkb_keycodes { include "evdev+aliases(qwerty)" };
+ xkb_types { include "complete" };
+ xkb_compat { include "complete" };
+ xkb_symbols { include "pc+us+inet(evdev)+rocket(rocket)" };
+ xkb_geometry { include "pc(pc105)" };
+};
diff --git a/kolwynia/home/bdunahu/files/.xkb/symbols/rocket b/kolwynia/home/bdunahu/files/.xkb/symbols/rocket
new file mode 100644
index 0000000..a206a9a
--- /dev/null
+++ b/kolwynia/home/bdunahu/files/.xkb/symbols/rocket
@@ -0,0 +1,9 @@
+partial modifier_keys
+xkb_symbols "rocket" {
+ key <TAB> {[ BackSpace, Escape, BackSpace, BackSpace ]};
+ key <CAPS> {[ space, space, space, nobreakspace ]};
+ key <LCTL> {[ Tab, ISO_Left_Tab, Tab, ISO_Left_Tab ]};
+ key <RTRN> {[ space, space, space, nobreakspace ]};
+ key <RCTL> {[ Return, Return, Return, Return ]};
+ key <SPCE> {[ Control_L, Control_R, Control_L, Control_R ]};
+};
diff --git a/kolwynia/home/bdunahu/packages.scm b/kolwynia/home/bdunahu/packages.scm
new file mode 100644
index 0000000..7d0e510
--- /dev/null
+++ b/kolwynia/home/bdunahu/packages.scm
@@ -0,0 +1,221 @@
+;;; Copyright © 2025,2026 bdunahu <bdunahu@operationnull.com>
+(define-module (kolwynia home bdunahu packages)
+ #:use-module (gnu)
+ #:use-module (gnu system)
+ #:use-module (guix packages)
+ #:use-module (tanelorn packages fonts)
+ #:use-module (tanelorn packages wm)
+ #:use-module (tanelorn packages engineering)
+ #:use-module (tanelorn packages emacs-xyz)
+ #:export (emacs
+ browser
+ pass
+ media
+ mail
+ tex
+ desktop
+ pdf
+ fonts
+ development
+ reverse-engineering
+ university
+ emulators
+ games))
+
+;;; Commentary:
+;;; Code:
+
+(use-package-modules
+ admin ;; netcat
+ algebra ;; bc
+ aspell ;; aspell, aspell-dict-en
+ assembly ;; nasm
+ base ;; make
+ chromium ;; ungoogled-chromium
+ clojure ;; clojure
+ commencement ;; gcc-toolchain
+ compton ;; picom
+ cpp ;; ccls
+ education ;; anki
+ emacs-build ;; emacs-dash
+ emacs ;; emacs-next
+ emacs-xyz ;; emacs-emms, etc.
+ emulators ;; mupen64plus*, bsnes, mgba
+ engineering ;; radare2, iaito
+ fonts ;; font-terminus, font-openmoji
+ fontutils ;; fontconfig
+ freedesktop ;; xdg-utils
+ games ;; nethack
+ gdb ;; gdb
+ gimp ;; gimp-next
+ gnome ;; brightnessctl
+ gnupg ;; gnupg, pinentry-emacs
+ graphviz ;; graphviz
+ haskell-xyz ;; pandoc
+ image ;; flameshot
+ imagemagick ;; imagemagick
+ image-viewers ;; feh, nsxiv
+ libreoffice ;; libreoffice
+ librewolf ;; librewolf
+ linux ;; tlp, alsa-plugins
+ lisp ;; sbcl
+ luanti ;; luanti
+ machine-learning ;; llama-cpp
+ mail ;; offlineimap
+ password-utils ;; pass-otp, password-store
+ pdf ;; xpdf, pdfgrep, zathura, zathura-pdf-mupdf
+ photo ;; perl-image-exiftool
+ pkg-config ;; pkg-config
+ pulseaudio ;; pavucontrol, pulseaudio,
+ python ;; python
+ python-xyz ;; python-lsp-server
+ rsync ;; rsync
+ texlive ;; texlive, texlive-biber
+ tex ;; texlive-dvipng
+ tor-browsers ;; torbrowser
+ video ;; ffmpeg, mpv, yt-dlp
+ virtualization ;; qemu
+ web ;; jq
+ xdisorg ;; xdotool, xrdb, wmctrl
+ xorg) ;; xf86-input-libinput, xf86-video-fbdev, xinit...
+
+
+
+(define emacs
+ (list emacs-next
+ pinentry-emacs
+ ;;
+ emacs-atomic-chrome
+ emacs-cider
+ emacs-clojure-mode
+ emacs-dash
+ emacs-denote
+ emacs-denote-journal
+ emacs-elpher
+ emacs-emms
+ emacs-exwm
+ emacs-f
+ emacs-gptel
+ emacs-guix
+ emacs-hydra
+ emacs-jeison
+ emacs-lua-mode
+ emacs-paredit
+ emacs-pinentry
+ emacs-rainbow-delimiters
+ emacs-rainbow-mode
+ emacs-s
+ emacs-slime
+ emacs-vterm
+ emacs-yasnippet
+ ;;
+ aspell ;for ispell
+ aspell-dict-en ;
+ wmctrl ;for exwm
+ perl-image-exiftool ;for emms
+ ccls ;for eglot
+ python-lsp-server ;
+ llama-cpp)) ;for emacs-gptel
+
+(define browser
+ (list torbrowser
+ ungoogled-chromium
+ librewolf))
+
+(define pass
+ (list gnupg
+ pass-otp
+ password-store))
+
+(define media
+ (list feh
+ ffmpeg
+ flameshot
+ gimp
+ imagemagick
+ mpv
+ nsxiv
+ pandoc
+ yt-dlp))
+
+(define mail
+ (list offlineimap3))
+
+(define tex
+ (list texlive
+ texlive-biber
+ texlive-dvipng
+ texlive-pgfgantt
+ texlive-libertinus))
+
+(define desktop
+ (list alsa-plugins
+ eww/x11
+ pavucontrol
+ picom
+ pulseaudio
+ setxkbmap
+ xdg-utils
+ xf86-input-libinput
+ xf86-video-fbdev
+ xinit
+ xinput
+ xkbcomp
+ xorg-server
+ xrandr
+ xrdb
+ xset
+ xss-lock))
+
+(define pdf
+ (list pdfgrep
+ xpdf ;pdftotext
+ zathura
+ zathura-pdf-mupdf))
+
+(define fonts
+ (list fontconfig
+ font-iosevka
+ font-medieval-sharp
+ font-openmoji
+ font-runa-mono
+ font-terminus
+ font-libertinus))
+
+(define development
+ (list bc
+ cl-asdf
+ clojure
+ clojure-tools
+ gcc-toolchain
+ gdb
+ gnu-make
+ pkg-config
+ python
+ jq
+ netcat
+ sbcl))
+
+(define reverse-engineering
+ (list nasm
+ iaito
+ radare2))
+
+(define university
+ (list anki
+ graphviz
+ libreoffice
+ qemu))
+
+(define emulators
+ (list bsnes
+ dolphin-emu
+ mgba
+ mupen64plus-ui-console
+ mupen64plus-video-glide64mk2))
+
+(define games
+ (list crawl-tiles
+ luanti))
+
+;;; packages.scm ends here
diff --git a/kolwynia/home/bdunahu/ssh.scm b/kolwynia/home/bdunahu/ssh.scm
new file mode 100644
index 0000000..5c137b2
--- /dev/null
+++ b/kolwynia/home/bdunahu/ssh.scm
@@ -0,0 +1,43 @@
+;;; Copyright © 2026 bdunahu <bdunahu@operationnull.com>
+(define-module (kolwynia home bdunahu ssh)
+ #:use-module (gnu home services ssh)
+ #:export (garmr
+ heimdallr
+ nott
+ surt
+ codeberg))
+
+;;; Commentary:
+;;; Code:
+
+(define garmr
+ (openssh-host (name "garmr")
+ (host-name "localhost")
+ (user "root")))
+
+(define heimdallr
+ (openssh-host (name "heimdallr")
+ (host-name "operationnull.com")
+ (user "root")))
+
+(define nott
+ (openssh-host (name "nott")
+ (host-name "localhost")
+ (user "bdunahu")
+ (port 2222)
+ (extra-content " UserKnownHostsFile=/dev/null
+ StrictHostKeyChecking=no
+")))
+
+(define surt
+ (openssh-host (name "surt")
+ (host-name "192.168.1.250")
+ (user "root")))
+
+(define codeberg
+ (openssh-host (name "codeberg.org")
+ (host-name "codeberg.org")
+ (user "git")
+ (identity-file "~/.ssh/codeberg")))
+
+;;; ssh.scm ends here
diff --git a/kolwynia/os/garmr.scm b/kolwynia/os/garmr.scm
new file mode 100644
index 0000000..ad6cace
--- /dev/null
+++ b/kolwynia/os/garmr.scm
@@ -0,0 +1,61 @@
+(define-module (kolwynia os garmr)
+ #:use-module (gnu)
+ #:use-module (gnu packages)
+ #:use-module (gnu packages linux)
+ #:use-module (gnu packages gnome)
+ #:use-module (gnu packages networking)
+ #:use-module (gnu services)
+ #:use-module (gnu services guix)
+ #:use-module (gnu services pm)
+ #:use-module (guix gexp)
+ #:use-module (kolwynia os ymir)
+ #:use-module ((kolwynia os garmr file-systems)
+ #:prefix fs:)
+ #:use-module ((kolwynia os ymir packages)
+ #:prefix pkg:)
+ #:use-module ((kolwynia os ymir users)
+ #:prefix users:)
+ #:use-module (kolwynia home bdunahu))
+
+;;; Commentary:
+;;;
+;;; garmr is a corebooted and fully-libre laptop
+;;;
+;;; Code:
+
+(operating-system
+ (inherit ymir)
+ (host-name "garmr")
+
+ (packages (cons* brightnessctl tlp pkg:ymir-packages))
+
+ (services
+ (cons*
+ ;; laptop power management
+ (service tlp-service-type
+ (tlp-configuration
+ (cpu-scaling-governor-on-ac (list "powersave"))
+ (cpu-scaling-governor-on-bat (list "powersave"))
+ (energy-perf-policy-on-ac "powersave")
+ (energy-perf-policy-on-bat "powersave")
+ (pcie-aspm-on-ac "powersave")
+ (pcie-aspm-on-bat "powersave")
+ (sched-powersave-on-bat? #t)
+ (sched-powersave-on-ac? #t)
+ (sata-linkpwr-on-ac "min_power")
+ (sata-linkpwr-on-bat "min_power")
+ (start-charge-thresh-bat0 60)
+ (stop-charge-thresh-bat0 80)))
+ (service thermald-service-type)
+ (simple-service 'network-manager-applet
+ profile-service-type
+ (list network-manager-applet))
+ (service guix-home-service-type
+ `((,users:bdunahu-str ,bdunahu)))
+
+ (operating-system-user-services ymir)))
+
+ (mapped-devices fs:%mapped-devices)
+ (file-systems fs:%file-systems))
+
+;;; garmr.scm ends here
diff --git a/kolwynia/os/garmr/file-systems.scm b/kolwynia/os/garmr/file-systems.scm
new file mode 100644
index 0000000..352d1f2
--- /dev/null
+++ b/kolwynia/os/garmr/file-systems.scm
@@ -0,0 +1,39 @@
+;;; Copyright © 2025 bdunahu <bdunahu@operationnull.com>
+(define-module (kolwynia os garmr file-systems)
+ #:use-module (gnu system mapped-devices)
+ #:use-module (gnu system file-systems)
+ #:export (%mapped-devices %file-systems))
+
+;;; Commentary:
+;;;
+;;; filesystems for garmr
+;;;
+;;; Code:
+
+(define %mapped-devices
+ (list (mapped-device
+ (source (uuid "671c8094-c8cd-47f9-8332-25513f16917b"))
+ (target "hidden")
+ (type luks-device-mapping))))
+
+(define %file-systems
+ (cons* (file-system
+ (mount-point "/boot/efi")
+ (device (uuid
+ "1A7A-026F"
+ 'fat32))
+ (type "vfat"))
+ (file-system
+ (mount-point "/home")
+ (device "/dev/mapper/hidden")
+ (type "ext4")
+ (dependencies %mapped-devices))
+ (file-system
+ (mount-point "/")
+ (device (uuid
+ "0a0b9520-308f-4072-a62b-b91ffacdc5b0"
+ 'ext4))
+ (type "ext4"))
+ %base-file-systems))
+
+;;; file-systems.scm ends here
diff --git a/kolwynia/os/hel.scm b/kolwynia/os/hel.scm
new file mode 100644
index 0000000..e9b6289
--- /dev/null
+++ b/kolwynia/os/hel.scm
@@ -0,0 +1,45 @@
+(define-module (kolwynia os hel)
+ #:use-module (gnu)
+ #:use-module (gnu packages)
+ #:use-module (gnu packages linux)
+ #:use-module (gnu packages gnome)
+ #:use-module (gnu packages networking)
+ #:use-module (gnu packages xorg)
+ #:use-module (gnu services)
+ #:use-module (gnu services guix)
+ #:use-module (guix gexp)
+ #:use-module (kolwynia os ymir)
+ #:use-module ((kolwynia os hel file-systems)
+ #:prefix fs:)
+ #:use-module ((kolwynia os ymir packages)
+ #:prefix pkg:)
+ #:use-module ((kolwynia os ymir users)
+ #:prefix users:)
+ #:use-module (kolwynia home bdunahu))
+
+;;; Commentary:
+;;;
+;;; hel is a intel i5 with RTX 1050ti
+;;;
+;;; Code:
+
+(operating-system
+ (inherit ymir)
+ (host-name "hel")
+
+ (packages (cons* xf86-video-nouveau pkg:ymir-packages))
+
+ (services
+ (cons*
+ (simple-service 'network-manager-applet
+ profile-service-type
+ (list network-manager-applet))
+ (service guix-home-service-type
+ `((,users:bdunahu-str ,bdunahu)))
+
+ (operating-system-user-services ymir)))
+
+ (mapped-devices fs:%mapped-devices)
+ (file-systems fs:%file-systems))
+
+;;; hel.scm ends here
diff --git a/kolwynia/os/hel/file-systems.scm b/kolwynia/os/hel/file-systems.scm
new file mode 100644
index 0000000..b975c56
--- /dev/null
+++ b/kolwynia/os/hel/file-systems.scm
@@ -0,0 +1,39 @@
+;;; Copyright © 2026 bdunahu <bdunahu@operationnull.com>
+(define-module (kolwynia os hel file-systems)
+ #:use-module (gnu system mapped-devices)
+ #:use-module (gnu system file-systems)
+ #:export (%mapped-devices %file-systems))
+
+;;; Commentary:
+;;;
+;;; filesystems for hel
+;;;
+;;; Code:
+
+(define %mapped-devices
+ (list (mapped-device
+ (source (uuid "671c8094-c8cd-47f9-8332-25513f16917b"))
+ (target "hidden")
+ (type luks-device-mapping))))
+
+(define %file-systems
+ (cons* (file-system
+ (mount-point "/boot/efi")
+ (device (uuid
+ "1A7A-026F"
+ 'fat32))
+ (type "vfat"))
+ (file-system
+ (mount-point "/home")
+ (device "/dev/mapper/hidden")
+ (type "ext4")
+ (dependencies %mapped-devices))
+ (file-system
+ (mount-point "/")
+ (device (uuid
+ "0a0b9520-308f-4072-a62b-b91ffacdc5b0"
+ 'ext4))
+ (type "ext4"))
+ %base-file-systems))
+
+;;; file-systems.scm ends here
diff --git a/kolwynia/os/ymir.scm b/kolwynia/os/ymir.scm
new file mode 100644
index 0000000..bba1b3b
--- /dev/null
+++ b/kolwynia/os/ymir.scm
@@ -0,0 +1,117 @@
+;;; Copyright © 2025 bdunahu <bdunahu@operationnull.com>
+(define-module (kolwynia os ymir)
+ #:use-module ((kolwynia os ymir users)
+ #:prefix users:)
+ #:use-module ((kolwynia os ymir packages)
+ #:prefix pkg:)
+ #:use-module (gnu)
+ #:use-module (gnu services desktop)
+ #:use-module (gnu system)
+ #:use-module (gnu packages suckless)
+ #:export (ymir))
+
+;;; Commentary:
+;;;
+;;; ymir defines a default system configuration
+;;;
+;;; Code:
+
+(use-service-modules audio admin avahi base
+ dbus desktop file-sharing
+ networking sound ssh xorg)
+
+;;; generates a temporary file to notify a keyboard was plugged.
+(define %keyboard-udev-rule
+ (udev-rule
+ "90-keyboard-hotplug.rules"
+ (string-append "ATTR{idVendor}==\"04b4\", ATTR{idProduct}==\"0510\", ACTION==\"add\", RUN+=\"/run/current-system/profile/bin/touch /tmp/keyboard_plugged\" RUN+=\"/run/current-system/profile/bin/chown " users:bdunahu-str " /tmp/keyboard_plugged\"")))
+
+(define ymir
+ (operating-system
+ (host-name "ymir")
+ (locale "en_US.utf8")
+ (timezone
+ (if #f
+ "America/New_York"
+ "America/Denver"))
+ (keyboard-layout
+ (keyboard-layout "us"
+ #:options '("ctrl:hyper_capscontrol")))
+ (kernel-arguments
+ (delete "quiet"
+ %default-kernel-arguments))
+
+ (users (cons* users:bdunahu
+ %base-user-accounts))
+
+ (bootloader (bootloader-configuration
+ (bootloader grub-efi-bootloader)
+ (targets (list "/boot/efi"))
+ (timeout 1)
+ (keyboard-layout keyboard-layout)))
+
+ (packages pkg:ymir-packages)
+ (services
+ `(
+ ;; so that non-root users in the wheel group can
+ ;; perform administrative tasks (similar to "sudo").
+ ,polkit-wheel-service
+ ;; The global fontconfig cache directory can sometimes contain
+ ;; stale entries, possibly referencing fonts that have been GC'd,
+ ;; so mount it read-only.
+ ,fontconfig-file-system-service
+
+ ,(service network-manager-service-type)
+ ,(service wpa-supplicant-service-type) ; needed by NetworkManager
+ ,(service modem-manager-service-type)
+ ,(service usb-modeswitch-service-type)
+
+ ;; The D-Bus clique.
+ ,(service avahi-service-type)
+ ,(service udisks-service-type)
+ ,(service upower-service-type)
+ ,(service accountsservice-service-type)
+ ,(service cups-pk-helper-service-type)
+ ,(service colord-service-type)
+ ,(service geoclue-service-type)
+ ,(service polkit-service-type)
+ ,(service elogind-service-type
+ (elogind-configuration
+ (handle-power-key 'hibernate)))
+ ,(service dbus-root-service-type)
+
+ ,(service ntp-service-type)
+
+ ;; openssh
+ ,(service openssh-service-type
+ (openssh-configuration
+ (x11-forwarding? #t)
+ (permit-root-login 'prohibit-password)
+ (password-authentication? #f)))
+
+ ;; xorg
+ ,(service screen-locker-service-type
+ (screen-locker-configuration
+ (name "slock")
+ (program (file-append slock "/bin/slock"))))
+ ,(service x11-socket-directory-service-type)
+ ,(udev-rules-service 'keyboard-hotplug %keyboard-udev-rule)
+
+ ;; audio
+ ,(service pulseaudio-service-type)
+ ,(service alsa-service-type)
+
+ ;; TODO: don't do this expensive operation; just copy file.
+ ;; ,(simple-service 'add-extra-hosts
+ ;; hosts-service-type
+ ;; (map
+ ;; (lambda (x)
+ ;; (host "0.0.0.0" x))
+ ;; (read-hosts "/home/bdunahu/.config/guix/assets/blocklist.txt")))
+
+ ,@%base-services))
+
+ ;; OVERRIDE
+ (file-systems '())))
+
+;;; ymir.scm ends here
diff --git a/kolwynia/os/ymir/packages.scm b/kolwynia/os/ymir/packages.scm
new file mode 100644
index 0000000..9331b10
--- /dev/null
+++ b/kolwynia/os/ymir/packages.scm
@@ -0,0 +1,106 @@
+;;; Copyright © 2025 bdunahu <bdunahu@operationnull.com>
+(define-module (kolwynia os ymir packages)
+ #:use-module (gnu)
+ #:use-module (gnu system)
+ #:use-module (guix packages)
+ #:export (ymir-packages-utils
+ ymir-packages-linux
+ ymir-packages-interactive
+ ymir-packages-networking
+ ymir-packages))
+
+;;; Commentary:
+;;; Code:
+
+(use-package-modules
+ admin
+ bash
+ compression
+ cryptsetup
+ curl
+ databases
+ firmware
+ gawk
+ guile
+ guile-xyz
+ less
+ linux
+ man
+ networking
+ nss
+ pciutils
+ rust-apps
+ screen
+ texinfo
+ text-editors
+ version-control
+ vim
+ wget)
+
+(define ymir-packages-utils
+ (list bash
+ bzip2
+ coreutils
+ curl
+ diffutils
+ e2fsprogs
+ findutils
+ gawk
+ grep
+ guile-3.0-latest
+ gzip
+ lzip
+ man-pages
+ patch
+ procps
+ psmisc
+ recutils
+ screen
+ sed
+ shadow
+ tar
+ unzip
+ which
+ xz
+ zip
+ zstd))
+
+(define ymir-packages-linux
+ (list cryptsetup
+ eudev
+ kmod
+ pciutils
+ usbutils
+ util-linux+udev))
+
+(define ymir-packages-interactive
+ (list git
+ guile-colorized
+ guile-readline
+ info-reader
+ kbd
+ less
+ man-db
+ mg
+ ripgrep
+ sudo
+ sysstat
+ vim))
+
+(define ymir-packages-networking
+ (list inetutils
+ iproute
+ isc-dhcp
+ iw
+ macchanger
+ nss-certs
+ wget
+ wireless-tools))
+
+(define ymir-packages
+ (append ymir-packages-interactive
+ ymir-packages-linux
+ ymir-packages-networking
+ ymir-packages-utils))
+
+;;; packages.scm ends here
diff --git a/kolwynia/os/ymir/users.scm b/kolwynia/os/ymir/users.scm
new file mode 100644
index 0000000..a577998
--- /dev/null
+++ b/kolwynia/os/ymir/users.scm
@@ -0,0 +1,28 @@
+;;; Copyright © 2025 bdunahu <bdunahu@operationnull.com>
+(define-module (kolwynia os ymir users)
+ #:use-module (gnu system accounts)
+ #:use-module (kolwynia utils)
+ #:export (bdunahu-str bdunahu))
+
+;;; Commentary:
+;;;
+;;; provides the bdunahu user configuration
+;;;
+;;; Code:
+
+(define bdunahu-str "bdunahu")
+(define bdunahu
+ (user-account
+ (name bdunahu-str)
+ (comment bdunahu-str)
+ (group "users")
+ (home-directory (string-append "/home/" bdunahu-str))
+ (supplementary-groups
+ (cons* "audio"
+ "dialout"
+ "netdev"
+ "video"
+ "wheel"
+ (garmr?* "kvm")))))
+
+;;; users.scm ends here
diff --git a/kolwynia/utils.scm b/kolwynia/utils.scm
new file mode 100644
index 0000000..990cef1
--- /dev/null
+++ b/kolwynia/utils.scm
@@ -0,0 +1,14 @@
+;;; Copyright © 2025 bdunahu <bdunahu@operationnull.com>
+(define-module (kolwynia utils)
+ #:export (garmr?*))
+
+;;; Commentary:
+;;; provides various utility functions
+;;; Code:
+
+(define (garmr?* . thing)
+ (if (equal? (gethostname) "garmr")
+ thing
+ '()))
+
+;;; utils.scm ends here